使用mutate、across和case_when,基于条件将列名连接到一个列中

24

我想要:

  1. 使用 across 和 case_when 检查列A1-A3是否等于1
  2. 连接A1-A3等于1的列名,并
  3. 创建一个新列,其中包含连接后的列名

我的数据框:

df <- tribble(
~ID,    ~A1,    ~A2,    ~A3,
1, 0, 1, 1, 
2, 0, 1, 1, 
3, 1, 1, 1, 
4, 1, 0, 1, 
5, 0, 1, 0)

期望输出:

# A tibble: 5 x 5
     ID    A1    A2    A3 New_Col 
  <dbl> <dbl> <dbl> <dbl> <chr>   
1     1     0     1     1 A2 A3   
2     2     0     1     1 A2 A3   
3     3     1     1     1 A1 A2 A3
4     4     1     0     1 A1 A3   
5     5     0     1     0 A2   

到目前为止,我已经尝试过:

df %>% 
  rowwise() %>% 
  mutate(New_Col = across(A1:A3, ~ case_when(. == 1 ~ paste0("colnames(.)", collapse = " "))))

输出不正常:

     ID    A1    A2    A3 New_Col$A1  $A2         $A3        
  <dbl> <dbl> <dbl> <dbl> <chr>       <chr>       <chr>      
1     1     0     1     1 NA          colnames(.) colnames(.)
2     2     0     1     1 NA          colnames(.) colnames(.)
3     3     1     1     1 colnames(.) colnames(.) colnames(.)
4     4     1     0     1 colnames(.) NA          colnames(.)
5     5     0     1     0 NA          colnames(.) NA   

我想学的内容:

  1. 是否可以使用 across 来检查多个列的条件?
  2. 如果可以,那么在 case_when 的 ~ 后面是什么样的代码来获取特定的列名?
  3. 如何在使用 mutateacrosscase_when 后只获取一列,而不是像这里一样获取3列。

我曾经以为自己已经掌握了这个任务,但不知怎么回事现在却忘记了...

4个回答

11
要在case_when中使用across,可以这样做 -
library(dplyr)
library(tidyr)

df %>% 
  mutate(across(A1:A3, ~case_when(. == 1 ~ cur_column()), .names = 'new_{col}')) %>%
  unite(New_Col, starts_with('new'), na.rm = TRUE, sep = ' ')

#    ID    A1    A2    A3 New_Col 
#  <dbl> <dbl> <dbl> <dbl> <chr>   
#1     1     0     1     1 A2 A3   
#2     2     0     1     1 A2 A3   
#3     3     1     1     1 A1 A2 A3
#4     4     1     0     1 A1 A3   
#5     5     0     1     0 A2      

across 创建了三列新的列,分别命名为 new_A1, new_A2, 和 new_A3,如果该值为 1 或者 NA 的话,使用该列名,否则不创建。使用 unite 函数将这三列合并成一个名为 New_col 的新列。


此外,我们可以使用 rowwisec_across -

df %>% 
  rowwise() %>% 
  mutate(New_Col = paste0(names(.[-1])[c_across(A1:A3) == 1], collapse = ' '))

Ronak,我们能否直接在这里使用cur_column而不是names()? - AnilGoyal
你是指在 rowwisegroup_by ID 中对吧?我认为我们做不到,因为 cur_column 只能在 across 中使用。 - Ronak Shah
是的,它只返回这个错误。谢谢你的解释 :) - AnilGoyal

7

如果没有使用 rowwise/ across,您也可以使用 cur_data() 来获得相同的结果。

df %>% group_by(ID) %>%
  mutate(new_col = paste0(names(df[-1])[as.logical(cur_data())], collapse = ' '))

# A tibble: 5 x 5
# Groups:   ID [5]
     ID    A1    A2    A3 new_col 
  <dbl> <dbl> <dbl> <dbl> <chr>   
1     1     0     1     1 A2 A3   
2     2     0     1     1 A2 A3   
3     3     1     1     1 A1 A2 A3
4     4     1     0     1 A1 A3   
5     5     0     1     0 A2 

在 mutate 中使用 . 而不是 df 也可以。
df %>% group_by(ID) %>%
  mutate(new_col = paste0(names(.[-1])[as.logical(cur_data())], collapse = ' '))

1
厉害的Anil ji和Ronak,我有一个问题,这里的cur_data是每个组,如果每个组有多行,它还能工作吗?因为我尝试了as.logical(df[-1]),期望得到一个由TRUEFALSE组成的DF,但是却得到了这个错误:Error: 'list' object cannot be coerced to type 'logical'。那么cur_datacur_group之间有什么区别呢? - Karthik S
1
嗨@KarthikS,你可以叫我Anil,在这里查看一些解释[https://dplyr.tidyverse.org/reference/context.html]。`cur_data`返回当前数据(当然是分组的),而`cur_group`表示组键。因此,`cur_data`将在此处返回二进制值,而`cur_group`将返回ID。希望这很清楚。 - AnilGoyal

4

使用 base R

df$New_Col <- apply(df[-1], 1, \(x) paste(names(x)[as.logical(x)], collapse=' '))
df$New_Col
#[1] "A2 A3"    "A2 A3"    "A1 A2 A3" "A1 A3"    "A2"  

或者使用 tidyverse

library(dplyr)
library(purrr)
library(stringr)
df %>%
   mutate(New_Col = across(A1:A3, ~ c('', cur_column())[. + 1] ) %>% 
                       invoke(str_c, .))

3

涉及到purrr的一个选项可能是:

df %>%
 mutate(New_Col = pmap_chr(across(-ID), 
                           ~ paste(names(c(...))[which(c(...) == 1)], collapse = " ")))

     ID    A1    A2    A3 New_Col 
  <dbl> <dbl> <dbl> <dbl> <chr>   
1     1     0     1     1 A2 A3   
2     2     0     1     1 A2 A3   
3     3     1     1     1 A1 A2 A3
4     4     1     0     1 A1 A3   
5     5     0     1     0 A2 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接