Combine populations for weighted outcomes#

Use the patient population to combine the outcomes from the separate cohorts. This notebook combines the added utility, shift in mean mRS, and proportion with mRS<=2. The following notebook combines the mRS distributions.

Plain English summary#

In the previous notebook we calculated outcomes for patients across all of England and Wales. This assumed fixed times to treatment except for travel times to the stroke units. We calculated one set of times for the drip-and-ship scenario, where patients first go to their nearest stroke unit and are later transferred to the MT unit if they need thrombectomy, and a second set of times for the mothership scenario, where every patient goes directly to the MT unit.

The existing data has separate entries for each cohort of patients:

  • patients with an nLVO treated with IVT

  • patients with a LVO treated with IVT only

  • patients with a LVO treated with MT only

  • patients with a LVO treated with both IVT and MT

In this notebook we will combine the data of multiple groups of patients.

Aims#

To find the averaged outcome data for added utility, shift in mean mRS, and proportion with an mRS score of 2 or less for the following groups:

  • patients with a LVO and a mix of the available treatment types

  • patients with an nLVO and patients with a LVO and a mix of the available treatment types

Method#

Use the example patient proportions to calculate a weighted sum of the separate cohorts.

Notebook setup#

import pandas as pd
import numpy as np
import os

import stroke_outcome.outcome_utilities
dir_output = 'output'

Import data#

Patient proportions:

proportions = pd.read_csv(
    os.path.join(dir_output, 'patient_proportions.csv'),
    index_col=0, header=None).squeeze()
proportions
0
haemorrhagic         0.13600
lvo_no_treatment     0.14648
lvo_ivt_only         0.00840
lvo_ivt_mt           0.08500
lvo_mt_only          0.01500
nlvo_no_treatment    0.50252
nlvo_ivt             0.10660
Name: 1, dtype: float64

Calculate some additional proportions:

# Proportion of treated LVO patients:
prop_lvo_treated = 0.0

for key, value in proportions.items():
    if (('lvo' in key) & ('nlvo' not in key) & ('no_treat' not in key)):
        print(key)
        prop_lvo_treated += value

prop_lvo_treated
lvo_ivt_only
lvo_ivt_mt
lvo_mt_only
0.10840000000000001
# Proportion of treated ischaemic patients:
prop_ischaemic_treated = 0.0

for key, value in proportions.items():
    if (('lvo' in key) & ('no_treat' not in key)):
        print(key)
        prop_ischaemic_treated += value

prop_ischaemic_treated
lvo_ivt_only
lvo_ivt_mt
lvo_mt_only
nlvo_ivt
0.21500000000000002
# Proportion of ischaemic patients:
prop_ischaemic = 1.0 - proportions['haemorrhagic']

prop_ischaemic
0.864

Outcomes:

df_results = pd.read_csv(os.path.join(dir_output, 'cohort_outcomes.csv'), index_col=0)
df_results.info()
<class 'pandas.core.frame.DataFrame'>
Index: 34752 entries, Adur 001A to York 024F
Data columns (total 51 columns):
 #   Column                                        Non-Null Count  Dtype  
