Skip to content

Commit 6af5522

Browse files
authored
Merge pull request #182 from CodeForPhilly/112-model
112 model
2 parents d621775 + 7068329 commit 6af5522

File tree

7 files changed

+291
-303
lines changed

7 files changed

+291
-303
lines changed

src/app.py

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
"""App."""
22

3-
import altair as alt
4-
import streamlit as st
3+
import altair as alt # type: ignore
4+
import streamlit as st # type: ignore
55

66
from penn_chime.presentation import (
7-
additional_projections_chart,
8-
admitted_patients_chart,
7+
#additional_projections_chart,
98
display_header,
109
display_sidebar,
1110
display_n_days_slider,
1211
draw_census_table,
1312
draw_projected_admissions_table,
1413
draw_raw_sir_simulation_table,
1514
hide_menu_style,
16-
new_admissions_chart,
1715
show_additional_projections,
1816
show_more_info_about_this_tool,
1917
write_definitions,
2018
write_footer,
2119
)
22-
from penn_chime.utils import build_admissions_df, build_census_df
23-
from penn_chime.settings import DEFAULTS
24-
25-
20+
from penn_chime.settings import DEFAULTS
21+
from penn_chime.models import sim_sir_df, build_admissions_df, build_census_df
22+
from penn_chime.charts import additional_projections_chart, admitted_patients_chart, new_admissions_chart
2623
# This is somewhat dangerous:
2724
# Hide the main menu with "Rerun", "run on Save", "clear cache", and "record a screencast"
2825
# This should not be hidden in prod, but removed
@@ -31,35 +28,15 @@
3128

3229
p = display_sidebar(st, DEFAULTS)
3330

34-
display_header(
35-
st,
36-
total_infections=p.infected,
37-
initial_infections=p.known_infected,
38-
detection_prob=p.detection_probability,
39-
current_hosp=p.current_hospitalized,
40-
hosp_rate=p.hospitalized.rate,
41-
S=p.susceptible,
42-
market_share=p.market_share,
43-
recovery_days=p.recovery_days,
44-
r_naught=p.r_naught,
45-
doubling_time=p.doubling_time,
46-
relative_contact_rate=p.relative_contact_rate,
47-
r_t=p.r_t,
48-
doubling_time_t=p.doubling_time_t,
49-
)
31+
display_header(st, p)
32+
5033
if st.checkbox("Show more info about this tool"):
5134
notes = "The total size of the susceptible population will be the entire catchment area for Penn Medicine entities (HUP, PAH, PMC, CCH)"
5235
show_more_info_about_this_tool(
5336
st=st,
54-
recovery_days=p.recovery_days,
55-
doubling_time=p.doubling_time,
56-
r_naught=p.r_naught,
57-
relative_contact_rate=p.relative_contact_rate,
58-
doubling_time_t=p.doubling_time_t,
59-
r_t=p.r_t,
37+
parameters=p,
6038
inputs=DEFAULTS,
6139
notes=notes
62-
6340
)
6441

6542
# PRESENTATION
@@ -68,30 +45,36 @@
6845
display_n_days_slider(st, p, DEFAULTS)
6946

7047
# begin format data
71-
projection_admits = build_admissions_df(p.n_days, *p.dispositions)
72-
census_df = build_census_df(projection_admits, *p.lengths_of_stay)
48+
admissions_df = build_admissions_df(p=p) # p.n_days, *p.dispositions)
49+
census_df = build_census_df(admissions_df, parameters=p)
7350
# end format data
7451

