|
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"]) |
|
|