Search code examples
rggplot2likert

Likert plot with ggplot2 in R with different ranking levels and display percentages


i have a data frame in R called df that looks like this :

  df
# A tibble: 90 × 3
# Groups:   item [18]
   item  Response          Percentage
   <chr> <fct>                  <dbl>
 1 A     Very Dissatisfied        0  
 2 A     Dissatisfied             0  
 3 A     Average                 33.3
 4 A     Satisfied               11.1
 5 A     Very Satisfied          55.6
 6 B     Very Dissatisfied        0  
 7 B     Dissatisfied             0  
 8 B     Average                 44.4
 9 B     Satisfied                0  
10 B     Very Satisfied          55.6
# ℹ 80 more rows
# ℹ Use `print(n = ...)` to see more rows

the response column is Likert scale column with 5 levels. Plotting it with ggplot2



# Creating a mapping of responses to numeric values
response_mapping <- c("Very Dissatisfied" = 1,
                      "Dissatisfied" = 2,
                      "Average" = 3,
                      "Satisfied" = 4,
                      "Very Satisfied" = 5)

# Applying the mapping and calculate the sign
data_f_sum <- df %>% 
  ungroup() %>% 
  mutate(res.sgn = sign(response_mapping[as.character(Response)] - 3)) %>% 
  summarise(sum.prcnt = sum(Percentage),
            .by = c(item, res.sgn))
likert_levels =  c("Very Dissatisfied", 
                   "Dissatisfied" ,
                   "Average" ,
                   "Satisfied", 
                   "Very Satisfied")

df = df%>%
  mutate(Response = factor(Response , levels = likert_levels))

ggplot(data = df, 
       aes(Percentage, item, fill = Response)) +
  geom_col(position = position_likert()) +
  scale_x_continuous(breaks = seq(-1, 1, 0.5),
                     labels = ggstats::label_percent_abs()) +
  geom_label(data = data_f_sum,
             aes(label = sprintf("%.1f", sum.prcnt), y = item, x = res.sgn * 0.5),
             alpha = 0.3, inherit.aes = FALSE) +
  scale_fill_brewer(type = "div", palette = "RdYlGn") +
  theme_bw()

i receive the following plot (picture)

enter image description here

i want to solve some issues that i have here and i need some help:

  1. I want to fix the average level in the middle and horizontically to start from 0%.

  2. i want in the middle of each item in the average level to display the percentage of the average level. at the left of each item to display the percentage sum of the two lower levels (i.e very dissatisfied and satisfied) and at the right to display the percentage sum of the two upper levels (i.e satisfied and very satisfied) .Something like this :

enter image description here

Can someone help me solve these issues ?

data are here :

 structure(list(item = c("A", "A", "A", "A", "A", "B", "B", "B", 
"B", "B", "C", "C", "C", "C", "C", "D", "D", "D", "D", "D", "E", 
"E", "E", "E", "E", "F", "F", "F", "F", "F", "G", "G", "G", "G", 
"G", "H", "H", "H", "H", "H", "I", "I", "I", "I", "I", "J", "J", 
"J", "J", "J", "K", "K", "K", "K", "K", "L", "L", "L", "L", "L", 
"M", "M", "M", "M", "M", "N", "N", "N", "N", "N", "O", "O", "O", 
"O", "O", "P", "P", "P", "P", "P", "Q", "Q", "Q", "Q", "Q", "R", 
"R", "R", "R", "R"), Response = structure(c(4L, 2L, 1L, 3L, 5L, 
4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 
2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 
1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 
3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 
5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 
4L, 2L, 1L, 3L, 5L), levels = c("Average", "Dissatisfied", "Satisfied", 
"Very Dissatisfied", "Very Satisfied"), class = "factor"), Percentage = c(0, 
0, 33.3, 11.1, 55.6, 0, 0, 44.4, 0, 55.6, 0, 0, 22.2, 33.3, 44.4, 
0, 0, 33.3, 11.1, 55.6, 0, 22.2, 11.1, 11.1, 55.6, 0, 0, 44.4, 
11.1, 44.4, 0, 0, 11.1, 33.3, 55.6, 0, 0, 33.3, 22.2, 44.4, 0, 
0, 11.1, 33.3, 55.6, 0, 0, 22.2, 22.2, 55.6, 0, 0, 11.1, 11.1, 
77.8, 0, 0, 11.1, 33.3, 55.6, 0, 0, 33.3, 0, 66.7, 0, 0, 33.3, 
11.1, 55.6, 0, 11.1, 0, 33.3, 55.6, 0, 0, 22.2, 22.2, 55.6, 0, 
22.2, 22.2, 11.1, 44.4, 0, 11.1, 22.2, 11.1, 55.6)), class = c("grouped_df", 
"tbl_df", "tbl", "data.frame"), row.names = c(NA, -90L), groups = structure(list(
    item = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", 
    "K", "L", "M", "N", "O", "P", "Q", "R"), .rows = structure(list(
        1:5, 6:10, 11:15, 16:20, 21:25, 26:30, 31:35, 36:40, 
        41:45, 46:50, 51:55, 56:60, 61:65, 66:70, 71:75, 76:80, 
        81:85, 86:90), ptype = integer(0), class = c("vctrs_list_of", 
    "vctrs_vctr", "list"))), class = c("tbl_df", "tbl", "data.frame"
), row.names = c(NA, -18L), .drop = TRUE))


Solution

  • You can center the likert scale by hard-setting limits for the x axis, using coord_cartesian, so that your plot actually goes from -100% to 100% instead of adjusting to the data ranges, and you can place the labels on the edges of the plot just by using -1 or 1 as your x value:

    library(ggplot2)
    library(ggstats)
    
    ggplot(data = df, 
           aes(Percentage, item, fill = Response)) +
      geom_col(position = position_likert()) +
      scale_x_continuous(#breaks = seq(-1, 1, 0.5),
                          
                         labels = ggstats::label_percent_abs()) +
      geom_label(data = data_f_sum,
                 aes(label = sprintf("%.1f", sum.prcnt), y = item, x = res.sgn),
                 alpha = 0.3, inherit.aes = FALSE) +
      coord_cartesian(xlim = c(-1, 1)) +
      scale_fill_brewer(type = "div", palette = "RdYlGn") +
      theme_bw()
    

    enter image description here