---  ------                                        --------------  -----  
 0   closest_ivt_time                              34752 non-null  float64
 1   closest_ivt_unit                              34752 non-null  object 
 2   closest_mt_time                               34752 non-null  float64
 3   closest_mt_unit                               34752 non-null  object 
 4   transfer_mt_time                              34752 non-null  float64
 5   transfer_mt_unit                              34752 non-null  object 
 6   mt_transfer_required                          34752 non-null  bool   
 7   ivt_drip_ship                                 34752 non-null  float64
 8   mt_drip_ship                                  34752 non-null  float64
 9   ivt_mothership                                34752 non-null  float64
 10  mt_mothership                                 34752 non-null  float64
 11  drip_ship_nlvo_ivt_added_utility              34752 non-null  float64
 12  drip_ship_nlvo_ivt_mean_mrs                   34752 non-null  float64
 13  drip_ship_nlvo_ivt_mrs_less_equal_2           34752 non-null  float64
 14  drip_ship_nlvo_ivt_mrs_shift                  34752 non-null  float64
 15  drip_ship_nlvo_ivt_added_mrs_less_equal_2     34752 non-null  float64
 16  drip_ship_lvo_ivt_added_utility               34752 non-null  float64
 17  drip_ship_lvo_ivt_mean_mrs                    34752 non-null  float64
 18  drip_ship_lvo_ivt_mrs_less_equal_2            34752 non-null  float64
 19  drip_ship_lvo_ivt_mrs_shift                   34752 non-null  float64
 20  drip_ship_lvo_ivt_added_mrs_less_equal_2      34752 non-null  float64
 21  drip_ship_lvo_ivt_mt_added_utility            34752 non-null  float64
 22  drip_ship_lvo_ivt_mt_mean_mrs                 34752 non-null  float64
 23  drip_ship_lvo_ivt_mt_mrs_less_equal_2         34752 non-null  float64
 24  drip_ship_lvo_ivt_mt_mrs_shift                34752 non-null  float64
 25  drip_ship_lvo_ivt_mt_added_mrs_less_equal_2   34752 non-null  float64
 26  drip_ship_lvo_mt_added_utility                34752 non-null  float64
 27  drip_ship_lvo_mt_mean_mrs                     34752 non-null  float64
 28  drip_ship_lvo_mt_mrs_less_equal_2             34752 non-null  float64
 29  drip_ship_lvo_mt_mrs_shift                    34752 non-null  float64
 30  drip_ship_lvo_mt_added_mrs_less_equal_2       34752 non-null  float64
 31  mothership_nlvo_ivt_added_utility             34752 non-null  float64
 32  mothership_nlvo_ivt_mean_mrs                  34752 non-null  float64
 33  mothership_nlvo_ivt_mrs_less_equal_2          34752 non-null  float64
 34  mothership_nlvo_ivt_mrs_shift                 34752 non-null  float64
 35  mothership_nlvo_ivt_added_mrs_less_equal_2    34752 non-null  float64
 36  mothership_lvo_ivt_added_utility              34752 non-null  float64
 37  mothership_lvo_ivt_mean_mrs                   34752 non-null  float64
 38  mothership_lvo_ivt_mrs_less_equal_2           34752 non-null  float64
 39  mothership_lvo_ivt_mrs_shift                  34752 non-null  float64
 40  mothership_lvo_ivt_added_mrs_less_equal_2     34752 non-null  float64
 41  mothership_lvo_ivt_mt_added_utility           34752 non-null  float64
 42  mothership_lvo_ivt_mt_mean_mrs                34752 non-null  float64
 43  mothership_lvo_ivt_mt_mrs_less_equal_2        34752 non-null  float64
 44  mothership_lvo_ivt_mt_mrs_shift               34752 non-null  float64
 45  mothership_lvo_ivt_mt_added_mrs_less_equal_2  34752 non-null  float64
 46  mothership_lvo_mt_added_utility               34752 non-null  float64
 47  mothership_lvo_mt_mean_mrs                    34752 non-null  float64
 48  mothership_lvo_mt_mrs_less_equal_2            34752 non-null  float64
 49  mothership_lvo_mt_mrs_shift                   34752 non-null  float64
 50  mothership_lvo_mt_added_mrs_less_equal_2      34752 non-null  float64
dtypes: bool(1), float64(47), object(3)
memory usage: 13.6+ MB

Mean outcomes#

Combine LVO results#

Combine the separate IVT, MT, and IVT and MT cohorts for LVO into one “LVO” group.

for model_name in ['drip_ship', 'mothership']:
    for outcome_name in ['added_utility', 'mrs_less_equal_2', 'mrs_shift', 'added_mrs_less_equal_2']:
        # New column for results:
        col = f'{model_name}_lvo_mix_{outcome_name}'
        
        # Combine results:
        df_results[col] = (
            (df_results[f'{model_name}_lvo_ivt_{outcome_name}'] * proportions['lvo_ivt_only']) +
            (df_results[f'{model_name}_lvo_mt_{outcome_name}'] * proportions['lvo_mt_only']) +
            (df_results[f'{model_name}_lvo_ivt_mt_{outcome_name}'] * proportions['lvo_ivt_mt'])
        )

        # Divide by proportion of LVO patients:
        df_results[col] = df_results[col] / prop_lvo_treated
        
        # Round the results:
        df_results[col] = np.round(df_results[col], 5)

Calculate treatment rates based on expected proportions of LVO/nLVO#

Add weighted benefit based on proportions of treated LVO and nLVO: Add weighted utility, mrs<=2, and mean_shift based on stroke type and treatment rates.

The expected shift from the no-treatment outcomes is zero for the following groups:

  • Haemorrhagic

  • LVO no treatment

  • nLVO no treatment

so we can skip adding them in the following equation.

