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

Exp test patterns #15

Closed
wants to merge 13 commits into from
2 changes: 1 addition & 1 deletion crcsim/experiment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ The subdirectories and files in `scenarios/` must be uploaded to AWS S3 for the

To upload the files to S3, run
```
aws s3 cp ./scenarios s3://crcsim-exp-crccp-sensitivity01/scenarios --recursive
aws s3 cp ./scenarios s3://crcsim-exp-test-patterns/scenarios --recursive
```
*(Another note: this manual step is necessary because `boto3` does not include functionality to upload a directory to S3 recursively. Future experiments could improve this workflow by writing a function to upload the directory recursively in `prepare.py`. Or submit a patch to resolve https://github.com/boto/boto3/issues/358)*

Expand Down
8 changes: 4 additions & 4 deletions crcsim/experiment/parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
"cost": 22,
"proportion_perforation": 0,
"cost_perforation": 0,
"compliance_rate_given_prev_compliant": [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ],
"compliance_rate_given_not_prev_compliant": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]
"compliance_rate_given_prev_compliant": [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ],
"compliance_rate_given_not_prev_compliant": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]
},
"Colonoscopy": {
"proportion": 0.0,
Expand All @@ -98,8 +98,8 @@
"cost": 700,
"proportion_perforation": 0.001,
"cost_perforation": 6487,
"compliance_rate_given_prev_compliant": [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ],
"compliance_rate_given_not_prev_compliant": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]
"compliance_rate_given_prev_compliant": [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ],
"compliance_rate_given_not_prev_compliant": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]
}
},
"routine_tests": [ "FIT", "Colonoscopy" ],
Expand Down
236 changes: 115 additions & 121 deletions crcsim/experiment/prepare.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import json
import random
from enum import Enum, unique
from pathlib import Path
from typing import Callable, Dict, List, Optional
from copy import deepcopy

import fire

Expand All @@ -20,6 +20,18 @@ def transform(self, transformer: Callable) -> "Scenario":
return self


@unique
class ConditionalComplianceParam(Enum):
PREV_COMPLIANT = "compliance_rate_given_prev_compliant"
NOT_PREV_COMPLIANT = "compliance_rate_given_not_prev_compliant"


@unique
class Test(Enum):
FIT = "FIT"
COLONOSCOPY = "Colonoscopy"


