Factor ordering with forcats

Viewed 102

I have data that I am binning and converting to a factor. I'm having some trouble understanding what's going on with my factor variable though. I am trying to order the factor variable based on a continuous variable.

I've read up on it but all the examples I've seen include just one instance of each factor level whereas my example includes more than one instance of some factor levels.

Here's the sample data:

df <- structure(list(Group = c("Grp1", "Grp1", "Grp1", "Grp1", "Grp1", 
"Grp1", "Grp1", "Grp2", "Grp2", "Grp2", "Grp2", "Grp2"), Ind = c("A", 
"B", "C", "D", "E", "F", "G", "A", "B", "C", "D", "E"), Value = c(0.155903329567489, 
0.0582906870761889, 0.180600101489814, 0.26357423622443, 0.0637832368895064, 
0.213803701918138, 0.0640447068344333, 0.333501508730367, 0.160676738803951, 
0.279178514111584, 0.145767023637501, 0.0808762147165962)), row.names = c(NA, 
-12L), class = c("tbl_df", "tbl", "data.frame"))

From these data, I created a factor and checked the ordering for each element.

library(dplyr)
library(forcats)
df %>% 
  group_by(Group) %>% 
  mutate(Bin = cut_interval(Value, n = nrow(.))) %>% 
  mutate(Order = labels(Bin)) %>% 
  ungroup()

# A tibble: 12 x 5
   Group Ind    Value Bin             Order
   <chr> <chr>  <dbl> <fct>           <chr>
 1 Grp1  A     0.156  (0.144,0.161]   1    
 2 Grp1  B     0.0583 [0.0583,0.0754] 2    
 3 Grp1  C     0.181  (0.178,0.195]   3    
 4 Grp1  D     0.264  (0.246,0.264]   4    
 5 Grp1  E     0.0638 [0.0583,0.0754] 5    
 6 Grp1  F     0.214  (0.212,0.229]   6    
 7 Grp1  G     0.0640 [0.0583,0.0754] 7    
 8 Grp2  A     0.334  (0.312,0.334]   1    
 9 Grp2  B     0.161  (0.144,0.165]   2    
10 Grp2  C     0.279  (0.27,0.291]    3    
11 Grp2  D     0.146  (0.144,0.165]   4    
12 Grp2  E     0.0809 [0.0809,0.102]  5

Then tried to reorder the factor based on "Value" after creating the it but the order didn't appear to change.

df %>% 
  group_by(Group) %>% 
  mutate(Bin = cut_interval(Value, n = nrow(.)), 
         Bin = fct_reorder(Bin, Value)) %>% 
  mutate(Order = labels(Bin)) %>% 
  ungroup()

# A tibble: 12 x 5
   Group Ind    Value Bin             Order
   <chr> <chr>  <dbl> <fct>           <chr>
 1 Grp1  A     0.156  (0.144,0.161]   1    
 2 Grp1  B     0.0583 [0.0583,0.0754] 2    
 3 Grp1  C     0.181  (0.178,0.195]   3    
 4 Grp1  D     0.264  (0.246,0.264]   4    
 5 Grp1  E     0.0638 [0.0583,0.0754] 5    
 6 Grp1  F     0.214  (0.212,0.229]   6    
 7 Grp1  G     0.0640 [0.0583,0.0754] 7    
 8 Grp2  A     0.334  (0.312,0.334]   1    
 9 Grp2  B     0.161  (0.144,0.165]   2    
10 Grp2  C     0.279  (0.27,0.291]    3    
11 Grp2  D     0.146  (0.144,0.165]   4    
12 Grp2  E     0.0809 [0.0809,0.102]  5 

Then I arranged the data on "Value" before creating the factor and got the order right.

df %>% 
  arrange(Group, Value) %>% 
  group_by(Group) %>% 
  mutate(Bin = cut_interval(Value, n = nrow(.))) %>% 
  mutate(Order = labels(Bin)) %>% 
  ungroup()

# A tibble: 12 x 5
   Group Ind    Value Bin             Order
   <chr> <chr>  <dbl> <fct>           <chr>
 1 Grp1  B     0.0583 [0.0583,0.0754] 1    
 2 Grp1  E     0.0638 [0.0583,0.0754] 2    
 3 Grp1  G     0.0640 [0.0583,0.0754] 3    
 4 Grp1  A     0.156  (0.144,0.161]   4    
 5 Grp1  C     0.181  (0.178,0.195]   5    
 6 Grp1  F     0.214  (0.212,0.229]   6    
 7 Grp1  D     0.264  (0.246,0.264]   7    
 8 Grp2  E     0.0809 [0.0809,0.102]  1    
 9 Grp2  D     0.146  (0.144,0.165]   2    
10 Grp2  B     0.161  (0.144,0.165]   3    
11 Grp2  C     0.279  (0.27,0.291]    4    
12 Grp2  A     0.334  (0.312,0.334]   5

