---
title: "Survival prediction and ggplot workflows"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Survival prediction and ggplot workflows}
  %\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
)
```

Unlike stepwise Cox `survfit` curves, `spsurv` evaluates survival on a **dense
time grid** using the fitted Bernstein baseline. This vignette connects observed
Kaplan-Meier curves to model-based predictions.

## Before you model

```{r setup}
library(spsurv)
library(KMsurv)
library(survival)
library(ggplot2)
data(larynx)
larynx$stage <- factor(larynx$stage)
```

```{r eda-km, fig.cap = "Kaplan-Meier by stage with censoring marks (+)."}
km_stage <- survfit(Surv(time, delta) ~ stage, data = larynx)
km_long <- data.frame(
  time = km_stage$time,
  surv = km_stage$surv,
  stage = rep(levels(larynx$stage), km_stage$strata)
)
ggplot(km_long, aes(x = time, y = surv, color = stage)) +
  geom_step(linewidth = 0.6) +
  geom_point(
    data = subset(larynx, delta == 0),
    aes(x = time, y = 0, color = stage),
    shape = 3,
    size = 1.5,
    alpha = 0.5,
    inherit.aes = FALSE
  ) +
  labs(x = "Time (years)", y = "Survival probability", color = "Stage") +
  theme_bw() +
  theme(legend.position = "bottom")
```

We compare predictions at **age = 70** across all stages — a clinically
interpretable profile that holds age fixed while varying disease stage.

### Why this is insightful

KM curves show **unadjusted** stage separation. Censoring marks (`+`) remind you
that late follow-up estimates rest on fewer subjects.

### What to decide

- Clear stage ordering in KM → including `stage` in the model is justified.
- Crossing KM curves → proportional hazards may be questionable; see the
  *Choosing PH, PO, and AFT models* vignette.

## Fit and summarize

```{r fit}
fit <- bpph(
  Surv(time, delta) ~ age + stage,
  degree = 5,
  data = larynx,
  approach = "mle",
  init = 0
)
summary(fit)
```

## Prediction grid

```{r times}
plot_times <- seq(0, max(larynx$time), length.out = 121)
newdata <- data.frame(age = 70, stage = levels(larynx$stage))
newdata
```

## `predict()` — tidy data frame

```{r predict}
pr <- predict(fit, newdata = newdata, times = plot_times)
head(pr)
```

## Visual check — KM vs model overlay

**Panel A:** observed KM (step) vs model prediction (dashed) per stage.

```{r km-model-overlay, fig.cap = "Observed KM (solid) vs model prediction at age 70 (dashed)."}
curve_labs <- paste("Stage", newdata$stage)
pr$series <- curve_labs[match(as.character(pr$id), as.character(seq_len(nrow(newdata))))]

ggplot() +
  geom_step(
    data = km_long,
    aes(x = time, y = surv, color = stage),
    linewidth = 0.5,
    alpha = 0.8
  ) +
  geom_line(
    data = pr,
    aes(x = time, y = surv, color = series),
    linetype = "dashed",
    linewidth = 0.6
  ) +
  labs(x = "Time (years)", y = "Survival probability", color = NULL) +
  theme_bw() +
  theme(legend.position = "bottom")
```

**Panel B:** model-based ribbons (adjusted prediction with uncertainty).

```{r ggplot, fig.cap = "Stage-specific survival curves with 95% intervals."}
ggplot(pr, aes(x = time, y = surv, color = series, fill = series)) +
  geom_ribbon(aes(ymin = lower, ymax = upper), alpha = 0.2, colour = NA) +
  geom_line(linewidth = 0.5) +
  labs(x = "Time (years)", y = "Survival probability", color = NULL, fill = NULL) +
  theme_bw() +
  theme(legend.position = "bottom")
```

### Why this is insightful

Panel A connects the **smooth BP fit** to observed risk sets. Panel B shows
**covariate-adjusted** uncertainty — ribbons often widen late in follow-up when
data are sparse.

### What to decide

- Model curves systematically below KM → check covariate adjustment or
  misspecification.
- Wide bands late in follow-up → avoid over-interpreting tail survival.
- For publication → export `predict()` output directly to your ggplot pipeline.

## `survfit()` with `tidy = TRUE`

```{r survfit-tidy}
sf <- survfit(fit, newdata = newdata, times = plot_times, tidy = TRUE)
head(sf)
```

## Bayesian fits

For `approach = "bayes"`, credible bands use posterior draws with
`interval.type = "hpd"` and `monotone = TRUE` (default for Bayes).

```{r bayes-note, eval = FALSE}
fit_bayes <- bpph(
  Surv(time, delta) ~ age + stage,
  degree = 5,
  data = larynx,
  approach = "bayes",
  iter = 400,
  chains = 1,
  cores = 1
)
predict(fit_bayes, newdata = newdata, times = plot_times, interval.type = "hpd")
```

## Interval types (MLE)

Log–log intervals are undefined at `S = 1` (`time = 0`), so we evaluate at
`time > 0` only:

```{r interval-type}
loglog_times <- plot_times[plot_times > 0]
head(predict(fit, newdata = newdata, times = loglog_times, type = "log-log"))
```
