Search code examples
rggplot2ggrepelggalluvial

How to align and label the stratum in ggalluvial using ggrepel (or otherwise)


I have some alluvial plots that I have generated using ggalluvial in R.

The code example below produces something close to what I want to achieve. For example,

library("ggalluvial")
par(mar = c(1,1,1,1)*12, cex = 0.6, xpd=NA)

# generate some example data
somelongnames <- c("homo sapiens", "homo sapiens", letters[18],
                   "some other long name", letters[seq(4)])

df <- data.frame(x = factor(somelongnames),
                 y = factor(c("this label is long", "Golgi", 
                       letters[13:18])),
                 count = c(2, 10, 4, 5, 5, 1, 9, 3))

ll <- unique(c(as.character(df$x), as.character(df$y)))
grid.col <- rainbow(length(ll))
grid.col <- setNames(grid.col, ll)

# set colours for alluvial plot (this is a little tricky as strata 
# colour ordering is required for ggalluvial strata)
names(df) <- c("Condition1", "Condition2", "value")
levs1 <- levels(df$Condition1) 
levs2 <- levels(df$Condition2)
res1 <- unique(df$Condition1)
res2 <- unique(df$Condition2)
cond1_cols <- grid.col[levs1[levs1 %in% res1]]
cond2_cols <- grid.col[levs2[levs2 %in% res2]]
columnCols <- c(cond1_cols, cond2_cols)
stratCols <- c(rev(cond1_cols), rev(cond2_cols))

# plot alluvial diagram
q <- ggplot(df,
            aes(y = value, axis1 = Condition1, axis2 = Condition2)) +
  geom_alluvium(aes(fill = Condition1), width = 0) +
  scale_fill_manual(values = columnCols) +
  geom_stratum(width = 1/8, fill = paste0(stratCols), color = "white") +
  scale_x_discrete(limits = c("Condition1", "Condition2"), 
                   expand = c(.09, .09)) +
  scale_y_continuous(breaks = NULL) +
  theme_minimal() +
  theme(axis.ticks.y = element_blank(),
        axis.text.y = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.grid.major.x = element_blank()) +
  theme(legend.position = "none") +
  ylab(NULL)

# add labels
  q +
    geom_label(stat = "stratum", aes(label = after_stat(stratum)),
               color = stratCols, fontface = "bold", size = 3)     

enter image description here

I would like to move the labels so they sit left and right of the stratum and so they are not overlapping the plot (which is particularly annoying when labels are long). I have seen examples that use the ggrepel package to do this.

I have tried using a for loop and ifelse statement in the call to aes in geom_text_repel (as per Solution 2 here) but can't quite figure out the code. Can anyone help or has a better solution to achieve this?

Failed code example using ggrepel e.g.

q + ggrepel::geom_text_repel(
    aes(label = ifelse(Condition1 == as.character(res1)[1],as.character(res1)[1], NA)),
        stat = "stratum", size = 4, 
        direction = "y", nudge_x = -.5
) 

enter image description here

Ideally I would like to produce something like solution 2 in this example from Jason Cory Brunson.

> sessionInfo()
R version 4.0.5 (2021-03-31)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Big Sur 10.16

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8

attached base packages:
[1] parallel  stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ggalluvial_0.12.3    RColorBrewer_1.1-2   archivist_2.3.6      circlize_0.4.12      dplyr_1.0.5          viridis_0.6.0       
 [7] viridisLite_0.4.0    pheatmap_1.0.12      pRolocdata_1.29.1    ggplot2_3.3.3        bandle_1.0           pRoloc_1.30.0       
[13] BiocParallel_1.24.1  MLInterfaces_1.70.0  cluster_2.1.1        annotate_1.68.0      XML_3.99-0.6         AnnotationDbi_1.52.0
[19] IRanges_2.24.1       MSnbase_2.16.1       ProtGenerics_1.22.0  mzR_2.24.1           Rcpp_1.0.6           Biobase_2.50.0      
[25] S4Vectors_0.28.1     BiocGenerics_0.36.0  BiocStyle_2.18.1    

Solution

  • Maybe this fits your need. First. As I'm not that familiar with ggalluvial I changed the setup of your data to follow the code in the example in your link by first expanding your dataset and converting it to long format. Doing so allowed me to make map on the stratum and alluvium aes and made it easier to condition on your Condition variable.

    After doing so you could make use of default geom_text to align the labels by making use of an ifelse. Or you could make use of two geom_text_repel layers to put the labels on the left or right of the stratum.

    library(tidyr)
    library(dplyr)
    
    df_expanded <- df[rep(row.names(df), df$value), ]
    df_expanded <- df_expanded %>%
      mutate(id = row_number()) %>%
      pivot_longer(-c(value, id), names_to = "Condition", values_to = "label")
    
    # plot alluvial diagram
    q <- ggplot(df_expanded, aes(x = Condition, stratum = label, alluvium = id, fill = label)) +
      geom_flow(width = 0) +
      scale_fill_manual(values = columnCols) +
      scale_color_manual(values = stratCols) +
      geom_stratum(width = 1 / 8, color = "white") +
      scale_x_discrete(
        expand = c(.25, .25)
      ) +
      scale_y_continuous(breaks = NULL) +
      theme_minimal() +
      theme(
        axis.ticks.y = element_blank(),
        axis.text.y = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.grid.major.x = element_blank()
      ) +
      theme(legend.position = "none") +
      ylab(NULL)
    
    q +
      geom_text(
        aes(
          label = after_stat(stratum),
          hjust = ifelse(Condition == "Condition1", 1, 0),
          x = as.numeric(factor(Condition)) + .075 * ifelse(Condition == "Condition1", -1, 1),
          color = after_stat(stratum)
        ),
        stat = "stratum", fontface = "bold", size = 3
      )
    #> Warning: The `.dots` argument of `group_by()` is deprecated as of dplyr 1.0.0.
    

    q +
      ggrepel::geom_text_repel(
      aes(label = ifelse(after_stat(x) == 1, as.character(after_stat(stratum)), "")),
      stat = "stratum", size = 4, direction = "y", nudge_x = -.6) +
      ggrepel::geom_text_repel(
        aes(label = ifelse(after_stat(x)  == 2, as.character(after_stat(stratum)), "")),
        stat = "stratum", size = 4, direction = "y", nudge_x = .6
      )