Skip to content

Commit 2c0d471

Browse files
#559 Checkbox to toggle logarithmic / linear scale
#353 Adds Log Scale Checkbox in Sidebar
2 parents 45aeb49 + 75e5d1e commit 2c0d471

File tree

5 files changed

+198
-41
lines changed

5 files changed

+198
-41
lines changed

src/penn_chime/model/parameters.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def validate(string):
137137
"ventilated": ValDisposition,
138138
"hospitalized": ValDisposition,
139139
"icu": ValDisposition,
140+
"use_log_scale": OptionalValue
140141
}
141142

142143

@@ -161,16 +162,17 @@ def validate(string):
161162
"relative_contact_rate": "Social distancing reduction rate: 0.0 - 1.0",
162163
"ventilated_days": "Average days on ventilator",
163164
"ventilated_rate": "Ventilated Rate: 0.0 - 1.0",
165+
"use_log_scale": "Flag to use logarithmic scale on charts instead of linear scale."
164166
}
165167

166168

167169
ARGS = (
168170
(
169171
"parameters",
170172
str,
171-
None,
172-
None,
173-
False,
173+
None, # Min value
174+
None, # Max value
175+
False, # Whether it is required or optional.
174176
),
175177
(
176178
"current_hospitalized",
@@ -298,15 +300,24 @@ def validate(string):
298300
1.0,
299301
True,
300302
),
303+
(
304+
"use_log_scale",
305+
bool,
306+
None,
307+
None,
308+
False
309+
)
301310
)
302311

303312

304313
def to_cli(name):
305314
return "--" + name.replace('_', '-')
306315

307-
308316
class Parameters:
309-
"""Parameters."""
317+
"""
318+
Object containing all of the parameters that can be adjusted by the user, either from the command line or using
319+
the side bar of the web app.
320+
"""
310321

311322
@classmethod
312323
def parser(cls):
@@ -315,11 +326,20 @@ def parser(cls):
315326

316327
for name, cast, min_value, max_value, required in ARGS:
317328
arg = to_cli(name)
318-
parser.add_argument(
319-
arg,
320-
type=validator(arg, cast, min_value, max_value, required),
321-
help=HELP.get(name),
322-
)
329+
if cast == bool:
330+
# This argument is a command-line flag and does not need validation.
331+
parser.add_argument(
332+
arg,
333+
action='store_true',
334+
help=HELP.get(name),
335+
)
336+
else:
337+
# Use a custom validator for any arguments that take in values.
338+
parser.add_argument(
339+
arg,
340+
type=validator(arg, cast, min_value, max_value, required),
341+
help=HELP.get(name),
342+
)
323343
return parser
324344

325345
@classmethod
@@ -396,6 +416,7 @@ def __init__(self, **kwargs):
396416
self.relative_contact_rate = None
397417
self.recovered = None
398418
self.ventilated = None
419+
self.use_log_scale = False
399420

400421
passed_and_default_parameters = {}
401422
for key, value in kwargs.items():

src/penn_chime/view/charts.py

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Dict, Optional
22

3-
from altair import Chart
3+
from altair import Chart, Scale
44
import pandas as pd
55
import i18n
66
import numpy as np
@@ -9,12 +9,24 @@
99

1010

1111
def build_admits_chart(
12-
*, alt, admits_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None
12+
*, alt, admits_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None, use_log_scale: bool = False
1313
) -> Chart:
14-
"""Build admits chart."""
15-
y_scale = alt.Scale()
16-
if max_y_axis is not None:
17-
y_scale.domain = (0, max_y_axis)
14+
"""
15+
This builds the "New Admissions" chart, projecting daily admissions over time.
16+
17+
Args:
18+
alt: Reference to Altair package.
19+
admits_floor_df: Pandas data frame containing three columns: "admits_hospitalized", "admits_icu", and
20+
"admits_ventilated".
21+
max_y_axis: Optional maximum value for the Y axis of the chart.
22+
use_log_scale: Set to true to use a logarithmic scale on the Y axis. Default is linear scale.
23+
24+
Returns: The newly created chart.
25+
26+
"""
27+
28+
adjusted_admits_floor_df = __adjust_data_for_log_scale(admits_floor_df) if use_log_scale else admits_floor_df
29+
y_scale = __build_y_scale(alt, max_y_axis, use_log_scale)
1830

