Skip to content

Commit

Permalink
#559 Checkbox to toggle logarithmic / linear scale
Browse files Browse the repository at this point in the history
#353 Adds Log Scale Checkbox in Sidebar
  • Loading branch information
BrianThomasRoss authored Apr 29, 2020
2 parents 45aeb49 + 75e5d1e commit 2c0d471
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 41 deletions.
41 changes: 31 additions & 10 deletions src/penn_chime/model/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def validate(string):
"ventilated": ValDisposition,
"hospitalized": ValDisposition,
"icu": ValDisposition,
"use_log_scale": OptionalValue
}


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


ARGS = (
(
"parameters",
str,
None,
None,
False,
None, # Min value
None, # Max value
False, # Whether it is required or optional.
),
(
"current_hospitalized",
Expand Down Expand Up @@ -298,15 +300,24 @@ def validate(string):
1.0,
True,
),
(
"use_log_scale",
bool,
None,
None,
False
)
)


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


class Parameters:
"""Parameters."""
"""
Object containing all of the parameters that can be adjusted by the user, either from the command line or using
the side bar of the web app.
"""

@classmethod
def parser(cls):
Expand All @@ -315,11 +326,20 @@ def parser(cls):

for name, cast, min_value, max_value, required in ARGS:
arg = to_cli(name)
parser.add_argument(
arg,
type=validator(arg, cast, min_value, max_value, required),
help=HELP.get(name),
)
if cast == bool:
# This argument is a command-line flag and does not need validation.
parser.add_argument(
arg,
action='store_true',
help=HELP.get(name),
)
else:
# Use a custom validator for any arguments that take in values.
parser.add_argument(
arg,
type=validator(arg, cast, min_value, max_value, required),
help=HELP.get(name),
)
return parser

@classmethod
Expand Down Expand Up @@ -396,6 +416,7 @@ def __init__(self, **kwargs):
self.relative_contact_rate = None
self.recovered = None
self.ventilated = None
self.use_log_scale = False

passed_and_default_parameters = {}
for key, value in kwargs.items():
Expand Down
108 changes: 89 additions & 19 deletions src/penn_chime/view/charts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Dict, Optional

from altair import Chart
from altair import Chart, Scale
import pandas as pd
import i18n
import numpy as np
Expand All @@ -9,12 +9,24 @@


def build_admits_chart(
*, alt, admits_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None
*, alt, admits_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None, use_log_scale: bool = False
) -> Chart:
"""Build admits chart."""
y_scale = alt.Scale()
if max_y_axis is not None:
y_scale.domain = (0, max_y_axis)
"""
This builds the "New Admissions" chart, projecting daily admissions over time.
Args:
alt: Reference to Altair package.
admits_floor_df: Pandas data frame containing three columns: "admits_hospitalized", "admits_icu", and
"admits_ventilated".
max_y_axis: Optional maximum value for the Y axis of the chart.
use_log_scale: Set to true to use a logarithmic scale on the Y axis. Default is linear scale.
Returns: The newly created chart.
"""

adjusted_admits_floor_df = __adjust_data_for_log_scale(admits_floor_df) if use_log_scale else admits_floor_df
y_scale = __build_y_scale(alt, max_y_axis, use_log_scale)

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


def build_census_chart(
*, alt, census_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None
*, alt, census_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None, use_log_scale: bool = False
) -> Chart:
"""Build census chart."""
y_scale = alt.Scale()
if max_y_axis:
y_scale.domain = (0, max_y_axis)
"""
This builds the "Admitted Patients" census chart, projecting total number of patients in the hospital over time.
Args:
alt: Reference to Altair package.
census_floor_df: Pandas data frame containing three columns: "census_hospitalized", "census_icu", and
"census_ventilated".
max_y_axis: Optional maximum value for the Y axis of the chart.
use_log_scale: Set to true to use a logarithmic scale on the Y axis. Default is linear scale.
Returns: The newly created chart.
"""

adjusted_census_floor_df = __adjust_data_for_log_scale(census_floor_df) if use_log_scale else census_floor_df
y_scale = __build_y_scale(alt, max_y_axis, use_log_scale)

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


def build_sim_sir_w_date_chart(
*, alt, sim_sir_w_date_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None
*, alt, sim_sir_w_date_floor_df: pd.DataFrame, max_y_axis: Optional[int] = None, use_log_scale: bool = False
) -> Chart:
"""Build sim sir w date chart."""
y_scale = alt.Scale()
if max_y_axis is not None:
y_scale.domain = (0, max_y_axis)
"""
This builds the "Susceptible, Infected, and Recovered" chart, projecting the number of those individuals in the
hospital's region over time.
Args:
alt: Reference to the Altair package.
sim_sir_w_date_floor_df: A Pandas data frame with columns named "susceptible", "infected", and "recovered".
max_y_axis: Optional maximum value for the Y axis of the chart.
use_log_scale: Set to true to use a logarithmic scale on the Y axis. Default is linear scale.
Returns: The newly created chart.
"""

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
y_scale = __build_y_scale(alt, max_y_axis, use_log_scale)

