Search code examples
rggplot2labelfacet-grid

How to mirror strip.text in faceted ggplot on opposite side of plot and manipulate labels?


I am trying to plot data from nine survey items (q1...q9) that all measure dis/agreement on given statements on an 11-point scale. I rearranged the data into long format which helped me visualize it.

head(df)
# A tibble: 5 × 4
  name  value left                      right                         
  <chr> <dbl> <chr>                     <chr>                         
1 q1        8 increase social spending  decrease social spending      
2 q2        1 invest in climate         invest in economy             
3 q3        3 increase help for ukraine maintain neutrality           
4 q4        9 facilitate migration      restrict migration            
5 q5        4 more equity and diversity social equity already achieved

What I would like to produce is a faceted density plot for each 'variable' or rather group in the name variable (q1 to q9), much like ggridges. I am not actually using ggridges however, since it proved less versatile.

This is what I have. My attempt

Problem: I would like to plot the agree and disagree options on either side of the respective density plots (such as 'decrease social spending' ... 'increase social spending'). I think that two more steps are necessary to get there:

  1. Mirror the strip text to the right side of the plot.
  2. Manipulate the labels such that they contain the dis/agree options rather than the question labels.

Current code:

theme_set(theme_minimal())

ggplot(df, aes(x = value, y = ..count.., fill = name)) +
  geom_density(color = "transparent") +
  facet_grid(name ~ ., switch = "y") +
  scale_fill_viridis_d(direction = -1, guide = "none") + 
  labs(x="", y="") +
  xlim(-1, 13) +
  theme(panel.grid.minor = element_blank(),
        axis.text.y = element_blank(),
        strip.text.y.left = element_text(angle = 0, vjust = 0),
        strip.text.y.right = element_text(angle = 0, vjust = 0))

Example data: Here's the code that reproduces (a sample of) my df.

