<jupyter_start><jupyter_text>Lactate Discordance Project: Exploratory Analysis of Lactate in MIMIC-III by Illness Severity C.V. Cosgriff, MIT Critical DataThis notebook extracts the maximum lactate in the first 24 hours for all patients in the MIMIC-III. After excluding patients with length of stay below four hours, those lacking data, and those under the age of 16, the lactate levels are grouped using deciles of the OASIS score and then the distribution is plotted.__Updated 5/3/2019:__ Following the initial analysis described above, the use of vasopressors and invasive ventilation were examined in the top decile of the OASIS score stratified by normality of lactate. The analysis has also been edited to only include metavision data. 0 - Environment Setup<jupyter_code>import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import psycopg2

sns.set()
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

dbname = 'mimic'
schema_name = 'mimiciii'
query_schema = 'SET search_path TO ' + schema_name + ';'

con = psycopg2.connect(dbname=dbname)<jupyter_output><empty_output><jupyter_text>1 - Initial ExtractionExtract data from SQL database. Note we're using `labsfirstday`, `icustay_detail`, and `OASIS` materialized view. We also limit our cohort to patients from metavision.<jupyter_code>query = query_schema + '''
SELECT ied.icustay_id, ied.admission_age, la.lactate_max
       , os.oasis, ied.los_icu, ied.hospital_expire_flag         
FROM icustay_detail ied
LEFT JOIN labsfirstday la
    ON ied.icustay_id = la.icustay_id
INNER JOIN oasis os
    ON ied.icustay_id = os.icustay_id
LEFT JOIN icustays ie
    ON ied.icustay_id = ie.icustay_id
WHERE ie.dbsource = 'metavision';
'''

base_cohort = pd.read_sql_query(query, con)
display(base_cohort.head())
print(base_cohort.shape)<jupyter_output><empty_output><jupyter_text>Limit cohort to those who have a lactate, an OASIS score, are age >16, and have a LoS greater than 4 hours.<jupyter_code>base_cohort.loc[base_cohort.admission_age >= 90, 'admission_age'] = 91.3

include = (base_cohort.loc[:, 'admission_age']) > 16
include = include & (~pd.isna(base_cohort.loc[:, 'oasis']))
include = include & (~pd.isna(base_cohort.loc[:, 'lactate_max']))
incldue = include & (base_cohort.loc[:, 'los_icu'] >= 0.167)
cohort = base_cohort.loc[include, ['icustay_id', 'oasis', 'lactate_max']]<jupyter_output><empty_output><jupyter_text>2 - Initial Lactate vs. OASIS AnalysisWe then create deciles of the APACHE IVa score and assign each patient stay to a category.<jupyter_code>oasis_category = pd.qcut(x=cohort.oasis, q=np.arange(0, 1.1, 0.1), labels=np.arange(1, 11, 1))
cohort = cohort.assign(oasis_category=oasis_category)
cohort.loc[:, ['oasis_category', 'lactate_max']].groupby('oasis_category').describe()<jupyter_output><empty_output><jupyter_text>We can plot the above distributions.<jupyter_code>fig, ax = plt.subplots()
fig.set_size_inches(10,10)
for i in range(11):
    sns.distplot(cohort.loc[cohort.oasis_category == i, 'lactate_max'], hist=False, ax=ax, label=i)
plt.legend()
plt.title('Distribution of Maximum Lactate on Day 1, Grouped by OASIS Decile')<jupyter_output>/home/cvc/env/ML/lib/python3.6/site-packages/seaborn/distributions.py:198: RuntimeWarning: Mean of empty slice.
  line, = ax.plot(a.mean(), 0)
/home/cvc/env/ML/lib/python3.6/site-packages/numpy/core/_methods.py:85: RuntimeWarning: invalid value encountered in double_scalars
  ret = ret.dtype.type(ret / rcount)<jupyter_text>We can also answer the question, "within each decile, how many patient's have a _normal_ lactate?"<jupyter_code>def normal_lactate(x):
    return (x < 2).mean()

normal_lac_prop = [normal_lactate(cohort.loc[cohort.oasis_category == i, 'lactate_max']) for i in range(1,11)]

fig, ax = plt.subplots()
fig.set_size_inches(10,10)
sns.barplot(x=np.arange(1,11), y=normal_lac_prop, color=sns.xkcd_rgb['denim blue'], ax=ax)
plt.xlabel('OASIS Decile')
plt.ylabel('Proportion with Normal Lactate')<jupyter_output><empty_output><jupyter_text>3 - Brief Exploration of ICU Treatment DifferencesAfter presenting the above data to the Leo and the group the next steps for the analysis were decided below.__Focusing on day 1 as above extract these and examine in the top decile of OASIS:__* Vasopressor dose* Count of vasopressors* MV 0/1There exists materialized views for the different vasoproessor doses. We will want to use these views to get the total dose of vasopressor on the first day in the ICU for each drug listed here:* Dopamine* Epinephrine* Norepinephrine* Phenylephrine* VasopressinOnce we have the amount of each in the first day we can count the number of different vasopressors used on day 1. At our next meeting we will discuss conversion to a common metric if that is necessary.<jupyter_code>query = query_schema + '''
WITH vasopressor_dose AS (
    SELECT ie.icustay_id
    , SUM(nd.vaso_amount) AS norepi_dose -- total daily doses in mg
    , SUM(pe.vaso_amount) AS phenyl_dose
    , SUM(da.vaso_amount) AS dopa_dose
    , SUM(ep.vaso_amount) AS epi_dose
    , SUM(vp.vaso_amount) AS vaso_dose
    FROM icustays ie
    LEFT JOIN norepinephrine_dose nd
        ON ie.icustay_id = nd.icustay_id
        AND ie.intime - interval '4' hour < nd.starttime
        AND ie.intime + interval '28' hour > nd.endtime
    LEFT JOIN phenylephrine_dose pe
        ON ie.icustay_id = pe.icustay_id
        AND ie.intime - interval '4' hour < pe.starttime
        AND ie.intime + interval '28' hour > pe.endtime
    LEFT JOIN dopamine_dose da
        ON ie.icustay_id = da.icustay_id
        AND ie.intime - interval '4' hour < da.starttime
        AND ie.intime + interval '28' hour > da.endtime
    LEFT JOIN epinephrine_dose ep
        ON ie.icustay_id = ep.icustay_id
        AND ie.intime - interval '4' hour < ep.starttime
        AND ie.intime + interval '28' hour > ep.endtime
    LEFT JOIN vasopressin_dose vp
        ON ie.icustay_id = vp.icustay_id
        AND ie.intime - interval '4' hour < vp.starttime
        AND ie.intime + interval '28' hour > vp.endtime
    GROUP BY ie.icustay_id
)
SELECT vd.*, mv.vent
FROM vasopressor_dose vd
LEFT JOIN ventfirstday mv
    ON vd.icustay_id = mv.icustay_id;
