Tutorial

Predicting Patient Readmission with XGBoost and Claims Data

Difficulty: Intermediate Time: 15 min read

Introduction

Hospital readmission within 30 days is a critical quality metric and cost driver in healthcare. In this tutorial, we will use structured claims data to train an XGBoost model that predicts the probability of a patient being readmitted. We will also use SHAP values to explain the model's predictions to clinicians.

Architecture Overview


graph LR
    Syn(Synthea Claims Data) --> FE(Feature Engineering)
    FE --> Join(Join Encounters & Patients)
    Join --> Matrix(XGBoost DMatrix)
    Matrix --> Model(XGBClassifier)
    Model --> Pred(30-Day Risk Score)
    Model --> SHAP(SHAP Explainability)
        

Prerequisites

  • Python 3.8+
  • Pandas, Scikit-learn, XGBoost, SHAP
  • A synthetic claims dataset (e.g., Synthea)

Step 1: Data Preparation and Feature Engineering

Claims data requires significant feature engineering. We need to extract demographics, calculate the length of stay (LOS), and aggregate past utilization counts to build a predictive feature set.

import pandas as pd
import numpy as np

# Load synthetic encounters and patients
encounters = pd.read_csv('encounters.csv')
patients = pd.read_csv('patients.csv')

# Calculate Length of Stay (LOS)
encounters['START'] = pd.to_datetime(encounters['START'])
encounters['STOP'] = pd.to_datetime(encounters['STOP'])
encounters['LENGTH_OF_STAY'] = (encounters['STOP'] - encounters['START']).dt.days

# Join with Patients table to get demographics
df = pd.merge(encounters, patients, left_on='PATIENT', right_on='Id')

# Calculate Age at Encounter
df['BIRTHDATE'] = pd.to_datetime(df['BIRTHDATE'])
df['AGE'] = (df['START'] - df['BIRTHDATE']).dt.days // 365

# Create a target variable: Readmitted within 30 days (1=Yes, 0=No)
# We sort by patient and date to find the next admission
df = df.sort_values(['PATIENT', 'START'])
df['NEXT_ADMISSION'] = df.groupby('PATIENT')['START'].shift(-1)
df['DAYS_TO_READMISSION'] = (df['NEXT_ADMISSION'] - df['STOP']).dt.days
df['READMITTED_30D'] = np.where((df['DAYS_TO_READMISSION'] <= 30) & (df['DAYS_TO_READMISSION'] >= 0), 1, 0)

# Select predictive features
features = ['AGE', 'LENGTH_OF_STAY'] # In a real scenario, add comorbidity flags here
X = df[features]
y = df['READMITTED_30D']

Step 2: Training the XGBoost Model

XGBoost handles tabular healthcare data exceptionally well, including missing values natively.

import xgboost as xgb
from sklearn.model_selection import train_test_split

# Split into Train / Test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

model = xgb.XGBClassifier(
    n_estimators=200,
    max_depth=4,
    learning_rate=0.05,
    objective='binary:logistic',
    scale_pos_weight=5, # Handle class imbalance (readmissions are rare)
    use_label_encoder=False,
    eval_metric='logloss'
)

model.fit(X_train, y_train)

Step 3: Evaluation and Feature Importance

We evaluate the model using the ROC-AUC score.

from sklearn.metrics import roc_auc_score

preds = model.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, preds)
print(f"ROC-AUC: {auc:.3f}")

Step 4: Clinical Explainability with SHAP

Clinicians need to know why a patient is high risk. SHAP (SHapley Additive exPlanations) values provide individual-level feature attributions.

import shap
import matplotlib.pyplot as plt

# Create the explainer
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

# Plot summary plot for the entire cohort
shap.summary_plot(shap_values, X_test)

# Plot a force plot for a single high-risk patient
# shap.initjs()
# shap.force_plot(explainer.expected_value, shap_values[0,:], X_test.iloc[0,:])

Conclusion

Using XGBoost on structured claims data provides a fast, interpretable baseline for readmission risk scoring in population health management. By layering SHAP on top, we build trust with clinical end-users.