I try to figure out how to describe my continuous variable. Unfortunately, I did not understand all of the statistics. I would really appreciate I you guys would help me out here. To better illustrate my problem, I wrote the following example.
library(rms)
library(survival)
data(pbc)
d <- pbc
rm(pbc, pbcseq)
d$status <- ifelse(d$status != 0, 1, 0)
dd = datadist(d)
options(datadist='dd')
# linear model
f1 <- cph(Surv(time, status) ~ albumin, data=d)
p1 <- Predict(f1, fun=exp)
(a1 <- anova(f1))
Function(f1)
plot(p1, anova=a1, pval=TRUE, ylab="Hazard Ratio")
# rcs model
f2 <- cph(Surv(time, status) ~ rcs(albumin, 4), data=d)
p2 <- Predict(f2, fun=exp)
(a2 <- anova(f2))
Function(f2)
plot(p2, anova=a2, pval=TRUE, ylab="Hazard Ratio")
# minimal CI width
p1$diff <- p1$upper-p1$lower
min(p1$diff) # = 0.002321521
p1[which(p1$diff==min(p1$diff)),]$albumin # = 3.494002
describe(d$albumin) # mean = 3.497
p2$diff <- p2$upper-p2$lower
min(p2$diff) # = 0.2039817
p2[which(p2$diff==min(p2$diff)),]$albumin # = 3.502447
describe(d$albumin) # mean = 3.497
# both models in a single figure
p <- rbind(linear.model=p1, rcs.model=p2)
library(ggplot2)
df <- data.frame(albumin=p$albumin, yhat=p$yhat, lower=p$lower, upper=p$upper, predictor=p$.set.)
(g <- ggplot(data=df, aes(x=albumin, y=yhat, group=predictor, color=predictor)) + geom_line(size=1))
(g <- g + geom_ribbon(data=df, aes(ymin=lower, ymax=upper), alpha=0.2, linetype=0))
(g <- g + theme_bw())
(g <- g + xlab("Albumin"))
(g <- g + ylab("Hazard Ratio"))
(g <- g + theme(axis.line = element_line(color='black', size=1)))
(g <- g + theme(axis.ticks = element_line(color='black', size=1)))
(g <- g + theme( plot.background = element_blank() ))
(g <- g + theme( panel.grid.minor = element_blank() ))
(g <- g + theme( panel.border = element_blank() ))
- Why shows the plot of the linear model (p1) not a straight line?
- How can I plot the models f1 and f2 in the same figure?
- How can I compare the models f1 and f2 to investigate which models fits the data better? ... like anova() for coxph in the survival package
- Why is the minimal CI width near the mean of albumin more pronounce in the linear (f1) model?
- What does the P value in the plots mean? How do I have to interpret the output of anova(...)
Update #1
Following the answer from Harrell, I updated the code above showing how to combine spline plots of two predictors in a single figure. One last question: How can I compare the two rms models like anova(m1, m2)
of the survival package as shown below?
> m1 <- coxph(Surv(time, status) ~ albumin, data=d)
> m2 <- coxph(Surv(time, status) ~ pspline(albumin), data=d)
> anova(m1, m2) # compare models
Analysis of Deviance Table
Cox model: response is Surv(time, status)
Model 1: ~ albumin
Model 2: ~ pspline(albumin)
loglik Chisq Df P(>|Chi|)
1 -975.61
2 -973.26 4.6983 11 0.9449
> summary(m1)
Call:
coxph(formula = Surv(time, status) ~ albumin, data = d)
n= 418, number of events= 186
coef exp(coef) se(coef) z Pr(>|z|)
albumin -1.4695 0.2300 0.1714 -8.574 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
exp(coef) exp(-coef) lower .95 upper .95
albumin 0.23 4.347 0.1644 0.3219
Concordance= 0.688 (se = 0.023 )
Rsquare= 0.147 (max possible= 0.992 )
Likelihood ratio test= 66.6 on 1 df, p=3.331e-16
Wald test = 73.51 on 1 df, p=0
Score (logrank) test = 72.38 on 1 df, p=0
UPDATE #2
I think I just answered my "one last question" by myself (see below). I hope this does not show correct accidentally. I would think that I can compare models from cph
and coxph
that way, can't I? Is the way of calculating the degrees of freedom df
correct?
> # using coxph from survival
> m1 <- coxph(Surv(time, status) ~ albumin, data=d)
> m2 <- coxph(Surv(time, status) ~ albumin + age, data=d)
> # loglik = a vector of length 2 containing the log-likelihood with the initial values and with the final values of the coefficients.
> m1$loglik[2]
[1] -975.6126
> m2$loglik[2]
[1] -973.2272
> (df <- abs(length(m1$coefficients) - length(m2$coefficients)))
[1] 1
> (LR <- 2 * (m2$loglik[2] - m1$loglik[2]))
[1] 4.770787
> pchisq(LR, df, lower=FALSE)
[1] 0.02894659
> anova(m2, m1)
Analysis of Deviance Table
Cox model: response is Surv(time, status)
Model 1: ~ albumin + age
Model 2: ~ albumin
loglik Chisq Df P(>|Chi|)
1 -973.23
2 -975.61 4.7708 1 0.02895 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
> m1 <- cph(Surv(time, status) ~ albumin, data=d)
> m2 <- cph(Surv(time, status) ~ albumin + age, data=d)
> # loglik = a vector of length 2 containing the log-likelihood with the initial values and with the final values of the coefficients.
> m1$loglik[2]
[1] -975.6126
> m2$loglik[2]
[1] -973.2272
> (df <- abs(length(m1$coefficients) - length(m2$coefficients)))
[1] 1
> (LR <- 2 * (m2$loglik[2] - m1$loglik[2]))
[1] 4.770787
> pchisq(LR, df, lower=FALSE)
[1] 0.02894659
UPDATE #3 I changed the example following the kind answer from DWin as follows. This way the degrees of freedom should be calculated properly:
library(Hmisc)
library(rms)
library(ggplot2)
library(gridExtra)
data(pbc)
d <- pbc
rm(pbc, pbcseq)
d$status <- ifelse(d$status != 0, 1, 0)
### log likelihood test using a coxph model
m1 <- coxph(Surv(time, status) ~ albumin, data=d)
m2 <- coxph(Surv(time, status) ~ albumin + age, data=d)
# loglik = a vector of length 2 containing the log-likelihood with the initial values and with the final values of the coefficients.
m1$loglik[2]
m2$loglik[2]
(df <- abs(sum(anova(m1)$Df, na.rm=TRUE) - sum(anova(m2)$Df, na.rm=TRUE)))
(LR <- 2 * (m2$loglik[2] - m1$loglik[2])) # the most parsimonious models have to be first
pchisq(LR, df, lower=FALSE)
anova(m2, m1)
### log likelihood test using a cph model
dd = datadist(d)
options(datadist='dd')
m3 <- cph(Surv(time, status) ~ albumin, data=d)
m4 <- cph(Surv(time, status) ~ albumin + age, data=d)
# loglik = a vector of length 2 containing the log-likelihood with the initial values and with the final values of the coefficients.
m3$loglik[2]
m4$loglik[2]
(df <- abs(print(anova(m3)[, "d.f."])[['TOTAL']] - print(anova(m4)[, "d.f."])[['TOTAL']]))
(LR <- 2 * (m4$loglik[2] - m3$loglik[2])) # the most parsimonious models have to be first
pchisq(LR, df, lower=FALSE)
fun=exp
inPredict
". $\endgroup$