rdataframeggplot2

Horizontally match levels of two plots using ggplot2 in R


I have a data frame in R called df that contains likert data of 3 questions and and one grouping variable named it var :

var_levels <- c(LETTERS[1:5])
n = 500
likert_levels = c(
  "Very \n Dissatisfied",
  "Dissatisfied",
  "Neutral",
  "Satisfied",
  "Very \n Satisfied"
)

df <- tibble(
  var = sample(var_levels, n, replace = TRUE),  
  val1 = sample(likert_levels, n, replace = TRUE),
  val2 = sample(likert_levels, n, replace = TRUE),
  val3 = sample(likert_levels, n, replace = TRUE)
)

ok. So I have in total 500 responses.But I want to know how many belong to each level which I can obtain this information by count:

> df_n = df%>%
+   select(var)%>%
+   group_by(var)%>%
+   summarise(counts=n())
> df_n
# A tibble: 5 × 2
  var   counts
  <chr>  <int>
1 A         91
2 B         77
3 C        122
4 D        104
5 E        106

(because they are simulated data if you run them it will give you different numbers).

Now I will keep this information because I want to make a bar plot based on these number counts.

Now regarding the Likert data I append or if you like I pivot longer them using the tidyr function that does this:

df2 = df%>%
+   pivot_longer(!var, names_to = "Categories", values_to = "likert_values")%>%
+   select(-Categories)
> df2
# A tibble: 1,500 × 2
   var   likert_values         
   <chr> <chr>                 
 1 A     "Dissatisfied"        
 2 A     "Dissatisfied"        
 3 A     "Dissatisfied"        
 4 E     "Dissatisfied"        
 5 E     "Dissatisfied"        
 6 E     "Dissatisfied"        
 7 A     "Very \n Dissatisfied"
 8 A     "Very \n Dissatisfied"
 9 A     "Neutral"             
10 D     "Dissatisfied"   

What I have done here is that I have append the 3 questions the one after the other in the same column.I am doing that because I want to find the average percentages of these 3 questions keeping the likert scale.

if now I sort them based on the most dissatisfied answers :


dat <- df2 |>
  mutate(
    across(-var, ~ factor(.x, likert_levels))
  ) |>
  pivot_longer(-var, names_to = "group") |>
  count(var, value, group) |>
  complete(var, value, group, fill = list(n = 0)) |>
  mutate(
    prop = n / sum(n),
    prop_lower = sum(prop[value %in% likert_levels[1:2]]),
    prop_higher = sum(prop[value %in% likert_levels[4:5]]),
    .by = c(var, group)
  ) |>
  arrange(group, prop_lower) |>
  mutate(
    y_sort = paste(var, group, sep = "."),
    y_sort = fct_inorder(y_sort)
  )%>%
  select(-n)

and create the totals for both margins (left and right) :

top10 <- dat |>
  distinct(group, var, prop_lower) |>
  slice_max(prop_lower, n = 10, by = group)

dat <- dat |>
  semi_join(top10)




dat_tot <- dat |>
  distinct(group, var, y_sort, prop_lower, prop_higher) |>
  pivot_longer(-c(group, var, y_sort),
               names_to = c(".value", "name"),
               names_sep = "_"
  ) |>
  mutate(
    hjust_tot = ifelse(name == "lower", 1, 0),
    x_tot = ifelse(name == "lower", -0.6, 0.6)
  )


bind the first aggregation of level counts :

dat = dat%>%
  left_join(.,df_n,by="var") 
dat_bar = dat %>%
  select(var,group,counts)%>%
  distinct(var,group,counts)%>%
  mutate(y_sort=paste(var, group, sep = ".") )%>%
  select(-var)

now If I try and plot them :


p1 <- ggplot(dat, aes(y = y_sort, x = prop, fill = value)) +
  geom_col(position = position_likert(reverse = FALSE)) +
  geom_text(
    aes(
      label = label_percent_abs(hide_below = .05, accuracy = 1)(prop),
      color = after_scale(hex_bw(.data$fill))
    ),
    position = position_likert(vjust = 0.5, reverse = FALSE),
    size = 3.5
  ) +
  geom_label(
    aes(
      x = x_tot,
      label = label_percent_abs(accuracy = 1)(prop),
      hjust = hjust_tot,
      fill = NULL
    ),
    data = dat_tot,
    size = 3.5,
    color = "black",
    fontface = "bold",
    label.size = 0,
    show.legend = FALSE
  ) +
  scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
  scale_x_continuous(
    labels = label_percent_abs(),
    expand = c(0, .15)
  ) +
  scale_fill_brewer(palette = "BrBG") +
  facet_wrap(~group,
             scales = "free_y", ncol = 1,
             strip.position = "right"
  ) +
  theme_light() +
  theme(
    legend.position = "bottom",
    panel.grid.major.y = element_blank(),
    strip.text = element_blank()
  ) +
  labs(x = NULL, y = NULL, fill = NULL)

