forked from pennsignals/chime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
288 lines (235 loc) · 9.75 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import pandas as pd
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
hide_menu_style = """
<style>
#MainMenu {visibility: hidden;}
</style>
"""
st.markdown(hide_menu_style, unsafe_allow_html=True)
delaware = 564696
chester = 519293
montgomery = 826075
bucks = 628341
philly = 1581000
S_default = delaware + chester + montgomery + bucks + philly
known_infections = 31
# Widgets
initial_infections = st.sidebar.number_input(
"Currently Known Regional Infections", value=known_infections, step=10, format="%i"
)
current_hosp = st.sidebar.number_input(
"Currently Hospitalized COVID-19 Patients", value=2, step=1, format="%i"
)
doubling_time = st.sidebar.number_input(
"Doubling Time (days)", value=6, step=1, format="%i"
)
hosp_rate = (
st.sidebar.number_input("Hospitalization %", 0, 100, value=5, step=1, format="%i")
/ 100.0
)
icu_rate = (
st.sidebar.number_input("ICU %", 0, 100, value=2, step=1, format="%i") / 100.0
)
vent_rate = (
st.sidebar.number_input("Ventilated %", 0, 100, value=1, step=1, format="%i")
/ 100.0
)
hosp_los = st.sidebar.number_input("Hospital LOS", value=7, step=1, format="%i")
icu_los = st.sidebar.number_input("ICU LOS", value=9, step=1, format="%i")
vent_los = st.sidebar.number_input("Vent LOS", value=10, step=1, format="%i")
Penn_market_share = (
st.sidebar.number_input(
"Hospital Market Share (%)", 0.0, 100.0, value=15.0, step=1.0, format="%f"
)
/ 100.0
)
S = st.sidebar.number_input(
"Regional Population", value=S_default, step=100000, format="%i"
)
total_infections = current_hosp / Penn_market_share / hosp_rate
detection_prob = initial_infections / total_infections
st.title("COVID-19 Hospital Impact Model for Epidemics")
st.markdown(
"""*This tool was developed by the [Predictive Healthcare team](http://predictivehealthcare.pennmedicine.org/) at Penn Medicine. For questions and comments please see our [contact page](http://predictivehealthcare.pennmedicine.org/contact/).* **If you see any error messages please reload the page.**"""
)
if st.checkbox("Show more info about this tool"):
st.subheader(
"[Discrete-time SIR modeling](https://mathworld.wolfram.com/SIRModel.html) of infections/recovery"
)
st.markdown(
"""The model consists of individuals who are either _Susceptible_ ($S$), _Infected_ ($I$), or _Recovered_ ($R$).
The epidemic proceeds via a growth and decline process. This is the core model of infectious disease spread and has been in use in epidemiology for many years."""
)
st.markdown("""The dynamics are given by the following 3 equations.""")
st.latex("S_{t+1} = (-\\beta S_t I_t) + S_t")
st.latex("I_{t+1} = (\\beta S_t I_t - \\gamma I_t) + I_t")
st.latex("R_{t+1} = (\\gamma I_t) + R_t")
st.markdown(
"""To project the expected impact to Penn Medicine, we estimate the terms of the model.
To do this, we use a combination of estimates from other locations, informed estimates based on logical reasoning, and best guesses from the American Hospital Association.
### Parameters
First, we need to express the two parameters $\\beta$ and $\\gamma$ in terms of quantities we can estimate.
- The $\\gamma$ parameter represents 1 over the mean recovery time in days. Since the CDC is recommending 14 days of self-quarantine, we'll use $\\gamma = 1/14$.
- Next, the AHA says to expect a doubling time $T_d$ of 7-10 days. That means an early-phase rate of growth can be computed by using the doubling time formula:
"""
)
st.latex("g = 2^{1/T_d} - 1")
st.markdown(
"""
- Since the rate of new infections in the SIR model is $g = \\beta S - \\gamma$, and we've already computed $\\gamma$, $\\beta$ becomes a function of the initial population size of susceptible individuals.
$$\\beta = (g + \\gamma)/s$$
### Initial Conditions
- The total size of the susceptible population will be the entire catchment area for Penn Medicine entities (HUP, PAH, PMC, CCH)
- Delaware = {delaware}
- Chester = {chester}
- Montgomery = {montgomery}
- Bucks = {bucks}
- Philly = {philly}
- The initial number of infected will be the total number of confirmed cases in the area ({initial_infections}), divided by some detection probability to account for under testing {detection_prob:.2f}.""".format(
delaware=delaware,
chester=chester,
montgomery=montgomery,
bucks=bucks,
philly=philly,
initial_infections=initial_infections,
detection_prob=detection_prob,
)
)
# The SIR model, one time step
def sir(y, beta, gamma, N):
S, I, R = y
Sn = (-beta * S * I) + S
In = (beta * S * I - gamma * I) + I
Rn = gamma * I + R
if Sn < 0:
Sn = 0
if In < 0:
In = 0
if Rn < 0:
Rn = 0
scale = N / (Sn + In + Rn)
return Sn * scale, In * scale, Rn * scale
# Run the SIR model forward in time
def sim_sir(S, I, R, beta, gamma, n_days, beta_decay=None):
N = S + I + R
s, i, r = [S], [I], [R]
for day in range(n_days):
y = S, I, R
S, I, R = sir(y, beta, gamma, N)
if beta_decay:
beta = beta * (1 - beta_decay)
s.append(S)
i.append(I)
r.append(R)
s, i, r = np.array(s), np.array(i), np.array(r)
return s, i, r
## RUN THE MODEL
S, I, R = S, initial_infections / detection_prob, 0
intrinsic_growth_rate = 2 ** (1 / doubling_time) - 1
recovery_days = 14.0
# mean recovery rate, gamma, (in 1/days).
gamma = 1 / recovery_days
# Contact rate, beta
beta = (
intrinsic_growth_rate + gamma
) / S # {rate based on doubling time} / {initial S}
n_days = st.slider("Number of days to project", 30, 200, 60, 1, "%i")
beta_decay = 0.0
s, i, r = sim_sir(S, I, R, beta, gamma, n_days, beta_decay=beta_decay)
hosp = i * hosp_rate * Penn_market_share
icu = i * icu_rate * Penn_market_share
vent = i * vent_rate * Penn_market_share
days = np.array(range(0, n_days + 1))
data_list = [days, hosp, icu, vent]
data_dict = dict(zip(["day", "hosp", "icu", "vent"], data_list))
projection = pd.DataFrame.from_dict(data_dict)
st.subheader("New Admissions")
st.markdown("Projected number of **daily** COVID-19 admissions at Penn hospitals")
# New cases
projection_admits = projection.iloc[:-1, :] - projection.shift(1)
projection_admits[projection_admits < 0] = 0
plot_projection_days = n_days - 10
projection_admits["day"] = range(projection_admits.shape[0])
fig, ax = plt.subplots(1, 1, figsize=(10, 4))
ax.plot(
projection_admits.head(plot_projection_days)["hosp"], ".-", label="Hospitalized"
)
ax.plot(projection_admits.head(plot_projection_days)["icu"], ".-", label="ICU")
ax.plot(projection_admits.head(plot_projection_days)["vent"], ".-", label="Ventilated")
ax.legend(loc=0)
ax.set_xlabel("Days from today")
ax.grid("on")
ax.set_ylabel("Daily Admissions")
st.pyplot()
admits_table = projection_admits[np.mod(projection_admits.index, 7) == 0].copy()
admits_table["day"] = admits_table.index
admits_table.index = range(admits_table.shape[0])
admits_table = admits_table.fillna(0).astype(int)
if st.checkbox("Show Projected Admissions in tabular form"):
st.dataframe(admits_table)
st.subheader("Admitted Patients (Census)")
st.markdown(
"Projected **census** of COVID-19 patients, accounting for arrivals and discharges at Penn hospitals"
)
# ALOS for each category of COVID-19 case (total guesses)
los_dict = {
"hosp": hosp_los,
"icu": icu_los,
"vent": vent_los,
}
fig, ax = plt.subplots(1, 1, figsize=(10, 4))
census_dict = {}
for k, los in los_dict.items():
census = (
projection_admits.cumsum().iloc[:-los, :]
- projection_admits.cumsum().shift(los).fillna(0)
).apply(np.ceil)
census_dict[k] = census[k]
ax.plot(census.head(plot_projection_days)[k], ".-", label=k + " census")
ax.legend(loc=0)
ax.set_xlabel("Days from today")
ax.grid("on")
ax.set_ylabel("Census")
st.pyplot()
census_df = pd.DataFrame(census_dict)
census_df["day"] = census_df.index
census_df = census_df[["day", "hosp", "icu", "vent"]]
census_table = census_df[np.mod(census_df.index, 7) == 0].copy()
census_table.index = range(census_table.shape[0])
census_table.loc[0, :] = 0
census_table = census_table.dropna().astype(int)
if st.checkbox("Show Projected Census in tabular form"):
st.dataframe(census_table)
st.markdown(
"""**Click the checkbox below to view additional data generated by this simulation**"""
)
if st.checkbox("Show Additional Projections"):
st.subheader(
"The number of infected and recovered individuals in the hospital catchment region at any given moment"
)
fig, ax = plt.subplots(1, 1, figsize=(10, 4))
ax.plot(i, label="Infected")
ax.plot(r, label="Recovered")
ax.legend(loc=0)
ax.set_xlabel("days from today")
ax.set_ylabel("Case Volume")
ax.grid("on")
st.pyplot()
# Show data
days = np.array(range(0, n_days + 1))
data_list = [days, s, i, r]
data_dict = dict(zip(["day", "susceptible", "infections", "recovered"], data_list))
projection_area = pd.DataFrame.from_dict(data_dict)
infect_table = (projection_area.iloc[::7, :]).apply(np.floor)
infect_table.index = range(infect_table.shape[0])
if st.checkbox("Show Raw SIR Similation Data"):
st.dataframe(infect_table)
st.subheader("References & Acknowledgements")
st.markdown(
"""* AHA Webinar, Feb 26, James Lawler, MD, an associate professor University of Nebraska Medical Center, What Healthcare Leaders Need To Know: Preparing for the COVID-19
* We would like to recognize the valuable assistance in consultation and review of model assumptions by Michael Z. Levy, PhD, Associate Professor of Epidemiology, Department of Biostatistics, Epidemiology and Informatics at the Perelman School of Medicine
"""
)
st.markdown("© 2020, The Trustees of the University of Pennsylvania")