0
$\begingroup$

I have fit a Cox model with pspline() terms for the continuous covariates. I am trying to better understand how pspline() terms play with checking proportional hazards. I am using R and the survival package.

Here is a ReprEx from the splines vignette:

library(survival)

(mfit <- coxph(Surv(futime, death) ~ sex + pspline(age, df = 4), data = mgus))
# Call:
# coxph(formula = Surv(futime, death) ~ sex + pspline(age, df = 4), 
#     data = mgus)
# 
#                               coef se(coef)      se2    Chisq   DF      p
# sexmale                    0.22784  0.13883  0.13820  2.69335 1.00   0.10
# pspline(age, df = 4), lin  0.06682  0.00703  0.00703 90.22974 1.00 <2e-16
# pspline(age, df = 4), non                             3.44005 3.05   0.34
# 
# Iterations: 5 outer, 16 Newton-Raphson
#      Theta= 0.851 
# Degrees of freedom for terms= 1.0 4.1 
# Likelihood ratio test=108  on 5.04 df, p=<2e-16
# n= 241, number of events= 225 

termplot(mfit, term = 2, se = TRUE)

zph <- cox.zph(mfit)
plot(zph[2], resid = FALSE)

term plot from ReprEx smoothed, scaled Schoenfeld plot from ReprEx

Here are results from my real model. The outcome is misuse of a given prescription drug. BTW, the curves have the same shape: is it a coincidence or does it mean something?

term plot from study smoothed, scaled Schoenfeld plot from study

If I understand correctly:

  • termplot() shows the risk of death (or misuse) according to age, i.e. $\beta(age)$. See the splines vignette (end of section 1 page 3) for choosing the reference and getting $HR(age)$ rather that $\beta(age)$.
  • Smoothed, scaled Schoenfeld residuals plots (i.e. plot(cox.zph(*))) show the risk of death (or misuse) according to time for age, i.e. $\beta(time)$ (or $HR(time)$). But for what age? i.e. what is the reference value/the value shown?

In fact, I am trying to check whether the variation in the smoothed, scaled Schoenfeld residuals over time is small relative to the $\beta(age)$ returned by termplot() (in a similar way as explained here for 2-level categorical variables).

In summary, my questions are:

  • Should I check proportional hazards for pspline() terms?
  • Should I check the variation over time of pspline() terms relative the termplot() output?
  • What is the reference value/the value shown for a pspline() term on smoothed, scaled Schoenfeld residuals plots? I have tried cox.zph(*, terms = FALSE) but it does not seem to help.
  • Are the answers the same if using robust estimates (i.e. coxph(*, robust = TRUE))
$\endgroup$

1 Answer 1

3
$\begingroup$

In more-or-less reverse order:

Robust estimates in this context only affect the (co)variances of the coefficient estimates. There should be no effect on the scaled Schoenfeld residual plots.

There are two types of "reference values" involved here. One is the (set of) reference values for the covariate itself (age). The other is the reference value for the plot of scaled Schoenfeld residuals.

With respect to age itself, think about an age "reference value" at each event time. At each event time the calculation starts with the event-risk-weighted average of a covariate's values among all those then at risk. That's what I think of as the "reference value" for that covariate at that event time. The unscaled Schoenfeld residual for the individual having the event at that time is the difference between that individual's covariate value and the risk-weighted average at that time; it's then scaled based on the variances of covariate values.

For a predictor involved in multiple terms of the model the calculation is done for each term associated with the predictor; for pspline(age,df=4) in your reproducible example there are 14 such basis-function terms. You can see that by displaying the matrix of residuals: head(residuals(mfit, type="scaledsch")). With the way that you invoked cox.zph(), that function combines information from all of the basis-function terms, performs a score test using the total degrees of freedom associated with age, and returns the scaled Schoenfeld residuals combined over all the basis functions associated with pspline(age,df=4).

The "reference value" for the scaled Schoenfeld residuals (only relevant for the plots, not the score test) depends on whether it is for a single coefficient or a residual combined over multiple coefficients.

For a single coefficient you can think of the overall coefficient estimate from the Cox model as the "reference value" to which the individual residuals are added to produce the plot. That's an estimate of the time-varying coefficient itself, as explained on this page.

With residuals combined over multiple coefficients for a predictor, the software just adds 1 to the combined residual value. I suppose you could think of the "reference value" in that case as a value of 1. What you see in the plot in that case is thus the shape of the change in the overall association of the predictor with outcome over time rather than the actual coefficient over time. That shape applies to the entire association between age and outcome; it isn't specific to any particular value of age.

Again, you can see that in your reproducible example: the plot(zph[2], resid = FALSE) output is centered around a value of 1, while the coefficient for the linear term in age, which dominates the non-linear terms in that case, is 0.06682. Compare that plot against what you would get with a simple linear term for age.

The termplot() shows the overall nonlinear association between the predictor (age here) and outcome if the proportional hazards (PH) assumption holds. When PH doesn't hold it represents a type of event-averaged coefficient, as explained on this page.

I don't know of any simple way to think about individual basis functions and coefficients that come from pspline(). What's important is how they all work together to model the (potentially event-averaged) nonlinear association between age and outcome. I would not recommend paying attention to the basis functions individually (for PH or anything else).

The seemingly similar shape of the termplot() and plot.cox.zph() displays from your "real model" is probably a coincidence, based on the nature of your study.

The "event" is evidently "misuse of a given prescription drug." Unlike death events, whose risks typically increase with age and for which a termplot() output would tend to be increasing with age, the drop with age in your termplot() output suggests that a drug "misuse" event is more likely at younger than at older ages. That makes sense, at least based on what I've learned about substance abuse over the years. Remember, when PH doesn't hold, the termplot() is displaying a type of event-averaged association with age.

The drop in the plot.cox.zph() output with time means something different. It means that the overall association between age and outcome is strongest at times soon after your time = 0 reference (however that was defined in your study) and decreases thereafter. One possibility, again consistent with my personal observations, is that some people are more likely (for biological/social reasons) to misuse drugs and such individuals might thus be expected to misuse early on rather than later. There are other considerations, for example whether there is still ready access to the drugs at later time points for all individuals and whether there might be some informative censoring in your study design, which I outlined in this answer.

$\endgroup$
2
  • $\begingroup$ Once again, thank you very much! I think I'm now done with this analysis. I've learned a lot in the meantime, mainly thanks to your excellent answers on this site! $\endgroup$
    – Thomas
    Commented Oct 25 at 14:23
  • $\begingroup$ I feel like there's everything I need in this answer, but I'm going to need some time to process it all! Maybe I'll post some comments to ask for some clarification/confirmation later... Thanks again! $\endgroup$
    – Thomas
    Commented Oct 25 at 14:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.