diff --git a/renal_capacity_model/config_values.py b/renal_capacity_model/config_values.py index 5fe6f7c..0d5f8e3 100644 --- a/renal_capacity_model/config_values.py +++ b/renal_capacity_model/config_values.py @@ -264,151 +264,151 @@ "initialisation": { "not_listed": { "early": { - 1: [0.926963321, 2127.231053], - 2: [1.102888443, 2360.960064], - 3: [1.042024424, 1757.881403], - 4: [1.102673686, 1641.614239], - 5: [1.149855608, 1561.636782], - 6: [1.151814597, 1190.40286], + 1: {"shape": 0.926963321, "scale": 2127.231053}, + 2: {"shape": 1.102888443, "scale": 2360.960064}, + 3: {"shape": 1.042024424, "scale": 1757.881403}, + 4: {"shape": 1.102673686, "scale": 1641.614239}, + 5: {"shape": 1.149855608, "scale": 1561.636782}, + 6: {"shape": 1.151814597, "scale": 1190.40286}, }, "late": { - 1: [1.099732869, 4314.8445], - 2: [1.239811029, 2204.356655], - 3: [1.025879638, 1940.947327], - 4: [1.17279732, 1700.235305], - 5: [1.168619527, 1512.312581], - 6: [1.156286321, 1253.912137], + 1: {"shape": 1.099732869, "scale": 4314.8445}, + 2: {"shape": 1.239811029, "scale": 2204.356655}, + 3: {"shape": 1.025879638, "scale": 1940.947327}, + 4: {"shape": 1.17279732, "scale": 1700.235305}, + 5: {"shape": 1.168619527, "scale": 1512.312581}, + 6: {"shape": 1.156286321, "scale": 1253.912137}, }, }, "listed": { "early": { - 1: [1.426589565, 13064.21921], - 2: [1.501319694, 8229.734667], - 3: [1.475457685, 6126.819295], - 4: [1.388640715, 4221.17834], - 5: [1.381583619, 2805.868318], - 6: [1.262968437, 1862.925138], + 1: {"shape": 1.426589565, "scale": 13064.21921}, + 2: {"shape": 1.501319694, "scale": 8229.734667}, + 3: {"shape": 1.475457685, "scale": 6126.819295}, + 4: {"shape": 1.388640715, "scale": 4221.17834}, + 5: {"shape": 1.381583619, "scale": 2805.868318}, + 6: {"shape": 1.262968437, "scale": 1862.925138}, }, "late": { - 1: [1.403897083, 13250.96392], - 2: [1.359190822, 8393.578341], - 3: [1.435707658, 5981.108405], - 4: [1.326446673, 4096.723314], - 5: [1.469351223, 2905.94038], - 6: [1.244780918, 1800.673553], + 1: {"shape": 1.403897083, "scale": 13250.96392}, + 2: {"shape": 1.359190822, "scale": 8393.578341}, + 3: {"shape": 1.435707658, "scale": 5981.108405}, + 4: {"shape": 1.326446673, "scale": 4096.723314}, + 5: {"shape": 1.469351223, "scale": 2905.94038}, + 6: {"shape": 1.244780918, "scale": 1800.673553}, }, }, "received_Tx": { "early": { - 1: [1.945083614, 11291.09043], - 2: [1.765333669, 8449.099418], - 3: [1.750669869, 6535.672038], - 4: [1.61142907, 4693.44361], - 5: [1.51886545, 3189.62546], - 6: [1.315655704, 2078.132622], + 1: {"shape": 1.945083614, "scale": 11291.09043}, + 2: {"shape": 1.765333669, "scale": 8449.099418}, + 3: {"shape": 1.750669869, "scale": 6535.672038}, + 4: {"shape": 1.61142907, "scale": 4693.44361}, + 5: {"shape": 1.51886545, "scale": 3189.62546}, + 6: {"shape": 1.315655704, "scale": 2078.132622}, }, "late": { - 1: [1.982364823, 10573.47681], - 2: [1.658109448, 8611.590105], - 3: [1.699130074, 6499.195977], - 4: [1.491659213, 4669.1818], - 5: [1.678178577, 3296.367133], - 6: [1.306592647, 1990.733317], + 1: {"shape": 1.982364823, "scale": 10573.47681}, + 2: {"shape": 1.658109448, "scale": 8611.590105}, + 3: {"shape": 1.699130074, "scale": 6499.195977}, + 4: {"shape": 1.491659213, "scale": 4669.1818}, + 5: {"shape": 1.678178577, "scale": 3296.367133}, + 6: {"shape": 1.306592647, "scale": 1990.733317}, }, }, }, "incidence": { "not_listed": { "early": { - 1: [1.021124756, 1630.450226], - 2: [1.033558934, 1601.726618], - 3: [1.175150289, 1788.324784], - 4: [1.171027698, 1671.61871], - 5: [1.182843607, 1604.030346], - 6: [1.111932566, 1324.708399], + 1: {"shape": 1.021124756, "scale": 1630.450226}, + 2: {"shape": 1.033558934, "scale": 1601.726618}, + 3: {"shape": 1.175150289, "scale": 1788.324784}, + 4: {"shape": 1.171027698, "scale": 1671.61871}, + 5: {"shape": 1.182843607, "scale": 1604.030346}, + 6: {"shape": 1.111932566, "scale": 1324.708399}, }, "late": { - 1: [1.025446743, 2098.5119], - 2: [0.86558945, 2071.301577], - 3: [0.787953011, 1428.187242], - 4: [1.093861294, 1533.652413], - 5: [0.817406992, 1170.501979], - 6: [0.829891, 841.5931435], + 1: {"shape": 1.025446743, "scale": 2098.5119}, + 2: {"shape": 0.86558945, "scale": 2071.301577}, + 3: {"shape": 0.787953011, "scale": 1428.187242}, + 4: {"shape": 1.093861294, "scale": 1533.652413}, + 5: {"shape": 0.817406992, "scale": 1170.501979}, + 6: {"shape": 0.829891, "scale": 841.5931435}, }, }, "listed": { "early": { - 1: [1.76722409, 7565.454427], - 2: [2.071602421, 5267.578147], - 3: [1.842843082, 5294.787645], - 4: [2.276835063, 3862.063689], - 5: [1.990617668, 3148.737229], - 6: [2.435166079, 2479.412332], + 1: {"shape": 1.76722409, "scale": 7565.454427}, + 2: {"shape": 2.071602421, "scale": 5267.578147}, + 3: {"shape": 1.842843082, "scale": 5294.787645}, + 4: {"shape": 2.276835063, "scale": 3862.063689}, + 5: {"shape": 1.990617668, "scale": 3148.737229}, + 6: {"shape": 2.435166079, "scale": 2479.412332}, }, "late": { - 1: [2.732803839, 5100.306997], - 2: [1.7523112, 6999.685955], - 3: [2.667855187, 3817.522327], - 4: [2.39938038, 3728.91519], - 5: [2.195794462, 2550.374846], - 6: [2.435166079, 2479.412332], + 1: {"shape": 2.732803839, "scale": 5100.306997}, + 2: {"shape": 1.7523112, "scale": 6999.685955}, + 3: {"shape": 2.667855187, "scale": 3817.522327}, + 4: {"shape": 2.39938038, "scale": 3728.91519}, + 5: {"shape": 2.195794462, "scale": 2550.374846}, + 6: {"shape": 2.435166079, "scale": 2479.412332}, }, }, "received_Tx": { "early": { - 1: [2.51486622, 6903.239543], - 2: [3.159509376, 5391.985192], - 3: [2.70710626, 5381.509481], - 4: [2.893707823, 4282.49379], - 5: [2.35276178, 3550.906294], - 6: [4.316362639, 3118.52084], + 1: {"shape": 2.51486622, "scale": 6903.239543}, + 2: {"shape": 3.159509376, "scale": 5391.985192}, + 3: {"shape": 2.70710626, "scale": 5381.509481}, + 4: {"shape": 2.893707823, "scale": 4282.49379}, + 5: {"shape": 2.35276178, "scale": 3550.906294}, + 6: {"shape": 4.316362639, "scale": 3118.52084}, }, "late": { - 1: [3.160772244, 5169.606112], - 2: [2.323615011, 7944.757158], - 3: [3.8964386, 4637.1169], - 4: [5.45663996, 3913.875032], - 5: [3.071596253, 3120.115725], - 6: [4.316362639, 3118.52084], + 1: {"shape": 3.160772244, "scale": 5169.606112}, + 2: {"shape": 2.323615011, "scale": 7944.757158}, + 3: {"shape": 3.8964386, "scale": 4637.1169}, + 4: {"shape": 5.45663996, "scale": 3913.875032}, + 5: {"shape": 3.071596253, "scale": 3120.115725}, + 6: {"shape": 4.316362639, "scale": 3118.52084}, }, }, }, } tw_cadTx_values = { - 1: [1.12, 719], - 2: [1.23, 734], - 3: [1.31, 794], - 4: [1.31, 762], - 5: [1.23, 619], - 6: [1.02, 385], + 1: {"shape": 1.12, "scale": 719}, + 2: {"shape": 1.23, "scale": 734}, + 3: {"shape": 1.31, "scale": 794}, + 4: {"shape": 1.31, "scale": 762}, + 5: {"shape": 1.23, "scale": 619}, + 6: {"shape": 1.02, "scale": 385}, } tw_liveTx_values = { - 1: [1.03, 399], - 2: [1.06, 464], - 3: [1.08, 417], - 4: [1.09, 420], - 5: [1.22, 391], - 6: [1.01, 342], + 1: {"shape": 1.03, "scale": 399}, + 2: {"shape": 1.06, "scale": 464}, + 3: {"shape": 1.08, "scale": 417}, + 4: {"shape": 1.09, "scale": 420}, + 5: {"shape": 1.22, "scale": 391}, + 6: {"shape": 1.01, "scale": 342}, } tw_cadTx_initialisation_values = { - 1: [1.12, 1336], - 2: [1.13, 1163], - 3: [1.22, 1194], - 4: [1.26, 1023], - 5: [1.28, 839], - 6: [1.28, 409], + 1: {"shape": 1.12, "scale": 1336}, + 2: {"shape": 1.13, "scale": 1163}, + 3: {"shape": 1.22, "scale": 1194}, + 4: {"shape": 1.26, "scale": 1023}, + 5: {"shape": 1.28, "scale": 839}, + 6: {"shape": 1.28, "scale": 409}, } tw_liveTx_initialisation_values = { - 1: [0.96, 752], - 2: [1.05, 1006], - 3: [0.96, 903], - 4: [1.01, 665], - 5: [1.27, 765], - 6: [1.27, 765], + 1: {"shape": 0.96, "scale": 752}, + 2: {"shape": 1.05, "scale": 1006}, + 3: {"shape": 0.96, "scale": 903}, + 4: {"shape": 1.01, "scale": 665}, + 5: {"shape": 1.27, "scale": 765}, + 6: {"shape": 1.27, "scale": 765}, } # Time waiting before dialysis diff --git a/renal_capacity_model/entity.py b/renal_capacity_model/entity.py index 9f45141..21fdcde 100644 --- a/renal_capacity_model/entity.py +++ b/renal_capacity_model/entity.py @@ -40,6 +40,6 @@ def __init__( self.time_living_with_live_transplant: float | None = None self.time_living_with_cadaver_transplant: float | None = None self.transplant_count = 0 - self.time_on_waiting_list = 0 + self.time_on_waiting_list: float = 0 self.time_enters_waiting_list: float | None = None self.time_of_transplant: float | None = None diff --git a/renal_capacity_model/helpers.py b/renal_capacity_model/helpers.py index 51602dc..9d9f37a 100644 --- a/renal_capacity_model/helpers.py +++ b/renal_capacity_model/helpers.py @@ -315,3 +315,21 @@ def check_time_to_event_curve_dfs(tte_df_name: str, tte_df: pd.DataFrame): assert list(tte_df.columns) == cols except AssertionError: raise ValueError(f"Check {tte_df_name} csv - incorrect indices/columns") + + +def calculate_time_to_event( + rng: np.random.Generator, scale: float, shape: float, multiplier: float = 1 +) -> float: + """Calculate time to event, sampling from a Weibull distribution and multiplying + it with a scale and optional multiplier + + Args: + rng (np.random.Generator): Random Number Generator + scale (float): Scale to be used for the calculation + shape (float): Shape parameter for Weibull distribution + multiplier (float, optional): Optional multiplier. Defaults to 1. + + Returns: + float: Sampled time to event + """ + return (scale * rng.weibull(shape)) * multiplier diff --git a/renal_capacity_model/main.py b/renal_capacity_model/main.py index dcf674e..14373c5 100644 --- a/renal_capacity_model/main.py +++ b/renal_capacity_model/main.py @@ -60,7 +60,12 @@ def main( ) for excel_file in [path_to_inputs_file, path_to_outputs_file]: filepaths.append(copy_excel_files(excel_file, run_start_time)) - write_results_to_excel(filepaths[1], combined_results, trial.costs_dfs) + write_results_to_excel( + filepaths[1], + combined_results, + trial.costs_dfs, + sim_years=int(config.sim_duration / 365), + ) if __name__ == "__main__": diff --git a/renal_capacity_model/model.py b/renal_capacity_model/model.py index e06f450..787088d 100644 --- a/renal_capacity_model/model.py +++ b/renal_capacity_model/model.py @@ -12,6 +12,7 @@ process_event_log, calculate_model_results, truncate_2dp, + calculate_time_to_event, ) from renal_capacity_model.process_outputs import ( create_results_folder, @@ -95,9 +96,11 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) print( f"Patient {p.id} of age group {p.age_group} is in conservative care at time {self.env.now}." ) - sampled_con_care_time = self.config.ttd_con_care[ - "scale" - ] * self.rng.weibull(a=self.config.ttd_con_care["shape"], size=None) + sampled_con_care_time = calculate_time_to_event( + self.rng, + scale=self.config.ttd_con_care["scale"], + shape=self.config.ttd_con_care["shape"], + ) self._update_event_log( p, "conservative_care", @@ -121,18 +124,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) ): ## they aren't suitable for transplant p.transplant_suitable = False - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["not_listed"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["not_listed"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["not_listed"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["not_listed"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) if self.config.trace: print( @@ -149,18 +149,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) p.time_on_waiting_list = ( self.config.sim_duration + 1 ) # they're listed but don't receive a transplant in the simulation period - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["listed"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["listed"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["listed"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["listed"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) p.time_enters_waiting_list = self.env.now if self.config.trace: @@ -170,18 +167,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) else: # they do receive a transplant in the simulation period p.transplant_suitable = True - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["received_Tx"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["received_Tx"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["received_Tx"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["received_Tx"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) p.time_enters_waiting_list = self.env.now if self.config.trace: @@ -194,23 +188,27 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) ): # it's a live transplant, but not pre-emptive as they're already on dialysis p.transplant_type = "live" - p.time_on_waiting_list = ( - self.config.tw_liveTx_initialisation[p.age_group][1] - * self.rng.weibull( - a=self.config.tw_liveTx_initialisation[p.age_group][0], - size=None, - ) - * self.config.multipliers["tw"]["prev"]["live"] + p.time_on_waiting_list = calculate_time_to_event( + self.rng, + scale=self.config.tw_liveTx_initialisation[p.age_group][ + "scale" + ], + shape=self.config.tw_liveTx_initialisation[p.age_group][ + "shape" + ], + multiplier=self.config.multipliers["tw"]["prev"]["live"], ) else: p.transplant_type = "cadaver" - p.time_on_waiting_list = ( - self.config.tw_cadTx_initialisation[p.age_group][1] - * self.rng.weibull( - a=self.config.tw_cadTx_initialisation[p.age_group][0], - size=None, - ) - * self.config.multipliers["tw"]["prev"]["cadaver"] + p.time_on_waiting_list = calculate_time_to_event( + self.rng, + scale=self.config.tw_cadTx_initialisation[p.age_group][ + "scale" + ], + shape=self.config.tw_cadTx_initialisation[p.age_group][ + "shape" + ], + multiplier=self.config.multipliers["tw"]["prev"]["cadaver"], ) p.pre_emptive_transplant = False self.env.process(self.start_dialysis_modality(p)) @@ -222,18 +220,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) ): ## they aren't suitable for transplant p.transplant_suitable = False - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["not_listed"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["not_listed"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["not_listed"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["not_listed"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) if self.config.trace: print( @@ -250,18 +245,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) p.time_on_waiting_list = ( self.config.sim_duration + 1 ) # they're listed but don't receive a transplant in the simulation period - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["listed"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["listed"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["listed"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["listed"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) p.time_enters_waiting_list = self.env.now if self.config.trace: @@ -271,18 +263,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) else: # they do receive a transplant in the simulation period p.transplant_suitable = True - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["received_Tx"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["received_Tx"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["received_Tx"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["received_Tx"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) p.time_enters_waiting_list = self.env.now if self.config.trace: @@ -295,23 +284,27 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) ): # it's a live transplant, but not pre-emptive as they're already on dialysis p.transplant_type = "live" - p.time_on_waiting_list = ( - self.config.tw_liveTx_initialisation[p.age_group][1] - * self.rng.weibull( - a=self.config.tw_liveTx_initialisation[p.age_group][0], - size=None, - ) - * self.config.multipliers["tw"]["prev"]["live"] + p.time_on_waiting_list = calculate_time_to_event( + self.rng, + scale=self.config.tw_liveTx_initialisation[p.age_group][ + "scale" + ], + shape=self.config.tw_liveTx_initialisation[p.age_group][ + "shape" + ], + multiplier=self.config.multipliers["tw"]["prev"]["live"], ) else: p.transplant_type = "cadaver" - p.time_on_waiting_list = ( - self.config.tw_cadTx_initialisation[p.age_group][1] - * self.rng.weibull( - a=self.config.tw_cadTx_initialisation[p.age_group][0], - size=None, - ) - * self.config.multipliers["tw"]["prev"]["cadaver"] + p.time_on_waiting_list = calculate_time_to_event( + self.rng, + scale=self.config.tw_cadTx_initialisation[p.age_group][ + "scale" + ], + shape=self.config.tw_cadTx_initialisation[p.age_group][ + "shape" + ], + multiplier=self.config.multipliers["tw"]["prev"]["cadaver"], ) p.pre_emptive_transplant = False self.env.process(self.start_dialysis_modality(p)) @@ -323,18 +316,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) ): ## they aren't suitable for transplant p.transplant_suitable = False - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["not_listed"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["not_listed"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["not_listed"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["not_listed"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) if self.config.trace: print( @@ -351,18 +341,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) p.time_on_waiting_list = ( self.config.sim_duration + 1 ) # they're listed but don't receive a transplant in the simulation period - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["listed"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["listed"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["listed"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["listed"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) p.time_enters_waiting_list = self.env.now if self.config.trace: @@ -372,18 +359,15 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) else: # they do receive a transplant in the simulation period p.transplant_suitable = True - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["received_Tx"][ + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["received_Tx"][ p.referral_type - ][p.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["received_Tx"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["received_Tx"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) p.time_enters_waiting_list = self.env.now if self.config.trace: @@ -396,70 +380,49 @@ def generator_prevalent_patient_arrivals(self, patient_type: str, location: str) ): # it's a live transplant, but not pre-emptive as they're already on dialysis p.transplant_type = "live" - p.time_on_waiting_list = ( - self.config.tw_liveTx_initialisation[p.age_group][1] - * self.rng.weibull( - a=self.config.tw_liveTx_initialisation[p.age_group][0], - size=None, - ) - * self.config.multipliers["tw"]["prev"]["live"] + p.time_on_waiting_list = calculate_time_to_event( + self.rng, + scale=self.config.tw_liveTx_initialisation[p.age_group][ + "scale" + ], + shape=self.config.tw_liveTx_initialisation[p.age_group][ + "shape" + ], + multiplier=self.config.multipliers["tw"]["prev"]["live"], ) else: p.transplant_type = "cadaver" - p.time_on_waiting_list = ( - self.config.tw_cadTx_initialisation[p.age_group][1] - * self.rng.weibull( - a=self.config.tw_cadTx_initialisation[p.age_group][0], - size=None, - ) - * self.config.multipliers["tw"]["prev"]["cadaver"] + p.time_on_waiting_list = calculate_time_to_event( + self.rng, + scale=self.config.tw_cadTx_initialisation[p.age_group][ + "scale" + ], + shape=self.config.tw_cadTx_initialisation[p.age_group][ + "shape" + ], + multiplier=self.config.multipliers["tw"]["prev"]["cadaver"], ) p.pre_emptive_transplant = False self.env.process(self.start_dialysis_modality(p)) - elif location == "live_transplant": - p.transplant_suitable = True - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["received_Tx"][p.referral_type][ - p.age_group - ][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["received_Tx"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, - ) - p.pre_emptive_transplant = None # Unknown for prevalent patients - p.time_of_transplant = self.env.now - p.transplant_type = "live" - if self.config.trace: - print( - f"Patient {p.id} of age group {p.age_group} is living with live donor transplant at time {self.env.now}." - ) - self.env.process(self.start_transplant(p)) - elif location == "cadaver_transplant": + elif "transplant" in location: + transplant_type = location.split("_")[0] p.transplant_suitable = True - p.time_until_death = min( - self.config.ttd_krt["initialisation"]["received_Tx"][p.referral_type][ - p.age_group - ][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["received_Tx"][ - p.referral_type - ][p.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + p.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["received_Tx"][ + p.referral_type + ][p.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["received_Tx"][ + p.referral_type + ][p.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) p.pre_emptive_transplant = None # Unknown for prevalent patients p.time_of_transplant = self.env.now - p.transplant_type = "cadaver" + p.transplant_type = transplant_type if self.config.trace: print( - f"Patient {p.id} of age group {p.age_group} is living with cadaver donor transplant at time {self.env.now}." + f"Patient {p.id} of age group {p.age_group} is living with {transplant_type} transplant at time {self.env.now}." ) self.env.process(self.start_transplant(p)) @@ -571,20 +534,16 @@ def start_krt(self, patient: Patient) -> Generator: # Patient is not suitable for transplant and so starts dialysis only pathway patient.transplant_suitable = False if patient.time_until_death == 0: - patient.time_until_death = min( - self.config.ttd_krt["incidence"]["not_listed"][ + patient.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["incidence"]["not_listed"][ patient.referral_type - ][patient.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["incidence"]["not_listed"][ - patient.referral_type - ][patient.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["inc"], - self.config.sim_duration + 1, + ][patient.age_group]["scale"], + shape=self.config.ttd_krt["incidence"]["not_listed"][ + patient.referral_type + ][patient.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["inc"], ) - if self.config.trace: print( f"Patient {patient.id} of age group {patient.age_group} started dialysis only pathway at time {self.env.now}." @@ -600,18 +559,15 @@ def start_krt(self, patient: Patient) -> Generator: ): # Although suitable for transplant, patient does not receive a transplant in the simulation period if patient.time_until_death == 0: - patient.time_until_death = min( - self.config.ttd_krt["incidence"]["listed"][ + patient.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["incidence"]["listed"][ patient.referral_type - ][patient.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["incidence"]["listed"][ - patient.referral_type - ][patient.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["inc"], - self.config.sim_duration + 1, + ][patient.age_group]["scale"], + shape=self.config.ttd_krt["incidence"]["listed"][ + patient.referral_type + ][patient.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["inc"], ) patient.time_on_waiting_list = ( self.config.sim_duration + 1 @@ -625,20 +581,16 @@ def start_krt(self, patient: Patient) -> Generator: else: # Patient may receive a transplant in the simulation period if patient.time_until_death == 0: - patient.time_until_death = min( - self.config.ttd_krt["incidence"]["received_Tx"][ + patient.time_until_death = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["incidence"]["received_Tx"][ patient.referral_type - ][patient.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["incidence"]["received_Tx"][ - patient.referral_type - ][patient.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["inc"], - self.config.sim_duration + 1, + ][patient.age_group]["scale"], + shape=self.config.ttd_krt["incidence"]["received_Tx"][ + patient.referral_type + ][patient.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["inc"], ) - # We now assign a transplant type: live or cadaver as this impacts the probability of starting pre-emptive transplant if ( self.rng.uniform(0, 1) @@ -779,18 +731,15 @@ def start_transplant(self, patient: Patient) -> Generator: # adjust the time to death as this is a patient that has recieve (not just been listed for) a transplant if patient.patient_flag == "incident": random_number = truncate_2dp(self.rng.uniform(0, 1)) - sampled_time = min( - self.config.ttd_krt["incidence"]["received_Tx"][ + sampled_time = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["incidence"]["received_Tx"][ patient.referral_type - ][patient.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["incidence"]["received_Tx"][ - patient.referral_type - ][patient.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["inc"], - self.config.sim_duration + 1, + ][patient.age_group]["scale"], + shape=self.config.ttd_krt["incidence"]["received_Tx"][ + patient.referral_type + ][patient.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["inc"], ) current_tud = patient.time_until_death patient.time_until_death = max( @@ -799,18 +748,15 @@ def start_transplant(self, patient: Patient) -> Generator: ) else: # prevalent patient random_number = truncate_2dp(self.rng.uniform(0, 1)) - sampled_time = min( - self.config.ttd_krt["initialisation"]["received_Tx"][ + sampled_time = calculate_time_to_event( + self.rng, + scale=self.config.ttd_krt["initialisation"]["received_Tx"][ patient.referral_type - ][patient.age_group][1] - * self.rng.weibull( - a=self.config.ttd_krt["initialisation"]["received_Tx"][ - patient.referral_type - ][patient.age_group][0], - size=None, - ) - * self.config.multipliers["ttd"]["prev"], - self.config.sim_duration + 1, + ][patient.age_group]["scale"], + shape=self.config.ttd_krt["initialisation"]["received_Tx"][ + patient.referral_type + ][patient.age_group]["shape"], + multiplier=self.config.multipliers["ttd"]["prev"], ) current_tud = patient.time_until_death patient.time_until_death = max( diff --git a/renal_capacity_model/process_outputs.py b/renal_capacity_model/process_outputs.py index 85b0fe5..8a70561 100644 --- a/renal_capacity_model/process_outputs.py +++ b/renal_capacity_model/process_outputs.py @@ -195,6 +195,7 @@ def write_results_to_excel( path_to_results_excel_file: str, combined_df: pd.DataFrame, costs_dfs: dict[str, pd.DataFrame], + sim_years: int, ): """Write combined model results from all model runs to Excel file @@ -203,6 +204,7 @@ def write_results_to_excel( combined_df (pd.DataFrame): Dataframe of all model results combined and processed costs_dfs (pd.DataFrame): Dataframe of costs of activity for each year of model simulation, for each model run + sim_years (int): Number of years of sim duration, to truncate model result dataframes """ with pd.ExcelWriter( path_to_results_excel_file, @@ -211,11 +213,13 @@ def write_results_to_excel( if_sheet_exists="replace", ) as writer: for outcome in combined_df.index.get_level_values(0).drop_duplicates(): - combined_df.loc[outcome].to_excel( + combined_df.loc[outcome].iloc[:, : sim_years + 1].to_excel( writer, sheet_name=outcome.replace("waiting_for_transplant", "wft") ) for activity, df in costs_dfs.items(): - df.to_excel(writer, sheet_name=f"{activity}_yearly") + df.iloc[:, : sim_years + 1].to_excel( + writer, sheet_name=f"{activity}_yearly" + ) logger.info( f"✅ 💾 Excel format model results written to: \n{path_to_results_excel_file}" )