structure(list(name = c("q1", "q2", "q3", "q4", "q5", "q6", "q7", 
                        "q8", "q9", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", 
                        "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q1", "q2", 
                        "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q1", "q2", "q3", "q4", 
                        "q5", "q6", "q7", "q8", "q9", "q1", "q2", "q3", "q4", "q5", "q6", 
                        "q7", "q8", "q9", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", 
                        "q9", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q1", 
                        "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q1", "q2", "q3", 
                        "q4", "q5", "q6", "q7", "q8", "q9"), 
               value = structure(c(8, 1, 3, 9, 4, 11, 3, 10, 6, 5, 1, 11, 8, 1, 7, 8, 9, 5, 4, 8, 6, 6, 
                                   8, 7, 4, 6, 5, 1, 3, 11, 9, 9, 6, 4, 10, 4, 7, 3, 5, 9, 6, 9, 
                                   6, 10, 8, 1, 3, 6, 8, 6, 11, 11, 11, 11, 3, 3, 1, 6, 9, 11, 6, 
                                   11, 8, 3, 9, 11, 1, 3, 11, 11, 3, 11, 5, 4, 11, 2, 8, 10, 6, 
                                   6, 9, 6, 10, 5, 6, 11, 10, 3, 8, 1)),
               left = c("increase social spending", "invest in climate", 
                        "increase help for ukraine", "facilitate migration", "more equity and diversity", 
                        "more family aid for rich", "more surveillance more security", 
                        "further european integration", "maintain mask mandate", 
                        "increase social spending", "invest in climate", "increase help for ukraine", 
                        "facilitate migration", "more equity and diversity", "more family aid for rich", 
                        "more surveillance more security", "further european integration", 
                        "maintain mask mandate", "increase social spending", "invest in climate", 
                        "increase help for ukraine", "facilitate migration", "more equity and diversity", 
                        "more family aid for rich", "more surveillance more security", 
                        "further european integration", "maintain mask mandate", 
                        "increase social spending", "invest in climate", "increase help for ukraine", 
                        "facilitate migration", "more equity and diversity", "more family aid for rich", 
                        "more surveillance more security", "further european integration", 
                        "maintain mask mandate", "increase social spending", "invest in climate", 
                        "increase help for ukraine", "facilitate migration", "more equity and diversity", 
                        "more family aid for rich", "more surveillance more security", 
                        "further european integration", "maintain mask mandate", 
                        "increase social spending", "invest in climate", "increase help for ukraine", 
                        "facilitate migration", "more equity and diversity", "more family aid for rich", 
                        "more surveillance more security", "further european integration", 
                        "maintain mask mandate", "increase social spending", "invest in climate", 
                        "increase help for ukraine", "facilitate migration", "more equity and diversity", 
                        "more family aid for rich", "more surveillance more security", 
                        "further european integration", "maintain mask mandate", 
                        "increase social spending", "invest in climate", "increase help for ukraine", 
                        "facilitate migration", "more equity and diversity", "more family aid for rich", 
                        "more surveillance more security", "further european integration", 
                        "maintain mask mandate", "increase social spending", "invest in climate", 
                        "increase help for ukraine", "facilitate migration", "more equity and diversity", 
                        "more family aid for rich", "more surveillance more security", 
                        "further european integration", "maintain mask mandate", 
                        "increase social spending", "invest in climate", "increase help for ukraine", 
                        "facilitate migration", "more equity and diversity", "more family aid for rich", 
                        "more surveillance more security", "further european integration", 
                        "maintain mask mandate"), 
               right = c("decrease social spending", 
                         "invest in economy", "maintain neutrality", "restrict migration", 
                         "social equity already achieved", "less family aid for rich", 
                         "less surveillance more freedom", "european integration gone too far", 
                         "abolish mask mandate", "decrease social spending", "invest in economy", 
                         "maintain neutrality", "restrict migration", "social equity already achieved", 
                         "less family aid for rich", "less surveillance more freedom", 
                         "european integration gone too far", "abolish mask mandate", 
                         "decrease social spending", "invest in economy", "maintain neutrality", 
                         "restrict migration", "social equity already achieved", "less family aid for rich", 
                         "less surveillance more freedom", "european integration gone too far", 
                         "abolish mask mandate", "decrease social spending", "invest in economy", 
                         "maintain neutrality", "restrict migration", "social equity already achieved", 
                         "less family aid for rich", "less surveillance more freedom", 
                         "european integration gone too far", "abolish mask mandate", 
                         "decrease social spending", "invest in economy", "maintain neutrality", 
                         "restrict migration", "social equity already achieved", "less family aid for rich", 
                         "less surveillance more freedom", "european integration gone too far", 
                         "abolish mask mandate", "decrease social spending", "invest in economy", 
                         "maintain neutrality", "restrict migration", "social equity already achieved", 
                         "less family aid for rich", "less surveillance more freedom", 
                         "european integration gone too far", "abolish mask mandate", 
                         "decrease social spending", "invest in economy", "maintain neutrality", 
                         "restrict migration", "social equity already achieved", "less family aid for rich", 
                         "less surveillance more freedom", "european integration gone too far", 
                         "abolish mask mandate", "decrease social spending", "invest in economy", 
                         "maintain neutrality", "restrict migration", "social equity already achieved", 
                         "less family aid for rich", "less surveillance more freedom", 
                         "european integration gone too far", "abolish mask mandate", 
                         "decrease social spending", "invest in economy", "maintain neutrality", 
                         "restrict migration", "social equity already achieved", "less family aid for rich", 
                         "less surveillance more freedom", "european integration gone too far", 
                         "abolish mask mandate", "decrease social spending", "invest in economy", 
                         "maintain neutrality", "restrict migration", "social equity already achieved", 
                         "less family aid for rich", "less surveillance more freedom", 
                         "european integration gone too far", "abolish mask mandate")), 
          row.names = c(NA, -90L), class = c("tbl_df", "tbl", "data.frame"))