'''

intervention_df = pd.read_sql_query(query, con)
print(intervention_df.shape)
display(intervention_df.head())
display(intervention_df.dropna().head())<jupyter_output>(61532, 7)<jupyter_text>As many patients will not have all or any of these medications, lets visualize the pattern of missingness for this data.<jupyter_code>sns.heatmap(intervention_df.reset_index().isna(), cbar=False)
(~pd.isna(intervention_df)).sum()<jupyter_output><empty_output><jupyter_text>Careful evaluation of our code doesn't suggest an obvious error, but it is strange that norepinephrine is used less than phenylephrine, and doesn't quite square with our domain knowledge. This also just may reflect older practice.Next we'll count the number of vasopressors present for each patient/icustay_id and then merge it with the cohort from above.<jupyter_code>pressor_count = intervention_df.drop(['icustay_id', 'vent'], axis=1).apply(lambda x: x.count(), axis=1)
intervention_df = intervention_df.assign(pressor_count=pressor_count)
cohort = cohort.merge(right=intervention_df, how='left', on='icustay_id')
print(cohort.shape)
display(cohort.head())<jupyter_output>(15811, 11)<jupyter_text>Now that we have merged, we have limited our vasopressor information to the metavision patients; lets repeat the examination of missingness to see if norepinephrine is more common in this recent cohort.<jupyter_code>sns.heatmap(cohort.reset_index().isna(), cbar=False)
(~pd.isna(cohort)).sum()<jupyter_output><empty_output><jupyter_text>Still a bit more patient's with phenylephrine doses. This data is up until 2012, but we'll have to carefully review our code. For now we'll go forward and limit our analysis to the top decile of OASIS.<jupyter_code>eda_df = cohort.loc[cohort.oasis_category == 10, :].copy()
print(eda_df.shape)
display(eda_df.head())<jupyter_output>(1482, 11)<jupyter_text>For exploring this data it will be useful to have a binary indiactor for _normal_ lactate (<2, as above).<jupyter_code>normal_lactate = (eda_df.lactate_max < 2).astype(int)
eda_df = eda_df.assign(normal_lactate=normal_lactate)<jupyter_output><empty_output><jupyter_text>We then plot the vasopressor count and stratify by whether or not lactate levels were normal.<jupyter_code>sns.countplot(x='pressor_count', hue='normal_lactate', data=eda_df)<jupyter_output><empty_output><jupyter_text>Patients with normal lactate received far less vasopressor therapy. It is curious why a patient with a maximum lactate of 2 would recieve any vasopressors, but these patients were also very sick. Perhaps these represent true non-responders; however, it is too soon to draw a conclusion and next steps may be to examine these patients more closely.Similarly, we can plot ventilation status on the first day against the lactate level.<jupyter_code>sns.catplot(x='normal_lactate', y='vent', kind='point', data=eda_df)<jupyter_output><empty_output><jupyter_text>Essentially everyone in both groups is ventilated. Before concluding, we can visualize the difference in total dose of each vasopressor between groups, however, we expect the dose to be higher in the patients with elevated lactate across the board.<jupyter_code>eda_df.groupby('normal_lactate').mean().loc[:, ['norepi_dose']].plot(kind='bar')
eda_df.groupby('normal_lactate').mean().loc[:, ['phenyl_dose']].plot(kind='bar')
eda_df.groupby('normal_lactate').mean().loc[:, ['dopa_dose']].plot(kind='bar')
eda_df.groupby('normal_lactate').mean().loc[:, ['epi_dose']].plot(kind='bar')
eda_df.groupby('normal_lactate').mean().loc[:, ['vaso_dose']].plot(kind='bar')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10001019471913548, 'fraction_numerical': 0.011825874197165869, 'mean_word_length': 5.432786885245902, 'pattern_counts': {'":': 0, '<': 65, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '196509', 'license': 'MIT', 'n_tokens_mistral': 3513, 'n_tokens_neox': 3228, 'n_words': 975, 'path': 'lactate-v-OASIS_MIMIC.ipynb', 'repo_name': 'cosgriffc/pred-lactate-discord', 'size': '9809'}