-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcohort.py
More file actions
172 lines (149 loc) · 6.33 KB
/
cohort.py
File metadata and controls
172 lines (149 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import math
import constants
from life_tables import LifeTables
class Population(object):
'''
Hold a cohort and run the markov model simulation.
'''
start_age = None
sex = None
NIHSS = None
horizon = None
def __init__(self, ais_outcomes, simtype):
'''
We get a dictionary containing (for ischemic stroke patients):
'p_good', 'p_tpa', 'p_evt', 'p_transfer'
'''
# Current edit: TODO -> sketch out patients that we need to divide
# out between the various states to capture costs
# -> tpa, evt, etc.
# does it make sense to do this here or does it make sense to do it
# in a separate section of the model??
self.ais_outcomes = ais_outcomes
self.costs_per_year = []
self.simtype = simtype
self.states_in_markov = self.break_into_states()
self.states = self.run_markov(self.states_in_markov,
Population.start_age, Population.sex)
self.qalys_per_year = get_qalys(self.states)
get_costs_per_year(self.costs_per_year, self.states)
self.qalys = simpsons_1_3rd_correction(self.qalys_per_year,
Population.horizon)
self.costs = simpsons_1_3rd_correction(self.costs_per_year,
Population.horizon)
def break_into_states(self):
call_population = 1
pop_mimic = call_population * constants.p_call_is_mimic()
pop_hemorrhagic = call_population * constants.p_call_is_hemorrhagic()
pop_ischemic = call_population - pop_mimic - pop_hemorrhagic
states = [0 for enum in range(constants.States.NUMBER_OF_STATES)]
# We assume that mimics are at gen pop (headache, migraine, etc.)
states[constants.States.GEN_POP] += pop_mimic
# Get the mRS breakdown of patients with acute ischemic strokes and
# remember to adjust for population of ischemic patients when
# adding into state matrix
mrs_of_ais = constants.break_up_ais_patients(
self.ais_outcomes['p_good'], Population.NIHSS)
states_ischemic = [
pop_ischemic * mrs_of_ais[i]
for i in range(constants.States.NUMBER_OF_STATES)
]
# Now we need the mRS breakdown for patients with hemorrhagic strokes
# Currently making the conservative estimate that there is no
# difference in outcomes for ICH versus AIS patients, even though
# there is evidence to suggest to suggest ICH patients do almost
# about twice as well.
# This estimate also adjusts hemorrhagic stroke outcomes based on
# time to center.
states_hemorrhagic = [
pop_hemorrhagic * mrs_of_ais[i]
for i in range(constants.States.NUMBER_OF_STATES)
]
# Add on first year costs
baseline_year_one_costs = constants.first_year_costs(
states_hemorrhagic, states_ischemic)
baseline_year_one_costs += (
constants.cost_ivt() * self.ais_outcomes['p_tpa'] * pop_ischemic)
baseline_year_one_costs += (
constants.cost_evt() * self.ais_outcomes['p_evt'] * pop_ischemic)
baseline_year_one_costs += (
constants.cost_transfer() * self.ais_outcomes['p_transfer'] *
pop_ischemic)
self.costs_per_year.append(baseline_year_one_costs)
states = [
states[i] + states_ischemic[i] + states_hemorrhagic[i]
for i in range(constants.States.NUMBER_OF_STATES)
]
return states
def run_markov(self, states, start_age, sex):
'''
import a list of states at each year from start to age 100
'''
current_state = states
start_of_cycles = []
current_age = start_age
while current_age < 100:
start_of_cycles.append([i for i in current_state])
# We run a range up to death because we don't want to include death
# since it only markovs to itself
for mrs in range(constants.States.DEATH):
p_dead = LifeTables.adjusted_mortality(
sex, current_age, constants.hazard_mort(mrs))
current_state[constants.States.DEATH] += current_state[
mrs] * p_dead
current_state[mrs] -= current_state[mrs] * p_dead
current_age += 1
# Add on final age
start_of_cycles.append([i for i in current_state])
return start_of_cycles
def get_costs_per_year(costs_per_year, states):
continuous_discount = 0.03
discreet_discount = math.exp(continuous_discount) - 1
for cycle, state in enumerate(states):
# We added on cycle 0, first year, costs during the markov model since
# it's dependent on hemorrhagic vs. ischemic
if cycle == 0:
continue
else:
costs = constants.annual_cost(state)
costs /= ((1 + discreet_discount)**(cycle))
costs_per_year.append(costs)
def get_qalys(states):
'''
Returns an array of discounted quality-adjusted life-years at each year
'''
continuous_discount = 0.03
discreet_discount = math.exp(continuous_discount) - 1
qalys = []
for cycle, state in enumerate(states):
qaly = 0
for mrs in range(constants.States.DEATH):
qaly += state[mrs] * constants.utilities_mrs(mrs)
# Discount
qaly /= ((1 + discreet_discount)**(cycle))
qalys.append(qaly)
return qalys
def simpsons_1_3rd_correction(yearly_value, years_horizon=None):
'''
Returns the sum of the one-dimensional array inputted for either
discounted costs or QALYs. Default is to run a lifetime horizon, but
can run for the correction for any number of years as long as it is
specified.
'''
multiplier = 1 / 3
sum_ = yearly_value[0] * multiplier
start_index = 1
end_index = len(yearly_value) - 1
if years_horizon is not None and years_horizon <= end_index:
end_index = years_horizon
# Since for in range in [a, b)
for i in range(start_index, end_index + 1):
if i == end_index:
multiplier = 1 / 3
else:
if i % 2 == 0:
multiplier = 2 / 3
else:
multiplier = 4 / 3
sum_ += (yearly_value[i] * multiplier)
return sum_