---
title: "Inference and model comparison (MLE)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Inference and model comparison (MLE)}
  %\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
)
```

Maximum-likelihood fits support classical inference on the **full Bernstein
likelihood** (not Cox partial likelihood).

## Before you model

```{r setup}
library(spsurv)
library(generics)
library(survival)
library(ggplot2)
data(veteran)
```

We compare a null model (intercept-only baseline) with a model including Karnofsky
score — the same question as in the paper: is performance status worth including?

## Fit and summarize

```{r fit}
fit0 <- bpph(
  Surv(time, status) ~ 1,
  degree = 5,
  data = veteran,
  approach = "mle",
  init = 0
)
fit1 <- bpph(
  Surv(time, status) ~ karno,
  degree = 5,
  data = veteran,
  approach = "mle",
  init = 0
)
fit2 <- bpph(
  Surv(time, status) ~ karno + factor(celltype),
  degree = 5,
  data = veteran,
  approach = "mle",
  init = 0
)
```

```{r tidy}
td <- tidy(fit1, conf.int = TRUE, exponentiate = TRUE)
td
```

```{r glance}
glance(fit1)
```

## Visual check — forest plot

```{r forest, fig.cap = "Karnofsky hazard ratio with 95% CI."}
td$term <- factor(td$term, levels = rev(td$term))
ggplot(td, aes(x = estimate, y = term, xmin = conf.low, xmax = conf.high)) +
  geom_vline(xintercept = 1, linetype = "dashed", color = "grey50") +
  geom_pointrange(linewidth = 0.4) +
  labs(x = "Hazard ratio", y = NULL) +
  theme_bw()
```

## Visual check — model comparison

```{r criterion-compare, fig.cap = "AIC and log-likelihood: null vs Karnofsky model."}
cmp <- data.frame(
  model = c("Null", "Karnofsky"),
  AIC = c(AIC(fit0), AIC(fit1)),
  logLik = c(as.numeric(logLik(fit0)), as.numeric(logLik(fit1))),
  df = c(attr(logLik(fit0), "df"), attr(logLik(fit1), "df"))
)
cmp
```

```{r anova}
anova(fit0, fit1)
anova(fit1, fit2)
anova(fit2)
```

Pairwise comparisons report the likelihood-ratio statistic on the simpler model
row. A single fitted model produces a sequential table (`NULL`, then each term
in formula order), matching `survival::survreg` conventions.

### Why this is insightful

Forest plots translate coefficients into decision-relevant effect sizes. The LR
test and AIC answer whether Karnofsky improves fit enough to justify the extra
regression parameter (Bernstein degree held constant).

### What to decide

- HR CI excludes 1 and significant LR → include covariate.
- AIC favors complex model but effect negligible → balance parsimony vs fit.
- Compare models at the **same Bernstein degree** — `df` includes all `gamma`
  parameters.

## Degrees of freedom (visual)

```{r df-table}
df_tbl <- data.frame(
  Quantity = c(
    "logLik() / AIC df",
    "summary() logtest df",
    "anova() nested df diff"
  ),
  fit0 = c(
    attr(logLik(fit0), "df"),
    "\u2014",
    "\u2014"
  ),
  fit1 = c(
    attr(logLik(fit1), "df"),
    summary(fit1)$logtest["df"],
    attr(logLik(fit1), "df") - attr(logLik(fit0), "df")
  ),
  Counts = c(
    "Regression + length(bp.param)",
    "Regression terms only",
    "Total parameter difference"
  )
)
df_tbl
```

## Confidence intervals and variance

```{r confint}
confint(fit1, level = 0.95)
```

```{r vcov}
dim(vcov(fit1))
dim(vcov(fit1, bp.param = TRUE))
```

## `estimates()`, `se()`, and `extractAIC()`

```{r estimates}
head(estimates(fit1))
extractAIC(fit1)
```

## Null model

```{r null}
summary(fit0)
logLik(fit0)
```
