Skip to content

Commit a1ca638

Browse files
Merge pull request #256 from CodeForPhilly/fix_dfs
Fix dispositions and census construction
2 parents 330792c + 0a520ad commit a1ca638

File tree

2 files changed

+78
-79
lines changed

2 files changed

+78
-79
lines changed

src/penn_chime/models.py

Lines changed: 74 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,75 +18,81 @@
1818
class SimSirModel:
1919

2020
def __init__(self, p: Parameters) -> SimSirModel:
21+
# TODO missing initial recovered value
22+
susceptible = p.susceptible
23+
recovered = 0.0
24+
recovery_days = p.recovery_days
25+
26+
rates = {
27+
key: d.rate
28+
for key, d in p.dispositions.items()
29+
}
30+
31+
lengths_of_stay = {
32+
key: d.length_of_stay
33+
for key, d in p.dispositions.items()
34+
}
2135

2236
# Note: this should not be an integer.
2337
# We're appoximating infected from what we do know.
2438
# TODO market_share > 0, hosp_rate > 0
25-
self.infected = infected = (
39+
infected = (
2640
p.current_hospitalized / p.market_share / p.hospitalized.rate
2741
)
2842

29-
self.detection_probability = (
43+
detection_probability = (
3044
p.known_infected / infected if infected > 1.0e-7 else None
3145
)
3246

33-
# TODO missing initial recovered value
34-
self.recovered = recovered = 0.0
35-
36-
self.intrinsic_growth_rate = intrinsic_growth_rate = \
47+
intrinsic_growth_rate = \
3748
(2.0 ** (1.0 / p.doubling_time) - 1.0) if p.doubling_time > 0.0 else 0.0
3849

39-
self.gamma = gamma = 1.0 / p.recovery_days
50+
gamma = 1.0 / recovery_days
4051

4152
# Contact rate, beta
42-
self.beta = beta = (
53+
beta = (
4354
(intrinsic_growth_rate + gamma)
44-
/ p.susceptible
55+
/ susceptible
4556
* (1.0 - p.relative_contact_rate)
4657
) # {rate based on doubling time} / {initial susceptible}
4758

4859
# r_t is r_0 after distancing
49-
self.r_t = beta / gamma * p.susceptible
60+
r_t = beta / gamma * susceptible
5061

5162
# Simplify equation to avoid division by zero:
5263
# self.r_naught = r_t / (1.0 - relative_contact_rate)
53-
self.r_naught = (intrinsic_growth_rate + gamma) / gamma
54-
55-
# doubling time after distancing
56-
# TODO constrain values np.log2(...) > 0.0
57-
self.doubling_time_t = 1.0 / np.log2(
64+
r_naught = (intrinsic_growth_rate + gamma) / gamma
65+
doubling_time_t = 1.0 / np.log2(
5866
beta * p.susceptible - gamma + 1)
5967

60-
self.raw_df = raw_df = sim_sir_df(
68+
raw_df = sim_sir_df(
6169
p.susceptible,
6270
infected,
6371
recovered,
6472
beta,
6573
gamma,
6674
p.n_days,
6775
)
68-
69-
rates = {
70-
key: d.rate
71-
for key, d in p.dispositions.items()
72-
}
73-
74-
lengths_of_stay = {
75-
key: d.length_of_stay
76-
for key, d in p.dispositions.items()
77-
}
78-
79-
i_dict_v = get_dispositions(raw_df.infected, rates, p.market_share)
80-
r_dict_v = get_dispositions(raw_df.recovered, rates, p.market_share)
81-
82-
self.dispositions = {
83-
key: value + r_dict_v[key]
84-
for key, value in i_dict_v.items()
85-
}
86-
87-
self.dispositions_df = pd.DataFrame(self.dispositions)
88-
self.admits_df = admits_df = build_admits_df(p.n_days, self.dispositions)
89-
self.census_df = build_census_df(admits_df, lengths_of_stay)
76+
dispositions_df = build_dispositions_df(raw_df, rates, p.market_share)
77+
admits_df = build_admits_df(dispositions_df)
78+
census_df = build_census_df(admits_df, lengths_of_stay)
79+
80+
self.susceptible = susceptible
81+
self.infected = infected
82+
self.recovered = recovered
83+
84+
self.detection_probability = detection_probability
85+
self.recovered = recovered
86+
self.intrinsic_growth_rate = intrinsic_growth_rate
87+
self.gamma = gamma
88+
self.beta = beta
89+
self.r_t = r_t
90+
self.r_naught = r_naught
91+
self.doubling_time_t = doubling_time_t
92+
self.raw_df = raw_df
93+
self.dispositions_df = dispositions_df
94+
self.admits_df = admits_df
95+
self.census_df = census_df
9096

9197

9298
def sir(
@@ -119,55 +125,49 @@ def gen_sir(
119125

120126

121127
def sim_sir_df(
122-
s: float, i: float, r: float, beta: float, gamma: float, n_days
128+
s: float, i: float, r: float, beta: float, gamma: float, n_days: int
123129
) -> pd.DataFrame:
124130
"""Simulate the SIR model forward in time."""
125131
return pd.DataFrame(
126132
data=gen_sir(s, i, r, beta, gamma, n_days),
127133
columns=("day", "susceptible", "infected", "recovered"),
128134
)
129135

130-
131-
def get_dispositions(
132-
patients: np.ndarray,
136+
def build_dispositions_df(
137+
sim_sir_df: pd.DataFrame,
133138
rates: Dict[str, float],
134139
market_share: float,
135-
) -> Dict[str, np.ndarray]:
140+
) -> pd.DataFrame:
136141
"""Get dispositions of patients adjusted by rate and market_share."""
137-
return {
138-
key: patients * rate * market_share
139-
for key, rate in rates.items()
140-
}
141-
142-
143-
def build_admits_df(n_days, dispositions) -> pd.DataFrame:
144-
"""Build admits dataframe from Parameters and Model."""
145-
days = np.arange(0, n_days + 1)
146-
projection = pd.DataFrame({
147-
"day": days,
148-
**dispositions,
142+
patients = sim_sir_df.infected + sim_sir_df.recovered
143+
return pd.DataFrame({
144+
"day": sim_sir_df.day,
145+
**{
146+
key: patients * rate * market_share
147+
for key, rate in rates.items()
148+
}
149149
})
150-
# New cases
151-
admits_df = projection.iloc[:-1, :] - projection.shift(1)
152-
admits_df["day"] = range(admits_df.shape[0])
150+
151+
152+
def build_admits_df(dispositions_df: pd.DataFrame) -> pd.DataFrame:
153+
"""Build admits dataframe from dispositions."""
154+
admits_df = dispositions_df.iloc[:-1, :] - dispositions_df.shift(1)
155+
admits_df.day = dispositions_df.day
153156
return admits_df
154157

155158

156159
def build_census_df(
157-
admits_df: pd.DataFrame, lengths_of_stay
160+
admits_df: pd.DataFrame,
161+
lengths_of_stay: Dict[str, int],
158162
) -> pd.DataFrame:
159-
"""ALOS for each category of COVID-19 case (total guesses)"""
160-
n_days = np.shape(admits_df)[0]
161-
census_dict = {}
162-
for key, los in lengths_of_stay.items():
163-
census = (
164-
admits_df.cumsum().iloc[:-los, :]
165-
- admits_df.cumsum().shift(los).fillna(0)
166-
).apply(np.ceil)
167-
census_dict[key] = census[key]
168-
169-
census_df = pd.DataFrame(census_dict)
170-
census_df["day"] = census_df.index
171-
census_df = census_df[["day", *lengths_of_stay.keys()]]
172-
census_df = census_df.head(n_days)
173-
return census_df
163+
"""ALOS for each disposition of COVID-19 case (total guesses)"""
164+
return pd.DataFrame({
165+
'day': admits_df.day,
166+
**{
167+
key: (
168+
admits_df[key].cumsum().iloc[:-los]
169+
- admits_df[key].cumsum().shift(los).fillna(0)
170+
).apply(np.ceil)
171+
for key, los in lengths_of_stay.items()
172+
}
173+
})

tests/test_app.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,12 @@ def test_model(model=MODEL, param=PARAM):
249249
assert round(last.susceptible, 0) == 67202
250250
assert round(raw_df.recovered[30], 0) == 224048
251251

252-
assert [d[0] for d in model.dispositions.values()] == [100.0, 40.0, 20.0]
253-
assert [round(d[60], 0) for d in model.dispositions.values()] == [1182.0, 473.0, 236.0]
252+
assert list(model.dispositions_df.iloc[0, :]) == [0, 100.0, 40.0, 20.0]
253+
assert [round(i, 0) for i in model.dispositions_df.iloc[60, :]] == [60, 1182.0, 473.0, 236.0]
254254

255255
# test that admissions are being properly calculated (thanks @PhilMiller)
256-
admissions = build_admits_df(param.n_days, model.dispositions)
257-
cumulative_admissions = admissions.cumsum()
258-
diff = cumulative_admissions["hospitalized"][1:-1] - (
256+
cumulative_admits = model.admits_df.cumsum()
257+
diff = cumulative_admits.hospitalized[1:-1] - (
259258
0.05 * 0.05 * (raw_df.infected[1:-1] + raw_df.recovered[1:-1]) - 100
260259
)
261260
assert (diff.abs() < 0.1).all()

0 commit comments

Comments
 (0)