0

I'm currently working on reporting Likert item response analyses by using diverging stacked bar charts. Though I have found references to these type of charts, there is little code guidance on how to achieve one particular result using ggplot2: Holding the neutral values aside.

This has been shown possible using the Likert package: As answered here before. This package however sets certain limitations on the customisation you are able to get.

Thus far I can make the whole chart using every response category (or I can remove the neutral one) but I cant find a way to hold it aside (as if it was its own bar chart next to the other one but in the same plot).

This is my code currently:

set.seed(1)
dftest <-  data.frame(it1 = sample(1:5, 300, replace = T),
                  it2 = sample(1:5, 300, replace = T),
                  it3 = sample(1:5, 300, replace = T),
                  it4 = sample(1:5, 300, replace = T),
                  it5 = sample(1:5, 300, replace = T))
dftest <- sapply(dftest,function(x){prop.table(table(factor(x, levels=1:5)))}) %>%
  as.data.frame %>% mutate(code = c(1:5)) %>% pivot_longer(cols = names(dftest)[1:5]) %>%
  mutate(code = factor(code,levels=c("5","4","3", "2", "1")))

dftest %>% 
  ggplot(aes(x=name,y=value,fill=code, label = ifelse(abs(value)>.05, 
                                                      paste0(sprintf('%.1f',round(abs(value)*100,1))),NA)))+
  geom_bar(position="stack", stat="identity")+
  coord_flip(clip="off")+
  scale_fill_manual(values=c("#26cc00","#7be382","#d2f2d4","#26cc00","#7be382"), # Colores
                    labels = c("Ni de acuerdo, ni en desacuerdo",
                               "Totalmente de acuerdo","De acuerdo",
                               "En desacuerdo", "Totalmente en desacuerdo"),
                    guide = guide_legend(reverse = TRUE)) +
  geom_text(size = 2.5, position = position_stack(vjust = 0.5)) +
  theme_bw()+ylab("")+xlab("") + 
  theme(legend.position="top", legend.title = element_blank(),
        legend.margin=margin(5,0,5,0), legend.box.margin=margin(0,0,-10,0),
        legend.justification="right") +
  scale_y_continuous(breaks = c(-1,-0.8,-0.6,-0.4,-0.2,0,0.2,0.4,0.6,0.8,1),
                     labels=c("100%","80%","60%","40%","20%","0%","20%","40%","60%","80%","100%"),
                     expand = c(0.01, 0.01)) +
  theme(plot.margin = margin(10,10,10,10))

And it provides this:

Current chart

And I want to get to:

Goal type of chart

Thanks in advance!

1 Answers1

1

One approach to achieve your desired result would be via the patchwork package, i.e. make two separate plots and glue them together:

To reduce code duplication I make use of a custom function which basically is your plotting code, but which I simplified a bit.

library(ggplot2)
library(patchwork)
library(dplyr)

cols <- c("#26cc00", "#7be382", "#d2f2d4", "#26cc00", "#7be382")
names(cols) <- c(5, 4, 3, 1, 2)
labels <- c(
  "Ni de acuerdo, ni en desacuerdo",
  "Totalmente de acuerdo", "De acuerdo",
  "En desacuerdo", "Totalmente en desacuerdo"
)
names(labels) <- c(3, 5, 4, 2, 1)

plot_fun <- function(.data, ylim = c(-1, 1)) {
  ggplot(
    .data,
    aes(
      x = ifelse(code %in% c(5, 4), -value, value), y = name,
      fill = code, label = ifelse(abs(value) > .05, scales::percent(abs(value), accuracy = .1), NA)
    )
  ) +
    geom_col() +
    geom_text(size = 2.5, position = position_stack(vjust = 0.5)) +
    scale_x_continuous(
      breaks = seq(-1, 1, .2),
      labels = ~ scales::percent(abs(.x)),
      expand = c(0.01, 0),
      limits = ylim
    ) +
    scale_fill_manual(
      values = cols,
      labels = labels,
      limits = as.character(rev(1:5))
    ) +
    coord_cartesian(clip = "off") +
    theme_bw() +
    labs(x = NULL, y = NULL)
}

# Recode `code` to get the stacked bars in the right order

dftest$code <- factor(dftest$code, levels = c("5", "4", "3", "1", "2"))

df_neutral <- filter(dftest, code == 3)
df_nonneutral <- filter(dftest, !code == 3)

p1 <- plot_fun(df_nonneutral, ylim = c(-.5, .5)) + geom_vline(xintercept = 0)
p2 <- plot_fun(df_neutral, ylim = c(0, .25))

p1 + p2 +
  plot_layout(widths = c(4, 1), guides = "collect") &
  theme(
    legend.position = "top", legend.title = element_blank(),
    legend.margin = margin(5, 0, 5, 0), legend.box.margin = margin(0, 0, -10, 0),
    legend.justification = "right",
    plot.margin = margin(10, 10, 10, 10)
  )

stefan
  • 90,330
  • 6
  • 25
  • 51