1931
x = dict(shorthand="date:T", title=i18n.t("charts-date"), axis=alt.Axis(format=(DATE_FORMAT)))
2032
y = dict(shorthand="value:Q", title=i18n.t("charts-daily-admissions"), scale=y_scale)
@@ -40,7 +52,7 @@ def build_admits_chart(
4052
.transform_filter(alt.datum.day == 0)
4153
.mark_rule(color="black", opacity=0.35, size=2)
4254
)
43-
admits_floor_df_renamed = admits_floor_df.rename({
55+
admits_floor_df_renamed = adjusted_admits_floor_df.rename({
4456
"admits_hospitalized": i18n.t("admits_hospitalized"),
4557
"admits_icu": i18n.t("admits_icu"),
4658
"admits_ventilated": i18n.t("admits_ventilated")
@@ -53,12 +65,24 @@ def build_admits_chart(
5365

5466

5567
def build_census_chart(
56-
*, alt, census_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None
68+
*, alt, census_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None, use_log_scale: bool = False
5769
) -> Chart:
58-
"""Build census chart."""
59-
y_scale = alt.Scale()
60-
if max_y_axis:
61-
y_scale.domain = (0, max_y_axis)
70+
"""
71+
This builds the "Admitted Patients" census chart, projecting total number of patients in the hospital over time.
72+
73+
Args:
74+
alt: Reference to Altair package.
75+
census_floor_df: Pandas data frame containing three columns: "census_hospitalized", "census_icu", and
76+
"census_ventilated".
77+
max_y_axis: Optional maximum value for the Y axis of the chart.
78+
use_log_scale: Set to true to use a logarithmic scale on the Y axis. Default is linear scale.
79+
80+
Returns: The newly created chart.
81+
82+
"""
83+
84+
adjusted_census_floor_df = __adjust_data_for_log_scale(census_floor_df) if use_log_scale else census_floor_df
85+
y_scale = __build_y_scale(alt, max_y_axis, use_log_scale)
6286

6387
x = dict(shorthand="date:T", title=i18n.t("charts-date"), axis=alt.Axis(format=(DATE_FORMAT)))
6488
y = dict(shorthand="value:Q", title=i18n.t("charts-census"), scale=y_scale)
@@ -84,7 +108,7 @@ def build_census_chart(
84108
.transform_filter(alt.datum.day == 0)
85109
.mark_rule(color="black", opacity=0.35, size=2)
86110
)
87-
census_floor_df_renamed = census_floor_df.rename({
111+
census_floor_df_renamed = adjusted_census_floor_df.rename({
88112
"census_hospitalized": i18n.t("census_hospitalized"),
89113
"census_icu": i18n.t("census_icu"),
90114
"census_ventilated": i18n.t("census_ventilated")
@@ -97,12 +121,24 @@ def build_census_chart(
97121

98122

99123
def build_sim_sir_w_date_chart(
100-
*, alt, sim_sir_w_date_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None
124+
*, alt, sim_sir_w_date_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None, use_log_scale: bool = False
101125
) -> Chart:
102-
"""Build sim sir w date chart."""
103-
y_scale = alt.Scale()
104-
if max_y_axis is not None:
105-
y_scale.domain = (0, max_y_axis)
126+
"""
127+
This builds the "Susceptible, Infected, and Recovered" chart, projecting the number of those individuals in the
128+
hospital's region over time.
129+
130+
Args:
131+
alt: Reference to the Altair package.
132+
sim_sir_w_date_floor_df: A Pandas data frame with columns named "susceptible", "infected", and "recovered".
133+
max_y_axis: Optional maximum value for the Y axis of the chart.
134+
use_log_scale: Set to true to use a logarithmic scale on the Y axis. Default is linear scale.
135+
136+
Returns: The newly created chart.
137+
138+
"""
139+
140+
adjusted_sim_sir_w_date_floor_df = __adjust_data_for_log_scale(sim_sir_w_date_floor_df) if use_log_scale else sim_sir_w_date_floor_df
141+
y_scale = __build_y_scale(alt, max_y_axis, use_log_scale)
106142

107143
x = dict(shorthand="date:T", title=i18n.t("charts-date"), axis=alt.Axis(format=(DATE_FORMAT)))
108144
y = dict(shorthand="value:Q", title=i18n.t("charts-count"), scale=y_scale)
@@ -128,7 +164,7 @@ def build_sim_sir_w_date_chart(
128164
.transform_filter(alt.datum.day == 0)
129165
.mark_rule(color="black", opacity=0.35, size=2)
130166
)
131-
sim_sir_w_date_floor_df_renamed = sim_sir_w_date_floor_df.rename({
167+
sim_sir_w_date_floor_df_renamed = adjusted_sim_sir_w_date_floor_df.rename({
132168
"susceptible": i18n.t("susceptible"),
133169
"infected": i18n.t("infected"),
134170
"recovered": i18n.t("recovered")
@@ -146,3 +182,37 @@ def build_table(
146182
table_df.date = table_df.date.dt.strftime(DATE_FORMAT)
147183
table_df_renamed = table_df.rename(labels, axis=1)
148184
return table_df_renamed
185+
186+
187+
def __adjust_data_for_log_scale(dataframe: pd.DataFrame) -> pd.DataFrame:
188+
"""
189+
This will clean and adjust some of the data so that Altair can plot it using a logarithmic scale. Altair does not
190+
allow zero values on the Y axis when plotting with a logarithmic scale, as log(0) is undefined.
191+
192+
Args:
193+
dataframe: The data to plot on the chart.
194+
195+
Returns: A new data frame with the appropriate adjustments for plotting on a log scale.
196+
197+
"""
198+
return dataframe.replace(0, float('nan')) # We use NaN so that the values will not appear at all on the chart.
199+
200+
201+
def __build_y_scale(alt, max_y_axis: Optional[int] = None, use_log_scale: bool = False) -> Scale:
202+
"""
203+
Creates the Y axis of the chart, taking into account some of the configuration parameters set by the user.
204+
205+
Args:
206+
alt: Reference to Altair package.
207+
max_y_axis: The maximum value of the Y axis. This is optional.
208+
use_log_scale: Whether to use a logarithmic scale instead of a linear scale.
209+
210+
Returns: A newly created Scale instance.
211+
212+
"""
213+
scale_type = 'log' if use_log_scale else 'linear'
214+
y_scale = alt.Scale(type=scale_type)
215+
if max_y_axis is not None:
216+
y_scale.domain = (0, max_y_axis)
217+
218+
return y_scale

src/penn_chime/view/st_app.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ def main():
4949

5050
st.subheader(i18n.t("app-new-admissions-title"))
5151
st.markdown(i18n.t("app-new-admissions-text"))
52-
admits_chart = build_admits_chart(alt=alt, admits_floor_df=m.admits_floor_df, max_y_axis=p.max_y_axis)
52+
admits_chart = build_admits_chart(alt=alt, admits_floor_df=m.admits_floor_df, max_y_axis=p.max_y_axis, use_log_scale=p.use_log_scale)
53+
5354
st.altair_chart(admits_chart, use_container_width=True)
5455
display_download_link(
5556
st,
@@ -60,7 +61,8 @@ def main():
6061

6162
st.subheader(i18n.t("app-admitted-patients-title"))
6263
st.markdown(i18n.t("app-admitted-patients-text"))
63-
census_chart = build_census_chart(alt=alt, census_floor_df=m.census_floor_df, max_y_axis=p.max_y_axis)
64+
census_chart = build_census_chart(alt=alt, census_floor_df=m.census_floor_df, max_y_axis=p.max_y_axis, use_log_scale=p.use_log_scale)
65+
6466
st.altair_chart(census_chart, use_container_width=True)
6567
display_download_link(
6668
st,
@@ -93,7 +95,8 @@ def main():
9395

9496
st.subheader(i18n.t("app-SIR-title"))
9597
st.markdown(i18n.t("app-SIR-text"))
96-
sim_sir_w_date_chart = build_sim_sir_w_date_chart(alt=alt, sim_sir_w_date_floor_df=m.sim_sir_w_date_floor_df)
98+
sim_sir_w_date_chart = build_sim_sir_w_date_chart(alt=alt, sim_sir_w_date_floor_df=m.sim_sir_w_date_floor_df, use_log_scale=p.use_log_scale)
99+
97100
st.altair_chart(sim_sir_w_date_chart, use_container_width=True)
98101
display_download_link(
99102
st,

src/penn_chime/view/st_display.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ def __init__(self, st_obj, label, value=None, key=None):
166166

167167

168168
def display_sidebar(st, d: Parameters) -> Parameters:
169-
# Initialize variables
170-
# these functions create input elements and bind the values they are set to
171-
# to the variables they are set equal to
172-
# it's kindof like ember or angular if you are familiar with those
169+
"""
170+
Initializes the UI in the sidebar. These function calls create input elements, and bind the values they are set to
171+
to the appropriate variables. It's similar to Ember or Angular, if you are familiar with those frameworks.
172+
"""
173173

174174
st_obj = st.sidebar
175175
# used_widget_key = st.get_last_used_widget_key ( )
@@ -361,7 +361,9 @@ def display_sidebar(st, d: Parameters) -> Parameters:
361361
max_y_axis = max_y_axis_input()
362362

363363
current_date = current_date_input()
364-
#Subscribe implementation
364+
use_log_scale = st.sidebar.checkbox(label="Use logarithmic scale on charts instead of linear scale.", value=d.use_log_scale)
365+
366+
# Subscribe implementation
365367
subscribe(st_obj)
366368

367369
return Parameters(
@@ -386,9 +388,10 @@ def display_sidebar(st, d: Parameters) -> Parameters:
386388
ventilated=Disposition.create(
387389
rate=ventilated_rate,
388390
days=ventilated_days),
391+
use_log_scale=use_log_scale
389392
)
390393

391-
#Read the environment variables and cteate json key object to use with ServiceAccountCredentials
394+
# Read the environment variables and create json key object to use with ServiceAccountCredentials
392395
def readGoogleApiSecrets():
393396
client_secret = {}
394397
os.getenv

0 commit comments

Comments
 (0)