rggplot2rms

How to modify the line style in the drawing of the rms package


I can set the line style of the grouped lines through the following code:

scale_linetype_manual(values = c("solid", "dashed", "dashed"))

x <- lm(Sepal.Length ~ Sepal.Width + Species, iris) 
ggplot(x, aes(Sepal.Width, Sepal.Length, color = Species, linetype = Species)) + 
geom_line() +    
scale_color_manual(values = c("black", "red", "blue")) +   
scale_linetype_manual(values = c("solid", "dashed", "dashed"))

But not after the rms package. What did I miss?

scale_linetype_manual(values = c("solid", "dashed", "dashed")) did not work in the following code.

require(survival)
require(ggplot2)
require(rms)

n <- 1000
set.seed(731)
age <- 50 + 12*rnorm(n)
label(age) <- "Age"
sex <- factor(sample(c('Male','Female'), n, 
                     rep=TRUE, prob=c(.6, .4)))
cens <- 15*runif(n)
h <- .02*exp(.04*(age-50)+.8*(sex=='Female'))
dt <- -log(runif(n))/h
label(dt) <- 'Follow-up Time'
e <- ifelse(dt <= cens,1,0)
dt <- pmin(dt, cens)
units(dt) <- "Year"
dd <- datadist(age, sex)
options(datadist='dd')
S <- Surv(dt,e)

f <- cph(S ~ rcs(age,4) + sex, x=TRUE, y=TRUE)
cox.zph(f, "rank")             # tests of PH
anova(f)
x <- Predict(f, age, sex)
ggplot(x,,aes(age, yhat, linetype=sex)) +
  geom_line() +
  scale_color_manual(values = c("black", "black")) +
  scale_linetype_manual(values = c("solid", "dashed"))

Solution

  • It's because the input to ggplot is an object of class "predict" rather than simply a dataframe.

    By intercepting the ggplot layer data, it's possible to update the linetype and then get the dashed line.

    Alternatively, you could convert the predict object to a tibble and then build the ggplot in the usual way with geom_ribbon() etc.

    library(survival)
    library(tidyverse)
    library(rms)
    
    # Data
    n <- 1000
    set.seed(731)
    age <- 50 + 12*rnorm(n)
    label(age) <- "Age"
    sex <- factor(sample(c('Male','Female'), n, 
                         rep=TRUE, prob=c(.6, .4)))
    cens <- 15*runif(n)
    h <- .02*exp(.04*(age-50)+.8*(sex=='Female'))
    dt <- -log(runif(n))/h
    label(dt) <- 'Follow-up Time'
    e <- ifelse(dt <= cens,1,0)
    dt <- pmin(dt, cens)
    units(dt) <- "Year"
    dd <- datadist(age, sex)
    options(datadist='dd')
    S <- Surv(dt,e)
    
    f <- cph(S ~ rcs(age,4) + sex, x=TRUE, y=TRUE)
    cox.zph(f, "rank")            
    anova(f)
    x <- Predict(f, age, sex)
    
    # Plotting
    class(x) # Special "Predict" class
    #> [1] "Predict"    "data.frame"
    
    p <- ggplot(x, aes(age, yhat, linetype = sex)) +
      scale_linetype_manual(values = c("solid", "dashed"))
    
    p # no dashed line
    

    
    head(layer_data(p, 1)) # no special linetype in ggplot layer data
    #>    colour        x           y PANEL group flipped_aes linewidth linetype alpha
    #> 1 #000000 19.71985 -0.05545631     1     1       FALSE       0.5        1    NA
    #> 2 #000000 20.00869 -0.06045969     1     1       FALSE       0.5        1    NA
    #> 3 #000000 20.29754 -0.06546307     1     1       FALSE       0.5        1    NA
    #> 4 #000000 20.58638 -0.07046645     1     1       FALSE       0.5        1    NA
    #> 5 #000000 20.87523 -0.07546982     1     1       FALSE       0.5        1    NA
    #> 6 #000000 21.16408 -0.08047320     1     1       FALSE       0.5        1    NA
    
    # update the layer based on the group (sex)
    p[["layers"]][[1]][["aes_params"]][["linetype"]] <- layer_data(p, 1) |> 
      mutate(linetype = if_else(group == 1, "solid", "dashed")) |> pull(linetype)
    
    p # now we have the dashed line
    

    
    # You could also convert to a tibble then build the plot adding `geom_ribbon()` etc. (which may be easier)
    x |> 
      as_tibble() |> 
      ggplot(aes(age, yhat, linetype = sex)) +
      geom_line() +
      scale_linetype_manual(values = c("solid", "dashed"))
    

    enter image description here

    Created on 2024-04-25 with reprex v2.1.0