This post is a supplementary material for an assignment. The assignment is part of the Augmented Machine Learning unit for a Specialised Diploma in Data Science for Business. The aim of the assignment is to use DataRobot for predictive modelling. Exploratory data analysis and feature engineering will be done here in R before the data is imported into DataRobot.
Intro
The aim of this project is to classify if patients with Community Acquired Pneumonia (CAP) became better after seeing a doctor or became worse despite seeing a doctor. The variables of the dataset can be classified into 13 categories. The first 8 categories have been explored in the previous post. The remaining categories will be explored in this post.
library(tidyverse)
theme_set(theme_light())
# previously and partial EDA dataset
# https://github.com/notast/pneumonia-outcomes/blob/master/pneumonia_EDA1.Rmd
load("CAP_EDA1.RData")
# 13 categories
categories13<- readxl::read_excel("Incidence rate of community-acquired pneumonia in adults a population-based prospective active surveillance study in three cities in South America.xls", sheet=3)
categories13 %>% DT::datatable(rownames = F, options = list(searchHighlight = TRUE, paging= T))
Customized EDA functions from the previous post will be used here.
Sodium levels Lab_Na should be in numeric form but it is registered as a string. Upon closer inspection, there are no characters found in the variable. The variable can be converted into a numeric variable.
(dtype(df,"Lab"))
## tibble [2,112 x 20] (S3: tbl_df/tbl/data.frame)
## $ Lab_RBC : num [1:2112] 32 43 27 33 10.4 33 45 35 35.5 21.3 ...
## $ Lab_Hb : num [1:2112] 10.7 14.2 9.6 11.4 4.8 11 13.3 11.2 11 6.8 ...
## $ Lab_WBC : num [1:2112] 15.7 12.5 6.6 9.5 75.3 18.9 8.3 13.1 8.5 13.9 ...
## $ Lab_NeuImu : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
## $ Lab_Neu : num [1:2112] 89 80 86 76 NA 89 88 82 81 92 ...
## $ Lab_plt : num [1:2112] 175 170 120 274 27 128 333 621 496 180 ...
## $ Lab_Na : chr [1:2112] NA NA NA NA ...
## $ Lab_urea : num [1:2112] 60 NA 99 56 143 56.3 49 19 25 214 ...
## $ Lab_Cr : num [1:2112] 1.61 NA 0.77 0.84 2.94 0.88 0.95 0.83 0.73 8.11 ...
## $ Lab_Bicarb : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
## $ Lab_Sugar : num [1:2112] 76 NA 83 111 88 70 93 78 100 75 ...
## $ Lab_Alb : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
## $ Lab_lactate : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
## $ Lab_lactateHigh: chr [1:2112] "Unavailable" "Unavailable" "Unavailable" "Unavailable" ...
## $ Lab_CRP : num [1:2112] 48 96 NA 92 192 48 96 48 48 192 ...
## $ Lab_CRPHigh : chr [1:2112] "Yes" "Yes" "Unavailable" "Yes" ...
## $ Lab_pH : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
## $ Lab_CO2 : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
## $ Lab_O2 : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
## $ Lab_FiO2 : num [1:2112] NA NA NA NA NA NA NA NA NA NA ...
# convert Lab_na to num
df<-df %>% mutate(Lab_Na=as.numeric(Lab_Na))
Missing Lab values
More than half of the Lab_ variables have >40% missing values. These variables will be removed. Lab_CRPHigh and Lab_lactateHigh are binary variables indicating if CRP Lab_CRP and lactate levels Lab_lactate are above normal limits. As Lab_CRP and Lab_lactate will be dropped due to too many missing values, Lab_CRPHigh and Lab_lactateHigh will also be dropped .
# insert decimal point
df<-df %>% mutate(Lab_Hb= if_else(Lab_Hb>100, Lab_Hb/10, Lab_Hb))
Low Lab_Neu
Neutrophil <40 are considered below normal limits. The initially hypothesis is that these patients with low neutrophil have either HIV or on immunosuppression drugs. However, majority of these patients have neither. Although, the initial hypothesis was incorrect, there are other differentials for low neutrophil levels. Considering, the number of these outliers is small (n=22), these observations shall be kept.
## # A tibble: 22 x 3
## Lab_Neu Hx_HIV Hx_immune
## <dbl> <chr> <chr>
## 1 11 No No
## 2 12 No Yes
## 3 0 Unavailable No
## 4 24 No No
## 5 6 No No
## 6 34 No No
## 7 0 No <NA>
## 8 2 No Yes
## 9 5 Unavailable No
## 10 1 No No
## # ... with 12 more rows
## # A tibble: 1 x 2
## Hx_diabetes n
## <chr> <int>
## 1 Yes 7
10 CS_ cultures related category
There are 23 variables under CS and the most important variables are CS_Organism1 and CS_Organism2 as they indicate which organism is causing the CAP. The majority of the other CS variables are methods to identify the organism. However, there are >90% missing values for CS_Organism1 and CS_Organism2 thus the methods of identifying the organisms though may not be missing are useless. All variables under CS will be removed.
Map values from Abx_Class to replace NA values in Abx_ClassOther. After using Abx_Class to expand Abx_ClassOther, Abx_Class will be dropped. Rename the updated Abx_ClassOther to Abx_ClassUpdated for more intuitive understanding of variable name
From the antibiotics given, the number of antibiotics given can be calculated. There are 4 observations with NA values being calculated. These observations shall be examined to see if there are missing values or if no antibiotics were given to begin with. (Perhaps, the doctor had high index of suspicion it was a viral CAP. In such situation, antibiotics would be ineffective)
## function to extract abx taken by pt (lond df)
abx_taken_Longdf<- function(dfr){
# select case number and abx col
dfr %>% select(Pt_CaseNumber, starts_with("Abx")) %>%
# remove unrelated abx columns
select(- c(ends_with("Start") | ends_with("End") | Abx_Duration | starts_with("Abx_Class")|ends_with("Detail"))) %>%
# into long df
pivot_longer(-Pt_CaseNumber, names_to="Abx_type", values_to="Used") %>%
# filter abx taken
filter(Used=="Yes")
}
## join no of abx taken w main df
df<-left_join(x= df,
y=df %>% abx_taken_Longdf() %>% group_by(Pt_CaseNumber) %>% count(Used, name= "New_Abx_no") %>% ungroup(),
by= "Pt_CaseNumber")
df %>% count(New_Abx_no)
# convert abx as integer to numeric as Error: Problem with `mutate()` input `Abx_no`. x must be a double vector, not an integer vector. i Input `Abx_no` is `case_when(...)`.
df<- df %>% mutate(New_Abx_no= as.numeric(New_Abx_no))
Patient 254, 916, 964 did not receive any antibiotic. The number of antibiotics taken will be 0 and the antibiotics duration will also be 0. Patient 1864 received Macrolides class antibiotics as an empirical treatment. Fill the number of antibiotics taken as 1 and fill up other antibiotics taken Abx_OtherYN as Yes and fill up details of other antibiotics taken Abx_OtherDetail as Macrolides.
12 observations had NA antibiotic duration. Mostly like due to some data calculation or data entry error as only 3 observations in the entire dataset did not receive antibiotics.
An attempt is made to calculate the duration using start and end dates of the antibiotics given.
3/12 patients with missing antibiotics duration had the start dates of their antibiotics captured. However, these patients took other antibiotics which did not have the start dates captured. We are unable to impute any of the missing antibiotic duration by calculating the difference in antibiotic start and end dates. We will impute the missing antibiotic duration by other means later. Antibiotic start and end dates will be removed as there are no longer useful and have no predictive power.
# abx with start dates
abx_date<-df %>% select(ends_with("Start")) %>% colnames() %>% str_replace("Start","")
# type of abx taken for pt w m/s abx duration
abx_ms<-
# filter pt with m/s abx_duration
df %>% filter(is.na(Abx_Duration)) %>%
# used above function to find out abx taken
abx_taken_Longdf() %>%
# distinct abx taken by this group of pts
distinct(Abx_type) %>% pull()
# types of abx taken for pt w m/s abx duration which have date of abx captured
abx_msAndDateStarted<-intersect(abx_date, abx_ms)
# pt w m/s abx duration who took abx with at least one abx start date
(df %>% filter(is.na(Abx_Duration)) %>%
abx_taken_Longdf() %>%
group_by(Pt_CaseNumber) %>% filter(any(Abx_type==abx_msAndDateStarted[[1]]) | any(Abx_type==abx_msAndDateStarted[[2]])) %>% summarise(n=n(), .groups="drop"))
## # A tibble: 3 x 2
## Pt_CaseNumber n
## <int> <int>
## 1 453 3
## 2 1198 4
## 3 1393 4
# remove start and end date
df<-df %>% select(- c(ends_with("Start") | ends_with("End")))
Care_admit indicates if the patient was admitted to a hospital and Care_ICU indicates if patient had an ICU stay. 324 patients who were hospitalized also had ICU stay. The labels in Care_admit will include details to reflect patient who were admitted AND had ICU stay (label as Yes (w ICU)). After using information from Care_ICU to expand Care_admit, Care_ICU will be dropped.
## # A tibble: 4 x 2
## Care_admit new_tally
## <chr> <int>
## 1 No 631
## 2 Unavailable 2
## 3 Yes 1149
## 4 Yes (w ICU) 330
Breathing aid
Care_breathingAid indicates if patient in ICU used any breathing aids. Care_ breathingAidType details the type of breathing aids used.
Details from Care_breathingAidType will be integrated into Care_breathingAid and the Care_breathingAidType will be dropped.
Currently each V_ column indicates if the patient has received that particular vaccine. As there are only two columns, the values of both columns will be united to indicate which vaccines the patient has received.
## # A tibble: 4 x 2
## V_vaccine n
## <chr> <int>
## 1 flu 313
## 2 no/unavailable 1434
## 3 pneumococcal, 41
## 4 pneumococcal,flu 324
Wrap up
The original dataset had 2302 rows and 176 columns, after EDA the dataset has 2112 rows and 78 columns. More than half of the columns were removed and compressed via EDA.
# Clean up intermediate columns created during EDA
df<-df %>% select(-Used) %>% rename(Abx_no=New_Abx_no)
dim(df)
## [1] 2112 78
The cleaned up dataset is ready for some action. In the next post, some feature engineering will be done.