Home > Back-end >  Sort/arrange character column with condition
Sort/arrange character column with condition

Time:09-17

I am trying to use dplyr's arrange to sort character strings based on a condition. I'd like to arrange on a column, but in ascending order if a second column is equal to one value and in descending order if the second column is equal to another value.

I found a couple similar questions (one of them one of my own previous questions), but they weren't directly applicable. Both of them sorted on numeric columns and they wouldn't work on character columns.

R - Conditionally sort multiple columns as ascending or descending by group

Sort/arrange within group for only chosen groups

Here's sample data:

df <- mtcars %>% as_tibble(rownames = "model") %>% select(model, mpg, cyl) %>% slice(1:8) %>% mutate(cond = c(rep("A", 4), rep("B", 4)))

If cond == "A", I want to sort "model" in ascending order. If cond == "B", I want to sort "model" in descending order. I'd like a solution that doesn't rely on bind_rows or a similar solution.

Possibly something in the form of:

library(dplyr)
df %>% arrange(ifelse(cond == "A", model, desc(model)))

The desired solution looks like:

## A tibble: 8 x 8
#  model              mpg   cyl cond 
#  <chr>              <dbl> <dbl> <chr>
#1 Datsun 710         22.8     4 A    
#2 Hornet 4 Drive     21.4     6 A    
#3 Mazda RX4          21       6 A    
#4 Mazda RX4 Wag      21       6 A    
#5 Valiant            18.1     6 B    
#6 Merc 240D          24.4     4 B    
#7 Hornet Sportabout  18.7     8 B    
#8 Duster 360         14.3     8 B    

CodePudding user response:

We may use group_modify and group_by

library(dplyr)
df %>% 
    group_by(cond) %>%
    group_modify(~.x %>% 
              arrange(if(.y == 'A') model else desc(model)) ) %>%
    ungroup %>%
    select(names(df))

-output

# A tibble: 8 x 4
  model               mpg   cyl cond 
  <chr>             <dbl> <dbl> <chr>
1 Datsun 710         22.8     4 A    
2 Hornet 4 Drive     21.4     6 A    
3 Mazda RX4          21       6 A    
4 Mazda RX4 Wag      21       6 A    
5 Valiant            18.1     6 B    
6 Merc 240D          24.4     4 B    
7 Hornet Sportabout  18.7     8 B    
8 Duster 360         14.3     8 B   

It is also possible to subset the values of 'model' sort it separately and specify those as levels in factor to be get arranged accordingly

df %>%  
   arrange(factor(model, levels = c(sort(model[cond == 'A']), 
           sort(model[cond != "A"], decreasing = TRUE))))

-output

# A tibble: 8 x 4
  model               mpg   cyl cond 
  <chr>             <dbl> <dbl> <chr>
1 Datsun 710         22.8     4 A    
2 Hornet 4 Drive     21.4     6 A    
3 Mazda RX4          21       6 A    
4 Mazda RX4 Wag      21       6 A    
5 Valiant            18.1     6 B    
6 Merc 240D          24.4     4 B    
7 Hornet Sportabout  18.7     8 B    
8 Duster 360         14.3     8 B    

CodePudding user response:

It's pretty messy, but it works.

mtcars %>% 
  as_tibble(rownames = "model") %>% 
  select(model, mpg, cyl) %>% 
  slice(1:8) %>% 
  mutate(cond = c(rep("A", 4), rep("B", 4))) %>%
  group_by(cond) %>%
  arrange(model, .by_group = TRUE) %>%
  add_rownames(., var = "rowna") %>%
  mutate(rowna = as.numeric(rowna)) %>%
  rowwise() %>%
  mutate(idx = ifelse(cond == "A", rowna, 8 - rowna)) %>%
  group_by(cond) %>%
  arrange(idx, .by_group = TRUE) %>%
  select(-idx, -rowna)

  model               mpg   cyl cond 
  <chr>             <dbl> <dbl> <chr>
1 Datsun 710         22.8     4 A    
2 Hornet 4 Drive     21.4     6 A    
3 Mazda RX4          21       6 A    
4 Mazda RX4 Wag      21       6 A    
5 Valiant            18.1     6 B    
6 Merc 240D          24.4     4 B    
7 Hornet Sportabout  18.7     8 B    
8 Duster 360         14.3     8 B 

CodePudding user response:

As I find is often the case, a base R solution is much simpler:

split(df, df$cond) <- lapply(split(df, df$cond), function(d) {
    d[order(d$model, decreasing = all(d$cond == "B")),]
})

This splits the dataset by condition, then orders each split based on whether the condition is "A" or "B", and then re-inserts the splits into their respective places.

CodePudding user response:

This can easily be done with dplyr::arrange and ifelse. The trick is that {dplyr} has no asc function, so we need to reverse the outcome of desc:

library(dplyr)

df %>% 
  arrange(cond, ifelse(cond == "B", desc(model), rev(desc(model))))
#> # A tibble: 8 x 4
#>   model               mpg   cyl cond 
#>   <chr>             <dbl> <dbl> <chr>
#> 1 Datsun 710         22.8     4 A    
#> 2 Mazda RX4          21       6 A    
#> 3 Hornet 4 Drive     21.4     6 A    
#> 4 Mazda RX4 Wag      21       6 A    
#> 5 Valiant            18.1     6 B    
#> 6 Merc 240D          24.4     4 B    
#> 7 Hornet Sportabout  18.7     8 B    
#> 8 Duster 360         14.3     8 B

We can also write a wrapper function asc.

asc <- function(x) {
  rev(dplyr::desc(x))
}

df %>% 
  arrange(cond, ifelse(cond == "B", desc(model), asc(model)))
#> # A tibble: 8 x 4
#>   model               mpg   cyl cond 
#>   <chr>             <dbl> <dbl> <chr>
#> 1 Datsun 710         22.8     4 A    
#> 2 Mazda RX4          21       6 A    
#> 3 Hornet 4 Drive     21.4     6 A    
#> 4 Mazda RX4 Wag      21       6 A    
#> 5 Valiant            18.1     6 B    
#> 6 Merc 240D          24.4     4 B    
#> 7 Hornet Sportabout  18.7     8 B    
#> 8 Duster 360         14.3     8 B

Created on 2021-09-14 by the reprex package (v2.0.1)

  • Related