x = dict(shorthand="date:T", title=i18n.t("charts-date"), axis=alt.Axis(format=(DATE_FORMAT)))
y = dict(shorthand="value:Q", title=i18n.t("charts-count"), scale=y_scale)
Expand All @@ -128,7 +164,7 @@ def build_sim_sir_w_date_chart(
.transform_filter(alt.datum.day == 0)
.mark_rule(color="black", opacity=0.35, size=2)
)
sim_sir_w_date_floor_df_renamed = sim_sir_w_date_floor_df.rename({
sim_sir_w_date_floor_df_renamed = adjusted_sim_sir_w_date_floor_df.rename({
"susceptible": i18n.t("susceptible"),
"infected": i18n.t("infected"),
"recovered": i18n.t("recovered")
Expand All @@ -146,3 +182,37 @@ def build_table(
table_df.date = table_df.date.dt.strftime(DATE_FORMAT)
table_df_renamed = table_df.rename(labels, axis=1)
return table_df_renamed


def __adjust_data_for_log_scale(dataframe: pd.DataFrame) -> pd.DataFrame:
"""
This will clean and adjust some of the data so that Altair can plot it using a logarithmic scale. Altair does not
allow zero values on the Y axis when plotting with a logarithmic scale, as log(0) is undefined.
Args:
dataframe: The data to plot on the chart.
Returns: A new data frame with the appropriate adjustments for plotting on a log scale.
"""
return dataframe.replace(0, float('nan')) # We use NaN so that the values will not appear at all on the chart.


def __build_y_scale(alt, max_y_axis: Optional[int] = None, use_log_scale: bool = False) -> Scale:
"""
Creates the Y axis of the chart, taking into account some of the configuration parameters set by the user.
Args:
alt: Reference to Altair package.
max_y_axis: The maximum value of the Y axis. This is optional.
use_log_scale: Whether to use a logarithmic scale instead of a linear scale.
Returns: A newly created Scale instance.
"""
scale_type = 'log' if use_log_scale else 'linear'
y_scale = alt.Scale(type=scale_type)
if max_y_axis is not None:
y_scale.domain = (0, max_y_axis)

return y_scale
9 changes: 6 additions & 3 deletions src/penn_chime/view/st_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def main():

st.subheader(i18n.t("app-new-admissions-title"))
st.markdown(i18n.t("app-new-admissions-text"))
admits_chart = build_admits_chart(alt=alt, admits_floor_df=m.admits_floor_df, max_y_axis=p.max_y_axis)
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)

st.altair_chart(admits_chart, use_container_width=True)
display_download_link(
st,
Expand All @@ -60,7 +61,8 @@ def main():

st.subheader(i18n.t("app-admitted-patients-title"))
st.markdown(i18n.t("app-admitted-patients-text"))
census_chart = build_census_chart(alt=alt, census_floor_df=m.census_floor_df, max_y_axis=p.max_y_axis)
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)

st.altair_chart(census_chart, use_container_width=True)
display_download_link(
st,
Expand Down Expand Up @@ -93,7 +95,8 @@ def main():

st.subheader(i18n.t("app-SIR-title"))
st.markdown(i18n.t("app-SIR-text"))
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)
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)

st.altair_chart(sim_sir_w_date_chart, use_container_width=True)
display_download_link(
st,
Expand Down
15 changes: 9 additions & 6 deletions src/penn_chime/view/st_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ def __init__(self, st_obj, label, value=None, key=None):


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

st_obj = st.sidebar
# used_widget_key = st.get_last_used_widget_key ( )
Expand Down Expand Up @@ -361,7 +361,9 @@ def display_sidebar(st, d: Parameters) -> Parameters:
max_y_axis = max_y_axis_input()

current_date = current_date_input()
#Subscribe implementation
use_log_scale = st.sidebar.checkbox(label="Use logarithmic scale on charts instead of linear scale.", value=d.use_log_scale)

# Subscribe implementation
subscribe(st_obj)

return Parameters(
Expand All @@ -386,9 +388,10 @@ def display_sidebar(st, d: Parameters) -> Parameters:
ventilated=Disposition.create(
rate=ventilated_rate,
days=ventilated_days),
use_log_scale=use_log_scale
)

#Read the environment variables and cteate json key object to use with ServiceAccountCredentials
# Read the environment variables and create json key object to use with ServiceAccountCredentials
def readGoogleApiSecrets():
client_secret = {}
os.getenv
Expand Down
Loading

0 comments on commit 2c0d471

Please sign in to comment.