base::ifelse() within dplyr::arrange() 用于分组行的条件排列

base::ifelse() within dplyr::arrange() for conditional arrangement of grouped rows

我正在尝试根据另一列的值对 data.frame 的行进行排序。

下面是一个例子:

library(magrittr)
library(dplyr)

df <- data.frame(grp = c(1,1,1,2,2,2), 
                 ori = c("f","f","f","r","r","r"),
                 ite = c("A","B","C","A","B","C"))

df

# #  grp ori ite
# 1   1   f   A
# 2   1   f   B
# 3   1   f   C
# 4   2   r   A
# 5   2   r   B
# 6   2   r   C

df %>%
  group_by(grp) %>%
  arrange(ifelse(ori == "f", ite, desc(ite)), .by_group = TRUE) %>%
  ungroup()

# # A tibble: 6 × 3
# # Groups:   grp [2]
#     grp ori   ite  
#   <dbl> <chr> <chr>
# 1     1 f     A    
# 2     1 f     B    
# 3     1 f     C    
# 4     2 r     A    
# 5     2 r     B    
# 6     2 r     C   

预期输出为:

# #  grp ori ite
# 1   1   f   A
# 2   1   f   B
# 3   1   f   C
# 4   2   r   C
# 5   2   r   B
# 6   2   r   A

我对它不起作用的原因有一个大概的了解:arrange() 不能逐行查看事物,而这正是 ifelse() 要求它做的。

有没有更好的方法来完成这个?

实现您想要的结果的一个选择是使用 split + arrange + bind_rows 像这样:

library(dplyr)
library(purrr)

df %>%
  split(.$ori) %>% 
  purrr::imap(
    ~ if (.y == "f") arrange(.x, grp, ite) else arrange(.x, grp, desc(ite))
  ) %>% 
  dplyr::bind_rows()
#>   grp ori ite
#> 1   1   f   A
#> 2   1   f   B
#> 3   1   f   C
#> 4   2   r   C
#> 5   2   r   B
#> 6   2   r   A

感谢@MartinGal 的建议,我们可以通过使用 purrr::imap_dfr:

来节省 bind_rows 步骤

df %>%
  split(.$ori) %>% 
  purrr::imap_dfr(
    ~ if (.y == "f") arrange(.x, grp, ite) else arrange(.x, grp, desc(ite))
  )

一种可能的方法是拆分列 ori 并创建一个函数,然后按如下方式合并结果:

df %>% 
  split(.$ori) %>% 
  map(function(x) {
    
    if ('f' %in% x$ori) {
      
      x %>% 
        group_by(grp) %>% 
        arrange(ite, .by_group = TRUE)
    }
    else {
        x %>% 
        group_by(grp) %>% 
        arrange(desc(ite), .by_group = TRUE)
      }
    }) %>% 
  bind_rows()
# A tibble: 6 x 3
## Groups:   grp [2]
#    grp ori   ite  
#  <dbl> <chr> <chr>
#1     1 f     A    
#2     1 f     B    
#3     1 f     C    
#4     2 r     C    
#5     2 r     B    
#6     2 r     A  

这是另一个解决方案。

df %>% 
  arrange(
    ifelse(ori == 'f', ite, NA), 
    desc(ifelse(ori != 'f', ite, NA))
    ) 

使用ifelse(ori == "f", ite, desc(ite))的想法基本上是好的,不幸的是desc(ite)的输出是一个负数值向量,而ite的输出是一个字符向量。要使用与输入相同的输出以相反的顺序带来 ite 的结果,我们可以将其包装在 base::rev 中而不是 dplyr::desc.

library(dplyr)

df <- data.frame(grp = c(1,1,1,2,2,2), 
                 ori = c("f","f","f","r","r","r"),
                 ite = c("A","B","C","A","B","C"))

df %>% 
  arrange(ori, ifelse(ori == "r", rev(ite), ite))
#>   grp ori ite
#> 1   1   f   A
#> 2   1   f   B
#> 3   1   f   C
#> 4   2   r   C
#> 5   2   r   B
#> 6   2   r   A

reprex package (v0.3.0)

于 2021-09-18 创建