--- title: "Using Constellation to Identify Sepsis" author: "Mark Sendak" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Identify Sepsis} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ## Sepsis Implementing a sepsis definition motivated development of the constellation package. There is ongoing debate in the medical literature about what constitutes sepsis, but our local team of clinical experts agreed to define sepsis as the occurrence of all 3 following criteria: 1. 2 or more SIRS criteria + Temperature > 100.4 F or Temperature < 96.8 F (**6 hours**) + Heart Rate > 90 (**6 hours**) + Respiration Rate > 20 (**6 hours**) + WBC Count > 12, WBC Count < 4 (**24 hours**) 2. Blood culture order (**24 hours**) 3. End organ damage + Creatinine > 2.0 (**24 hours**) + INR > 1.5 (**24 hours**) + Total bilirubin > 2.0 (**24 hours**) + systolic BP < 90 or decrease in SBP by > 40 over 6 hours (**24 hours**) + Platelets < 100 (**24 hours**) + Lactate >= 2 (**24 hours**) For each criteria, there is both a threshold to signify dysfunction as well as a relevant time window to search for events. A major challenge of working with medical data is that the sampling rate varies across measurements. For example, vital signs are sampled more frequently than blood labs, so their information is carried forward for a shorter period of time than lab results. There are 3 steps to identify sepsis and each step corresponds to a constellation function: 1. Identify instances of systolic blood pressure drops > 40 over 6 hours (**value_change()** function) 2. Identify instances of SIRS >= 2 (**constellate_criteria()** function) 3. Identify instances in which patients have SIRS >= 2, a blood culture order, and evidence of end organ damage (**constellate()** function) Lastly, it may be required to separate distinct sepsis episodes during a single inpatient admission. For example, you may consider that sepsis events separated by more than 72 hours are distinct episodes that require additional evaluation and treatment. Thus, instances within 72 hours of an incident event are considered to be the same episode and instances separated by a minimum of 72 hours are considered to be distinct episodes. This logic is implemented via the **incidents()** function. ## View Vignette with Output The .Rmd version of the vignette will not show code output. If you'd like to see code output, please do the following: ```{r vignette_view, message = FALSE, eval = FALSE} library(constellation) vignette("identify_sepsis", package = "constellation") ``` ## Environment and Data Prep First, load constellation and fasttime ```{r setup, message = FALSE} library(constellation) library(data.table) ``` Next, prep the timestamps ```{r timestamps, message = FALSE} for (dt in list(vitals, labs, orders)) { date_col <- grep("TIME", names(dt), value = TRUE) set(dt, j = date_col, value = as.POSIXct(dt[[date_col]], format = "%Y-%m-%dT%H:%M:%SZ", tz = "UTC")) } ``` ## value_change() - identify systolic blood pressure drops Use this function to identify all instances of a drop in systolic blood pressure of greater than 40 within 6 hours. ```{r bp_drop, message = FALSE} systolic_bp <- vitals[VARIABLE == "SYSTOLIC_BP"] systolic_bp_drop <- value_change(systolic_bp, value = 40, direction = "down", window_hours = 6, join_key = "PAT_ID", time_var = "RECORDED_TIME", value_var = "VALUE", mult = "all") head(systolic_bp_drop) ``` ## constellate_criteria() - identify SIRS >= 2 First, break out all the SIRS component tables applying dysfunction thresholds ```{r sirs_components} temp <- vitals[VARIABLE == "TEMPERATURE" & (VALUE > 100.4 | VALUE < 96.8)] pulse <- vitals[VARIABLE == "PULSE" & VALUE > 90] resp <- vitals[VARIABLE == "RESPIRATORY_RATE" & VALUE > 20] wbc <- labs[VARIABLE == "WBC" & (VALUE > 12 | VALUE < 4)] ``` Next, identify which criteria are met at every measurement time ```{r sirs_calculate, message = FALSE} sirs <- constellate_criteria(temp, pulse, resp, wbc, criteria_names = c("TEMPERATURE", "PULSE", "RESPIRATORY_RATE", "WBC"), window_hours = c(6, 6, 6, 24), join_key = "PAT_ID", time_var = "RECORDED_TIME") head(sirs) ``` Sum values in the 4 columns to calculate SIRS score at every measurement time and subset to instances where SIRS >= 2 ```{r sirs_sum, message = FALSE} sirs[, SIRS_SCORE := TEMPERATURE + PULSE + RESPIRATORY_RATE + WBC] sirs <- sirs[SIRS_SCORE >= 2] head(sirs) ``` ## constellate() - identify instances where 3 sepsis criteria are met #### Compile end organ damage First, build a table that combines all measurements for end organ damage: ```{r end_organ, message = FALSE} ## Subset values end_organ <- labs[ (VARIABLE == "CREATININE" & VALUE > 2.0) | (VARIABLE == "INR" & VALUE > 1.5) | (VARIABLE == "BILIRUBIN" & VALUE > 2.0) | (VARIABLE == "PLATELETS" & VALUE < 100) | (VARIABLE == "LACTATE" & VALUE >= 2.0) ] ## normalize systolic_bp_drop systolic_bp_drop <- systolic_bp_drop[,.(PAT_ID, CURRENT_RECORDED_TIME, CURRENT_VALUE)] systolic_bp_drop[, VARIABLE := "SYSTOLIC_BP_DROP"] setnames(systolic_bp_drop, c("CURRENT_RECORDED_TIME", "CURRENT_VALUE"), c("RECORDED_TIME", "VALUE")) ## subset low systolic_bp systolic_bp <- systolic_bp[VALUE < 90] ## Combine SBP drop and < 90 end_organ <- rbind(end_organ, systolic_bp, systolic_bp_drop) ``` #### Normalize blood culture orders Normalize blood culture order data to feed into constellate() function. The timestamp variable in all time series data frames passed to constellate() must be identical. ```{r bc_orders, message = FALSE} setnames(orders, "ORDER_TIME", "RECORDED_TIME") ``` #### Run constellate() Calculate the first instant in which all 3 criteria are met for every patient. ```{r sepsis_def_first, message = FALSE} ## Find first sepsis events sepsis <- constellate(sirs, orders, end_organ, window_hours = c(24, 24, 24), join_key = "PAT_ID", time_var = "RECORDED_TIME", event_name = "SEPSIS", mult = "first") head(sepsis) ``` Calculate the last instant in which all 3 criteria are met for every patient. ```{r sepsis_def_last, message = FALSE} ## Find last sepsis events sepsis <- constellate(sirs, orders, end_organ, window_hours = c(24, 24, 24), join_key = "PAT_ID", time_var = "RECORDED_TIME", event_name = "SEPSIS", mult = "last") head(sepsis) ``` Calculate every instance in which all 3 criteria are met for every patient. ```{r sepsis_def_all, message = FALSE} ## Find all sepsis events sepsis <- constellate(sirs, orders, end_organ, window_hours = c(24, 24, 24), join_key = "PAT_ID", time_var = "RECORDED_TIME", event_name = "SEPSIS", mult = "all") head(sepsis) ``` ## Separate incident sepsis events for every patient Separate sepsis events that occur more than 72 hours apart for each patient. ```{r sepsis_incidents, message = FALSE} ## Find incident sepsis events for each patient sepsis <- incidents(sepsis, window_hours = 72, time_var = "SEPSIS_TIME", join_key = "PAT_ID") head(sepsis) ``` ## Technical References If you'd like to learn more about our sepsis model, please see our publications from the [International Conerence on Machine Learning](http://proceedings.mlr.press/v70/futoma17a/futoma17a.pdf) and [Machine Learning in Health Care](http://mucmd.org/CameraReadySubmissions/53%5CCameraReadySubmission%5CCR.pdf).