app.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import faicons as fa
  2. import plotly.express as px
  3. # Load data and compute static values
  4. from shared import app_dir, tips
  5. from shinywidgets import render_plotly
  6. from shiny import reactive, render
  7. from shiny.express import input, ui
  8. bill_rng = (min(tips.total_bill), max(tips.total_bill))
  9. # Add page title and sidebar
  10. ui.page_opts(title="Restaurant tipping", fillable=True)
  11. with ui.sidebar(open="desktop"):
  12. ui.input_slider(
  13. "total_bill",
  14. "Bill amount",
  15. min=bill_rng[0],
  16. max=bill_rng[1],
  17. value=bill_rng,
  18. pre="$",
  19. )
  20. ui.input_checkbox_group(
  21. "time",
  22. "Food service",
  23. ["Lunch", "Dinner"],
  24. selected=["Lunch", "Dinner"],
  25. inline=True,
  26. )
  27. ui.input_action_button("reset", "Reset filter")
  28. # Add main content
  29. ICONS = {
  30. "user": fa.icon_svg("user", "regular"),
  31. "wallet": fa.icon_svg("wallet"),
  32. "currency-dollar": fa.icon_svg("dollar-sign"),
  33. "ellipsis": fa.icon_svg("ellipsis"),
  34. }
  35. with ui.layout_columns(fill=False):
  36. with ui.value_box(showcase=ICONS["user"]):
  37. "Total tippers"
  38. @render.express
  39. def total_tippers():
  40. tips_data().shape[0]
  41. with ui.value_box(showcase=ICONS["wallet"]):
  42. "Average tip"
  43. @render.express
  44. def average_tip():
  45. d = tips_data()
  46. if d.shape[0] > 0:
  47. perc = d.tip / d.total_bill
  48. f"{perc.mean():.1%}"
  49. with ui.value_box(showcase=ICONS["currency-dollar"]):
  50. "Average bill"
  51. @render.express
  52. def average_bill():
  53. d = tips_data()
  54. if d.shape[0] > 0:
  55. bill = d.total_bill.mean()
  56. f"${bill:.2f}"
  57. with ui.layout_columns(col_widths=[6, 6, 12]):
  58. with ui.card(full_screen=True):
  59. ui.card_header("Tips data")
  60. @render.data_frame
  61. def table():
  62. return render.DataGrid(tips_data())
  63. with ui.card(full_screen=True):
  64. with ui.card_header(class_="d-flex justify-content-between align-items-center"):
  65. "Total bill vs tip"
  66. with ui.popover(title="Add a color variable", placement="top"):
  67. ICONS["ellipsis"]
  68. ui.input_radio_buttons(
  69. "scatter_color",
  70. None,
  71. ["none", "sex", "smoker", "day", "time"],
  72. inline=True,
  73. )
  74. @render_plotly
  75. def scatterplot():
  76. color = input.scatter_color()
  77. return px.scatter(
  78. tips_data(),
  79. x="total_bill",
  80. y="tip",
  81. color=None if color == "none" else color,
  82. trendline="lowess",
  83. )
  84. with ui.card(full_screen=True):
  85. with ui.card_header(class_="d-flex justify-content-between align-items-center"):
  86. "Tip percentages"
  87. with ui.popover(title="Add a color variable"):
  88. ICONS["ellipsis"]
  89. ui.input_radio_buttons(
  90. "tip_perc_y",
  91. "Split by:",
  92. ["sex", "smoker", "day", "time"],
  93. selected="day",
  94. inline=True,
  95. )
  96. @render_plotly
  97. def tip_perc():
  98. from ridgeplot import ridgeplot
  99. dat = tips_data()
  100. dat["percent"] = dat.tip / dat.total_bill
  101. yvar = input.tip_perc_y()
  102. uvals = dat[yvar].unique()
  103. samples = [[dat.percent[dat[yvar] == val]] for val in uvals]
  104. plt = ridgeplot(
  105. samples=samples,
  106. labels=uvals,
  107. bandwidth=0.01,
  108. colorscale="viridis",
  109. colormode="row-index",
  110. )
  111. plt.update_layout(
  112. legend=dict(
  113. orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5
  114. )
  115. )
  116. return plt
  117. ui.include_css(app_dir / "styles.css")
  118. # --------------------------------------------------------
  119. # Reactive calculations and effects
  120. # --------------------------------------------------------
  121. @reactive.calc
  122. def tips_data():
  123. bill = input.total_bill()
  124. idx1 = tips.total_bill.between(bill[0], bill[1])
  125. idx2 = tips.time.isin(input.time())
  126. return tips[idx1 & idx2]
  127. @reactive.effect
  128. @reactive.event(input.reset)
  129. def _():
  130. ui.update_slider("total_bill", value=bill_rng)
  131. ui.update_checkbox_group("time", selected=["Lunch", "Dinner"])