--- title: "Bernstein polynomial degree and baseline plots" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Bernstein polynomial degree and baseline plots} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4.5, dpi = 96, message = FALSE, warning = FALSE ) ``` The unknown baseline is approximated by a **Bernstein polynomial** of degree `m`. The vector `bp.param` holds the `m` baseline coefficients (`gamma`). ## Before you model ```{r setup} library(spsurv) library(survival) library(ggplot2) data(veteran) ``` ```{r eda-event-times, fig.cap = "Distribution of observed event times."} ggplot(subset(veteran, status == 1), aes(x = time)) + geom_histogram(bins = 25, fill = "steelblue", color = "white", alpha = 0.8) + labs(x = "Time (days)", y = "Count", title = "Event times only") + theme_bw() ``` The support of event times motivates the upper limit `tau` used in Bernstein basis evaluation (`max(time)` by default). ### Why this is insightful Sparse events at long follow-up times limit how wiggly a high-degree baseline can be estimated reliably. ### What to decide - Very few late events → prefer moderate degree; inspect baseline smoothness. ## Fit and summarize ```{r fit-degrees} fit_low <- bpph( Surv(time, status) ~ karno, degree = 3, data = veteran, approach = "mle", init = 0 ) fit_high <- bpph( Surv(time, status) ~ karno, degree = 8, data = veteran, approach = "mle", init = 0 ) c(low = length(fit_low$bp.param), high = length(fit_high$bp.param)) ``` ```{r aic-compare} AIC(fit_low, fit_high) ``` ## Visual check — baseline overlay ```{r baseline-overlay, fig.cap = "Cumulative baseline hazard: degree 3 vs degree 8."} times <- seq(0, max(veteran$time), length.out = 100) tau <- max(veteran$time) baseline_curve <- function(fit, times, tau) { bb <- bp.basis(times, degree = length(fit$bp.param), tau = tau) data.frame( time = times, cumhaz = as.vector(bb$G %*% fit$bp.param), degree = paste0("m = ", length(fit$bp.param)) ) } bl_df <- rbind( baseline_curve(fit_low, times, tau), baseline_curve(fit_high, times, tau) ) ggplot(bl_df, aes(x = time, y = cumhaz, color = degree)) + geom_line(linewidth = 0.7) + labs(x = "Time (days)", y = "Cumulative baseline hazard", color = NULL) + theme_bw() ``` ```{r plot-baseline, fig.cap = "Estimated baseline hazard components (degree 8)."} plot(fit_high, graph = "baseline", cumulative = FALSE) ``` ```{r plot-basis, fig.cap = "Bernstein basis functions on [0, tau]."} plot(fit_high, graph = "basis", cumulative = FALSE) ``` ### Why this is insightful A wiggly high-degree baseline is an **overfitting signal**. Flat or unstable AIC combined with high gamma condition numbers suggests reducing `m`. ### What to decide - Oscillating baseline hazard → lower degree. - AIC improves but kappa explodes → prefer parsimony; bands may be unreliable. - Stable baseline shape across nearby degrees → degree choice is robust. ## Gamma information stability ```{r kappa-table} kappa_tbl <- data.frame( degree = c(3, 8), gamma_information_stable = c( attr(vcov(fit_low, bp.param = TRUE), "gamma_information_stable"), attr(vcov(fit_high, bp.param = TRUE), "gamma_information_stable") ), kappa_gamma = c( signif(attr(vcov(fit_low, bp.param = TRUE), "gamma_information_kappa"), 4), signif(attr(vcov(fit_high, bp.param = TRUE), "gamma_information_kappa"), 4) ) ) kappa_tbl ``` ## Basis functions ```{r bp-basis} times <- seq(0, max(veteran$time), length.out = 50) bb <- bp.basis(times, degree = 5, tau = max(veteran$time)) dim(bb$g) ``` For AFT models, the internal power-basis matrix comes from `pw.basis()`: ```{r pw-basis} pw <- pw.basis(degree = 5) dim(pw) ```