使用 Puppy Traits 仪表板学习 Shiny for Python
原文:towardsdatascience.com/learn-shiny-for-python-with-a-puppy-traits-dashboard-cc65f05e88c4。
原文:
towardsdatascience.com/learn-shiny-for-python-with-a-puppy-traits-dashboard-cc65f05e88c4
使用 Puppy Traits Web 应用程序探索 Shiny for Python
作者图片
Shiny在 R 生态系统中被推崇了十多年,最近其所有优点也被引入了 Python。Shiny 是一个可以用于创建交互式 Web 应用程序的 Web 应用程序框架,可以在后端运行代码。
我一直是 R Shiny 的忠实用户,因此当它也被引入 Python 时,我自然感到非常兴奋。在这篇文章中,我将介绍我创建“Who is the Goodest Doggy”应用程序的步骤,从基础到样式。让我们开始吧!
代码和数据可用性
在我的GitHub 仓库中可以找到重造本文中所有内容的代码。
数据:本文使用了我生成的假狗特征评分的合成数据。合成数据可在链接的 GitHub 页面找到,并受到了我在 TidyTuesday 上找到的数据的启发,感谢KKakey和美国犬业俱乐部的原始数据来源。
数据生成过程:为了生成数据,我选取了独特的狗和特征组合,并使用numpy库为每个品种和特征生成一个介于 2–5 之间的随机评分。生成此数据集的完整代码可以在数据文件夹中链接的仓库中找到。
设置 Python 环境
-
安装 Shiny for Python 扩展(针对 VSCode)
-
为您的项目创建虚拟环境(良好实践)
-
通过运行单个命令如“pip install shiny”或运行“pip install -r requirements.txt”来安装所需的包。
Shiny 的基础组件
这个基础命令是我最喜欢的,因为 Posit 团队使得使用基础 Shiny 模板启动变得非常容易。
Shiny create --help
作者图片
此命令打开了各种 Shiny 模板的选项。每次检查时,这个列表上都会有一个新的模板,因为它发展得非常快。上面的图片显示了运行命令时出现的内容。我将选择 basic-app 模板并运行命令shiny create -t basic-app。
此命令创建了一个包含简单app.py文件的模板,当运行时生成下面的 Shiny 仪表板。在这个应用程序中,用户可以选择滚动条上的一个数字,应用程序会更新下面的计算和文字。
图片由作者提供
让我们看看app.py文件的内容:
from shiny import App, render, ui # Import required packages
app_ui = ui.page_fluid(
ui.panel_title("Hello Shiny!"), # Create the panel for title
ui.input_slider("n", "N", 0, 100, 20), # Create the slider
ui.output_text_verbatim("txt"), # Create space for text on the UI
)
def server(input, output, session):
@render.text # Decorator to define output format
def txt(): # The function that will create the text to send over to the ui
return f"n*2 is {input.n() * 2}" # The text calculation
app = App(app_ui, server)
与 R 语言的 Shiny 类似,在 app.py 文件中有一个ui和server组件。ui包含应用程序的前端,而server逻辑处理后端。我将更新这个模板以探索小狗特征数据集。
个性化应用程序
添加数据框和图表输出
在选择基本模板后,我将更新app.py文件以满足我的数据和数据分析需求。为了在应用程序中输出数据框和图表,我可以更新ui和server逻辑以读取并显示数据框作为 pandas 对象,并在服务器端调用绘图函数。
from shiny import App, render, ui
import pandas as pd # Import pandas for working with data frames
from pathlib import Path
from trait_rating_plot import create_trait_rating_plot # Import plot function
# Read the dataframe
df = pd.read_csv(Path(__file__).parent / "dog_traits.csv", na_values = "NA")
# UI logic
app_ui = ui.page_fillable(
ui.output_data_frame("dog_df"), # Create space for plot output
ui.output_plot("breed_plot") # Create space for dataframe output
)
# Server logic
def server(input, output, session):
@render.data_frame # Decorator to define output structure for data frame
def dog_df():
return df
@render.plot # Decorator to define output structure for plot
def breed_plot():
fig = create_trait_rating_plot(df, "Bulldogs") # Call external function to generate the plot for Bulldogs
return fig
app = App(app_ui, server)
上述代码创建了下面的 Shiny 输出。由于它既不美观也不动态,还有很多工作要做。目前这是一个静态输出。数据框输出显示了整个数据集。图表输出硬编码为“斗牛犬”。它们的放置只是简单地一个接一个,因为应用程序还没有用其布局进行结构化。
图片由作者提供
添加动态组件:侧边栏布局和过滤器
侧边栏
这是我见过的 Shiny 应用程序中最常见的布局之一。在上面的代码中,我将ui组件包含在ui.page_sidebar()函数中。这个函数有两个部分:
-
第一部分位于
ui.sidebar()函数内部。所有需要在侧面板中显示的ui组件都编码在这个函数中。 -
第二部分是所有其他内容,它位于主面板区域。
侧边栏面板内的过滤器
现在我有了侧边栏,我将使用ui.input_select()函数添加一个用户输入下拉过滤器来选择狗的品种,这样我的图表就不会硬编码为“斗牛犬”,并且可以更新为任何狗。
我还会添加一个多选过滤器,用于用户感兴趣的狗的特征。这将只更新数据框以显示选定的特征,以便在狗之间进行比较。
更新的ui逻辑:
# Read the dataframe
df = pd.read_csv(Path(__file__).parent / "dog_traits.csv", na_values = "NA")
breeds = df.breed.unique().tolist()
traits = df.trait.unique().tolist()
app_ui = ui.page_fillable(
ui.page_sidebar( # Sidebar layout
ui.sidebar( # First part - sidebar panel
ui.input_select("inputbreed", # Single input filter for dog breed
label = "Select breed",
choices = breeds,
selected="Bulldogs"),
ui.input_selectize(id = "inputtrait", # Multi input filter for characteristics
label= "Select traits",
choices = traits,
multiple=True,
selected="Adaptability Level"),
),
ui.output_data_frame("dog_df"), # Main panel
ui.output_plot("breed_plot") # Main panel
),
)
在服务器端:
-
我将更新绘图输出函数,使其接受用户输入的品种而不是默认的“斗牛犬”。这使得图表能够根据狗品种输入动态响应。
-
我将更新数据框输出,以添加基于用户选择的特征过滤逻辑。这使得数据框能够根据用户选择的特征动态更新。
def server(input, output, session):
@render.data_frame
def dog_df(): # Filter and sort based on user characteristics selection
filtered_df = df[(df['trait'].isin(input.inputtrait()))]
return filtered_df.sort_values(by=["trait", "rating"], ascending=[True, False])
@render.plot
def breed_plot(): # filter and sort based on user breed selection
df_updated = df.copy()
fig = create_trait_rating_plot(df_updated, input.inputbreed())
return fig
如下图中所示,现在有一个侧边栏布局,过滤器位于侧面板中,其余内容都在主面板中。
图片由作者提供
使用“响应式计算”更新多个输出
到目前为止,我添加了两个过滤器,它们都只影响一个输出。我还想添加用户想要在数据框和绘图输出中反映的最小和最大评分的滑块输入。在这种情况下,我将在ui侧创建滑块输入,以便用户可以选择评分限制。
ui.input_slider(id = "ratingmin", # Slider input for minimum rating
label="Minimum rating",
min=1, max=5, value=1),
ui.input_slider(id = "ratingmax", # Slider input for maximum rating
label="Maximum rating",
min=1, max=5, value=5),
然后,我将在server端创建一个新的表格输出,根据所选的评分限制过滤整个表格。这被称为“反应性计算”,因此它位于装饰器reactive.Calc下。
我将使用这个新的反应性计算输出作为其他两个服务器函数(我的数据框和绘图)的输入,这样它们都会受到滑块输入的影响。反应性计算的结果可以通过反应括号在其他服务器函数中访问。
def server(input, output, session):
@reactive.Calc # New reactive calculation for filtering data by rating
def filtered_ratings():
filtered_rating = df[(df['rating'] >= input.ratingmin()) &
(df['rating'] <= input.ratingmax())]
return filtered_rating.sort_values(by=["trait", "rating"], ascending=[True, False])
@render.data_frame # updated to use "filtered_ratings()" instead of original df.
def dog_df():
filtered_df = filtered_ratings()[(filtered_ratings()['trait'].isin(input.inputtrait()))]
return filtered_df.sort_values(by=["trait", "rating"], ascending=[True, False])
@render.plot # updated to use "filtered_ratings()" instead of original df.
def breed_plot():
fig = create_trait_rating_plot(filtered_ratings(), input.inputbreed())
return fig
更新后的应用程序输出:
图片由作者提供
添加“应用”设置按钮
有多个过滤器的担忧是,每次其中一个更新时,应用程序会立即更新,而不是等待用户更新他们需要更改的每个过滤器后再刷新应用程序。我将添加一个“应用”按钮,允许在所有输入完成后并点击按钮时,根据所选过滤器更新输出。该按钮如下添加到ui逻辑中:
ui.input_action_button("apply", # id of the button to access on the server side
"Apply settings", # Externally visible label
class_="btn-secondary") # The color
在server端,我将更新数据框和绘图输出函数,使其成为“反应性事件”,这样它们只有在按下“应用”时才会反应。
@render.data_frame
@reactive.event(input.apply, ignore_none=False)
def dog_df():
filtered_df = filtered_ratings()[(filtered_ratings()['trait'].isin(input.inputtrait()))]
return filtered_df.sort_values(by=["trait", "rating"], ascending=[True, False])
@render.plot
@reactive.event(input.apply, ignore_none=False)
def breed_plot():
fig = create_trait_rating_plot(filtered_ratings(), input.inputbreed())
return fig
当应用程序运行时,应该在其他过滤器旁边有一个额外的“应用”按钮。
现在,我将最终努力使应用程序看起来更专业!
UI 布局
从这里,我将只更新将反映在前端上的ui组件。
在绘图周围添加边界并更新到列布局
我现在将通过将每个元素包含在ui.card()函数中来为我的数据框和绘图添加边界。此函数还允许通过在ui.card()函数中使用ui.card_header()来添加标题。
然后,我将使用ui.layout_columns()函数将这两个ui元素包含在内,以便我可以为每个ui组件分配它们想要占据的指定列空间。在一个列布局中,我可以为组件分配总共 12 个单位。我将为数据框分配 5 个单位,为绘图分配 7 个单位。
ui.layout_columns( # Encompass all ui components within the main panel into column layout
ui.card( # Encompass dataframe space within ui.card to create boundary
ui.card_header("Select the traits to update this plot"), # Card title
ui.output_data_frame("dog_df"),
),
ui.card( # Encompass plot space within ui.card to create boundary
ui.card_header("Select the breed to update this plot"), # Card title
ui.output_plot("breed_plot")
),
gap = "2rem", # Column layout function input for gap between columns
col_widths={"sm": (5, 7)}, # Size of the columns for each component
height = "400px" # Height of the columns
)
更新后的应用程序输出:
图片由作者提供
看起来已经干净多了。
图片、文本和面板
接下来,由于这是一个小狗仪表板,我想在仪表板的顶部添加一个无法抗拒的小狗图片,与标题并排。为了使这两个元素在仪表板顶部并排,我将在我的绘图和数据框部分上方添加一行。在这个行内,我将创建另一个列布局并使用以下函数。
-
ui.tags.img(): 我将使用此函数在此布局中添加图片。 -
ui.tags.h1(): 我将使用此函数添加仪表板标题。在 Shiny 文档中还有许多其他 ui.tags 的功能。 -
ui.markdown(): 我将使用此函数添加一个数据源提示,使用简单的文本。 -
ui.panel_absolute(): 我将使用此函数在面板中添加仪表板标题和 Markdown 文本,这样在滚动仪表板时面板仍然可见。 -
ui.panel_conditional(): 我将创建一个条件面板用于我的评分过滤器,这样它们只有在选中“为评分设置限制”的复选框时才可见。
# Puppy image url
dogimg_url = "https://images.unsplash.com/photo-1444212477490-ca407925329e?q=80&w=1856&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
# Row layout
ui.row(
# Multiple columns within each row
# First column for the image using ui.tags.img
ui.column(6, ui.tags.img(src=dogimg_url, height="90%", width="100%")),
# Second column for the dashboard heading and data source call out
ui.column(5,
ui.panel_absolute(
ui.panel_well(
ui.tags.h1("Who is the goodest doggy?!?"),
ui.markdown("Data: Synthetic dog trait ratings inspired by TidyTuesday dataset courtesy of [KKakey](https://github.com/kkakey/dog_traits_AKC/blob/main/README.md) sourced from the [American Kennel Club](https://www.akc.org/)."),
ui.markdown("Photo by [Anoir Chafik](https://unsplash.com/@anoirchafik?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/selective-focus-photography-of-three-brown-puppies-2_3c4dIFYFU?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)")
),
width="450px",
right="75px",
draggable=False,
)
)
)
条件面板的代码:
ui.input_checkbox("show", "Set limits for ratings", False), # Create a checkbox for conditional input
ui.panel_conditional( # Only show the components within this function when condition is true
"input.show", # Condition that the checkbox is selected
ui.input_slider(id = "ratingmin", label="Minimum rating", min=1, max=5, value=1),
ui.input_slider(id = "ratingmax", label="Maximum rating", min=1, max=5, value=5),
)
更新后的应用程序:
图片由作者提供
主题
色彩方案和主题非常重要,因为我认为它们定义了仪表板的个性和品牌。我将更新我的侧边栏面板的背景以及整个仪表板的主题。
-
侧边栏面板颜色:在
ui.sidebar()函数中,我将使用"bg"输入来提供背景颜色。我还会提供"open"输入,以便侧边栏默认打开,用户可以选择折叠并重新打开它。 -
我将使用
shinyswatch包来导入仪表板的主题。此包提供了多个主题,但对我来说,theme.minty()这个主题我很喜欢。
更新后的ui代码:
from shinyswatch import theme # import the package for various prebuilt themes
....
app_ui = ui.page_fillable(
theme.minty(), # Call minty() theme to update the page
...
ui.sidebar( # Sidebar panel
...,
bg="#f6e7e8", open="open" # Panel variable inputs
)
)
最终的应用程序
应用程序现在已经准备好了,所有我逐步添加的元素都已包含在内。这里是最终的输出,以及用于重现它的完整代码,可以在 GitHub 仓库中找到。
图片由作者提供
为什么现在是开始使用 Shiny for Python 的绝佳时机
我认为有几个原因使得现在进入这个领域是完美的时机。
-
Shiny for Python 才刚刚开始。如果以 R Shiny 为指标,还有更多内容即将到来,Posit 正在迅速将 Python 纳入其生态系统。
-
正在出现新的应用程序,如Shiny Express,这使得 Shiny for Python 更加易于快速原型设计。
-
如果您来自 Shiny for R 的背景,核心 Shiny for Python 是进入 Python 世界的一个很好的步骤。
-
如果您来自 Streamlit for Python 的背景,Shiny express 提供了 Streamlit 的快速原型设计和 Shiny 的高效之间的完美平衡。
希望您觉得这篇文章鼓励您去学习和构建 Shiny for Python 网络应用程序。请关注我关于 Python Shiny Express 的下一篇文章,这是最新加入的成员。
在github.com/deepshamenghani/shinypython_meetup/可以找到重现本文中所有内容的代码。
如果你想,可以在领英上找到我。
更多推荐



所有评论(0)