Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 7 additions & 3 deletions src/vivarium_csu_alzheimers/components/observers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
BBBM_AGE_MAX,
BBBM_AGE_MIN,
BBBM_TEST_RESULTS,
BBBM_TIMESTEPS_UNTIL_RETEST,
COLUMNS,
TESTING_STATES,
TIME_STEPS_UNTIL_NEXT_BBBM_TEST,
)
from vivarium_csu_alzheimers.constants.models import ALZHEIMERS_DISEASE_MODEL
from vivarium_csu_alzheimers.utilities import get_timedelta_from_step_size
Expand Down Expand Up @@ -257,10 +257,12 @@ def count_newly_eligible_simulants(self, pop: pd.DataFrame) -> float:
aged_in = (pop[COLUMNS.AGE] >= BBBM_AGE_MIN) & (
pop[COLUMNS.AGE] < BBBM_AGE_MIN + step_size_in_years
)
# Retest population will be simulants with previous test date >= 3 years ago,
# so we find newly eligible simulants whose last test is exactly 6 time steps ago
retest = pop[
COLUMNS.BBBM_TEST_DATE
] == self.clock() + self.step_size() - get_timedelta_from_step_size(
self.step_size().days, BBBM_TIMESTEPS_UNTIL_RETEST
self.step_size().days, min(TIME_STEPS_UNTIL_NEXT_BBBM_TEST)
)

return sum(eligible_baseline & (new_entrants | aged_in | retest))
Expand All @@ -273,7 +275,9 @@ def aggregate_eligible_person_time(self, pop: pd.DataFrame) -> float:
eligible_history = (pop[COLUMNS.BBBM_TEST_DATE].isna()) | (
pop[COLUMNS.BBBM_TEST_DATE]
<= event_time
- get_timedelta_from_step_size(self.step_size().days, BBBM_TIMESTEPS_UNTIL_RETEST)
- get_timedelta_from_step_size(
self.step_size().days, min(TIME_STEPS_UNTIL_NEXT_BBBM_TEST)
)
)
eligible_results = pop[COLUMNS.BBBM_TEST_RESULT] != BBBM_TEST_RESULTS.POSITIVE

Expand Down
68 changes: 45 additions & 23 deletions src/vivarium_csu_alzheimers/components/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
BBBM_TEST_RESULTS,
BBBM_TESTING_RATES,
BBBM_TESTING_START_DATE,
BBBM_TIMESTEPS_UNTIL_RETEST,
COLUMNS,
TESTING_STATES,
TIME_STEPS_UNTIL_NEXT_BBBM_TEST,
)
from vivarium_csu_alzheimers.constants.models import ALZHEIMERS_DISEASE_MODEL
from vivarium_csu_alzheimers.utilities import get_timedelta_from_step_size
Expand All @@ -39,6 +39,7 @@ def columns_created(self) -> list[str]:
COLUMNS.TESTING_PROPENSITY,
COLUMNS.TESTING_STATE,
COLUMNS.BBBM_TEST_DATE,
COLUMNS.NEXT_BBBM_TEST_DATE,
COLUMNS.BBBM_TEST_RESULT,
COLUMNS.BBBM_TEST_EVER_ELIGIBLE,
]
Expand Down Expand Up @@ -77,6 +78,7 @@ def on_initialize_simulants(self, pop_data: SimulantData) -> None:
pop[COLUMNS.BBBM_TEST_RESULT] = BBBM_TEST_RESULTS.NOT_TESTED
pop[COLUMNS.BBBM_TEST_EVER_ELIGIBLE] = False
pop[COLUMNS.BBBM_TEST_DATE] = pd.NaT
pop[COLUMNS.NEXT_BBBM_TEST_DATE] = pd.NaT