for model_name in ['drip_ship', 'mothership']:
    for outcome_name in ['added_utility', 'mrs_less_equal_2', 'mrs_shift', 'added_mrs_less_equal_2']:
        col = f'{model_name}_weighted_{outcome_name}'
        df_results[col] = (
            (df_results[f'{model_name}_nlvo_ivt_{outcome_name}'] * proportions['nlvo_ivt']) +
            (df_results[f'{model_name}_lvo_ivt_{outcome_name}'] * proportions['lvo_ivt_only']) +
            (df_results[f'{model_name}_lvo_mt_{outcome_name}'] * proportions['lvo_mt_only']) +
            (df_results[f'{model_name}_lvo_ivt_mt_{outcome_name}'] * proportions['lvo_ivt_mt'])
        )
        # Remove proportion of non-ischaemic patients:
        df_results[col] = df_results[col] / prop_ischaemic

        # Round the results:
        df_results[col] = np.round(df_results[col], 5)
for model_name in ['drip_ship', 'mothership']:
    for outcome_name in ['added_utility', 'mrs_less_equal_2', 'mrs_shift', 'added_mrs_less_equal_2']:
        col = f'{model_name}_weighted_treated_{outcome_name}'
        df_results[col] = (
            (df_results[f'{model_name}_nlvo_ivt_{outcome_name}'] * proportions['nlvo_ivt']) +
            (df_results[f'{model_name}_lvo_ivt_{outcome_name}'] * proportions['lvo_ivt_only']) +
            (df_results[f'{model_name}_lvo_mt_{outcome_name}'] * proportions['lvo_mt_only']) +
            (df_results[f'{model_name}_lvo_ivt_mt_{outcome_name}'] * proportions['lvo_ivt_mt'])
        )
        # Remove proportion of non-ischaemic patients:
        df_results[col] = df_results[col] / prop_ischaemic_treated

        # Round the results:
        df_results[col] = np.round(df_results[col], 5)
# Show results
df_results.head().T
LSOA Adur 001A Adur 001B Adur 001C Adur 001D Adur 001E
closest_ivt_time 17.6 18.7 17.6 17.6 16.5
closest_ivt_unit BN25BE BN25BE BN112DH BN112DH BN112DH
closest_mt_time 17.6 18.7 19.8 19.8 19.8
closest_mt_unit BN25BE BN25BE BN25BE BN25BE BN25BE
transfer_mt_time 0.0 0.0 31.6 31.6 31.6
... ... ... ... ... ...
drip_ship_weighted_treated_added_mrs_less_equal_2 0.14571 0.14512 0.11861 0.11861 0.11918
mothership_weighted_treated_added_utility 0.13633 0.13579 0.13525 0.13525 0.13525
mothership_weighted_treated_mrs_less_equal_2 0.56689 0.5663 0.56571 0.56571 0.56571
mothership_weighted_treated_mrs_shift -0.71592 -0.7129 -0.70988 -0.70988 -0.70988
mothership_weighted_treated_added_mrs_less_equal_2 0.14571 0.14512 0.14453 0.14453 0.14453

75 rows × 5 columns

Save to file:

# Save
df_results.to_csv(os.path.join(dir_output, 'cohort_outcomes_weighted.csv'), index_label='lsoa')
summary = df_results.mean(axis=0).T
summary.to_csv(os.path.join(dir_output, 'cohort_outcomes_weighted_summary.csv'), index_label='lsoa')
summary
/tmp/ipykernel_49202/164885061.py:1: FutureWarning: The default value of numeric_only in DataFrame.mean is deprecated. In a future version, it will default to False. In addition, specifying 'numeric_only=None' is deprecated. Select only valid columns or specify the value of numeric_only to silence this warning.
  summary = df_results.mean(axis=0).T
closest_ivt_time                                       19.548181
closest_mt_time                                        34.939310
transfer_mt_time                                       27.509447
mt_transfer_required                                    0.675098
ivt_drip_ship                                         109.548181
                                                         ...    
drip_ship_weighted_treated_added_mrs_less_equal_2       0.124662
mothership_weighted_treated_added_utility               0.127779
mothership_weighted_treated_mrs_less_equal_2            0.557609
mothership_weighted_treated_mrs_shift                  -0.668417
mothership_weighted_treated_added_mrs_less_equal_2      0.136428
Length: 72, dtype: float64

Save separate added utility for sharing#

These saved files are pretty big (several 10s of megabytes). The stroke outcome paper shows only the added utility, so create a separate file that contains only the added utility information in the maps to save file space.

Keep the following columns, which are anything to do with treatment or travel times or added utility:

