Joris
Joris

Reputation: 417

Reorder factor levels within group

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

Answers (4)

Andrew Gustar
Andrew Gustar

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

akrun
akrun

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

Rui Barradas
Rui Barradas

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

zx8754
zx8754

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

Related Questions