Steven
Steven

Reputation: 3292

Reorder ggplot barplot x-axis by facet_wrap

Let's say I have an example data frame:

frame <- 
    data.frame(group = c(rep(1, 3), rep(2, 3)), 
               idea = c(1, 2, 3, 1, 2, 4), 
               value = c(10000, 5000, 50, 5000, 7500, 100), 
               level = sample(c("rough", "detailed"), 6, TRUE))

I'd like a barplot of values with each idea within a group ordered by it's value. I can get close like this

library(dplyr)
library(ggplot2)

top_ideas <- 
    frame %>%
    group_by(group) %>%
    arrange(group, desc(value))

frame %>%    
    group_by(group) %>%
    mutate(idea = idea %>% factor(levels = top_ideas$idea)) %>%
    ggplot(aes_string(x = "idea", y = "value", fill = "level")) +
    geom_bar(stat = "identity") +
    theme(legend.position = "bottom", 
          axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
    facet_wrap(~group, scales = "free")

The mutate in the final dplyr line is setting the factor levels according to their ordering in the top_ideas dataframe above that. Unfortunately, because the idea nos 1 and 2 are shared by both groups 1 and 2, the ordering is set by the first group.

enter image description here

What I'd like to have is the ordering of ideas in both facets independent of each group. How can I do that in a dplyr string? Am I missing something simple?

I should note that this is example data. The actual data is much larger and encompasses more groups and more ideas that are shared.

Upvotes: 2

Views: 3248

Answers (2)

Gonzalo Rizzo
Gonzalo Rizzo

Reputation: 151

Here is a workaround with data.table package.

Load packages:

library(data.table)
library(ggplot2)

Data:

# setting seed to make solution reproducible
set.seed(123)
frame <- 
  data.frame(group = c(rep(1, 3), rep(2, 3)), 
             idea = c(1, 2, 3, 1, 2, 4), 
             value = c(10000, 5000, 50, 5000, 7500, 100), 
             level = sample(c("rough", "detailed"), 6, TRUE))

Reorder factor levels by group:

top_ideas <- as.data.table(frame)
top_ideas[, grp_idea:= paste0(group, '_', idea)]
top_ideas[, grp_idea:= factor(grp_idea, levels = grp_idea[order(factor(group), -value)])]

Output data:

   > top_ideas
       group idea value    level grp_idea
    1:     1    1 10000    rough      1_1
    2:     1    2  5000    rough      1_2
    3:     1    3    50    rough      1_3
    4:     2    1  5000 detailed      2_1
    5:     2    2  7500    rough      2_2
    6:     2    4   100 detailed      2_4

Plot:

top_ideas |>
  ggplot(aes(x = grp_idea, y = value, fill = level)) +
  geom_bar(stat = "identity") +
  theme(legend.position = "bottom", 
        axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
  facet_wrap(~group, scales = "free") +
  xlab("idea") +
  scale_x_discrete(breaks = top_ideas$grp_idea,
                   labels = top_ideas$idea)

The plot

Note: Although using the same set.seed as the one in the accepted answer, it produced a different order of the level variable than that shown in the accepted answer.

Upvotes: 0

acylam
acylam

Reputation: 18681

Here is a workaround:

Data:

# setting seed to make solution reproducible
set.seed(123)
frame <- 
  data.frame(group = c(rep(1, 3), rep(2, 3)), 
             idea = c(1, 2, 3, 1, 2, 4), 
             value = c(10000, 5000, 50, 5000, 7500, 100), 
             level = sample(c("rough", "detailed"), 6, TRUE))

Code:

library(dplyr)
library(tidyr)
library(ggplot2)

top_ideas <- 
  frame %>%
  group_by(group) %>%
  arrange(group, desc(value)) %>%
  unite("grp_idea", group, idea, sep = "_", remove = FALSE) %>%
  data.frame() %>%
  mutate(grp_idea = factor(grp_idea, levels = grp_idea))

top_ideas %>%    
  group_by(group) %>%
  ggplot(aes(x = grp_idea, y = value, fill = level)) +
  geom_bar(stat = "identity") +
  theme(legend.position = "bottom", 
        axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
  facet_wrap(~group, scales = "free") +
  xlab("idea") +
  scale_x_discrete(breaks = top_ideas$grp_idea,
                   labels = top_ideas$idea)

Results:

> top_ideas
  grp_idea group idea value    level
1      1_1     1    1 10000    rough
2      1_2     1    2  5000 detailed
3      1_3     1    3    50    rough
4      2_2     2    2  7500 detailed
5      2_1     2    1  5000 detailed
6      2_4     2    4   100    rough

enter image description here

Note:

Basically what I'm doing is to paste together group and idea variables, convert the new variable grp_idea to a factor with the desired levels, and use that as the x-axis instead of the original idea column. This ensures that the ordering of levels in each facet will not be affected by other facets since they no longer share the same levels. It is then easy enough to relabel the x-axis title and tick labels with xlab and scale_x_discrete.

Upvotes: 7

Related Questions