otteheng
otteheng

Reputation: 604

Bar graph: Combine one stacked bar with one dodged bar

I'm trying to recreate a bar graph found on page 4 of the following report:

enter image description here

The figure has three bars with the first two stacked and the third dodged next to it. I've seen iterations of this question but none that recreate the figure in this exact way.

Here is the data:

a <- rep(c('RHB', 'FERS', 'CSRS'), 3)
b <- c(rep('Assets', 3), rep('Amount Past Due', 3), 
       rep('Actuarial Liability', 3))
c <- c(45.0, 122.5, 152.3, 47.2, 3.4, 4.8, 114.4, 143.4, 181.3)

df <- data.frame(a,b,c)
names(df) <- c('Fund', 'Condition', 'Value')

And what I've managed so far:

p <- ggplot(subset_data, aes(fill=Condition, y=Value, x=Fund)) + 
  geom_bar(position="stack", stat="identity") + 
  coord_flip() 

I'm not partial to ggplot so if there's another tool that works better I'm ok using another package.

Upvotes: 2

Views: 458

Answers (2)

nniloc
nniloc

Reputation: 4243

Taking some ideas from the link @aosmith posted.

You can call geom_bar twice, once with Assets and Amounts Past Due stacked, and again with just Actuarial Liability.

You can use width to make the bars thinner, then nudge one set of bars so the two geom_bar calls are not overlapping. I chose to make the width 0.3 and nudge by 0.3 so the edges just line up. If you nudge by more you will see a gap between the two bars.

Edit: add some more formatting and numeric labels

library(tidyverse)
library(scales)

df_al  <- filter(df, Condition == 'Actuarial Liability')
df_xal <- filter(df, Condition != 'Actuarial Liability')

bar_width <- 0.3
hjust_lab <- 1.1
hjust_lab_small <- -0.2 # hjust for labels on small bars

ggplot() + 
  theme_classic() +
  geom_bar(data = df_al, 
           aes(fill=Condition, y=Value, x=Fund),
           position = position_nudge(x = -bar_width),
           width = bar_width,
           stat="identity") +
  geom_bar(data = df_xal, 
           aes(fill=Condition, y=Value, x=Fund),
           position="stack", 
           stat="identity",
           width = bar_width) +
  geom_text(data = df_al, 
            aes(label= dollar(Value, drop0trailing = TRUE), y=Value, x=Fund),
            position = position_nudge(x = -bar_width),
            hjust = hjust_lab) +
  geom_text(data = df_xal, 
            aes(label= dollar(Value, drop0trailing = TRUE), y=Value, x=Fund),
            position="stack",
            hjust = ifelse(df_xal$Value < 5, hjust_lab_small,  hjust_lab)) +
  scale_fill_manual(values = c('firebrick3', 'lightsalmon', 'dodgerblue')) +
  scale_y_continuous(breaks = seq(0,180, by = 20), labels = dollar) +
  coord_flip() +
  labs(x = NULL, y = NULL, fill = NULL) +
  theme(legend.position = "bottom")

enter image description here

Upvotes: 6

Allan Cameron
Allan Cameron

Reputation: 173858

I think I would use the "sneaky facet" method, after adding a dummy variable to dodge the columns and making Fund a factor with the correct order:

df$not_liability <-df$Condition != "Actuarial Liability"
df$Fund <- factor(df$Fund, levels = c('RHB', 'FERS', 'CSRS'))

Most of the plotting code is then an attempt to copy the look of the supplied plot:

ggplot(df, aes(fill=Condition, y=Value, x=not_liability)) + 
  geom_bar(position = "stack", stat = "identity") + 
  scale_x_discrete(expand = c(0.5, 0.5)) +
  scale_y_continuous(breaks = 0:10 * 20, labels = scales::dollar) +
  coord_flip() +
  facet_grid(Fund~., switch = "y") +
  scale_fill_manual(values = c("#c00000", "#f7c290", "#0071bf"), name = "") +
  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())

enter image description here

Upvotes: 4

Related Questions