# Customizing Population Time Plots

#### Sahir R. Bhatnagar

#### 2026-09-03

# Setup

    evaluate_vignette <- requireNamespace("colorspace", quietly = TRUE)
    knitr::opts_chunk$set(eval = evaluate_vignette)

    library(survival)
    library(casebase)
    library(ggplot2)
    library(data.table)
    library(colorspace)

    data("ERSPC")

    # create poptime object for ERSPC data with exposure attribute
    x <- popTime(ERSPC, time = "Follow.Up.Time", event = "DeadOfPrCa", exposure = "ScrArm")
    head(x)

    ##           ScrArm   time event original.time original.event event status ycoord
    ##           <fctr>  <num> <num>         <num>          <int>       <fctr>  <int>
    ## 1: Control group 0.0027     0        0.0027              0     censored  88232
    ## 2: Control group 0.0027     0        0.0027              0     censored  88231
    ## 3: Control group 0.0027     0        0.0027              0     censored  88230
    ## 4: Control group 0.0027     0        0.0027              0     censored  88229
    ## 5: Control group 0.0137     0        0.0137              0     censored  88228
    ## 6: Control group 0.0137     0        0.0137              0     censored  88227
    ##       yc n_available
    ##    <int>       <int>
    ## 1:     0           0
    ## 2:     0           0
    ## 3:     0           0
    ## 4:     0           0
    ## 5:     0           0
    ## 6:     0           0

# Introduction

In this vignette, we explain in details how to customize population time plots. More specifically, we details the inner workings of the `plot` method for objects of class `popTime`.

# The `.params` arguments

The user can have greater control over the aesthetics of a population plot by specifying these in the `.params` arguments. These need to be specified as lists and are subsequently passed on to the following `ggplot2` functions:

- `ribbon.params` –> `ggplot2::geom_ribbon()`
- `case.params` –> `ggplot2::geom_point()`
- `base.params` –> `ggplot2::geom_point()`
- `competing.params` –> `ggplot2::geom_point()`
- `fill.params` –> `ggplot2::scale_fill_manual()`
- `color.params` –> `ggplot2::scale_colour_manual()`
- `theme.params` –> `ggplot2::theme()`

Following the suggestion from the ggplot2 book, we use the `utils::modifyList` function to replace the corresponding default values. For example, the default arguments passed to the `geom_ribbon` function for plotting the area are given by

    list(data = x,
         mapping = aes(x = time, ymin = 0, ymax = ycoord),
         fill = "grey80",
         alpha = 0.5)

    ## $data
    ##                  ScrArm    time event original.time original.event event status
    ##                  <fctr>   <num> <num>         <num>          <int>       <fctr>
    ##      1:   Control group  0.0027     0        0.0027              0     censored
    ##      2:   Control group  0.0027     0        0.0027              0     censored
    ##      3:   Control group  0.0027     0        0.0027              0     censored
    ##      4:   Control group  0.0027     0        0.0027              0     censored
    ##      5:   Control group  0.0137     0        0.0137              0     censored
    ##     ---
    ## 159889: Screening group 14.9405     0       14.9405              0     censored
    ## 159890: Screening group 14.9405     0       14.9405              0     censored
    ## 159891: Screening group 14.9405     0       14.9405              0     censored
    ## 159892: Screening group 14.9405     0       14.9405              0     censored
    ## 159893: Screening group 14.9405     0       14.9405              0     censored
    ##         ycoord    yc n_available
    ##          <int> <int>       <int>
    ##      1:  88232     0           0
    ##      2:  88231     0           0
    ##      3:  88230     0           0
    ##      4:  88229     0           0
    ##      5:  88228     0           0
    ##     ---
    ## 159889:      5     0           0
    ## 159890:      4     0           0
    ## 159891:      3     0           0
    ## 159892:      2     0           0
    ## 159893:      1     0           0
    ##
    ## $mapping
    ## Aesthetic mapping:
    ## * `x`    -> `time`
    ## * `ymin` -> 0
    ## * `ymax` -> `ycoord`
    ##
    ## $fill
    ## [1] "grey80"
    ##
    ## $alpha
    ## [1] 0.5

