Code
library(survival)
km_g <- survfit(Surv(t, delta) ~ group, data = mydata)
plot(km_g, fun = "cloglog", # log H(t) vs log(t)
col = c("blue", "red"), lwd = 2,
xlab = "log(t)", ylab = "log H(t)")After fitting a Cox model, we should check two key assumptions:
Several diagnostic tools are available.
The simplest PH check for a categorical explanatory variable is the log-cumulative hazard plot (introduced in Chapter 21): plot \(\log\widehat{H}(t)\) from the KM estimator against \(\log t\) for each group. Under PH, the curves should be parallel.
library(survival)
km_g <- survfit(Surv(t, delta) ~ group, data = mydata)
plot(km_g, fun = "cloglog", # log H(t) vs log(t)
col = c("blue", "red"), lwd = 2,
xlab = "log(t)", ylab = "log H(t)")Under the PH assumption, the Schoenfeld residuals should have mean zero and no trend with time. A plot of \(r_{jk}\) against event time \(t_{(j)}\) that shows a systematic trend (increasing, decreasing, or non-linear) suggests that the PH assumption is violated for explanatory variable \(k\).
The cox.zph() function in R tests formally for a time trend in each explanatory variable’s Schoenfeld residuals.
library(survival)
cox_fit <- coxph(Surv(t, delta) ~ x1 + x2, data = mydata,
ties = "breslow")
# Schoenfeld residual test for PH
ph_test <- cox.zph(cox_fit)
print(ph_test) # p-values for each explanatory variable + global test
# Plot Schoenfeld residuals
plot(ph_test) # one plot per explanatory variable + globalA small \(p\)-value from cox.zph() suggests the PH assumption is violated for that explanatory variable.
Martingale residuals lie in \((-\infty, 1]\) and sum to zero. Plotting them against a continuous explanatory variable that was assumed to enter linearly can reveal non-linearity: a non-linear trend in the residuals suggests that a transformation of the explanatory variable (or a smooth term) would improve the fit.
# Martingale residuals from a null (intercept-only) model
null_fit <- coxph(Surv(t, delta) ~ 1, data = mydata, ties = "breslow")
mart_res <- residuals(null_fit, type = "martingale")
# Plot vs continuous explanatory variable to check linearity
plot(mydata$age, mart_res,
xlab = "Age", ylab = "Martingale residual")
lines(lowess(mydata$age, mart_res), col = "red", lwd = 2)
abline(h = 0, lty = 2)Deviance residuals are a transformation of the martingale residuals designed to be more symmetric around zero for a well-fitting model: \[ d_i = \text{sign}(M_i)\sqrt{-2\bigl(M_i + \delta_i\log(\delta_i - M_i)\bigr)}. \]
Large absolute deviance residuals (say, \(|d_i| > 3\)) may indicate influential observations or outliers.
dev_res <- residuals(cox_fit, type = "deviance")
plot(predict(cox_fit), dev_res,
xlab = "Linear predictor", ylab = "Deviance residual")
abline(h = c(-2, 0, 2), lty = c(2, 1, 2))library(survival)
h <- read.table("https://www.richardpmann.com/MATH3701/Datasets/hypernephroma.dat", header = TRUE)
h$age <- as.factor(h$age)
levels(h$age) <- c("<60", "60-70", ">70")
h$nephrectomy <- as.factor(h$nephrectomy)
levels(h$nephrectomy) <- c("no", "yes")
# Fit final model
fit_final <- coxph(Surv(time, status) ~ age + nephrectomy,
data = h, ties = "breslow")
# 1. PH test
ph_test <- cox.zph(fit_final)
print(ph_test)
plot(ph_test)
# 2. Martingale residuals (all explanatory variables are categorical here)
mart <- residuals(fit_final, type = "martingale")
plot(fitted(fit_final), mart,
xlab = "Fitted log-hazard", ylab = "Martingale residual")
# 3. Deviance residuals
dev <- residuals(fit_final, type = "deviance")
plot(seq_along(dev), dev, type = "h",
xlab = "Observation index", ylab = "Deviance residual")
abline(h = c(-2, 2), lty = 2)Think about log–log survival plots (\(\log(-\log\hat{S}(t))\) vs \(\log t\) for each group), Schoenfeld residuals plotted against time, and dfbeta or deviance residuals for influence assessment.
Checking the proportional hazards assumption. What plot(s) would you produce, what would you look for, and what would departures from PH look like?
Identifying influential observations. What residual-based diagnostics would you use, and what pattern in the plot would flag an observation as influential?
Use coxph() to fit the model, cox.zph() to test PH, plot(cox.zph(fit)) to visualise Schoenfeld residuals, and residuals(fit, type="deviance") for deviance residuals.
Fit a Cox model with group as the explanatory variable. Run cox.zph() and interpret the output.
Plot the Schoenfeld residuals against time. What pattern would confirm that the PH assumption is satisfied?
Plot the deviance residuals against the linear predictor. Identify any potential outliers.