#| '!! shinylive warning !!': |#| shinylive does not work in self-contained HTML documents.#| Please set `embed-resources: false` in your metadata.#| standalone: true#| #| components: [editor, viewer]#| layout: horizontal#| viewerHeight: 500from shiny.express import input, ui# titleui.page_opts(title="Restaurant tipping", fillable=True)# sidebar (empty for now)with ui.sidebar(open="desktop"): "sidebar inputs"# body of application# first row of value boxeswith ui.layout_columns(fill=False): with ui.value_box(): "Total tippers" "Value 1" with ui.value_box(): "Average tip" "Value 2" with ui.value_box(): "Average bill" "Value 3"# second row of cardswith ui.layout_columns(col_widths=[6, 6]): with ui.card(full_screen=True): ui.card_header("Tips data") with ui.card(full_screen=True): ui.card_header("Total bill vs tip")with ui.layout_columns(): with ui.card(full_screen=True): ui.card_header("Tip percentages")
Your turn: Add input components
02:00
#| '!! shinylive warning !!': |#| shinylive does not work in self-contained HTML documents.#| Please set `embed-resources: false` in your metadata.#| standalone: true#| #| components: [editor, viewer]#| layout: horizontal#| viewerHeight: 500from shiny.express import input, ui# titleui.page_opts(title="Restaurant tipping", fillable=True)# sidebarwith ui.sidebar(open="desktop"): ui.input_slider( id="slider", label="Bill amount", min=0, max=100, value=[0, 100], ) ui.input_checkbox_group( id="checkbox_group", label="Food service", choices={ "Lunch": "Lunch", "Dinner": "Dinner", }, selected=[ "Lunch", "Dinner", ], ) ui.input_action_button("action_button", "Reset filter")# body of application# first row of value boxeswith ui.layout_columns(fill=False): with ui.value_box(): "Total tippers" "Value 1" with ui.value_box(): "Average tip" "Value 2" with ui.value_box(): "Average bill" "Value 3"# second row of cardswith ui.layout_columns(col_widths=[6, 6]): with ui.card(full_screen=True): ui.card_header("Tips data") "Tips DataFrame" with ui.card(full_screen=True): ui.card_header("Total bill vs tip") "Scatterplot"with ui.layout_columns(): with ui.card(full_screen=True): ui.card_header("Tip percentages") "ridgeplot"
We can use a @reactive.calc to define and save a value that reacts to inputs
#| '!! shinylive warning !!': |#| shinylive does not work in self-contained HTML documents.#| Please set `embed-resources: false` in your metadata.#| standalone: true#| components: [editor, viewer]#| layout: horizontal#| viewerHeight: 500from shiny import reactivefrom shiny.express import input, render, uiui.input_slider("x", "Slider value", min=0, max=100, value=10)# we need to make a calculation from an input value@render.textdef x_squared_text(): return f"Squared value: {input.x() ** 2}"# we can save this calculation to be used later@reactive.calcdef x_squared(): return input.x() ** 2# we can use that saved calculation@render.textdef x_squared_calc_text(): return f"Saved squared: {x_squared()}"
Reuse reactive calculations
#| '!! shinylive warning !!': |#| shinylive does not work in self-contained HTML documents.#| Please set `embed-resources: false` in your metadata.#| standalone: true#| components: [editor, viewer]#| layout: horizontal#| viewerHeight: 500from shiny import reactivefrom shiny.express import input, render, uiui.input_slider("x", "Slider value", min=0, max=100, value=10)# we need to make a calculation from an input value@render.textdef x_squared_text(): return f"Squared value: {input.x() ** 2}"# we can save this calculation to be used later@reactive.calcdef x_squared(): return input.x() ** 2# we can use that saved calculation@render.textdef x_squared_calc_text(): return f"Saved squared: {x_squared()}"# we can build on top of that saved calculation@render.textdef x_squared_half_calc_text(): return f"Build on squared value: {x_squared() / 2}"# we don't need to re-calculate everything from the input again@render.textdef x_squared_half_text(): return f"Recalculate from input: {input.x() ** 2 / 2}"
Your turn: Filtered tips reactive.calc
05:00
#| '!! shinylive warning !!': |#| shinylive does not work in self-contained HTML documents.#| Please set `embed-resources: false` in your metadata.#| standalone: true#| components: [editor, viewer]#| layout: horizontal#| viewerHeight: 500import pandas as pdimport seaborn as snsfrom shiny.express import input, render, uitips = sns.load_dataset("tips")# titleui.page_opts(title="Restaurant tipping", fillable=True)# sidebarwith ui.sidebar(open="desktop"): ui.input_slider( id="slider", label="Bill amount", min=tips.total_bill.min(), max=tips.total_bill.max(), value=[tips.total_bill.min(), tips.total_bill.max()]) ui.input_checkbox_group( id="checkbox_group", label="Food service", choices={ "Lunch": "Lunch", "Dinner": "Dinner", }, selected=[ "Lunch", "Dinner", ], ) ui.input_action_button("action_button", "Reset filter")# body of application# first row of value boxeswith ui.layout_columns(fill=False): with ui.value_box(): "Total tippers" @render.text def total_tippers(): idx1 = tips.total_bill.between( left=input.slider()[0], right=input.slider()[1], inclusive="both", ) idx2 = tips.time.isin(input.checkbox_group()) tips_filtered = tips[idx1 & idx2] tips_filtered.shape[0]
The application
05:00
#| '!! shinylive warning !!': |#| shinylive does not work in self-contained HTML documents.#| Please set `embed-resources: false` in your metadata.#| standalone: true#| #| components: [editor, viewer]#| layout: horizontal#| viewerHeight: 500import plotly.express as pxfrom ridgeplot import ridgeplotimport seaborn as snsfrom shiny.express import input, ui, renderfrom shiny import reactivefrom shinywidgets import render_plotly, render_widgettips = sns.load_dataset("tips")# titleui.page_opts(title="Restaurant tipping", fillable=True)# sidebarwith ui.sidebar(open="desktop"): ui.input_slider( id="slider", label="Bill amount", min=tips.total_bill.min(), max=tips.total_bill.max(), value=[tips.total_bill.min(), tips.total_bill.max()]) ui.input_checkbox_group( id="checkbox_group", label="Food service", choices={ "Lunch": "Lunch", "Dinner": "Dinner", }, selected=[ "Lunch", "Dinner", ], ) ui.input_action_button("action_button", "Reset filter")@reactive.calcdef filtered_data(): idx1 = tips.total_bill.between( left=input.slider()[0], right=input.slider()[1], inclusive="both", ) idx2 = tips.time.isin(input.checkbox_group()) tips_filtered = tips[idx1 & idx2] return tips_filtered# body of application# first row of value boxeswith ui.layout_columns(fill=False): with ui.value_box(): "Total tippers" @render.text def total_tippers(): return filtered_data().shape[0] with ui.value_box(): "Average tip" @render.text def average_tip(): perc = filtered_data().tip / filtered_data().total_bill return f"{perc.mean():.1%}" with ui.value_box(): "Average bill" @render.text def average_bill(): bill = filtered_data().total_bill.mean() return f"${bill:.2f}"# second row of cardswith ui.layout_columns(col_widths=[6, 6]): with ui.card(full_screen=True): ui.card_header("Tips data") @render.data_frame def tips_data(): return filtered_data() with ui.card(full_screen=True): ui.card_header("Total bill vs tip") @render_plotly def scatterplot(): return px.scatter( filtered_data(), x="total_bill", y="tip", trendline="lowess" )with ui.layout_columns(): with ui.card(full_screen=True): ui.card_header("Tip percentages") @render_widget def ridge(): filtered_data()["percent"] = filtered_data().tip / filtered_data().total_bill uvals = filtered_data().day.unique() samples = [[filtered_data().percent[filtered_data().day == 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