Stacked barplot with colors depending on numbers and length of a vector

Viewed 47

I have a dataframe that looks like this

df <- data.frame("V1" = c(0,0,0,0,0,2,2,2,2,2),
                 "V2" = c(9,9,9,0,0,2,2,2,0,0))

I would like to create a stacked barplot in this fashion: enter image description here

So basically I map a certain value to a corresponding color (9 -> white (invisible), 0 -> green 2 -> red), and then create a bar of a length that is equal to the number of appearances of the value in a dataframe column. Is there any way to do something like this, i.e. using ggplot2?

3 Answers

You could change your dataframe to a long format using pivot_longer. Add a column with the colors you want to show the bars using case_when for example. You could use scale_fill_identity to fill the bars to the corresponding color. To get the same order fill in your stacked bars as your dataframe, you could use ggplot_build to modify the data of your plot by replace the column of "fill" with a vector of the same order as your dataframe. You can use the following code:

library(ggplot2)
library(tidyr)
library(dplyr)
library(scales)
# Create plot
p <- df %>%
  mutate(index = 1) %>%
  pivot_longer(cols = -index) %>%
  mutate(color = case_when(value == 9 ~ 'white',
                           value == 0 ~ 'darkgreen',
                           TRUE ~ 'red')) %>%
  ggplot(aes(x = index, y = name, fill = color)) +
  geom_col(width = 0.3) +
  scale_fill_identity() +
  theme_classic() +
  scale_x_continuous(expand = c(0,0), breaks = pretty_breaks(2)) +
  labs(x = '', y = '')

# Extract vector of colors in right order
vec_colors <- df %>%
  mutate(index = 1) %>%
  pivot_longer(cols = -index) %>%
  mutate(color = case_when(value == 9 ~ 'white',
                           value == 0 ~ 'darkgreen',
                           TRUE ~ 'red')) %>%
  arrange(name) %>%
  pull(color)

# right order colors
vec_colors
#>  [1] "darkgreen" "darkgreen" "darkgreen" "darkgreen" "darkgreen" "red"      
#>  [7] "red"       "red"       "red"       "red"       "white"     "white"    
#> [13] "white"     "darkgreen" "darkgreen" "red"       "red"       "red"      
#> [19] "darkgreen" "darkgreen"

# use ggplot_build to modify data and replace fill column with vector
q <- ggplot_build(p)
q$data[[1]] <- q$data[[1]] %>%
  group_by(y) %>%
  arrange(x, .by_group = TRUE) 

q$data[[1]]$fill <- vec_colors

q <- ggplot_gtable(q)

# plot 
plot(q)

Created on 2022-09-17 with reprex v2.0.2


To add the legend, you should specify guide = 'legend' in scale_fill_identity. To determine which elements you want to show in the legend, you should specify the labels and breaks like this:

library(ggplot2)
library(tidyr)
library(dplyr)
library(scales)
# Create plot
p <- df %>%
  mutate(index = 1) %>%
  pivot_longer(cols = -index) %>%
  mutate(color = case_when(value == 9 ~ 'white',
                           value == 0 ~ 'darkgreen',
                           TRUE ~ 'red')) %>%
  ggplot(aes(x = index, y = name, fill = color)) +
  geom_col(width = 0.3) +
  scale_fill_identity(guide = 'legend',
                      labels = c('darkgreen', 'red'),
                      breaks = c('darkgreen', 'red')) +
  theme_classic() +
  scale_x_continuous(expand = c(0,0), breaks = pretty_breaks(2)) +
  labs(x = '', y = '')

# Extract vector of colors in right order
vec_colors <- df %>%
  mutate(index = 1) %>%
  pivot_longer(cols = -index) %>%
  mutate(color = case_when(value == 9 ~ 'white',
                           value == 0 ~ 'darkgreen',
                           TRUE ~ 'red')) %>%
  arrange(name) %>%
  pull(color)

# use ggplot_build to modify data and replace fill column with vector
q <- ggplot_build(p)
q$data[[1]] <- q$data[[1]] %>%
  group_by(y) %>%
  arrange(x, .by_group = TRUE) 

q$data[[1]]$fill <- vec_colors

q <- ggplot_gtable(q)

# plot 
plot(q)

Created on 2022-09-24 with reprex v2.0.2

Here is a way. The complicated part is to get different values from the data given the groups they belong to.

df <- data.frame("V1" = c(0,0,0,0,0,2,2,2,2,2),
                 "V2" = c(9,9,9,0,0,2,2,2,0,0))

suppressPackageStartupMessages({
  library(dplyr)
  library(tidyr)
  library(ggplot2)
})

df %>%
  group_by(V1) %>%
  mutate(V1 = cur_group_id()) %>%
  group_by(V1, V2) %>%
  mutate(V2 = cur_group_id()) %>%
  pivot_longer(everything()) %>%
  mutate(value = factor(value)) %>% 
  ggplot(aes(name, fill = value)) +
  geom_bar(width = 0.75) +
  coord_flip() +
  scale_x_discrete(limits = rev) +
  scale_fill_manual(
    values = c(`1` = "darkolivegreen", `2` = "red", 
               `3` = "darkolivegreen", `4` = "white")
  ) +
  theme_classic()

Created on 2022-09-17 with reprex v2.0.2

Using rle we may account for duplicated values in columns V2, then use a factor with specific level= order. Then just barplot the column tables.

cvec <- c(0, 'darkgreen', 'red', 'darkgreen')
par(lty=0)  ## to omit borders
lapply(rev(df), \(x) factor(with(rle(x), rep.int(seq_along(values), lengths)), levels=c(3:1, 4))) |>
   sapply(table) |> barplot(width=.5, space=1, col=cvec, horiz=TRUE, ylim=c(-1, 3))

enter image description here


Data:

df <- structure(list(V1 = c(0, 0, 0, 0, 0, 2, 2, 2, 2, 2), V2 = c(9, 
9, 9, 0, 0, 2, 2, 2, 0, 0)), class = "data.frame", row.names = c(NA, 
-10L))
Related