This project uses simulated hospital data to build a model predicting the likelihood of patient readmission within 30 days after discharge. It demonstrates a full data science workflow using R: from data generation and exploration to model building and evaluation.
# Load cleaned data
patient_data <- read_csv("data/patient_data_clean.csv")
## Rows: 1000 Columns: 10
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (5): sex, admission_type, primary_diagnosis, discharge_disposition, read...
## dbl (5): patient_id, age, comorbidity_count, length_of_stay, prior_admissions
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Convert relevant columns to factors
patient_data <- patient_data %>%
mutate(across(
c(sex, admission_type, primary_diagnosis, discharge_disposition, readmitted_30_days),
as.factor
))
glimpse(patient_data)
## Rows: 1,000
## Columns: 10
## $ patient_id <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1…
## $ age <dbl> 66, 82, 42, 35, 66, 64, 41, 88, 54, 37, 43, 20, …
## $ sex <fct> Male, Male, Female, Female, Female, Female, Fema…
## $ admission_type <fct> Urgent, Emergency, Emergency, Urgent, Emergency,…
## $ primary_diagnosis <fct> COPD, COPD, Diabetes, Pneumonia, Kidney Disease,…
## $ comorbidity_count <dbl> 5, 6, 2, 0, 6, 2, 1, 4, 0, 5, 5, 1, 0, 0, 2, 3, …
## $ length_of_stay <dbl> 4, 11, 6, 6, 5, 4, 1, 9, 4, 3, 4, 7, 6, 8, 6, 5,…
## $ discharge_disposition <fct> Transferred, Transferred, Transferred, Home, Dec…
## $ prior_admissions <dbl> 2, 1, 5, 5, 5, 0, 2, 5, 1, 1, 4, 1, 3, 5, 0, 3, …
## $ readmitted_30_days <fct> Yes, Yes, No, Yes, No, No, No, No, No, No, No, N…
ggplot(patient_data, aes(x = age)) +
geom_histogram(binwidth = 5, fill = "steelblue", color = "white") +
labs(title = "Age Distribution of Patients", x = "Age", y = "Count")
patient_data %>%
count(primary_diagnosis, readmitted_30_days) %>%
ggplot(aes(x = primary_diagnosis, y = n, fill = readmitted_30_days)) +
geom_col(position = "dodge") +
labs(title = "Readmission by Diagnosis", x = "Diagnosis", y = "Count") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplot(patient_data, aes(x = comorbidity_count, fill = readmitted_30_days)) +
geom_bar(position = "fill") +
labs(title = "Comorbidities and Readmission Risk", x = "# of Comorbidities", y = "Proportion") +
scale_y_continuous(labels = scales::percent)
ggplot(patient_data, aes(x = admission_type, y = length_of_stay, fill = admission_type)) +
geom_boxplot() +
labs(title = "Length of Stay by Admission Type", x = "Admission Type", y = "Days") +
theme(legend.position = "none")
patient_data %>%
mutate(age_group = cut(age, breaks = c(18, 30, 45, 60, 75, 90), right = FALSE)) %>%
count(age_group, readmitted_30_days) %>%
ggplot(aes(x = age_group, y = n, fill = readmitted_30_days)) +
geom_col(position = "dodge") +
labs(title = "Readmission Rate by Age Group", x = "Age Group", y = "Count")
set.seed(123)
train_index <- createDataPartition(patient_data$readmitted_30_days, p = 0.8, list = FALSE)
train_data <- patient_data[train_index, ]
test_data <- patient_data[-train_index, ]
model <- train(
readmitted_30_days ~ age + sex + admission_type + primary_diagnosis +
comorbidity_count + length_of_stay + prior_admissions,
data = train_data,
method = "glm",
family = "binomial"
)
summary(model$finalModel)
##
## Call:
## NULL
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -0.997116 0.442508 -2.253 0.0242 *
## age 0.002905 0.004386 0.662 0.5078
## sexMale -0.177619 0.176181 -1.008 0.3134
## admission_typeEmergency -0.145475 0.222457 -0.654 0.5131
## admission_typeUrgent -0.058937 0.244151 -0.241 0.8092
## primary_diagnosisDiabetes 0.059944 0.260989 0.230 0.8183
## `primary_diagnosisHeart Failure` -0.225737 0.284474 -0.794 0.4275
## `primary_diagnosisKidney Disease` -0.048435 0.265691 -0.182 0.8553
## primary_diagnosisPneumonia -0.008623 0.278628 -0.031 0.9753
## comorbidity_count 0.009215 0.043678 0.211 0.8329
## length_of_stay -0.019338 0.044619 -0.433 0.6647
## prior_admissions -0.092873 0.053282 -1.743 0.0813 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 814.77 on 800 degrees of freedom
## Residual deviance: 808.51 on 789 degrees of freedom
## AIC: 832.51
##
## Number of Fisher Scoring iterations: 4
predictions <- predict(model, newdata = test_data)
conf_matrix <- confusionMatrix(predictions, test_data$readmitted_30_days)
conf_matrix
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 158 41
## Yes 0 0
##
## Accuracy : 0.794
## 95% CI : (0.7311, 0.8479)
## No Information Rate : 0.794
## P-Value [Acc > NIR] : 0.5417
##
## Kappa : 0
##
## Mcnemar's Test P-Value : 4.185e-10
##
## Sensitivity : 1.000
## Specificity : 0.000
## Pos Pred Value : 0.794
## Neg Pred Value : NaN
## Prevalence : 0.794
## Detection Rate : 0.794
## Detection Prevalence : 1.000
## Balanced Accuracy : 0.500
##
## 'Positive' Class : No
##
prob_predictions <- predict(model, newdata = test_data, type = "prob")
actual_binary <- ifelse(test_data$readmitted_30_days == "Yes", 1, 0)
predicted_prob <- prob_predictions[, "Yes"]
roc_obj <- roc(actual_binary, predicted_prob)
## Setting levels: control = 0, case = 1
## Setting direction: controls > cases
plot(roc_obj, main = "ROC Curve")
auc(roc_obj)
## Area under the curve: 0.4927
This logistic regression model provides a data-driven way to estimate 30-day hospital readmission risk based on patient characteristics. The analysis shows how variables like comorbidity count, diagnosis, and admission type influence readmission outcomes. The model’s ROC curve and AUC suggest it performs well and can support proactive care decisions if applied in real settings.