Note that the variables `time` and `ycoord` are columns created by the `casebase::popTime` function, so we should always leave these specified as is. Suppose we want to change the fill color. We simply specify this color in the `ribbon.params` argument:

    ribbon.params <- list(fill = "#0072B2")

We then call the `utils::modifyList` function to override the function defaults:

    (new_ribbon_params <- utils::modifyList(list(data = x,
                                                mapping = aes(x = time, ymin = 0, ymax = ycoord),
                                                fill = "grey80",
                                                alpha = 0.5),
                                           ribbon.params))

    ## $data
    ##                  ScrArm    time event original.time original.event event status
    ##                  <fctr>   <num> <num>         <num>          <int>       <fctr>
    ##      1:   Control group  0.0027     0        0.0027              0     censored
    ##      2:   Control group  0.0027     0        0.0027              0     censored
    ##      3:   Control group  0.0027     0        0.0027              0     censored
    ##      4:   Control group  0.0027     0        0.0027              0     censored
    ##      5:   Control group  0.0137     0        0.0137              0     censored
    ##     ---
    ## 159889: Screening group 14.9405     0       14.9405              0     censored
    ## 159890: Screening group 14.9405     0       14.9405              0     censored
    ## 159891: Screening group 14.9405     0       14.9405              0     censored
    ## 159892: Screening group 14.9405     0       14.9405              0     censored
    ## 159893: Screening group 14.9405     0       14.9405              0     censored
    ##         ycoord    yc n_available
    ##          <int> <int>       <int>
    ##      1:  88232     0           0
    ##      2:  88231     0           0
    ##      3:  88230     0           0
    ##      4:  88229     0           0
    ##      5:  88228     0           0
    ##     ---
    ## 159889:      5     0           0
    ## 159890:      4     0           0
    ## 159891:      3     0           0
    ## 159892:      2     0           0
    ## 159893:      1     0           0
    ##
    ## $mapping
    ## Aesthetic mapping:
    ## * `x`    -> `time`
    ## * `ymin` -> 0
    ## * `ymax` -> `ycoord`
    ##
    ## $fill
    ## [1] "#0072B2"
    ##
    ## $alpha
    ## [1] 0.5

Finally, we use `base::do.call` to execute the `geom_ribbon` function on this list:

    ggplot() + base::do.call("geom_ribbon", new_ribbon_params)

# Change the Facet Labels

The default arguments to the `facet.params` argument is given by:

    exposure_variable <- attr(x, "exposure")
    default_facet_params <- list(facets = exposure_variable, ncol = 1)

The population time area stratified by treatment arm is then plotted using the following code

    ggplot() +
        base::do.call("geom_ribbon", new_ribbon_params) +
        base::do.call("facet_wrap", default_facet_params)

    # this is equivalent to
    # plot(x, add.case.series = FALSE)

We can modify the facet labels by either changing the factor labels in the data or specifying the `labeller` argument. See this blog post for further details. Here is an example of how we can change the facet labels using the `plot` method provided by the casebase package:

    # Use character vectors as lookup tables:
    group_status <- c(
      `0` = "Control Arm",
      `1` = "Screening Arm"
    )

    plot(x,
         add.case.series = FALSE, # do not plot the case serires
         facet.params = list(labeller = labeller(ScrArm = group_status), # change labels
                             strip.position = "right") # change facet position
         )

    ## Warning: No shared levels found between `names(values)` of the manual scale and the
    ## data's colour values.

    ## Warning: No shared levels found between `names(values)` of the manual scale and the
    ## data's fill values.

# Changing the Plot Aesthetics

Suppose we want to change the color of the points and the legend labels. We use the `bmtcrr` dataset as the example in this section.

The reason there are both `fill.params` and `color.params` arguments, is because by default, we use `shape = 21` which is a filled circle (see the `pch` argument of the `graphics::points` function for details). Shapes from 21 to 25 can be colored and filled with different colors: `color.params` gives the border color and `fill.params` gives the fill color (sometimes referred to as the background color).