def prepare_experiment_dir(
scenarios: List[Scenario],
num_iterations: int,
Expand Down Expand Up @@ -87,148 +99,130 @@ def transform(params):
return transform


def transform_treatment_cost(stage: str, phase: str, value: int) -> Callable:
def transform(params):
params[f"cost_treatment_stage{stage}_{phase}"] = value

return transform

def transform_conditional_compliance_rates(
test: Test, param: ConditionalComplianceParam, rates: List[float]
) -> Callable:
"""
Replaces a conditional compliance parameter with a new set of rates.
"""

def transform_repeat_compliance(rate: float, test: str) -> Callable:
def transform(params):
params["tests"][test]["compliance_rate_given_prev_compliant"] = [
rate for _ in params["tests"][test]["compliance_rate_given_prev_compliant"]
]
params["tests"][test.value][param.value] = rates

return transform


def transform_diagnostic_compliance(rate) -> Callable:
def transform(params):
params["diagnostic_compliance_rate"] = rate

return transform

def transform_delayed_onset(test: Test, onset_age: int) -> Callable:
"""
Transforms the necessary compliance parameters such that, for the given test,
compliance is 0.0 for all years before the given age, and 1.0 for all years
after the given age.
"""

def transform_surveillance_frequency(stage: str, frequency: int) -> Callable:
def transform(params):
params[f"surveillance_freq_{stage}"] = frequency
start_age = params["tests"][test.value]["routine_start"]
# Testing years are inclusive, so add 1
testing_years = params["tests"][test.value]["routine_end"] - start_age + 1

# Initial compliance is 0
params["initial_compliance_rate"] = 0.0

# Then everyone remains noncompliant until the given age.
compliance_rate_given_not_prev_compliant = [0.0] * testing_years
onset_year = onset_age - start_age
compliance_rate_given_not_prev_compliant[onset_year] = 1.0
params["tests"][test.value][
"compliance_rate_given_not_prev_compliant"
] = compliance_rate_given_not_prev_compliant

# Then everyone remains compliant after the given age.
# We can just set the compliance rate to 1.0 for all years, including those before
# the onset age, because initial compliance is 0.0.
compliance_rate_given_prev_compliant = [1.0] * testing_years
params["tests"][test.value][
"compliance_rate_given_prev_compliant"
] = compliance_rate_given_prev_compliant

return transform


def transform_surveillance_end_age(age: int) -> Callable:
def transform_routine_freq(test: Test, freq: int) -> Callable:
def transform(params):
params["surveillance_end_age"] = age
params["tests"][test.value]["routine_freq"] = freq

return transform


def create_scenarios() -> List:
# For each health center, define the initial compliance rate in the baseline
# scenario and the implementation scenario.
initial_compliance = {
"fqhc1": (0.522, 0.593),
"fqhc2": (0.154, 0.421),
"fqhc3": (0.519, 0.615),
"fqhc4": (0.278, 0.374),
"fqhc5": (0.383, 0.572),
"fqhc6": (0.211, 0.392),
"fqhc7": (0.257, 0.354),
"fqhc8": (0.190, 0.390),
}
low_initial_stage_3_treatment_cost = 67_300
low_initial_stage_4_treatment_cost = 97_931
low_diagnostic_compliance_rate = 0.525
lower_repeat_compliance = 0.8
low_surveillance_freq_mild = 10
low_surveillance_freq_severe = 2
low_surveillance_end_age = 80
scenarios = []

for fqhc, rates in initial_compliance.items():
baseline = Scenario(
name=f"{fqhc}_baseline", params=get_default_params()
).transform(transform_initial_compliance(rates[0]))
scenarios.append(baseline)

implementation = Scenario(
name=f"{fqhc}_implementation", params=get_default_params()
).transform(transform_initial_compliance(rates[1]))
scenarios.append(implementation)

# Sensitivity Analysis 1. Lower repeat compliance (note that the baseline runs stay the same)

test_name = "FIT"
implementation_lower_repeat_compliance = deepcopy(implementation)
implementation_lower_repeat_compliance.transform(
transform_repeat_compliance(lower_repeat_compliance, test_name)
)
implementation_lower_repeat_compliance.name = (
f"{fqhc}_implementation_lower_repeat_compliance"
)
scenarios.append(implementation_lower_repeat_compliance)

# Sensitivity analysis 2. Lower cost for stage III and stage IV initial phase
baseline_low_cost = deepcopy(baseline)
baseline_low_cost.transform(
transform_treatment_cost("3", "initial", low_initial_stage_3_treatment_cost)
).transform(
transform_treatment_cost("4", "initial", low_initial_stage_4_treatment_cost)
)
baseline_low_cost.name = f"{fqhc}_baseline_low_initial_treat_cost"
scenarios.append(baseline_low_cost)

implementation_low_cost = deepcopy(implementation)
implementation_low_cost.transform(
transform_treatment_cost("3", "initial", low_initial_stage_3_treatment_cost)
).transform(
transform_treatment_cost("4", "initial", low_initial_stage_4_treatment_cost)
)
implementation_low_cost.name = f"{fqhc}_implementation_low_initial_treat_cost"
scenarios.append(implementation_low_cost)

# Sensitivity analysis 3. Lower compliance with diagnostic colonoscopy
baseline_lower_compliance = deepcopy(baseline)
baseline_lower_compliance.transform(
transform_diagnostic_compliance(low_diagnostic_compliance_rate)
)
baseline_lower_compliance.name = f"{fqhc}_baseline_lower_diagnostic_compliance"
scenarios.append(baseline_lower_compliance)

implementation_lower_compliance = deepcopy(implementation)
implementation_lower_compliance.transform(
transform_diagnostic_compliance(low_diagnostic_compliance_rate)
)
implementation_lower_compliance.name = (
f"{fqhc}_implementation_lower_diagnostic_compliance"
)
scenarios.append(implementation_lower_compliance)

# TODO: Sensitivity analysis 4. Lower surveillance frequency and end age.
scenarios = []

baseline_lower_surveillance = deepcopy(baseline)
baseline_lower_surveillance.transform(
transform_surveillance_frequency("polyp_mild", low_surveillance_freq_mild)
).transform(
transform_surveillance_frequency("polyp_severe", low_surveillance_freq_severe)
).transform(
transform_surveillance_end_age(low_surveillance_end_age)
# Scenarios: 100% FIT compliance
always_compliant = Scenario(
name="always_compliant", params=get_default_params()
).transform(transform_initial_compliance(1.0))
scenarios.append(always_compliant)

# Scenarios: 0% FIT Compliance
never_compliant = Scenario(
name="never_compliant", params=get_default_params()
).transform(transform_initial_compliance(0.0))
scenarios.append(never_compliant)

# Scenarios: Delayed Onset Until Age 60
delayed_onset_60 = Scenario(
name="delayed_onset_60", params=get_default_params()
).transform(transform_delayed_onset(Test.FIT, 60))
scenarios.append(delayed_onset_60)

# Scenarios: Delayed Onset Until Age 65
delayed_onset_65 = Scenario(
name="delayed_onset_65", params=get_default_params()
).transform(transform_delayed_onset(Test.FIT, 65))
scenarios.append(delayed_onset_65)

# Scenario for every other year testing
every_two_years = Scenario(
name="every_two_years", params=get_default_params()
).transform(transform_routine_freq(Test.FIT, 2))
scenarios.append(every_two_years)

# Scenario for every five years testing
every_five_years = Scenario(
name="every_five_years", params=get_default_params()
).transform(transform_routine_freq(Test.FIT, 5))
scenarios.append(every_five_years)

# Scenario with 50% compliance every year
#
# Shorthand to create conditional compliance arrays without specifying each year.
# Conditional compliance arrays are length 26, one for each year of testing (50-75).
fifty_percent_compliance_rates = [0.5] * 26

fifty_percent_compliance = Scenario(
name="fifty_percent_compliance", params=get_default_params()
).transform(
transform_conditional_compliance_rates(
Test.FIT,
ConditionalComplianceParam.PREV_COMPLIANT,
fifty_percent_compliance_rates,
)
baseline_lower_surveillance.name = f"{fqhc}_baseline_lower_surveillance"
scenarios.append(baseline_lower_surveillance)

implementation_lower_surveillance = deepcopy(implementation)
implementation_lower_surveillance.transform(
transform_surveillance_frequency("polyp_mild", low_surveillance_freq_mild)
).transform(
transform_surveillance_frequency("polyp_severe", low_surveillance_freq_severe)
).transform(
transform_surveillance_end_age(low_surveillance_end_age)
)
scenarios.append(fifty_percent_compliance)

# Scenario with 20% compliance every year
twenty_percent_compliance_rates = [0.2] * 26

twenty_percent_compliance = Scenario(
name="twenty_percent_compliance", params=get_default_params()
).transform(
transform_conditional_compliance_rates(
Test.FIT,
ConditionalComplianceParam.PREV_COMPLIANT,
twenty_percent_compliance_rates,
)
implementation_lower_surveillance.name = f"{fqhc}_implementation_lower_surveillance"
scenarios.append(implementation_lower_surveillance)

)
scenarios.append(twenty_percent_compliance)

return scenarios

Expand Down
6 changes: 3 additions & 3 deletions crcsim/experiment/run_iteration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if [ ! -d "$output_dir" ]; then
mkdir $output_dir
fi

aws s3 cp "s3://crcsim-exp-crccp-sensitivity01/scenarios/$scenario/params.json" "./params.json"
aws s3 cp "s3://crcsim-exp-test-patterns/scenarios/$scenario/params.json" "./params.json"

crc-simulate \
--npeople=$npeople \
Expand All @@ -23,5 +23,5 @@ crc-simulate \
crc-analyze \
--params-file=./params.json &&

aws s3 cp ./results.csv "s3://crcsim-exp-crccp-sensitivity01/scenarios/$scenario/results_$iteration.csv"
aws s3 cp ./output.csv "s3://crcsim-exp-crccp-sensitivity01/scenarios/$scenario/output_$iteration.csv"
aws s3 cp ./results.csv "s3://crcsim-exp-test-patterns/scenarios/$scenario/results_$iteration.csv"
aws s3 cp ./output.csv "s3://crcsim-exp-test-patterns/scenarios/$scenario/output_$iteration.csv"
Loading
Loading