Combining faceting with simplified risk tables #188
Replies: 6 comments 1 reply
-
Hey hey @adrianolszewski ! Thank you for the thoughtful post! I think faceting would be an amazing addition to the package. I think I'll need to think on this more deeply before i can full respond. My first thought is that the process of how our figures are created just won't jive with faceting (I think). One of the things I love most about the ggsurvfit package, is that the figures work seamlessly with the ggplot functions you already know and you can get highly customized plots (well, except faceted plots 😆 ). How we achieved this was by delaying the construction of the risk table until the moment when the ggplot is printed. In the ggsurvfit print method, we construct the risk table and ensure it aligns perfectly with the primary plot (regardless of whatever formatting a user has applied to the plot). After the risktable plot is created, we stack the plots with patchwork. It's tricky to see how this workflow can be integrated with faceting, right? 🤔 🤔 🤔 |
Beta Was this translation helpful? Give feedback.
-
I think that's exactly what I had in mind when summarizing the last two points. So, by default, when faceting is not requested, you proceed as usually. Nothing changes. Your risk table looks perfectly, no need to change a line of code! Note: For example
Now, when the user asks for faceting (like in the pic I showed), and if you can figure it out, then the risk table is not rendered, so the problem is eliminated. Now, regarding the risk table, in case of faceting and when the user switches it on, you could render the simplified table by adding these geom_texts(). The numbers will be ideally aligned, because that's the ggplot() task to put the geom_text according to aes(x, y). So, without faceting nothing changes. Something like this: (pseudocode)
|
Beta Was this translation helpful? Give feedback.
-
If we can't think of a way to get faceting to work (which to be honest, I am pessimistic about), perhaps a helper function to accomplish a similar goal. Something like this (obviously, would need significant work still) library(ggsurvfit)
#> Loading required package: ggplot2
packageVersion("ggsurvfit")
#> [1] '1.0.0'
ggsurvfit_facet <- function(data, strata, .f, ncol = NULL, nrow = NULL) {
# build plots ----------------------------------------------------------------
lst_ggsurvit_plts <-
data |>
tidyr::nest(.by = dplyr::all_of(strata), .key = "...ggsurvfit_facet_data...") |>
dplyr::mutate(
...ggsurvfit_facet_plot... =
purrr::map2(
.data$...ggsurvfit_facet_data...,
.data[[strata]],
.f
) |>
purrr::map(\(x) ggsurvfit::ggsurvfit_build(x))
) |>
dplyr::pull("...ggsurvfit_facet_plot...")
# combine plots --------------------------------------------------------------
rlang::inject(patchwork::wrap_plots(!!!lst_ggsurvit_plts, ncol = ncol, nrow = nrow))
}
df_lung |>
ggsurvfit_facet(
strata = "sex",
.f = function(data, strata) {
survfit2(Surv(time, status) ~ 1, data) |>
ggsurvfit() +
add_risktable() +
labs(title = strata)
}
) df_lung |>
ggsurvfit_facet(
strata = "sex",
.f = function(data, strata) {
survfit2(Surv(time, status) ~ 1, data) |>
ggsurvfit() +
add_risktable() +
labs(title = strata)
},
ncol = 1L
) Created on 2024-01-18 with reprex v2.0.2 |
Beta Was this translation helpful? Give feedback.
-
@adrianolszewski i don't think i want to go down the rabbit hole of detecting faceting, and doing something different if it is. That would lead to re-implementing the entire package is a faceting framework which will be a nightmare to maintain. |
Beta Was this translation helpful? Give feedback.
-
I understand. Thank you for your efforts in trying to handle this case! For me it's not a big issue. You know, I used to implement the survival curves manually for a long time. Especially that I often mix various cases together, like mixing CIFs from competing risks and the naive 1-KM for sensitivity analyses, adding faceted risk tables, adding non-standard tests (Max-Combo), adding CIs for the survival quantile and other functionalities that aren't yet supported by existing packages, so I can live with it already :) If one will, in any case, need to add a simplistic survival table on faceted KMs, one can always do this by processing the survival table (from survfit) and adding it via geom_text. I think I can write share some exemplary code on Gist/GitHub how to do this and this may suffice. Please keep developing your awesome work, it's very clean and mature! PS: the helper function you presented above is very useful, thanks! |
Beta Was this translation helpful? Give feedback.
-
@ddsjoberg This plots KM (or CIF) curves with facet_wrap() and stores as a OK, not the most intuitive way one could imagine, but: 1) easy to program without printing on plots and employing 3rd party packages (except patchwork), 2) handles faceting (wrapping ,gridding) natively, 3) aligns well together via patchwork (and also the ratio of heights can be set). One day it saved my life, when a sponsor asked me to provide a panel of KMs with small, simple event tables together, yet I had no time to program extensively.... This was naive, simple, but worked. |
Beta Was this translation helpful? Give feedback.
-
Hi!
Some time I read the documentation of ggsurvfit, curious is the risk tables can be combined with faceting. Yeah, I know how difficult it is. You went the same way I did - via patchwork (ggsurvminer probably used gridExtra or cowplot, I don't recall it now).
But - well - difficult or not, we work with a sponsor who performs a lot of observational studies with numerous exploratory analyses, requiring us to compare visually a number of KM curves across subgroups.
So we do a lot of multi-level (2x2, 2x3) faceting for KM and CIF curves (BTW, we utilize also the https://teunbrand.github.io/ggh4x/articles/Facets.html maybe you know it already).
The difficult part is that the sponsor constantly have been asking us to add the risk tables to each facet. Exactly, one facet/panel - one risk table. They need it for posters, where the space for content is very limited. So they want all the KM panels with own risk tables, even in the simplest form. And since the posters are big, these tables will be big enough to read.
In the first approach I experimented with patchwork. So, in a loop, I prepared a list of separate, faceted little KM plots, and "glued" to them their own risk tables. Then I combined these {plot-table} "combos" into the final grid (I had to conditionally show and hide appropriate facet "strips", but OK, it worked). And it worked really well! But occupied to much space, so it failed at a bigger number of panels.
Then I realized, that I don't need any "fancy" tables, just 2 (or 3) rows of numbers displayed below the curves!
Ideally in a format xx (xx), i.e. #at risk (#events), or #at risk (#censored), depending on sponsor's needs (and these needs varied between projects...)
So, I added some space below the curves, occupying the area of negative numbers, displayed a white rectangle (to hide the grid, and put the numbers on it via geom_text! I also had to modify a little the vertical axis to caption the series of numbers.
I know it looks very raw, simplistic, but it met the sponsor's expectations. It doesn't use any complex tricks to display the numbers, because geom_text() is naturally faceted.
Caption below the plot explains the format of the risk tables.
It doesn't work well:
but it does its job in cases like the one attached.
I just thought - maybe you could consider adding this method in your package?
What I mean precisely:
by default, for a single plot, nothing changes: the default pretty risk table is displayed as before.
when a user requests faceting (facet_wrap, facet_grid), all panels are enhanced by the simplified risk table. The user can define the format (either just 1 number: at risk, events, censored, or combination of 2 of them, if space permits), turn it on or off (by default it could be turned off, to save space).
The quality of the risk table is poorer, I agree, but it handles the case that is often asked for.
This would be something unique, that nobody else offers!
And - what's especially important - it doesn't needs you to employ any difficult techniques, 3rd party packages and so on. Just geom_text (or geom_labels) over a table that you already prepare (here just faceted/split-by-groups).
PS: if you wish I can share some codes extracted from the trials I analysed. I do everything from scratch (using geom_step and manually processing the survival tables), without any dedicated survival-plotting packages. But, when we finally have the KM plotted, adding the risk tables simplifies to adding some geom_text() :)
Beta Was this translation helpful? Give feedback.
All reactions