使用 python 来制作 shiny App

引言


之前我介绍过一些 R 里面的关于 shiny 的知识分享,自己也写过几个制作的 shiny 包,后面在 python 中也开发了类似于 shiny 的框架,Shiny for Python(2022 年首次推出) 是一个框架,专门用来使用 Python 创建交互式 web 应用程序,尤其在 数据分析 和 数据可视化 场景中表现优异。它灵感来源于 R 语言的 Shiny 框架,由 RStudio 团队(现在更名为 Posit)开发,具备与 R 版本类似的核心理念,但设计上更 Pythonic,并融入了 Python 的数据科学生态。虽然 Shiny 最初是为 R 语言设计的,但 Shiny for Python 并不是简单的移植,而是一个全新为 Python 生态设计的框架。
支持两种语法(Core 和 Express) :
Core 是原始的标准语法,提供更强的灵活性和细粒度的控制。
Express 是 2024 年推出的简化语法,降低了新手用户的开发门槛,适合快速开发和构建原型。
以下是 Core,Express 的区别:

两者应用区别:

R 的 Shiny
定位:专为 R 环境设计,主要面向统计学家、数据分析师,以及深度依赖 R 的用户。
生态:和 R 的统计和可视化生态深度结合,如 ggplot2 和 dplyr 等 R 包。Shiny 非常适合需要统计建模和高质量数据可视化的工作流程。
特性:框架更专注于数据展示和解释,这是 Shiny 和 R 的强项。
Shiny for Python
定位:面向 Python 用户,尤其是数据科学家、机器学习工程师和全栈开发者,填补了 Python 中交互式 web 应用开发的空白。
生态:可用 Python 的庞大生态(如 pandas、matplotlib、plotly、scikit-learn)。不仅适合数据分析,也能轻松扩展到机器学习、数据管道或更大规模的应用(如与 FastAPI 或 Flask 集成)。
灵活性:更适合作为多领域开发(数据科学 + 应用程序开发)的工具。
性能和扩展性:
R 的 Shiny

性能:由于 R 是单线程语言,Shiny 的性能在处理大规模数据或并发访问时会受到限制,可能需要通过扩展(如 shinyapps.io 的负载均衡)来提升。
扩展性:R 的 Shiny 专注于数据分析和展示,扩展性较低;不适合作为后端支持更复杂系统的核心服务。
Shiny for Python
性能:Python 可以支持多线程、多进程以及与高性能计算的良好集成,适合更复杂的数据流和并发场景。
扩展性:Shiny for Python 不仅可以用于创建简单的交互数据应用,也非常适合与其他 Python 框架(如 Flask、FastAPI)或工具(如 TensorFlow、PyTorch)配合,支持机器学习、API 开发等更复杂的场景。

安装

学习地址:https://shiny.posit.co/py/docs/overview.html

!pip install –upgrade pip wheel
!pip install shiny
!pip install –upgrade shiny htmltools
在 vscode 拓展里安装 shiny 拓展:

基础入门

主要包含 Inputs,Outputs 和 Layouts 三个部分,类似于 R shiny 的输入控件,ui.input_*() 来添加控件:
from shiny import reactive
from shiny.express import input, ui

ui.input_text(“text”, label=”Enter some text”)

@reactive.effect
def _():
print(input.text())
官网语法有两种,core 语法包含 ui 和 server 两部分,类似于 R,express 的是高度封装的,更易于使用和快速开发:
express:

core:

使用 @render.* 来展示输出:
from shiny.express import input, render, ui

ui.input_text(“text”, label=”Enter some text”)

@render.text
def text_out():
return f”Input text: {input.text()}”
使用 ui.layout_*() 展示不同的布局:
from shiny.express import ui

with ui.layout_column_wrap(gap=”2rem”):
ui.input_slider(“slider1”, “Slider 1”, min=0, max=100, value=50)
ui.input_slider(“slider2”, “Slider 2”, min=0, max=100, value=50)

官网提供了不同的布局示例:

不同类型的控件展示方式:

一个综合示例:
import faicons as fa
import plotly.express as px
from shinywidgets import render_plotly

from shiny import reactive, render, req
from shiny.express import input, ui

Load data and compute static values

tips = px.data.tips()
bill_rng = (min(tips.total_bill), max(tips.total_bill))