p2 <- ggplot(dat_bar, aes(y = y_sort, x = counts)) +
  geom_col() +
  geom_label(
    aes(
      label = label_number_abs(hide_below = .05, accuracy = 1)(counts)
    ),
    size = 3.5,
    hjust = 1,
    fill = NA,
    label.size = 0,
    color = "white"
  ) +
  scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
  scale_x_continuous(
    labels = label_number_abs(),
    expand = c(0, 0, 0, .05)
  )+
  # facet_wrap(~group,
  #            scales = "free_y", ncol = 1,
  #            strip.position = "right"
  # ) +
  theme_light() +
  theme(
    legend.position = "bottom",
    panel.grid.major.y = element_blank()
  ) +
  labs(x = NULL, y = NULL, fill = NULL)

library(patchwork)

p1 + p2 +
  plot_layout(
    axes = "collect", 
    guides = "collect") &
  theme(legend.position = "bottom")



I receive this :enter image description here

which the levels are not matching horizontally.

Obviously I cannot use the df2 (ie the appended data frame) because I cannot count over there.it will give the wrong number of counts on each category.

I want each level in the likert plot to be matched with the ones in the bar plot horizontally.

How can I do it in R ?


Solution

  • Basically this is the same answer as in your former post except that I drop the group column and compute the counts for dat_bar at the beginning using add_count():

    Note: When using random numbers you can simply use set.seed() for reproducibility.

    library(patchwork)
    library(tidyverse)
    library(ggstats)
    
    set.seed(123)
    
    var_levels <- c(LETTERS[1:5])
    n <- 500
    likert_levels <- c(
      "Very \n Dissatisfied",
      "Dissatisfied",
      "Neutral",
      "Satisfied",
      "Very \n Satisfied"
    )
    
    df <- tibble(
      var = sample(var_levels, n, replace = TRUE),
      val1 = sample(likert_levels, n, replace = TRUE),
      val2 = sample(likert_levels, n, replace = TRUE),
      val3 = sample(likert_levels, n, replace = TRUE)
    )
    
    dat <- df |>
      mutate(
        across(-var, ~ factor(.x, likert_levels))
      ) |>
      add_count(var, name = "counts") |> 
      pivot_longer(-c(var, counts), names_to = "group") |>
      summarise(n = n(), counts = unique(counts), .by = c(var, value)) |>
      complete(var, value, fill = list(n = 0)) |>
      mutate(
        prop = n / sum(n),
        prop_lower = sum(prop[value %in% likert_levels[1:2]]),
        prop_higher = sum(prop[value %in% likert_levels[4:5]]),
        .by = c(var)
      ) |>
      arrange(prop_lower) |>
      mutate(
        var = fct_inorder(var)
      )
    
    ### Only needed if you want the top10
    top10 <- dat |>
      distinct(var, prop_lower) |>
      slice_max(prop_lower, n = 10)
    
    dat <- dat |>
      semi_join(top10)
    #> Joining with `by = join_by(var, prop_lower)`
    ###
    
    dat_tot <- dat |>
      distinct(var, prop_lower, prop_higher) |>
      pivot_longer(-var,
        names_to = c(".value", "name"),
        names_sep = "_"
      ) |>
      mutate(
        hjust_tot = ifelse(name == "lower", 1, 0),
        x_tot = ifelse(name == "lower", -0.6, 0.6)
      )
    
    dat_bar <- dat %>%
      distinct(var, counts)
    
    p1 <- ggplot(dat, aes(y = var, x = prop, fill = value)) +
      geom_col(position = position_likert(reverse = FALSE)) +
      geom_text(
        aes(
          label = label_percent_abs(hide_below = .05, accuracy = 1)(prop),
          color = after_scale(hex_bw(.data$fill))
        ),
        position = position_likert(vjust = 0.5, reverse = FALSE),
        size = 3.5
      ) +
      geom_label(
        aes(
          x = x_tot,
          label = label_percent_abs(accuracy = 1)(prop),
          hjust = hjust_tot,
          fill = NULL
        ),
        data = dat_tot,
        size = 3.5,
        color = "black",
        fontface = "bold",
        label.size = 0,
        show.legend = FALSE
      ) +
      scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
      scale_x_continuous(
        labels = label_percent_abs(),
        expand = c(0, .15)
      ) +
      scale_fill_brewer(palette = "BrBG") +
      theme_light() +
      theme(
        legend.position = "bottom",
        panel.grid.major.y = element_blank(),
        strip.text = element_blank()
      ) +
      labs(x = NULL, y = NULL, fill = NULL)
    
    p2 <- ggplot(dat_bar, aes(y = var, x = counts)) +
      geom_col() +
      geom_label(
        aes(
          label = label_number_abs(hide_below = .05, accuracy = 1)(counts)
        ),
        size = 3.5,
        hjust = 1,
        fill = NA,
        label.size = 0,
        color = "white"
      ) +
      scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
      scale_x_continuous(
        labels = label_number_abs(),
        expand = c(0, 0, 0, .05)
      ) +
      theme_light() +
      theme(
        legend.position = "bottom",
        panel.grid.major.y = element_blank(),
        strip.text = element_blank()
      ) +
      labs(x = NULL, y = NULL, fill = NULL)
    
    p1 + p2 +
      plot_layout(
        guides = "collect"
      ) &
      theme(legend.position = "bottom")