Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test switching functionality #16

Merged
merged 10 commits into from
Jun 28, 2024
41 changes: 41 additions & 0 deletions crcsim/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,23 @@ def choose_tests(self):
self.diagnostic_test = self.params["diagnostic_test"]
self.surveillance_test = self.params["surveillance_test"]

if self.params["use_variable_routine_test"]:
# If the simulation is using variable routine tests, then we do not pick
# a single routine test for each person. Instead, we will refer to the
# routine_testing_year and routine_test_by_year parameters to determine
# which test to use each year. This allows a person to switch tests during
# their lifetime. We still assign the routine_test attribute here to
# avoid errors from yearly actions that expect a person to have a routine
# test attribute.
starting_test = self.params["routine_test_by_year"][0]
self.routine_test = starting_test
self.out.add_routine_test_chosen(
person_id=self.id,
test_name=starting_test,
time=self.scheduler.time,
)
return

# Choose the routine test based on proportions specified in
# parameters file.
#
Expand Down Expand Up @@ -903,6 +920,7 @@ def choose_tests(self):
self.out.add_routine_test_chosen(
person_id=self.id,
test_name=test,
time=self.scheduler.time,
)
break

Expand Down Expand Up @@ -1576,6 +1594,29 @@ def handle_ongoing_treatment(self, message="Ongoing treatment"):
)

def handle_yearly_actions(self, message="Conduct yearly actions"):
if self.params["use_variable_routine_test"]:
# If the simulation is using variable routine tests, then the parameters
# specify a single routine test that every person in the simulation will
# use for each testing year. This allows a person to switch tests during
# their lifetime. In this case, we assign the routine test for each year
# rather than choosing a single routine test at initiatilization.
#
# Note that indexing self.params["routine_testing_year"] will always
# return the min/max testing year, because crcsim.parameters raises an
# error if this parameter is not sorted in increasing order.
rjmorris marked this conversation as resolved.
Show resolved Hide resolved
if (
self.scheduler.time >= self.params["routine_testing_year"][0]
and self.scheduler.time <= self.params["routine_testing_year"][-1]
):
self.routine_test = self.params["variable_routine_test"](
self.scheduler.time
)
self.out.add_routine_test_chosen(
person_id=self.id,
test_name=self.routine_test,
time=self.scheduler.time,
)

self.do_tests()

self.scheduler.add_event(
Expand Down
19 changes: 16 additions & 3 deletions crcsim/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,25 @@ def summarize(self):

# Number of times each test was adopted for routine screening
routine_tests_chosen = self.raw_output[
self.raw_output.record_type.eq("test_chosen")
self.raw_output.record_type.eq("test_chosen") & self.raw_output.time.eq(0)
]
for rt in self.params["routine_tests"]:
rt_chosen = routine_tests_chosen[routine_tests_chosen.test_name.eq(rt)]
replication_output_row[f"{rt}_adopted"] = len(rt_chosen.index)

# Number of years each routine test was used
# (if test variable routine test was enabled in the simulation)
if self.params["use_variable_routine_test"]:
rt_years = self.raw_output[
self.raw_output.record_type.eq("test_chosen")
& self.raw_output.time.gt(0)
]
rt_years_grouped = rt_years.groupby(["test_name"]).agg(
count=("time", "count")
)
for ix, row in rt_years_grouped.iterrows():
replication_output_row[f"{ix}_available_as_routine"] = row["count"]

# Number of times each test was performed for routine screening
# and number of times per thousand unscreened and undiagnosed 40-year-olds
routine_tests = tests[tests.role.eq(str(TestingRole.ROUTINE))]
Expand Down Expand Up @@ -970,11 +983,11 @@ def compute_pop_rates(self, status_arrays: list):
"""
# Sum all of the person status arrays to get an array of counts of the number of
# people in each status for each year.
statuses: np.ndarray = sum(status_arrays)
status_array: np.ndarray = sum(status_arrays)

# Convert to DataFrame so we can index by column name
statuses = pd.DataFrame(
statuses,
status_array,
rjmorris marked this conversation as resolved.
Show resolved Hide resolved
columns=[
"alive",
"crc_death",
Expand Down
3 changes: 2 additions & 1 deletion crcsim/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,14 @@ def add_expected_lifespan(self, person_id: Any, time: float):
{"record_type": "lifespan", "person_id": person_id, "time": time}
)

def add_routine_test_chosen(self, person_id: Any, test_name: str):
def add_routine_test_chosen(self, person_id: Any, test_name: str, time: float):
self.rows.append(
{
"record_type": "test_chosen",
"person_id": person_id,
"test_name": test_name,
"role": TestingRole.ROUTINE,
"time": time,
}
)

Expand Down
19 changes: 19 additions & 0 deletions crcsim/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,23 @@ def load_params(file):
y=params[f"death_rate_{race}_{sex}_rates"],
)

if params["use_variable_routine_test"]:
params["variable_routine_test"] = StepFunction(
x=params["routine_testing_year"], y=params["routine_test_by_year"]
)
for test_name, test_params in params["tests"].items():
# Indexing 0 and -1 here safely returns the min and max testing years,
# because initializing the StepFunction raises an error if the testing
# years are not sorted in increasing order.
if test_params["routine_start"] != params["routine_testing_year"][0]:
raise ValueError(
f"routine_start for {test_name} does not equal the first year"
" of routine testing specified in routine_testing_year."
)
if test_params["routine_end"] != params["routine_testing_year"][-1]:
raise ValueError(
f"routine_start for {test_name} does not equal the last year"
" of routine testing specified in routine_testing_year."
)

return params
3 changes: 3 additions & 0 deletions parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@
"diagnostic_test": "Colonoscopy",
"surveillance_test": "Colonoscopy",
"polypectomy_proportion_lethal": 0.00002,
"use_variable_routine_test": true,
"routine_testing_year": [ 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 ],
"routine_test_by_year": ["Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "Colonoscopy", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT", "FIT" ],

"cost_discount_age": 50,
"cost_discount_rate": 0.03,
Expand Down
Loading
Loading