| | import faicons as fa |
| | import plotly.express as px |
| |
|
| | |
| | from shared import app_dir, tips |
| | from shinywidgets import render_plotly |
| |
|
| | from shiny import reactive, render |
| | from shiny.express import input, ui |
| |
|
| | bill_rng = (min(tips.total_bill), max(tips.total_bill)) |
| |
|
| | |
| | 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") |
| |
|
| | |
| | ICONS = { |
| | "user": fa.icon_svg("user", "regular"), |
| | "wallet": fa.icon_svg("wallet"), |
| | "currency-dollar": fa.icon_svg("dollar-sign"), |
| | "ellipsis": fa.icon_svg("ellipsis"), |
| | } |
| |
|
| | 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["ellipsis"] |
| | 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["ellipsis"] |
| | 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 |
| |
|
| | dat = tips_data() |
| | 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 |
| |
|
| |
|
| | ui.include_css(app_dir / "styles.css") |
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | @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"]) |
| |
|