Add page title and sidebar

ui.page_opts(title=”Restaurant tipping”, fillable=True)
with ui.sidebar(open=”desktop”):
ui.input_slider(“total_bill”, “Bill amount”, min=bill_rng[0], max=bill_rng[1], value=bill_rng, pre=”$”)
ui.input_checkbox_group(“time”, “Food service”, [“Lunch”, “Dinner”], selected=[“Lunch”, “Dinner”], inline=True)
ui.input_action_button(“reset”, “Reset filter”)

Add main content

ICONS = {
“user”: fa.icon_svg(“user”, “regular”),
“wallet”: fa.icon_svg(“wallet”),
“currency-dollar”: fa.icon_svg(“dollar-sign”),
“gear”: fa.icon_svg(“gear”)
}

with ui.layout_columns(fill=False):

with ui.value_box(showcase=ICONS["user"]):
    "Total tippers"
    @render.express
    def total_tippers():
        tips_data().shape[0]

with ui.value_box(showcase=ICONS["wallet"]):
    "Average tip"
    @render.express
    def average_tip():
        d = tips_data()
        if d.shape[0] > 0:
            perc = d.tip / d.total_bill
            f"{perc.mean():.1%}"

with ui.value_box(showcase=ICONS["currency-dollar"]):
    "Average bill"
    @render.express
    def average_bill():
        d = tips_data()
        if d.shape[0] > 0:
            bill = d.total_bill.mean()
            f"${bill:.2f}"

with ui.layout_columns(col_widths=[6, 6, 12]):

with ui.card(full_screen=True):
    ui.card_header("Tips data")
    @render.data_frame
    def table():
        return render.DataGrid(tips_data())

with ui.card(full_screen=True):
    with ui.card_header(class_="d-flex justify-content-between align-items-center"):
        "Total bill vs tip"
        with ui.popover(title="Add a color variable", placement="top"):
            ICONS["gear"]
            ui.input_radio_buttons(
                "scatter_color", None,
                ["none", "sex", "smoker", "day", "time"],
                inline=True
            )

    @render_plotly
    def scatterplot():
        color = input.scatter_color()
        return px.scatter(
            tips_data(),
            x="total_bill",
            y="tip",
            color=None if color == "none" else color,
            trendline="lowess"
        )

with ui.card(full_screen=True):
    with ui.card_header(class_="d-flex justify-content-between align-items-center"):
        "Tip percentages"
        with ui.popover(title="Add a color variable"):
            ICONS["gear"]
            ui.input_radio_buttons(
                "tip_perc_y", "Split by:",
                ["sex", "smoker", "day", "time"],
                selected="day",
                inline=True
            )

    @render_plotly
    def tip_perc():
        from ridgeplot import ridgeplot
        # Must make a copy of this pandas dataframe before we mutate it!
        # See https://shiny.posit.co/py/docs/reactive-mutable.html
        dat = tips_data().copy()
        dat["percent"] = dat.tip / dat.total_bill
        yvar = input.tip_perc_y()
        uvals = dat[yvar].unique()

        samples = [
            [ dat.percent[dat[yvar] == val] ]
            for val in uvals
        ]

        plt = ridgeplot(
            samples=samples, labels=uvals, bandwidth=0.01,
            colorscale="viridis", colormode="row-index"
        )

        plt.update_layout(
            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5)
        )

        return plt

——————————————————–

Reactive calculations and effects

——————————————————–

@reactive.calc
def tips_data():
bill = input.total_bill()
idx1 = tips.total_bill.between(bill[0], bill[1])
idx2 = tips.time.isin(input.time())
return tips[idx1 & idx2]

@reactive.effect
@reactive.event(input.reset)
def _():
ui.update_slider(“total_bill”, value=bill_rng)
ui.update_checkbox_group(“time”, selected=[“Lunch”, “Dinner”])
点击箭头 run 即可运行 app:


更多内容和学习可以参看官网教程。
结尾


路漫漫其修远兮,吾将上下而求索。
欢迎加入生信交流群。加我微信我也拉你进 微信群聊 老俊俊生信交流群 (微信交流群需收取 20 元入群费用,一旦交费,拒不退还!(防止骗子和便于管理)) 。

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/424648.html

联系我们
联系我们
分享本页
返回顶部