Solution

  • Easiest is to use custom annotation instead.

    First, create a data frame for your annotation, then use this for labelling. Using x = -Inf/Inf and hjust accordingly and turn off clipping will allow you to place the labels on the edges of the plot (Plot 1). You will notice, this "sticks" pretty close to the plot. You could use hjust of -0.i and +1.j, but this gives very weird results with the use of stringr::str_wrap. I therefore am suggesting a second alternative with plotting the labels separately and adding them to the plot with {patchwork} (Plot 2).

    library(tidyverse)
    
    ## make your labelling data frame
    df_lab <-
      df %>%
      distinct(name, right, left) %>%
      ## add the coordinates and hjust for a neater look
      pivot_longer(-name, names_to = "pos", values_to = "label") %>%
      mutate(
        x = ifelse(pos == "right", Inf, -Inf),
        hjust = ifelse(pos == "right", 0, 1)
      )
    
    ## use after_stat
    ggplot(mapping = aes(x = value, y = after_stat(count), fill = name)) +
      geom_density(data = df, color = "transparent") +
      ## add your labels. I am wrapping them in order to limit the width
      geom_text(
        data = df_lab, size = 8 * 5 / 14,
        aes(x,
          y = 1, label = stringr::str_wrap(label, 20),
          hjust = hjust
        )
      ) +
      facet_grid(name ~ ., switch = "y") +
      scale_fill_viridis_d(direction = -1, guide = "none") +
      ## use coord_cartesion(xlim = c(...,...)) instead of xlim()
      ## but even better is:
      scale_x_continuous(expand = c(0, 0)) +
      ## use NULL!!!
      labs(x = NULL, y = NULL) +
      ## clip off so that you can see the annotation fully
      coord_cartesian(clip = "off") +
      theme(
        panel.grid.minor = element_blank(),
        axis.text.y = element_blank(),
        ## you will need to add a margin to both sides
        plot.margin = margin(r = 1, l = 1, unit = "in"),
        ## now you could remove the strips altogether
        strip.background = element_blank(),
        strip.text = element_blank(),
        ##  the axis ticks look ugly, so I'm removing them
        axis.ticks.y = element_blank()
      )
    

    Using patchwork will give you more flexibility regarding label positioning.

    
    library(patchwork)
    p_main <- 
      ggplot(mapping = aes(x = value, y = after_stat(count), fill = name)) +
      geom_density(data = df, color = "transparent") +
      facet_grid(name ~ ., switch = "y") +
      scale_fill_viridis_d(direction = -1, guide = "none") +
      scale_x_continuous(expand = c(0, 0)) +
      labs(x = NULL, y = NULL) +
      theme(
        ##NB I am removing the plot margin completely
        plot.margin = margin(),
        panel.grid.minor = element_blank(),
        axis.text.y = element_blank(),
        strip.background = element_blank(),
        strip.text = element_blank(),
        axis.ticks.y = element_blank()
      )
    
    ## annotating with separate plots
    p_labs <- 
      lapply(c("right", "left"), function(side) {
      ggplot() +
        geom_text(
          data = filter(df_lab, pos == side), size = 8 * 5 / 14,
          aes(
            x = 1,
            y = 1, label = stringr::str_wrap(label, 19),
            hjust = hjust
          )
        ) +
        facet_grid(name ~ .) +
        coord_cartesian(clip = "off") +
        theme_void() +
        theme(
          strip.background = element_blank(),
          strip.text = element_blank()
        )
    })
    
    ## sadly, you will need to add individual plot margins
    (p_labs[[1]] + theme(plot.margin = margin(r = 35))) +
      p_main + 
    (p_labs[[2]] + theme(plot.margin = margin(l = 35))) +
      plot_layout(widths = c(.2, .6, .2))
    

    Created on 2023-03-06 with reprex v2.0.2