So first, why did fct_reorder not do what I wanted? And second, why are there 7 values in "Grp1" and 5 in "Grp2"? Shouldn't there be just 5 and 4, respectively, because of the repeated "Bin" values in each group?

1 Answers

It is the levels that are ordered. According to ?fct_reorder

.x, .y - The levels of f are reordered so that the values of .fun(.x) (for fct_reorder()) and fun(.x, .y) (for fct_reorder2()) are in ascending order.

After arrangeing the Bin, create the 'Order' by converting to integer after dropping the unused levels (droplevels)

library(dplyr)
library(forcats)
out <- df %>% 
  group_by(Group) %>% 
  mutate(Bin = cut_interval(Value, n = nrow(.)), 
         Bin = fct_reorder(Bin, Value)) %>% 
  arrange(as.integer(Bin)) %>%
  mutate(Order = as.integer(droplevels(Bin))) %>%
  ungroup
out
# A tibble: 12 x 5
   Group Ind    Value Bin             Order
   <chr> <chr>  <dbl> <fct>           <int>
 1 Grp1  B     0.0583 [0.0583,0.0754]     1
 2 Grp1  E     0.0638 [0.0583,0.0754]     1
 3 Grp1  G     0.0640 [0.0583,0.0754]     1
 4 Grp1  A     0.156  (0.144,0.161]       2
 5 Grp1  C     0.181  (0.178,0.195]       3
 6 Grp1  F     0.214  (0.212,0.229]       4
 7 Grp1  D     0.264  (0.246,0.264]       5
 8 Grp2  E     0.0809 [0.0809,0.102]      1
 9 Grp2  B     0.161  (0.144,0.165]       2
10 Grp2  D     0.146  (0.144,0.165]       2
11 Grp2  C     0.279  (0.27,0.291]        3
12 Grp2  A     0.334  (0.312,0.334]       4

Or use match with unique

 df %>% 
  group_by(Group) %>% 
  mutate(Bin = cut_interval(Value, n = nrow(.)), 
         Bin = fct_reorder(Bin, Value)) %>% 
  arrange(as.integer(Bin))  %>% mutate(Order = match(Bin, unique(Bin))) %>%
  ungroup
# A tibble: 12 x 5
   Group Ind    Value Bin             Order
   <chr> <chr>  <dbl> <fct>           <int>
 1 Grp1  B     0.0583 [0.0583,0.0754]     1
 2 Grp1  E     0.0638 [0.0583,0.0754]     1
 3 Grp1  G     0.0640 [0.0583,0.0754]     1
 4 Grp1  A     0.156  (0.144,0.161]       2
 5 Grp1  C     0.181  (0.178,0.195]       3
 6 Grp1  F     0.214  (0.212,0.229]       4
 7 Grp1  D     0.264  (0.246,0.264]       5
 8 Grp2  E     0.0809 [0.0809,0.102]      1
 9 Grp2  B     0.161  (0.144,0.165]       2
10 Grp2  D     0.146  (0.144,0.165]       2
11 Grp2  C     0.279  (0.27,0.291]        3
12 Grp2  A     0.334  (0.312,0.334]       4

Regarding fct_reorder didn't accomplish anything, check the levels before and after the `step

> tmp <-  df %>% 
  group_by(Group) %>% 
  mutate(Bin = cut_interval(Value, n = nrow(.)))
> tmp %>% pull(Bin) %>% levels
 [1] "[0.0583,0.0754]" "(0.0754,0.0925]" "(0.0925,0.11]"   "(0.11,0.127]"    "(0.127,0.144]"   "(0.144,0.161]"   "(0.161,0.178]"   "(0.178,0.195]"   "(0.195,0.212]"  
[10] "(0.212,0.229]"   "(0.229,0.246]"   "(0.246,0.264]"   "[0.0809,0.102]"  "(0.102,0.123]"   "(0.123,0.144]"   "(0.144,0.165]"   "(0.165,0.186]"   "(0.186,0.207]"  
[19] "(0.207,0.228]"   "(0.228,0.249]"   "(0.249,0.27]"    "(0.27,0.291]"    "(0.291,0.312]"   "(0.312,0.334]"  
> tmp %>% mutate(Bin = fct_reorder(Bin, Value))  %>% pull(Bin) %>% levels
 [1] "[0.0583,0.0754]" "(0.144,0.161]"   "(0.178,0.195]"   "(0.212,0.229]"   "(0.246,0.264]"   "(0.0754,0.0925]" "(0.0925,0.11]"   "(0.11,0.127]"    "(0.127,0.144]"  
[10] "(0.161,0.178]"   "(0.195,0.212]"   "(0.229,0.246]"   "[0.0809,0.102]"  "(0.102,0.123]"   "(0.123,0.144]"   "(0.144,0.165]"   "(0.165,0.186]"   "(0.186,0.207]"  
[19] "(0.207,0.228]"   "(0.228,0.249]"   "(0.249,0.27]"    "(0.27,0.291]"    "(0.291,0.312]"   "(0.312,0.334]"  
Related