Maximum-likelihood fits support classical inference on the full Bernstein likelihood (not Cox partial likelihood).
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?
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
)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()Karnofsky hazard ratio with 95% CI.
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
#> model AIC logLik df
#> 1 Null 1502.388 -746.1941 5
#> 2 Karnofsky 1461.642 -724.8211 6anova(fit0, fit1)
#> Analysis of Deviance Table
#>
#> Bernstein PH model (MLE), n = 137, degree = 5
#> Terms Resid. Df -2*LL Test Df Deviance Pr(>Chi)
#> 1 1 132 1492.4 1
#> 2 2 131 1449.6 1 1 42.746 6.233e-11 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
anova(fit1, fit2)
#> Analysis of Deviance Table
#>
#> Bernstein PH model (MLE), n = 137, degree = 5
#> Terms Resid. Df -2*LL Test Df Deviance Pr(>Chi)
#> 1 1 131 1449.6 1
#> 2 2 128 1432.0 2 3 17.616 0.0005277 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
anova(fit2)
#> Analysis of Deviance Table
#>
#> Bernstein PH model (MLE), n = 137, degree = 5
#>
#> Terms added sequentially (first to last)
#> Df Deviance Resid. Df -2*LL Pr(>Chi)
#> NULL 132 1492.4
#> karno 1 42.746 131 1449.6 6.233e-11 ***
#> factor(celltype) 3 17.616 128 1432.0 0.0005277 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1Pairwise 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.
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).
df includes all gamma parameters.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
#> Quantity fit0 fit1 Counts
#> 1 logLik() / AIC df 5 6 Regression + length(bp.param)
#> 2 summary() logtest df — 1 Regression terms only
#> 3 anova() nested df diff — 1 Total parameter differenceestimates(), se(), and
extractAIC()summary(fit0)
#> Call:
#> bpph(formula = Surv(time, status) ~ 1, degree = 5, data = veteran,
#> approach = "mle", init = 0, model = "ph")
#>
#>
#> log(gamma) gamma se(log(gamma)) z Pr(>z)
#> gamma[1] 7.5e-01 2.1e+00 1.5e-01 609.6 <2e-16 ***
#> gamma[2] -1.2e+00 2.9e-01 3.9e+00 3.2 7e-04 ***
#> gamma[3] 5.2e-01 1.7e+00 8.8e-01 85.0 <2e-16 ***
#> gamma[4] -1.2e+01 4.5e-06 5.2e+02 0.0 0.5
#> gamma[5] 1.8e-01 1.2e+00 7.0e-01 74.4 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Loglik(model)= -746 Loglik(baseline only)= -746
#> n= 137, number of events= 128
logLik(fit0)
#> 'log Lik.' -746.1941 (df=5)