The default fill colors for the case series, base series and competing event are given by the qualitative palette from the `colorspace` R package:

    fill_cols <- colorspace::qualitative_hcl(n = 3, palette = "Dark3")

    (fill_colors <- c("Case series" = fill_cols[1],
                     "Competing event" = fill_cols[3],
                     "Base series" = fill_cols[2]))

    ##     Case series Competing event     Base series
    ##       "#E16A86"       "#009ADE"       "#50A315"

The corresponding default border colors are given by the `colorspace::darken` function applied to the fill colors above:

    color_cols <- colorspace::darken(col = fill_cols, amount = 0.3)

    (color_colors <- c("Case series" = color_cols[1],
                       "Competing event" = color_cols[3],
                       "Base series" = color_cols[2]))

    ##     Case series Competing event     Base series
    ##       "#AB3A59"       "#026A9A"       "#347004"

This is what the points look like:

## Change only the point colors

If you only want to change the color points, you must specify a named vector exactly as specified in the `fill_colors` object created above. Note that the names `Case series`, `Base Series` and `Competing event` must remain the same, otherwise the function won’t know how to map the colors to the corresponding points. This is because the `colour` and `fill` aesthetic mappings in the `geom_point` functions have been set to `Case series`, `Base Series` and `Competing event`. For example, the default call to `geom_point` for the case series is given by:

    ggplot() + do.call("geom_point", list(data = x[event == 1],
                         mapping = aes(x = time, y = yc,
                                       colour = "Case series", fill = "Case series"),
                         size = 1.5,
                         alpha = 0.5,
                         shape = 21))

We define a new set of colors using a sequential (multi-hue) palette:

    fill_cols <- colorspace::sequential_hcl(n = 3, palette = "Viridis")

    (fill_colors <- c("Case series" = fill_cols[1],
                     "Competing event" = fill_cols[3],
                     "Base series" = fill_cols[2]))

    ##     Case series Competing event     Base series
    ##       "#4B0055"       "#FDE333"       "#009B95"

    color_cols <- colorspace::darken(col = fill_cols, amount = 0.3)

    (color_colors <- c("Case series" = color_cols[1],
                       "Competing event" = color_cols[3],
                       "Base series" = color_cols[2]))

    ##     Case series Competing event     Base series
    ##       "#3A0142"       "#AC9900"       "#0A6B66"

We then pass `fill_cols` and `color_cols` to the `fill.params` and `color.params` arguments, respectively. Internally, this gets passed to the `ggplot2::scale_fill_manual` and `ggplot2::scale_color_manual` functions, respectively:

    do.call("scale_fill_manual", utils::modifyList(
      list(name = element_blank(),
           breaks = c("Case series", "Competing event", "Base series"),
           values = old_cols), list(values = fill_colors))
    )

    do.call("scale_colour_manual", utils::modifyList(
      list(name = element_blank(),
           breaks = c("Case series", "Competing event", "Base series"),
           values = old_cols), list(values = color_colors))
    )

Here is the code to only change the colors:

    # this data ships with the casebase package
    data("bmtcrr")

    popTimeData <- popTime(data = bmtcrr, time = "ftime", exposure = "D")

    ## 'Status' will be used as the event variable

    plot(popTimeData,
         add.case.series = TRUE,
         add.base.series = TRUE,
         add.competing.event = TRUE,
         comprisk = TRUE,
         fill.params = list(values = fill_colors),
         color.params = list(value = color_colors))

    ## Warning in survival::Surv(data[[timeVar]], eventVar, type = surv_type): type=
    ## 'mstate' is deprecated, use a factor variable as status

    ## Warning: `label` cannot be a <ggplot2::element_blank> object.

Note that if you only specify one of the `fill.params` or `color.params` arguments, the plot method will automatically set one equal to the other and return a warning message:

    plot(popTimeData,
         add.case.series = TRUE,
         add.base.series = TRUE,
         add.competing.event = TRUE,
         ratio = 1,
         comprisk = TRUE,
         legend = TRUE,
         fill.params = list(values = fill_colors))

    ## Warning in plot.popTime(popTimeData, add.case.series = TRUE, add.base.series =
    ## TRUE, : fill.params has been specified by the user but color.params has not.
    ## Setting color.params to be equal to fill.params.

    ## Warning in survival::Surv(data[[timeVar]], eventVar, type = surv_type): type=
    ## 'mstate' is deprecated, use a factor variable as status

    ## Warning: `label` cannot be a <ggplot2::element_blank> object.