cols_to_keep = [
    'closest_ivt_time', 'closest_ivt_unit',
    'closest_mt_time', 'closest_mt_unit',
    'transfer_mt_time', 'transfer_mt_unit', 'mt_transfer_required',
    'ivt_drip_ship', 'mt_drip_ship',
    'ivt_mothership', 'mt_mothership'
]

cols_to_keep += [c for c in df_results.columns if 'added_utility' in c]

df_added_utility = df_results[cols_to_keep].copy()

Save this data to file:

df_added_utility.to_csv(os.path.join(dir_output, 'fig_data_englandwales_maps_added_utility.csv'))

This file does not include the “Advantage of mothership” data because it takes up more file space and is simple enough to calculate later.

To find this information, take the difference between matching data in the mothership and the drip and ship columns:

outcome_names = ['added_utility']
cohort_names = ['nlvo_ivt', 'lvo_ivt', 'lvo_mt', 'lvo_mix', 'weighted', 'weighted_treated']

cols_diff = [f'mothership_minus_dripship_{c}_{o}' for c in cohort_names for o in outcome_names]
cols_moth = [f'mothership_{c}_{o}' for c in cohort_names for o in outcome_names]
cols_drip = [f'drip_ship_{c}_{o}' for c in cohort_names for o in outcome_names]

df_added_utility[cols_diff] = df_added_utility[cols_moth].values - df_added_utility[cols_drip].values

# Round the values again in case of floating point error:
df_added_utility[cols_diff] = np.round(df_added_utility[cols_diff], 5)
df_added_utility.info()
<class 'pandas.core.frame.DataFrame'>
Index: 34752 entries, Adur 001A to York 024F
Data columns (total 31 columns):
 #   Column                                                    Non-Null Count  Dtype  
---  ------                                                    --------------  -----  
 0   closest_ivt_time                                          34752 non-null  float64
 1   closest_ivt_unit                                          34752 non-null  object 
 2   closest_mt_time                                           34752 non-null  float64
 3   closest_mt_unit                                           34752 non-null  object 
 4   transfer_mt_time                                          34752 non-null  float64
 5   transfer_mt_unit                                          34752 non-null  object 
 6   mt_transfer_required                                      34752 non-null  bool   
 7   ivt_drip_ship                                             34752 non-null  float64
 8   mt_drip_ship                                              34752 non-null  float64
 9   ivt_mothership                                            34752 non-null  float64
 10  mt_mothership                                             34752 non-null  float64
 11  drip_ship_nlvo_ivt_added_utility                          34752 non-null  float64
 12  drip_ship_lvo_ivt_added_utility                           34752 non-null  float64
 13  drip_ship_lvo_ivt_mt_added_utility                        34752 non-null  float64
 14  drip_ship_lvo_mt_added_utility                            34752 non-null  float64
 15  mothership_nlvo_ivt_added_utility                         34752 non-null  float64
 16  mothership_lvo_ivt_added_utility                          34752 non-null  float64
 17  mothership_lvo_ivt_mt_added_utility                       34752 non-null  float64
 18  mothership_lvo_mt_added_utility                           34752 non-null  float64
 19  drip_ship_lvo_mix_added_utility                           34752 non-null  float64
 20  mothership_lvo_mix_added_utility                          34752 non-null  float64
 21  drip_ship_weighted_added_utility                          34752 non-null  float64
 22  mothership_weighted_added_utility                         34752 non-null  float64
 23  drip_ship_weighted_treated_added_utility                  34752 non-null  float64
 24  mothership_weighted_treated_added_utility                 34752 non-null  float64
 25  mothership_minus_dripship_nlvo_ivt_added_utility          34752 non-null  float64
 26  mothership_minus_dripship_lvo_ivt_added_utility           34752 non-null  float64
 27  mothership_minus_dripship_lvo_mt_added_utility            34752 non-null  float64
 28  mothership_minus_dripship_lvo_mix_added_utility           34752 non-null  float64
 29  mothership_minus_dripship_weighted_added_utility          34752 non-null  float64
 30  mothership_minus_dripship_weighted_treated_added_utility  34752 non-null  float64
dtypes: bool(1), float64(27), object(3)
memory usage: 8.3+ MB

Conclusion#

We have calculated a weighted sum of the separate patient cohorts to find outcome data for a mix of LVO patients and for the treated ischaemic population.

We have also saved a file with just the added utility results from all of the patient cohorts. This file contains a copy of the data that will be used to create maps of added utility around England and Wales, and is saved separately so that this data can be shared without everyone needing to download a massive file of mostly irrelevant data.