Horizontal bar plot with both stack and cluster bars in R

Viewed 178

I am trying to create a horizontal bar plot where for each variable I have two bars. the first should be a stack bar with the count of positive and negative values, the second only the neutral values. So far I didn't manage to do that. Here is an example of the data:

df <- read.table(text = "Positive Negative Neutral
A 4 5 1
B 6 8 3
C 12 3 6
D 10 5 2
E 2 11 7", header = TRUE)

And here the plot I aiming to do (made with Excel):

enter image description here

Edit:

Thanks everyone for the help! I got great feedbacks already. I am planning to continue with the option provided by @Duck as I feel that with ggplot2 it's more flexible. However, I figured out I would like to add (and learn) one more things:

  • Reorder automatically the y axis values (id (A, B, C, D, E)) based on the number of positive values (I edited the plot above). For instance in this case would be C, D, B,A, E):

I tried with reorder() and using factors() but unsuccessfully. Should it be applied on the data or directly on ggplot? The solution I found so far do not have this case of double level of variable (here is the name of variable (id) and the flag (TRUE = positive/negative, FALSE = neutral).

2 Answers

I might be the only person on the planet using base R plotting these days, but it is actually pretty flexible for non-standard plots like this.

xl <- c(0,20)
cols <- c("blue","orange","grey")

bd <- t(as.matrix(df))
bp <- barplot(unname(bd[1:2, rbind(1:5,NA,NA)]), xlim=xl, horiz=TRUE, col=cols[1:2])
      barplot(unname(bd[3,   rbind(NA,1:5,NA)]), xlim=xl, horiz=TRUE, col=cols[3],
              axes=FALSE, add=TRUE)
axis(2, at=colMeans(matrix(bp, nrow=3)[1:2,]), labels=colnames(bd), las=1, lty=0)
par(xpd=NA)
legend("bottom", rownames(bd), fill=cols, horiz=TRUE, inset=-1 / par("pin")[2], bty="n")

Result:

enter image description here

Try with facets like this and smartly create a reference variable to create two variables and plot them using ggplot2 and some tidyverse functions:

library(ggplot2)
library(tidyverse)
#Data
df %>% rownames_to_column("id") %>%
  pivot_longer(-id) %>%
  mutate(Flag=name %in% c('Positive','Negative')) %>%
  ggplot(aes(x=Flag,y=value,fill=name))+
  geom_bar(stat = 'identity')+
  facet_grid(id~., switch = "y") +
  scale_fill_manual(values = c("red", "tomato", "cyan3"), name = "") +
  coord_flip() +
  theme_classic() +
  theme(panel.spacing = unit(0, "points"),
        strip.background = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.length.y = unit(0, "points"),
        axis.title = element_blank(),
        strip.placement = "outside",
        strip.text = element_text(),
        legend.position = "bottom",
        panel.grid.major.x = element_line())

Output:

enter image description here

Update: In order to have the desired order, you could create a dummy data to order the labels and then format the id variable as factor. Here the code:

#Auxiliar data
levs <- df %>% rownames_to_column("id") %>%
  pivot_longer(-id) %>%
  filter(name=='Positive') %>%
  arrange(desc(value))
#Data
df %>% rownames_to_column("id") %>%
  pivot_longer(-id) %>%
  mutate(Flag=name %in% c('Positive','Negative'),
         id=factor(id,levels = levs$id,ordered = T)) %>%
  ggplot(aes(x=Flag,y=value,fill=name))+
  geom_bar(stat = 'identity')+
  facet_grid(id~., switch = "y") +
  scale_fill_manual(values = c("red", "tomato", "cyan3"), name = "") +
  coord_flip() +
  theme_classic() +
  theme(panel.spacing = unit(0, "points"),
        strip.background = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.length.y = unit(0, "points"),
        axis.title = element_blank(),
        strip.placement = "outside",
        strip.text = element_text(),
        legend.position = "bottom",
        panel.grid.major.x = element_line())

Output:

enter image description here

Related