Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
7e39b63
first push of issue 255
quinn-dougherty Mar 26, 2020
11c6e2a
it now asks user date of first hospitalization
quinn-dougherty Mar 26, 2020
0a978ff
Merge branch 'develop' into quinn_issue_255
quinn-dougherty Mar 27, 2020
a3d1a9a
incremental progress
quinn-dougherty Mar 27, 2020
1404983
Merge branch 'quinn_issue_255' of https://github.com/CodeForPhilly/ch…
quinn-dougherty Mar 27, 2020
e9b4fbf
code is in, have to reset x axis
quinn-dougherty Mar 27, 2020
124a3fd
day zero of graph is now the user inputted date_first_hospitalized
quinn-dougherty Mar 27, 2020
13f82f8
fixed doubling time inference
quinn-dougherty Mar 27, 2020
0c96064
Merge branch 'develop' into quinn_issue_255
quinn-dougherty Mar 27, 2020
2a8469e
fixed graph
quinn-dougherty Mar 27, 2020
ef736d6
fixed download csv button and improved table situation
quinn-dougherty Mar 27, 2020
9ab74a7
Merge branch 'develop' into quinn_issue_255
quinn-dougherty Mar 27, 2020
97f3722
incremental progress on debugging
quinn-dougherty Mar 27, 2020
490fa2d
incremental progress debugging dates
quinn-dougherty Mar 27, 2020
545a68c
Merge branch 'develop' into quinn_issue_255
quinn-dougherty Mar 27, 2020
ecf51ee
relative_contact_pct_input from relative_contact_rate_input
quinn-dougherty Mar 27, 2020
8e7f8e9
Merge branch 'quinn_issue_255' of https://github.com/CodeForPhilly/ch…
quinn-dougherty Mar 27, 2020
6f8aa06
began argmin_days_since
quinn-dougherty Mar 27, 2020
2dd1bb0
i think i completed argmin over days_since?
quinn-dougherty Mar 27, 2020
714bbb0
Allow negative days from n_days_since to flow through dfs
jlubken Mar 28, 2020
7db3096
Fix some tests
jlubken Mar 28, 2020
05ae862
Replace add_date_column with numpy astype
jlubken Mar 28, 2020
448700d
Move input for current date
jlubken Mar 28, 2020
77a85ef
Add date to dispositions_df and flow date through
jlubken Mar 28, 2020
e78b6a6
Replace today with current_date, add recovery_days
jlubken Mar 28, 2020
a794ea5
Replace recovery_days with infectious_days
jlubken Mar 28, 2020
f75e381
Add settings.cfg for infectious_days
jlubken Mar 28, 2020
469daaa
Fix display_mode_info and infectious_days
jlubken Mar 28, 2020
023a1b5
Simplify chart descritpions
jlubken Mar 28, 2020
e6430f6
Fix census_df bug
jlubken Mar 28, 2020
d1ff2b4
Fix SIR chart
jlubken Mar 28, 2020
969c44f
Remove more presentation cruft
jlubken Mar 28, 2020
bfe74cd
Fix reference to p.current_date
jlubken Mar 28, 2020
a7fc457
Alphabetized state
jlubken Mar 28, 2020
e23ca69
Add mandatory named parameters to prevent mistakes
jlubken Mar 28, 2020
3066e26
Remove as_date
jlubken Mar 28, 2020
dea9361
Remove modulo sliders
jlubken Mar 28, 2020
4d8358a
Fix easy tests
jlubken Mar 28, 2020
12d594f
Fix cli
jlubken Mar 28, 2020
25dde08
Add logger
jlubken Mar 28, 2020
1c658da
Add fstrings
jlubken Mar 28, 2020
def26d7
Remove assertions
jlubken Mar 28, 2020
b0da967
Move change date and type, fix annotation
jlubken Mar 28, 2020
ad3851f
Add change date to cli
jlubken Mar 28, 2020
02f53ea
Remove bug hiding spot
jlubken Mar 28, 2020
cdb574d
Add todo for susceptible bug check
jlubken Mar 28, 2020
7a0c4f5
Fix more tests
jlubken Mar 28, 2020
fd6db51
Correct the fitting procedure
Mar 28, 2020
c168a75
Fix moar tests
jlubken Mar 28, 2020
394a350
Correct the fitting procedure
jlubken Mar 28, 2020
b38ddc8
Ensure i_days, and n_days_since exist in model
jlubken Mar 28, 2020
3b040e9
Add logging for set doubling_time
jlubken Mar 28, 2020
be114a3
Add current day ruler to census and sim_sir charts
jlubken Mar 29, 2020
22b6df9
Remove current_date ruler
jlubken Mar 29, 2020
3175b3b
Add working verical rule for current date
jlubken Mar 29, 2020
b07730d
Move constants to reduce circular dependencies
jlubken Mar 29, 2020
5bed704
Add current day to admits chart
jlubken Mar 29, 2020
03691bd
Fix test
jlubken Mar 29, 2020
8d2cdcb
changed p.current_hospitalized to 1 on line 58
quinn-dougherty Mar 29, 2020
2d153b8
fixed model_last test
quinn-dougherty Mar 29, 2020
252f5bd
Fix another test
jlubken Mar 29, 2020
f21fad6
fixed test_model test
quinn-dougherty Mar 29, 2020
45c55dc
Merge branch 'more_issue_255' of https://github.com/CodeForPhilly/chi…
quinn-dougherty Mar 29, 2020
25bdbb0
Remove duplicate computation
jlubken Mar 29, 2020
bd1d060
Replace n_days_since with i_day
jlubken Mar 29, 2020
ce6fa6c
Put social distancing effect back in. Starts at current day.
Mar 29, 2020
392e907
More logging of givens and estimates
jlubken Mar 29, 2020
57f3b99
Log len df and expected range
jlubken Mar 29, 2020
a562191
Resolve changes from upstream
jlubken Mar 29, 2020
2bdf133
Fix test reflecting social distancing
jlubken Mar 29, 2020
e160cbc
Add social distancing
jlubken Mar 29, 2020
ef8e99b
Move n_days back to 60 so social distancing can be seen in the plots
jlubken Mar 29, 2020
ba8d23a
Remove confirmed cases & detection prob
Mar 29, 2020
d2d06f9
Update change_date notice
jlubken Mar 29, 2020
38c2901
Copy params for tests
jlubken Mar 29, 2020
7755807
Add copy for "constant" PARAM to MODEL and HALVING_MODEL
jlubken Mar 29, 2020
da33480
Fix tests for r_t
jlubken Mar 29, 2020
5c3d3b6
Remove detection prob
jlubken Mar 29, 2020
5dab315
Patch all
jlubken Mar 29, 2020
3e7612c
Add settings
jlubken Mar 29, 2020
fa2f740
Try to fix cypress
jlubken Mar 29, 2020
6e4b2d2
Off by one for cypress
jlubken Mar 29, 2020
c8cad06
Add lots of time
jlubken Mar 29, 2020
0eed3c6
Resolve issue 255
jlubken Mar 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/penn_chime/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def new_admissions_chart(

tooltip_dict = {False: "day", True: "date:T"}
if as_date:
projection_admits = add_date_column(projection_admits)
projection_admits = add_date_column(projection_admits, parameters.date_first_hospitalized)
x_kwargs = {"shorthand": "date:T", "title": "Date", "axis": alt.Axis(format=(DATE_FORMAT))}
else:
x_kwargs = {"shorthand": "day", "title": "Days from today"}
Expand Down Expand Up @@ -66,7 +66,7 @@ def admitted_patients_chart(
max_y_axis = parameters.max_y_axis
as_date = parameters.as_date
if as_date:
census = add_date_column(census)
census = add_date_column(census, parameters.date_first_hospitalized)
x_kwargs = {"shorthand": "date:T", "title": "Date", "axis": alt.Axis(format=(DATE_FORMAT))}
idx = "date:T"
else:
Expand Down Expand Up @@ -113,7 +113,7 @@ def additional_projections_chart(
max_y_axis = parameters.max_y_axis

if as_date:
dat = add_date_column(dat)
dat = add_date_column(dat, parameters.date_first_hospitalized)
x_kwargs = {"shorthand": "date:T", "title": "Date", "axis": alt.Axis(format=(DATE_FORMAT))}
else:
x_kwargs = {"shorthand": "day", "title": "Days from today"}
Expand Down
7 changes: 6 additions & 1 deletion src/penn_chime/defaults.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Defaults."""
from typing import Optional
from datetime import date

from .utils import RateLos

Expand Down Expand Up @@ -29,9 +31,10 @@ def __init__(
icu: RateLos,
ventilated: RateLos,

date_first_hospitalized: Optional[date] = None,
as_date: bool = False,
market_share: float = 1.0,
max_y_axis: int = None,
max_y_axis: Optional[int] = None,
n_days: int = 60,
recovery_days: int = 14,
):
Expand All @@ -45,6 +48,8 @@ def __init__(
self.icu = icu
self.ventilated = ventilated

self.date_first_hospitalized = date_first_hospitalized
self.n_days_since_first_hospitalized = (date.today() - date_first_hospitalized).days
self.as_date = as_date
self.market_share = market_share
self.max_y_axis = max_y_axis
Expand Down
168 changes: 155 additions & 13 deletions src/penn_chime/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@

from __future__ import annotations

from typing import Dict, Generator, Tuple
from typing import Dict, Generator, Tuple, Optional

import numpy as np # type: ignore
import pandas as pd # type: ignore

from .parameters import Parameters

from .utils import SimSirModelAttributes

class SimSirModel:

def __init__(self, p: Parameters) -> SimSirModel:
def __init__(self, p: Parameters):
# TODO missing initial non-zero 'recovered' value
recovered = 0.0
recovery_days = p.recovery_days
Expand All @@ -32,6 +32,9 @@ def __init__(self, p: Parameters) -> SimSirModel:
for key, d in p.dispositions.items()
}

self._rates = rates
self._lengths_of_stay = lengths_of_stay

# Note: this should not be an integer.
# We're appoximating infected from what we do know.
# TODO market_share > 0, hosp_rate > 0
Expand All @@ -45,8 +48,8 @@ def __init__(self, p: Parameters) -> SimSirModel:
p.known_infected / infected if infected > 1.0e-7 else None
)

intrinsic_growth_rate = \
(2.0 ** (1.0 / p.doubling_time) - 1.0) if p.doubling_time > 0.0 else 0.0
# (2.0 ** (1.0 / p.doubling_time) - 1.0) if p.doubling_time > 0.0 else 0.0
intrinsic_growth_rate = self._intrinsic_growth_rate(p.doubling_time)

gamma = 1.0 / recovery_days

Expand Down Expand Up @@ -75,8 +78,8 @@ def __init__(self, p: Parameters) -> SimSirModel:
p.n_days,
)
dispositions_df = build_dispositions_df(raw_df, rates, p.market_share)
admits_df = build_admits_df(dispositions_df)
census_df = build_census_df(admits_df, lengths_of_stay)
admits_df = build_admits_df(dispositions_df, p.n_days_since_first_hospitalized)
census_df = build_census_df(admits_df, lengths_of_stay, p.n_days_since_first_hospitalized)

self.susceptible = susceptible
self.infected = infected
Expand All @@ -94,10 +97,128 @@ def __init__(self, p: Parameters) -> SimSirModel:
self.dispositions_df = dispositions_df
self.admits_df = admits_df
self.census_df = census_df

if p.n_days_since_first_hospitalized is not None and p.doubling_time is None:
# optimize doubling_time
argmin_dt = None
min_loss = 2.0**99
censes = dict()
for dt in np.linspace(1,15,29):
censes[dt] = self.run_projection(p, dt)
self.census_df = censes[dt] # log it into state for loss
loss_dt = self.loss_dt(p)
if loss_dt < min_loss:
min_loss = loss_dt
argmin_dt = dt
self.census_df = censes[dt]
p.doubling_time = argmin_dt

# update all state that is dependent on doubling time.
intrinsic_growth_rate = self._intrinsic_growth_rate(p.doubling_time)
gamma = 1 / recovery_days
beta = self._beta(intrinsic_growth_rate, gamma, susceptible, p.relative_contact_rate)
r_t = beta / gamma * susceptible
r_naught = (intrinsic_growth_rate + gamma) / gamma
doubling_time_t = 1.0 / np.log2(beta * susceptible - gamma + 1)
raw_df = sim_sir_df(
susceptible,
infected,
recovered,
beta,
gamma,
p.n_days
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be p.n_days + p.n_days_since_first_hospitalization?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all i'll say is that wouldn't be parity with the notebook. @cjbayesian

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so we'll fix this later then. We really want the raw_df to hold all of the data instead of having to bodge n_days_since... into every function.

I'll get a copy of the notebook and test for equivalent output later.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this input can no longer be described as the number of days to project as stated in the ui. It is projecting less than n_days, it is projecting from the first infection. The ui / users here will be confusing / confused.

)
dispositions_df = build_dispositions_df(raw_df, rates, p.market_share)
admits_df = build_admits_df(dispositions_df, p.n_days_since_first_hospitalized)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... then maybe the p.n_days_since_first_hospitalization doesn't need to be passed in here for admits or for the census?

census_df = build_census_df(admits_df, lengths_of_stay, p.n_days_since_first_hospitalized)

self.intrinsic_growth_rate = intrinsic_growth_rate
self.gamma = gamma
self.beta = beta
self.r_t = r_t
self.r_naught = r_naught
self.doubling_time_t = doubling_time_t
self.raw_df = raw_df
self.dispositions_df = dispositions_df
self.admits_df = admits_df
self.census_df = census_df

self.daily_growth = daily_growth_helper(p.doubling_time)
self.daily_growth_t = daily_growth_helper(doubling_time_t)

return None

def run_projection(self, p: Parameters, doubling_time: float) -> pd.DataFrame:
intrinsic_growth_rate = self._intrinsic_growth_rate(doubling_time)

recovery_days = p.recovery_days
market_share = p.market_share
initial_i = 1 / p.hospitalized.rate / market_share

S, I, R = self.susceptible, self.infected, self.recovered

# mean recovery rate (inv_recovery_days)
gamma = 1 / recovery_days

# contact rate
beta = (intrinsic_growth_rate + gamma) / S

n_days = p.n_days

raw_df = sim_sir_df(S,I,R,beta,gamma,n_days)

# dispositions_df = build_dispositions_df(raw_df, self._rates, p.market_share)

i_dict_v = get_dispositions(raw_df.infected, self._rates, market_share)
r_dict_v = get_dispositions(raw_df.recovered, self._rates, market_share)

dispositions = {
key: value + r_dict_v[key]
for key, value in i_dict_v.items()
}

dispositions_df = pd.DataFrame(dispositions)
dispositions_df = dispositions_df.assign(day=dispositions_df.index)

admits_df = build_admits_df(dispositions_df, p.n_days_since_first_hospitalized)
census_df = build_census_df(admits_df, self._lengths_of_stay, p.n_days_since_first_hospitalized)
return census_df

def loss_dt(self, p: Parameters) -> float:
"""Squared error: predicted_current_hospitalized vs. actual current hospitalized

gets prediction of current hospitalized from a census_df which
is dependent on a given doubling_time in state.
"""
# get the predicted number of hospitalized today
predicted_current_hospitalized = self.census_df.hospitalized.loc[p.n_days_since_first_hospitalized]

# compare against actual / user inputted number
# we shall optimize squared distance
return (p.current_hospitalized - predicted_current_hospitalized) ** 2


@staticmethod
def _intrinsic_growth_rate(doubling_time: Optional[float]) -> float:
if doubling_time is not None:
return (2.0 ** (1.0 / doubling_time) - 1.0) if doubling_time > 0.0 else 0.0
return 0.0

@staticmethod
def _beta(
intrinsic_growth_rate: float,
gamma: float,
susceptible: float,
relative_contact_rate: float) -> float:
return (
(intrinsic_growth_rate + gamma)
/ susceptible
* (1.0 - relative_contact_rate)
)

###################
## MODEL FUNCS ##
###################
def sir(
s: float, i: float, r: float, beta: float, gamma: float, n: float
) -> Tuple[float, float, float]:
Expand All @@ -118,7 +239,7 @@ def sir(

def gen_sir(
s: float, i: float, r: float, beta: float, gamma: float, n_days: int
) -> Generator[Tuple[float, float, float], None, None]:
) -> Generator[Tuple[int, float, float, float], None, None]:
"""Simulate SIR model forward in time yielding tuples."""
s, i, r = (float(v) for v in (s, i, r))
n = s + i + r
Expand All @@ -136,6 +257,19 @@ def sim_sir_df(
columns=("day", "susceptible", "infected", "recovered"),
)


def get_dispositions(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should not be needed. It was replaced with build_dispositions_df that includes the days column.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct -- was just triplechecking my parity with notebook, but in the current push function is no longer being called

patients: np.ndarray,
rates: Dict[str, float],
market_share: float,
) -> Dict[str, np.ndarray]:
"""Get dispositions of patients adjusted by rate and market_share."""
return {
key: patients * rate * market_share
for key, rate in rates.items()
}


def build_dispositions_df(
sim_sir_df: pd.DataFrame,
rates: Dict[str, float],
Expand All @@ -152,18 +286,22 @@ def build_dispositions_df(
})


def build_admits_df(dispositions_df: pd.DataFrame) -> pd.DataFrame:
def build_admits_df(dispositions_df: pd.DataFrame, n_days_since_first_hospitalized: int) -> pd.DataFrame:
"""Build admits dataframe from dispositions."""
admits_df = dispositions_df.iloc[:-1, :] - dispositions_df.shift(1)
admits_df.day = dispositions_df.day
if n_days_since_first_hospitalized is not None:
admits_df.day = dispositions_df.day - n_days_since_first_hospitalized
else:
admits_df.day = dispositions_df.day
return admits_df


def build_census_df(
admits_df: pd.DataFrame,
lengths_of_stay: Dict[str, int],
n_days_since_first_hospitalized: int
) -> pd.DataFrame:
"""ALOS for each disposition of COVID-19 case (total guesses)"""
"""Average Length of Stay for each disposition of COVID-19 case (total guesses)"""
return pd.DataFrame({
'day': admits_df.day,
**{
Expand All @@ -176,9 +314,13 @@ def build_census_df(
})


def daily_growth_helper(doubling_time):

#############
## UTILS ##
#############
def daily_growth_helper(doubling_time: float) -> float:
"""Calculates average daily growth rate from doubling time"""
result = 0
if doubling_time != 0:
if doubling_time != 0 and doubling_time is not None:
result = (np.power(2, 1.0 / doubling_time) - 1) * 100
return result
12 changes: 11 additions & 1 deletion src/penn_chime/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
`change_date`, so users can see when results have last changed
"""

from typing import Optional
from datetime import date

from .utils import RateLos


Expand All @@ -25,7 +28,8 @@ def __init__(

as_date: bool = False,
market_share: float = 1.0,
max_y_axis: int = None,
date_first_hospitalized: Optional[date] = None,
max_y_axis: Optional[int] = None,
n_days: int = 60,
recovery_days: int = 14,
):
Expand All @@ -44,6 +48,12 @@ def __init__(
self.max_y_axis = max_y_axis
self.n_days = n_days
self.recovery_days = recovery_days
if date_first_hospitalized:
self.date_first_hospitalized = date_first_hospitalized # needed for utils.add_date_column
self.n_days_since_first_hospitalized = (date.today() - date_first_hospitalized).days
else:
self.date_first_hospitalized = None # needed for utils.add_date_column
self.n_days_since_first_hospitalized = None

self.labels = {
"hospitalized": "Hospitalized",
Expand Down
Loading