Code
# Load all required packages with pacman
pacman::p_load(
lubridate, ggsurvfit, gtsummary, tidycmprsk, tidyverse, cardx, broom.helpers,
dplyr, gt, survival, broom, survminer, survRM2, ggpubr, caret, randomForestSRC
)This report performs survival analysis on breast cancer patients using clinical and demographic data. We model time-to-event outcomes using Cox Proportional Hazards, accounting for censoring in patient survival.
# Load all required packages with pacman
pacman::p_load(
lubridate, ggsurvfit, gtsummary, tidycmprsk, tidyverse, cardx, broom.helpers,
dplyr, gt, survival, broom, survminer, survRM2, ggpubr, caret, randomForestSRC
)You can download this dataset from the following website.
data <- read.csv("Breast_Cancer.csv")The dataset consists of 4,024 observations and 16 variables related to breast cancer patient characteristics. These include: - Demographics: Age, Race, Marital Status - Tumor Staging and Grading: T Stage, N Stage, 6th Stage, A Stage, Grade, differentiate - Tumor Properties: Tumor Size, Estrogen Status, Progesterone Status - Lymph Node Information: Regional Node Examined, Reginol Node Positive Outcomes: - Survival Months (numeric survival duration) - Status (binary: Alive or Dead) — We will use this as the target for classification.
# Data preprocessing
data <- distinct(data) # Remove duplicates
# Convert relevant variables to factors
cat_cols <- sapply(data, is.character)
data[cat_cols] <- lapply(data[cat_cols], factor)
# Create survival object
data$Status <- factor(data$Status, levels = c("Alive", "Dead"))
data$event <- as.numeric(data$Status == "Dead")
surv_object <- Surv(time = data$Survival.Months, event = data$event)
# Fix typo in variable name (Reginol → Regional)
names(data)[names(data) == "Reginol.Node.Positive"] <- "Regional.Node.Positive"We removed duplicate rows to ensure unique patient records, converted character columns to factors for proper model handling, and created a Surv object using survival time and event status. We also corrected a variable name typo to prevent formula errors in modeling.
# Fit Cox proportional hazards model
cox_model <- coxph(
surv_object ~ Age + Race + Tumor.Size + T.Stage + N.Stage +
Estrogen.Status + Progesterone.Status +
Regional.Node.Examined + Regional.Node.Positive,
data = data
)
cox_modelCall:
coxph(formula = surv_object ~ Age + Race + Tumor.Size + T.Stage +
N.Stage + Estrogen.Status + Progesterone.Status + Regional.Node.Examined +
Regional.Node.Positive, data = data)
coef exp(coef) se(coef) z p
Age 0.017846 1.018006 0.004697 3.800 0.000145
RaceOther -0.879055 0.415175 0.210775 -4.171 3.04e-05
RaceWhite -0.480338 0.618574 0.126338 -3.802 0.000144
Tumor.Size 0.002263 1.002266 0.003111 0.727 0.466949
T.StageT2 0.388800 1.475210 0.113547 3.424 0.000617
T.StageT3 0.431219 1.539133 0.218913 1.970 0.048859
T.StageT4 0.911015 2.486846 0.231115 3.942 8.09e-05
N.StageN2 0.458247 1.581300 0.109581 4.182 2.89e-05
N.StageN3 0.640475 1.897382 0.178862 3.581 0.000342
Estrogen.StatusPositive -0.782999 0.457033 0.132835 -5.895 3.76e-09
Progesterone.StatusPositive -0.528910 0.589247 0.106342 -4.974 6.57e-07
Regional.Node.Examined -0.032523 0.968000 0.006492 -5.010 5.45e-07
Regional.Node.Positive 0.061905 1.063861 0.011284 5.486 4.11e-08
Likelihood ratio test=456.4 on 13 df, p=< 2.2e-16
n= 4023, number of events= 616
Race differences: Compared to the reference group, patients identified as White or from Other racial backgrounds had better survival outcomes.
Tumor stage is critical: Patients with larger or more advanced tumors (T2, T3, or T4) had a higher risk of dying than those with smaller tumors (T1). For example, those with T4 tumors had more than double the risk.
Lymph node involvement: The more lymph nodes that were affected by cancer (N2 or N3), the higher the risk. This means cancer that has spread to many lymph nodes is more dangerous.
Hormone receptor status offers protection: Patients whose tumors were sensitive to estrogen or progesterone had significantly better chances of survival.
Lymph node testing helps: Having more lymph nodes examined during diagnosis was linked to better outcomes, likely because it helps guide effective treatment. However, having more positive (cancer-affected) nodes was linked to worse outcomes.
Tumor size alone wasn’t a strong predictor of survival when other factors were considered, such as stage and lymph node involvement.
# Build survival object
data$event <- ifelse(data$Status == "Dead", 1, 0)
surv_object <- Surv(time = data$Survival.Months, event = data$event)
# Fit Cox model
cox_model <- coxph(surv_object ~ Age + Race + T.Stage + N.Stage +
Estrogen.Status + Progesterone.Status + Tumor.Size,
data = data)
# Generate styled summary table
cox_tbl <- tbl_regression(cox_model, exp = TRUE) %>%
add_global_p() %>%
add_significance_stars() %>%
bold_p(t = 0.05) %>%
bold_labels()
# Display it
cox_tbl| Characteristic | HR1 | SE |
|---|---|---|
| Age | 1.02*** | 0.005 |
| Race | *** |
|
| Black | — | — |
| Other | 0.42 | 0.211 |
| White | 0.63 | 0.126 |
| T.Stage | *** |
|
| T1 | — | — |
| T2 | 1.42 | 0.114 |
| T3 | 1.40 | 0.220 |
| T4 | 2.37 | 0.232 |
| N.Stage | *** |
|
| N1 | — | — |
| N2 | 1.84 | 0.101 |
| N3 | 3.42 | 0.102 |
| Estrogen.Status | *** |
|
| Negative | — | — |
| Positive | 0.46 | 0.132 |
| Progesterone.Status | *** |
|
| Negative | — | — |
| Positive | 0.59 | 0.106 |
| Tumor.Size | 1.00 | 0.003 |
| Abbreviations: CI = Confidence Interval, HR = Hazard Ratio, SE = Standard Error | ||
| 1 *p<0.05; **p<0.01; ***p<0.001 | ||
ph_test <- cox.zph(cox_model)
print(ph_test) chisq df p
Age 0.208 1 0.65
Race 1.463 2 0.48
T.Stage 0.247 3 0.97
N.Stage 2.158 2 0.34
Estrogen.Status 32.015 1 1.5e-08
Progesterone.Status 33.527 1 7.0e-09
Tumor.Size 1.161 1 0.28
GLOBAL 47.059 11 2.1e-06
To ensure the validity of our survival analysis, we tested whether each variable in the model met the proportional hazards assumption—a key requirement of the Cox model that assumes the effect of a variable on survival remains consistent over time.
Most variables, including age, race, tumor size, tumor and nodal stage, and number of lymph nodes, did not violate this assumption. Their p-values were well above 0.05, indicating no significant time-dependent effects.
Although estrogen and progesterone receptor status showed statistically significant test results (p < 0.001), this could suggest a potential violation of the proportional hazards assumption for these variables. It’s worth exploring this further with time-varying models or stratification.
The global test for the entire model was also significant (p = 6.0e-06), suggesting that overall, some violation of the proportional hazards assumption exists in the model.
While most predictors behaved consistently over time, hormone receptor status may have effects that change during the follow-up period. Further checks or model adjustments (like stratification or adding time-varying covariates) might be necessary.
# Create forest plot of hazard ratios
forest_plot <- ggforest(cox_model, data = data, fontsize = 0.6)
print(forest_plot)The Wald chi-square tests indicate that only estrogen and progesterone receptor status significantly contribute to the model (p < 0.001). Other variables such as age, stage, tumor size, and node involvement do not show statistically significant effects when tested individually, possibly due to multicollinearity or overlapping effects. However, the global test is highly significant (p = 6.0e-06), suggesting the model as a whole explains survival differences effectively.
fit_km <- survfit(surv_object ~ 1, data = data)
km_plot <- ggsurvplot(
fit_km, data = data,
conf.int = TRUE,
risk.table = TRUE,
pval = FALSE,
surv.median.line = "hv",
ggtheme = theme_bw(),
palette = "jco",
legend.labs = "Overall",
title = "Overall Survival Curve"
)Warning in .add_surv_median(p, fit, type = surv.median.line, fun = fun, :
Median survival not reached.
print(km_plot)The curve begins at 100%, meaning all patients were alive at the start of observation. As time progresses, the curve gradually declines, reflecting patients who experienced the event (death). By the end of the observation period (100 units of time), the estimated survival probability remains above 70%, indicating that the majority of patients survived through most of the follow-up period. The table below the graph shows the number of patients still being followed (“at risk”) at each time point. For example, while 4,023 patients were at risk at the beginning, only 444 remained under observation at the final time point—this is expected due to both events (deaths) and censoring (patients lost to follow-up or study end). Together with our previous analysis, this curve provides a clear, visual summary of overall patient survival and supports the use of the Cox model for understanding what influences differences in survival.
Next, we stratify by the stages of the progression and we observe that patients in T1 and T2 had survival probability above 0.75 over the study period. While those at T3 stage had a survival probability above 0.65. T4 patients had a survival probability slightly above 0.5 by the end of the study period. These results show that survival probability is strongly associated with the Tumor Stage.
# Fit model or plot
fit <- survfit(surv_object ~ T.Stage, data = data)
# Plot survival curve only
surv_curve <- ggsurvplot(
fit,
data = data,
pval = TRUE,
conf.int = FALSE,
risk.table = FALSE, # <--- turn off risk table
palette = "jco",
ggtheme = theme_bw(base_size = 14),
title = "Survival Probability by Tumor Stage (T Stage)",
xlab = "Time (months)",
ylab = "Survival Probability",
legend.title = "Tumor Stage"
)
# Plot risk table only
risk_tbl <- ggrisktable(
fit,
data = data,
palette = "jco",
title = "Number at Risk by Tumor Stage",
xlab = "Time (months)"
)
# Display separately
print(surv_curve)print(risk_tbl)The Kaplan-Meier survival analysis revealed clear differences in survival probability over time based on tumor stage at diagnosis (T1–T4). Patients with T1 tumors showed the highest survival probability throughout the follow-up period, while those with T4 tumors experienced the steepest decline, indicating significantly poorer outcomes. This trend demonstrates a consistent gradient where survival worsens with advancing tumor stage.
The log-rank test confirmed that these differences are statistically significant (p < 0.0001), suggesting that tumor stage is a strong predictor of survival. The accompanying number-at-risk table shows how many patients remained under observation at each time point. Notably, the T4 group had substantially fewer patients and experienced faster attrition, reflecting higher event rates in this group. These results reinforce the prognostic importance of early tumor stage detection and support stage-based stratification in clinical decision-making and patient monitoring.