7552
st.subheader("New Admissions")
7653
st.markdown("Projected number of **daily** COVID-19 admissions at Penn hospitals")
7754
st.altair_chart(
78-
new_admissions_chart(alt, projection_admits, p.n_days - 10, as_date=as_date, max_y_axis=p.max_y_axis), use_container_width=True
55+
new_admissions_chart(alt, admissions_df, parameters=p, as_date=as_date), use_container_width=True
7956
)
8057
if st.checkbox("Show Projected Admissions in tabular form"):
81-
draw_projected_admissions_table(st, projection_admits, as_date=as_date)
58+
draw_projected_admissions_table(st, admissions_df, as_date=as_date)
8259
st.subheader("Admitted Patients (Census)")
8360
st.markdown(
8461
"Projected **census** of COVID-19 patients, accounting for arrivals and discharges at Penn hospitals"
8562
)
86-
st.altair_chart(admitted_patients_chart(alt, census_df, p.n_days - 10, as_date=as_date, max_y_axis=p.max_y_axis), use_container_width=True)
63+
st.altair_chart(
64+
admitted_patients_chart(alt=alt, census=census_df, parameters=p, as_date=as_date), use_container_width=True
65+
)
8766
if st.checkbox("Show Projected Census in tabular form"):
8867
draw_census_table(st, census_df, as_date=as_date)
8968
st.markdown(
9069
"""**Click the checkbox below to view additional data generated by this simulation**"""
9170
)
9271
if st.checkbox("Show Additional Projections"):
93-
show_additional_projections(st, alt, additional_projections_chart, p.infected_v, p.recovered_v, as_date=as_date, max_y_axis=p.max_y_axis)
72+
show_additional_projections(
73+
st, alt,
74+
additional_projections_chart,
75+
parameters=p,
76+
as_date=as_date)
9477
if st.checkbox("Show Raw SIR Simulation Data"):
95-
draw_raw_sir_simulation_table(st, p.n_days, p.susceptible_v, p.infected_v, p.recovered_v, as_date=as_date)
78+
draw_raw_sir_simulation_table(st, parameters=p, as_date=as_date)
9679
write_definitions(st)
9780
write_footer(st)

src/penn_chime/charts.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
2+
from altair import Chart # type: ignore
3+
import pandas as pd # type: ignore
4+
import numpy as np # type: ignore
5+
6+
from .parameters import Parameters
7+
from .utils import add_date_column
8+
9+
10+
def new_admissions_chart(
11+
alt,
12+
projection_admits: pd.DataFrame,
13+
parameters: Parameters,
14+
as_date: bool = False,
15+
) -> Chart:
16+
"""docstring"""
17+
plot_projection_days = parameters.n_days - 10
18+
max_y_axis = parameters.max_y_axis
19+
20+
y_scale = alt.Scale()
21+
22+
if max_y_axis is not None:
23+
y_scale.domain = (0, max_y_axis)
24+
y_scale.clamp = True
25+
26+
tooltip_dict = {False: "day", True: "date:T"}
27+
if as_date:
28+
projection_admits = add_date_column(projection_admits)
29+
x_kwargs = {"shorthand": "date:T", "title": "Date"}
30+
else:
31+
x_kwargs = {"shorthand": "day", "title": "Days from today"}
32+
33+
return (
34+
alt.Chart(projection_admits.head(plot_projection_days))
35+
.transform_fold(fold=["Hospitalized", "ICU", "Ventilated"])
36+
.mark_line(point=True)
37+
.encode(
38+
x=alt.X(**x_kwargs),
39+
y=alt.Y("value:Q", title="Daily admissions", scale=y_scale),
40+
color="key:N",
41+
tooltip=[
42+
tooltip_dict[as_date],
43+
alt.Tooltip("value:Q", format=".0f", title="Admissions"),
44+
"key:N",
45+
],
46+
)
47+
.interactive()
48+
)
49+
50+
51+
def admitted_patients_chart(
52+
alt,
53+
census: pd.DataFrame,
54+
parameters: Parameters,
55+
as_date: bool = False
56+
) -> Chart:
57+
"""docstring"""
58+
59+
plot_projection_days = parameters.n_days - 10
60+
max_y_axis = parameters.max_y_axis
61+
if as_date:
62+
census = add_date_column(census)
63+
x_kwargs = {"shorthand": "date:T", "title": "Date"}
64+
idx = "date:T"
65+
else:
66+
x_kwargs ={"shorthand": "day", "title": "Days from today"}
67+
idx = "day"
68+
69+
y_scale = alt.Scale()
70+
71+
if max_y_axis:
72+
y_scale.domain = (0, max_y_axis)
73+
y_scale.clamp = True
74+
75+
return (
76+
alt.Chart(census.head(plot_projection_days))
77+
.transform_fold(fold=["Hospitalized Census", "ICU Census", "Ventilated Census"])
78+
.mark_line(point=True)
79+
.encode(
80+
x=alt.X(**x_kwargs),
81+
y=alt.Y("value:Q", title="Census", scale=y_scale),
82+
color="key:N",
83+
tooltip=[
84+
idx,
85+
alt.Tooltip("value:Q", format=".0f", title="Census"),
86+
"key:N",
87+
],
88+
)
89+
.interactive()
90+
)
91+
92+
93+
def additional_projections_chart(
94+
alt,
95+
i: np.ndarray,
96+
r: np.ndarray,
97+
as_date: bool = False,
98+
max_y_axis: int = None
99+
) -> Chart:
100+
dat = pd.DataFrame({"Infected": i, "Recovered": r})
101+
dat["day"] = dat.index
102+
if as_date:
103+
dat = add_date_column(dat)
104+
x_kwargs = {"shorthand": "date:T", "title": "Date"}
105+
else:
106+
x_kwargs = {"shorthand": "day", "title": "Days from today"}
107+
108+
y_scale = alt.Scale()
109+
110+
if max_y_axis is not None:
111+
y_scale.domain = (0, max_y_axis)
112+
y_scale.clamp = True
113+
114+
return (
115+
alt.Chart(dat)
116+
.transform_fold(fold=["Infected", "Recovered"])
117+
.mark_line()
118+
.encode(
119+
x=alt.X(**x_kwargs),
120+
y=alt.Y("value:Q", title="Case Volume", scale=y_scale),
121+
tooltip=["key:N", "value:Q"],
122+
color="key:N",
123+
)
124+
.interactive()
125+
)