## Change Point Color and Legend Labels

In order to change both the point colors and legend labels, we must modify the aesthetic mapping of the `geom_point` calls as follows:

    # this data ships with the casebase package
    data("bmtcrr")

    popTimeData <- popTime(data = bmtcrr, time = "ftime", exposure = "D")

    ## 'Status' will be used as the event variable

    plot(popTimeData,
         add.case.series = TRUE,
         add.base.series = TRUE,
         add.competing.event = TRUE,
         comprisk = TRUE,
         case.params = list(mapping = aes(x = time, y = yc, fill = "Relapse", colour = "Relapse")),
         base.params = list(mapping = aes(x = time, y = ycoord, fill = "Base series", colour = "Base series")),
         competing.params = list(mapping = aes(x = time, y = yc, fill = "Competing event", colour = "Competing event")),
         fill.params = list(name = "Legend Name",
                              breaks = c("Relapse", "Base series", "Competing event"),
                              values = c("Relapse" = "blue", "Competing event" = "hotpink", "Base series" = "orange")))

    ## Warning in plot.popTime(popTimeData, add.case.series = TRUE, add.base.series =
    ## TRUE, : fill.params has been specified by the user but color.params has not.
    ## Setting color.params to be equal to fill.params.

    ## Warning in survival::Surv(data[[timeVar]], eventVar, type = surv_type): type=
    ## 'mstate' is deprecated, use a factor variable as status

NOTE: the lists being passed to the `.params` arguments must be named arguments, otherwise they will give unexpected behavior. For example

    # this will work because mapping is the name of the
    # argument of the list
    case.params = list(mapping = aes(x = time, y = yc, colour = "Relapse", fill = "Relapse"))

    # this will NOT work because the argument of the list has no name
    # and therefore utils::modifyList, will not override the defaults.
    case.params = list(aes(x = time, y = yc, colour = "Relapse", fill = "Relapse"))

# Session information

    ## R version 4.6.1 (2026-06-24)
    ## Platform: x86_64-pc-linux-gnu
    ## Running under: Ubuntu 24.04.4 LTS
    ##
    ## Matrix products: default
    ## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
    ## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
    ##
    ## attached base packages:
    ## [1] stats     graphics  grDevices utils     datasets  methods   base
    ##
    ## other attached packages:
    ## [1] colorspace_2.1-3    data.table_1.18.6.1 ggplot2_4.0.3
    ## [4] survival_3.8-11     casebase_0.10.7
    ##
    ## loaded via a namespace (and not attached):
    ##  [1] Matrix_1.7-6       gtable_0.3.6       jsonlite_2.0.0     dplyr_1.2.1
    ##  [5] compiler_4.6.1     tidyselect_1.2.1   VGAM_1.1-14        jquerylib_0.1.4
    ##  [9] splines_4.6.1      scales_1.4.0       yaml_2.3.12        fastmap_1.2.0
    ## [13] lattice_0.23-1     R6_2.6.1           labeling_0.4.3     generics_0.1.4
    ## [17] knitr_1.51         tibble_3.3.1       bslib_0.12.0       pillar_1.11.1
    ## [21] RColorBrewer_1.1-3 rlang_1.3.0        cachem_1.1.0       xfun_0.60
    ## [25] sass_0.4.10        S7_0.2.2           otel_0.2.0         cli_3.6.6
    ## [29] withr_3.0.3        magrittr_2.0.5     mgcv_1.9-4         digest_0.6.39
    ## [33] grid_4.6.1         lifecycle_1.0.5    nlme_3.1-171       vctrs_0.7.3
    ## [37] evaluate_1.0.5     glue_1.8.1         farver_2.1.2       stats4_4.6.1
    ## [41] rmarkdown_2.32     tools_4.6.1        pkgconfig_2.0.3    htmltools_0.5.9