The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
Residual diagnostics help you decide whether to trust a fitted model, add covariates, change the regression family (PH vs PO), or simplify the Bernstein degree.
library(spsurv)
library(survival)
library(ggplot2)
data(veteran)
veteran$celltype <- factor(veteran$celltype)eda <- data.frame(
n = nrow(veteran),
events = sum(veteran$status),
censored = sum(1 - veteran$status),
pct_censored = round(100 * mean(1 - veteran$status), 1)
)
eda
#> n events censored pct_censored
#> 1 137 128 9 6.6km_all <- survfit(Surv(time, status) ~ 1, data = veteran)
km_df <- data.frame(
time = km_all$time,
surv = km_all$surv
)
ggplot(km_df, aes(x = time, y = surv)) +
geom_step(linewidth = 0.6) +
labs(x = "Time (days)", y = "Survival probability", title = "Overall KM") +
theme_bw()The overall KM and event rate tell you how much information is available for baseline estimation. Heavy censoring or a short follow-up window limits how far you can extrapolate smooth Bernstein baselines.
m (see the Bernstein polynomial degree
vignette).fit <- bpph(
Surv(time, status) ~ karno + celltype,
degree = 5,
data = veteran,
approach = "mle",
init = 0
)
summary(fit)
#> Call:
#> bpph(formula = Surv(time, status) ~ karno + celltype, degree = 5,
#> data = veteran, approach = "mle", init = 0, model = "ph")
#>
#> Bernstein PH model:
#> Regression coefficients:
#> Estimate 2.5% 97.5% Std. Error z value Pr(>|z|)
#> karno -0.0306 -0.0406 -0.0206 0.0051 -6.0 2e-09 ***
#> celltypesmallcell 0.7380 0.2458 1.2303 0.2511 2.9 0.003 **
#> celltypeadeno 1.1536 0.5800 1.7272 0.2927 3.9 8e-05 ***
#> celltypelarge 0.3302 -0.2110 0.8714 0.2761 1.2 0.232
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Exponentiated coefficients:
#> Estimate 2.5% 97.5%
#> karno 0.97 0.96 1.0
#> celltypesmallcell 2.09 1.28 3.4
#> celltypeadeno 3.17 1.79 5.6
#> celltypelarge 1.39 0.81 2.4
#>
#> ---
#> loglik = -716 AIC = 1450Use MLE plug-in fits for residual plots (Bayesian fits average over draws; see note at the end).
Martingale residuals should scatter around zero without a strong trend against fitted cumulative hazard.
mart <- residuals(fit, type = "martingale")
csnell <- residuals(fit, type = "cox-snell")
fitted_ch <- csnellmart_df <- data.frame(
fitted = fitted_ch,
martingale = mart,
status = factor(veteran$status),
celltype = veteran$celltype
)
ggplot(mart_df, aes(x = fitted, y = martingale, color = status)) +
geom_point(alpha = 0.6, size = 1.8) +
geom_hline(yintercept = 0, linetype = "dashed", color = "grey40") +
geom_smooth(aes(group = 1), method = "loess", se = FALSE, color = "black", linewidth = 0.5) +
coord_cartesian(ylim = c(-4, 2)) +
labs(
x = "Fitted cumulative hazard (Cox-Snell)",
y = "Martingale residual",
color = "Event"
) +
theme_bw() +
theme(legend.position = "bottom")ggplot(mart_df, aes(x = fitted, y = martingale)) +
geom_point(alpha = 0.6, size = 1.5) +
geom_hline(yintercept = 0, linetype = "dashed", color = "grey40") +
geom_smooth(method = "loess", se = FALSE, color = "black", linewidth = 0.5) +
facet_wrap(~ celltype, scales = "free_x") +
coord_cartesian(ylim = c(-4, 2)) +
labs(x = "Fitted cumulative hazard", y = "Martingale residual") +
theme_bw()A systematic trend in the LOESS smooth (e.g. upward curve at high fitted values) suggests covariate or baseline misspecification. Faceting reveals subgroups where the model fits poorly.
| Pattern | Suggested action |
|---|---|
| Clear trend vs fitted CH | Add terms, try PO (bppo), or lower/increase degree |
| One facet behaves differently | Consider interaction or separate analysis |
| Flat scatter around zero | Proceed; still check Cox-Snell calibration |
Under a well-specified model, the Kaplan-Meier of
exp(-CS) should track the Exponential(1)
reference (45-degree line on a cumulative-hazard scale).
km_cs <- survfit(Surv(csnell, veteran$status) ~ 1)
cs_df <- data.frame(
cs = km_cs$time,
cumhaz = -log(pmax(km_cs$surv, .Machine$double.eps))
)
ggplot(cs_df, aes(x = cs, y = cumhaz)) +
geom_step(linewidth = 0.6) +
geom_abline(slope = 1, intercept = 0, color = "steelblue", linetype = "dashed") +
labs(
x = "Cox-Snell residual",
y = "Nelson-Aalen cumulative hazard of exp(-CS)",
title = "Cox-Snell calibration"
) +
theme_bw()Curvature above the reference line indicates overall miscalibration of the cumulative hazard (baseline too low or too high on average). This is a global check complementary to martingale trends.
Large deviance residuals flag observations poorly explained by the fitted model.
dev <- residuals(fit, type = "deviance")
dev_df <- data.frame(
index = seq_along(dev),
deviance = dev,
highlight = abs(dev) >= quantile(abs(dev), 0.95)
)
ggplot(dev_df, aes(x = index, y = deviance, color = highlight)) +
geom_point(size = 1.8, alpha = 0.7) +
scale_color_manual(values = c("FALSE" = "grey40", "TRUE" = "firebrick"), guide = "none") +
labs(x = "Observation index", y = "Deviance residual") +
theme_bw()cox_fit <- coxph(Surv(time, status) ~ karno + celltype, data = veteran)
mart_cox <- residuals(cox_fit, type = "martingale")
ggplot(data.frame(bp = mart, cox = mart_cox), aes(x = bp, y = cox)) +
geom_point(alpha = 0.5, size = 1.8) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "grey40") +
labs(x = "spsurv martingale", y = "Cox martingale") +
theme_bw()Disagreement is expected in general (different likelihoods and baselines). Large outliers in the comparison warrant case-level review.
v <- vcov(fit, bp.param = TRUE)
stab <- data.frame(
gamma_information_stable = attr(v, "gamma_information_stable"),
kappa_gamma = signif(attr(v, "gamma_information_kappa"), 4)
)
stab
#> gamma_information_stable kappa_gamma
#> 1 TRUE 217000gamma_information_stable == FALSE → lower Bernstein
degree; treat survfit() standard errors and bands as
unreliable until stability improves.For approach = "bayes", residuals() uses
posterior-mean cumulative hazards. For diagnostic plots comparable to
classical methods, refit with MLE or use posterior predictive checks
outside this vignette.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.