# Update to reflect history
event_time = pop_data.creation_time + get_timedelta_from_step_size(self.step_size)
Expand All @@ -86,13 +88,13 @@ def on_initialize_simulants(self, pop_data: SimulantData) -> None:
test_history_mask = bbbm_eligible_mask & (
pop[COLUMNS.TESTING_PROPENSITY] < testing_rate
)
pop[COLUMNS.BBBM_TEST_DATE] = self._generate_bbbm_testing_history(
Copy link
Contributor

Choose a reason for hiding this comment

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

don't we still need to provide the historical date? Or is it just not useful since it's in the past at that point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We do not need history necessarily, we just need a way to sample 3-5 years instead of every 3 years so we are generating future tests since that is easier.

pop[COLUMNS.NEXT_BBBM_TEST_DATE] = self._generate_bbbm_testing_data(
pop, test_history_mask, event_time
)

self._update_baseline_testing(pop)

bbbm_tested_now_mask = pop[COLUMNS.BBBM_TEST_DATE] == event_time
bbbm_tested_now_mask = pop[COLUMNS.NEXT_BBBM_TEST_DATE] == event_time
self._update_bbbm_testing(
pop,
bbbm_tested_now_mask,
Expand Down Expand Up @@ -168,19 +170,29 @@ def _update_bbbm_testing(
)

# Update BBBM-specific columns for those who had BBBM tests
pop.loc[tested_mask, COLUMNS.BBBM_TEST_DATE] = event_time
pop.loc[tested_mask, COLUMNS.TESTING_STATE] = TESTING_STATES.BBBM
pop.loc[tested_mask, COLUMNS.BBBM_TEST_RESULT] = test_results
pop.loc[tested_mask, COLUMNS.BBBM_TEST_DATE] = event_time
# Choose next test date for negative tests
negative_test = (test_results == BBBM_TEST_RESULTS.NEGATIVE) & tested_mask
time_steps_until_next_test = self.randomness.choice(
index=pop[negative_test].index,
choices=TIME_STEPS_UNTIL_NEXT_BBBM_TEST,
additional_key="bbbm_time_until_next_test",
)
pop.loc[
negative_test, COLUMNS.NEXT_BBBM_TEST_DATE
] = event_time + get_timedelta_from_step_size(
self.step_size, time_steps_until_next_test
)

def _get_bbbm_eligible_simulants(
self, pop: pd.DataFrame, event_time: Time
) -> pd.Series[bool]:
eligible_state = pop[COLUMNS.DISEASE_STATE] == ALZHEIMERS_DISEASE_MODEL.BBBM_STATE
eligible_age = (pop[COLUMNS.AGE] >= BBBM_AGE_MIN) & (pop[COLUMNS.AGE] < BBBM_AGE_MAX)
eligible_history = (pop[COLUMNS.BBBM_TEST_DATE].isna()) | (
pop[COLUMNS.BBBM_TEST_DATE]
<= event_time
- get_timedelta_from_step_size(self.step_size, BBBM_TIMESTEPS_UNTIL_RETEST)
eligible_history = (pop[COLUMNS.NEXT_BBBM_TEST_DATE].isna()) | (
pop[COLUMNS.NEXT_BBBM_TEST_DATE] <= event_time
)
eligible_results = pop[COLUMNS.BBBM_TEST_RESULT] != BBBM_TEST_RESULTS.POSITIVE
return eligible_state & eligible_age & eligible_history & eligible_results
Expand All @@ -202,27 +214,37 @@ def _get_bbbm_testing_rate(self, event_time: pd.Timestamp) -> float:

return np.interp(event_time.value, timestamps, rates)

def _generate_bbbm_testing_history(
def _generate_bbbm_testing_data(
self, simulants: pd.DataFrame, eligible_sims: pd.Series, time_of_event: Time
) -> pd.Series[Time]:
"""Generates previous BBBM test data for new simulants between 0 and 2.5 years prior
to entering the simulation."""

test_dates = simulants[COLUMNS.BBBM_TEST_DATE].copy()
"""Generates BBBM test data for new simulants next BBBM test 0 to 4.5 into the future.
This will sa mple the 3-5 year range for time until the next test. It will then sample
how far along that simulant is in that range to determine the test date. For example,
if a simulant is intering the simulation on 2030-01-01, and CRN samples that their test
will be 4 years after their previous test, then then will take a sample between 0 and 4
years. If that sample is 1 year, their COLUMNS.BBBM_TEST_DATE will be 2033-01-01.
"""

test_dates = simulants[COLUMNS.NEXT_BBBM_TEST_DATE].copy()
if not self.scenario.bbbm_testing or time_of_event < BBBM_TESTING_START_DATE:
return test_dates

test_date_options = [
time_of_event + get_timedelta_from_step_size(self.step_size, time_step)
for time_step in range(0, -6, -1)
if time_of_event + get_timedelta_from_step_size(self.step_size, time_step)
>= BBBM_TESTING_START_DATE
]
# Calculate test results
test_dates.loc[eligible_sims] = self.randomness.choice(
time_steps_until_next_test = self.randomness.choice(
index=simulants[eligible_sims].index,
choices=test_date_options,
additional_key="bbbm_test_date_history",
choices=TIME_STEPS_UNTIL_NEXT_BBBM_TEST[:-1],
Copy link
Contributor

Choose a reason for hiding this comment

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

Remind me why we exclude the last one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We know that simulants will have a test at most 5 years (10 time steps) from their last test. We are going to have simulants test now, or in one of the next 9 time steps.

additional_key="bbbm_time_until_next_test_history",
)
# Sample uniformly from [0, time_steps_until_next_test] for each simulant
time_steps_into_test_interval = np.round(
self.randomness.get_draw(
index=simulants[eligible_sims].index,
additional_key="bbbm_time_into_test_interval_history",
)
* time_steps_until_next_test
)
steps_until_next_test = time_steps_until_next_test - time_steps_into_test_interval
test_dates.loc[eligible_sims] = time_of_event + get_timedelta_from_step_size(
self.step_size, steps_until_next_test
)

return test_dates
32 changes: 10 additions & 22 deletions src/vivarium_csu_alzheimers/constants/data_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class __Columns(NamedTuple):
f"{TREATMENT_DISEASE_MODEL.NO_EFFECT_NEVER_TREATED_STATE}_event_count"
)
TREATMENT_DURATION: str = "treatment_duration"
NEXT_BBBM_TEST_DATE: str = "next_bbbm_test_date"


COLUMNS = __Columns()
Expand Down Expand Up @@ -153,10 +154,9 @@ class TestingRates(NamedTuple):
}


BBBM_AGE_MIN = 60
BBBM_AGE_MIN = 65
BBBM_AGE_MAX = 80
BBBM_TIMESTEPS_UNTIL_RETEST = 6 # three years b/c time step is ~6 months
BBBM_POSITIVE_DIAGNOSIS_PROBABILITY = 0.9
BBBM_POSITIVE_DIAGNOSIS_PROBABILITY = 0.5


class __BBBMTestResults(NamedTuple):
Expand All @@ -167,34 +167,22 @@ class __BBBMTestResults(NamedTuple):

BBBM_TEST_RESULTS = __BBBMTestResults()

# bbbm testing rates are piecewise-linear starting at 2030 and maxing out in 2045
# bbbm testing rates are piecewise-linear starting at 2027 and maxing out in 2045
BBBM_TESTING_RATES = [
(pd.Timestamp("2030-01-01"), 0.1), # step increase from 0 in 2030
(pd.Timestamp("2035-01-01"), 0.2),
(pd.Timestamp("2040-01-01"), 0.4),
(pd.Timestamp("2045-01-01"), 0.6), # plateaus from here on out
(pd.Timestamp("2027-01-01"), 0.0), # step increase from 0 in 2027
(pd.Timestamp("2035-01-01") + pd.Timedelta(days=182), 0.1),
(pd.Timestamp("2045-01-01"), 0.5),
(pd.Timestamp("2055-01-01"), 0.6), # plateaus from here on out
]
BBBM_TESTING_START_DATE = BBBM_TESTING_RATES[0][0]
TIME_STEPS_UNTIL_NEXT_BBBM_TEST = [6.0, 7.0, 8.0, 9.0, 10.0]

# TODO: update time start of ramp with test model updates
TREATMENT_PROBS_RAMP = [
(pd.Timestamp("2030-01-01"), 0.0),
(pd.Timestamp("2027-01-01"), 0.0),
(pd.Timestamp("2035-01-01") + pd.Timedelta(days=182), 0.3),
(pd.Timestamp("2100-01-01"), 0.8),
]

# LOCATION_TREATMENT_PROBS = {
# "united_states_of_america": 0.3,
# "germany": TREATMENT_PROBS_RAMP,
# "spain": TREATMENT_PROBS_RAMP,
# "sweden": TREATMENT_PROBS_RAMP,
# "united_kingdom": TREATMENT_PROBS_RAMP,
# "japan": 0.8,
# "israel": TREATMENT_PROBS_RAMP,
# "taiwan_(province_of_china)": TREATMENT_PROBS_RAMP,
# "brazil": TREATMENT_PROBS_RAMP,
# "china": TREATMENT_PROBS_RAMP,
# }
DWELL_TIME_AWAITING_EFFECT_TIMESTEPS = 1 # 6 months
DWELL_TIME_TREATMENT_EFFECT_TIMESTEPS = 12 # 6 years
DWELL_TIME_WANING_EFFECT_TIMESTEPS = 22 # 11 years
Expand Down
1 change: 0 additions & 1 deletion src/vivarium_csu_alzheimers/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from vivarium_public_health.risks.data_transformations import pivot_categorical

from vivarium_csu_alzheimers.constants import metadata
from vivarium_csu_alzheimers.constants.data_values import BBBM_TIMESTEPS_UNTIL_RETEST


def len_longest_location() -> int:
Expand Down