Reputation: 417
I would like to reorder the levels of a factor in one column, but within groups defined by a grouping column.
Simple example data set:
df <- structure(list(a_factor = structure(1:6, .Label = c("a", "b",
"c", "d", "e", "f"), class = "factor"), group = structure(c(1L,
1L, 1L, 2L, 2L, 2L), .Label = c("group1", "group2"), class = "factor"),
value = 1:6), class = "data.frame", row.names = c(NA, -6L
))
> df
a_factor group value
1 a group1 1
2 b group1 2
3 c group1 3
4 d group2 4
5 e group2 5
6 f group2 6
More precisely, how do I reorder the factor levels, e.g. descending by value
where df$group == "group1"
, but ascending by value
where df$group == "group2"
, preferably in dplyr?
An expected output might be:
> df
a_factor group value
1 c group1 3
2 b group1 2
3 a group1 1
4 d group2 4
5 e group2 5
6 f group2 6
Although, the question is more generally about how to tackle this in dplyr.
Upvotes: 5
Views: 5643
Reputation: 18445
To reorder the factor levels you can use forcats
(part of the tidyverse
), and do something like this...
library(forcats)
df2 <- df %>% mutate(a_factor = fct_reorder(a_factor,
value*(-1 + 2 * (group=="group1"))))
levels(df2$a_factor)
[1] "f" "e" "d" "a" "b" "c"
This does not rearrange the dataframe itself...
df2
a_factor group value
1 a group1 1
2 b group1 2
3 c group1 3
4 d group2 4
5 e group2 5
6 f group2 6
Upvotes: 1
Reputation: 887981
An option is to do a group_split
and pass a list
of logical values corresponding to how the arrange
needs to be executed
library(tidyverse)
df %>%
group_split(group) %>%
map2_df(., list(FALSE, TRUE), ~ if(.y) .x %>%
arrange(value) else .x %>% arrange(desc(value)))
# A tibble: 6 x 3
# a_factor group value
# <fct> <fct> <int>
#1 c group1 3
#2 b group1 2
#3 a group1 1
#4 d group2 4
#5 e group2 5
#6 f group2 6
Upvotes: 1
Reputation: 76683
The following is a base R solution.
sp <- split(df$value, df$group)
sp <- lapply(seq_along(sp), function(i) sort(sp[[i]], decreasing = i == 1))
df$a_factor <- factor(df$a_factor, levels = df$a_factor[unlist(sp)])
df$a_factor
#[1] a b c d e f
#Levels: c b a d e f
df[order(df$a_factor), ]
# a_factor group value
#3 c group1 3
#2 b group1 2
#1 a group1 1
#4 d group2 4
#5 e group2 5
#6 f group2 6
Upvotes: 2
Reputation: 56259
We can negate based on group value then order:
df %>%
arrange(case_when(
group == "group1" ~ -value,
group == "group2" ~ value))
# a_factor group value
# 1 c group1 3
# 2 b group1 2
# 3 a group1 1
# 4 d group2 4
# 5 e group2 5
# 6 f group2 6
Upvotes: 2