src/penn_chime/models.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
from typing import Generator, Tuple
44

5-
import numpy as np
6-
import pandas as pd
5+
import numpy as np # type: ignore
6+
import pandas as pd # type: ignore
77

88

99
def sir(
@@ -58,13 +58,13 @@ def sim_sir(
5858
)
5959

6060

61-
def sim_sir_df(
62-
s: float, i: float, r: float,
63-
beta: float, gamma: float, n_days: int
64-
) -> pd.DataFrame:
65-
"""Simulate the SIR model forward in time."""
61+
def sim_sir_df(p) -> pd.DataFrame:
62+
"""Simulate the SIR model forward in time.
63+
64+
p is a Parameters instance. for circuluar dependency reasons i can't annotate it.
65+
"""
6666
return pd.DataFrame(
67-
data=gen_sir(s, i, r, beta, gamma, n_days),
67+
data=gen_sir(p.susceptible, p.infected, p.recovered, p.beta, p.gamma, p.n_days),
6868
columns=("Susceptible", "Infected", "Recovered"),
6969
)
7070

@@ -74,3 +74,51 @@ def get_dispositions(
7474
) -> Tuple[np.ndarray, ...]:
7575
"""Get dispositions of infected adjusted by rate and market_share."""
7676
return (*(infected * rate * market_share for rate in rates),)
77+
78+
79+
80+
def build_admissions_df(p) -> pd.DataFrame:
81+
"""Build admissions dataframe from Parameters."""
82+
days = np.array(range(0, p.n_days + 1))
83+
data_dict = dict(zip(["day", "Hospitalized", "ICU", "Ventilated"],
84+
[days] + [disposition for disposition in p.dispositions]
85+
))
86+
projection = pd.DataFrame.from_dict(data_dict)
87+
# New cases
88+
projection_admits = projection.iloc[:-1, :] - projection.shift(1)
89+
projection_admits[projection_admits < 0] = 0
90+
projection_admits["day"] = range(projection_admits.shape[0])
91+
return projection_admits
92+
93+
94+
def build_census_df(
95+
projection_admits: pd.DataFrame,
96+
parameters
97+
) -> pd.DataFrame:
98+
"""ALOS for each category of COVID-19 case (total guesses)"""
99+
n_days = np.shape(projection_admits)[0]
100+
hosp_los, icu_los, vent_los = parameters.lengths_of_stay
101+
los_dict = {
102+
"Hospitalized": hosp_los,
103+
"ICU": icu_los,
104+
"Ventilated": vent_los,
105+
}
106+
107+
census_dict = dict()
108+
for k, los in los_dict.items():
109+
census = (
110+
projection_admits.cumsum().iloc[:-los, :]
111+
- projection_admits.cumsum().shift(los).fillna(0)
112+
).apply(np.ceil)
113+
census_dict[k] = census[k]
114+
115+
census_df = pd.DataFrame(census_dict)
116+
census_df["day"] = census_df.index
117+
census_df = census_df[["day", "Hospitalized", "ICU", "Ventilated"]]
118+
census_df = census_df.head(n_days)
119+
census_df = census_df.rename(
120+
columns={disposition: f"{disposition} Census"
121+
for disposition
122+
in ("Hospitalized", "ICU", "Ventilated")}
123+
)
124+
return census_df

src/penn_chime/parameters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Parameters."""
22

3-
from numpy import log2
3+
from numpy import log2 # type: ignore
44

55
from .utils import RateLos
66
from .models import (

0 commit comments

Comments
 (0)