如何对未指定的列执行条件变异(例如匹配正则表达式)?

How to perform conditional mutate on unspecified columns (e.g. matching regex)?

我有一个嵌套的 data.frame - df_nested,其中一列包含 df:

df <- tibble(ID_Value = 1:8,
             xyz001 = c("text4", NA, NA, NA, NA, NA, NA, "text2"), 
             xyz002 = c(NA, NA, NA, "text3", "text1", NA, NA, NA),
             xyz003 = c(NA, "text1", NA, NA, "text2", NA, "text2", NA)) 

我想找到一种方法,如何根据这些要求改变这个 df:

  1. mutate(across(matches("\d")
  2. 有 4 个案例 - 4 个优先级。文本 4 <- 文本 3 <- 文本 2 <- 文本 1: 我需要查找并保留仅包含最高级别文本的列值。例如如果列包含 text4,我想删除 text3、text2、text1 并将它们替换为 NA。 如果它包含多个最高阶文本,我们应该保留所有这些值(例如列 xyz003)。
  3. 如何在不指定列名的情况下应用这些条件,因为列名中可以有任何数字。
  4. 如果列包含所有 NA,则什么也不做。

我的尝试:

df_nested <- df_nested %>%
    mutate(df = map(data, ~.x %>%
       mutate(across(matches("\dd"), function (x) {
                      conditions (ifelse, case_when or other)
                      ...}

此外,我们应该更好地使用 across(),还是 vars() 仍然是一个很好的方法? 提前谢谢你。

预期输出

df <- tibble(ID_Value = 1:8,
             xyz001 = c("text4", NA, NA, NA, NA, NA, NA, NA), 
             xyz002 = c(NA, NA, NA, "text3", NA, NA, NA, NA),
             xyz003 = c(NA, NA, NA, NA, "text2", NA, "text2", NA))

您可以将 rowwisec_across 一起使用:

library(dplyr)
library(tidyr)

df %>%
  rowwise() %>%
  mutate(col = suppressWarnings(max(c_across(matches('\d+')), na.rm = TRUE)))

#  ID_Value xyz001 xyz002 xyz003 col  
#     <int> <chr>  <chr>  <chr>  <chr>
#1        1 tier4  NA     NA     tier4
#2        2 NA     NA     tier1  tier1
#3        3 NA     NA     NA     NA   
#4        4 NA     tier3  NA     tier3
#5        5 NA     tier1  tier2  tier2
#6        6 NA     NA     NA     NA   
#7        7 NA     NA     tier2  tier2
#8        8 tier2  NA     NA     tier2

对字符值取 max 没有意义(并产生警告)但在这里我们可以使用它直接获取输出。


为了仅保留每行中的最大值,我们可以重塑数据:

df %>%
  pivot_longer(cols = -ID_Value) %>%
  group_by(ID_Value) %>%
  mutate(value = replace(value, -which.max(readr::parse_number(value)), NA)) %>%
  pivot_wider()

#  ID_Value xyz001 xyz002 xyz003
#     <int> <chr>  <chr>  <chr> 
#1        1 tier4  NA     NA    
#2        2 NA     NA     tier1 
#3        3 NA     NA     NA    
#4        4 NA     tier3  NA    
#5        5 NA     NA     tier2 
#6        6 NA     NA     NA    
#7        7 NA     NA     tier2 
#8        8 tier2  NA     NA    
  1. 使用 factor 类型指定您想要的顺序。
  2. 对匹配项执行行或 column-wise 操作。

考虑这个功能

max_only <- function(x, lvls) {
  fct <- droplevels(factor(x, lvls))
  `[<-`(x, as.integer(fct) != length(levels(fct)), NA_character_)
}

然后您可以指定任何顺序

> max_only(c("apple", "banana", NA_character_), c("banana", "apple"))
[1] "apple" NA      NA     
> max_only(c("apple", "banana", NA_character_), c("apple", "banana"))
[1] NA       "banana" NA   

案例 1:column-wise 操作

df %>% 
  mutate(across(matches("\d"), max_only, c("tier1", "tier2", "tier3", "tier4")))

输出(这个看起来更像你预期的输出)

# A tibble: 8 x 4
  ID_Value xyz001 xyz002 xyz003
     <int> <chr>  <chr>  <chr> 
1        1 tier4  NA     NA    
2        2 NA     NA     NA    
3        3 NA     NA     NA    
4        4 NA     tier3  NA    
5        5 NA     NA     tier2 
6        6 NA     NA     NA    
7        7 NA     NA     tier2 
8        8 NA     NA     NA    

案例 2:row-wise 操作

df %>% 
  mutate(as.data.frame(t(apply(
    across(matches("\d")), 1L, 
    max_only, c("tier1", "tier2", "tier3", "tier4")
  ))))

输出

# A tibble: 8 x 4
  ID_Value xyz001 xyz002 xyz003
     <int> <chr>  <chr>  <chr> 
1        1 tier4  NA     NA    
2        2 NA     NA     tier1 
3        3 NA     NA     NA    
4        4 NA     tier3  NA    
5        5 NA     NA     tier2 
6        6 NA     NA     NA    
7        7 NA     NA     tier2 
8        8 tier2  NA     NA    

解释

  1. [<- 几乎等同于 x[...] <- y; x。如果 ... 是逻辑向量(即 TRUE/FALSE),则 x 中索引为 TRUE 的值将被 y 替换。例如,

     > x <- c("a", "b" ,"c")
     > `[<-`(x, c(FALSE, TRUE, TRUE), NA_character_)
     [1] "a" NA  NA 
     > x[c(FALSE, TRUE, TRUE)] <- NA_character_; x
     [1] "a" NA  NA 
    
  2. NA_character_是字符类型的NA值。

  3. as.integer(fct) != length(levels(fct)) returns 与 fct 长度相同的逻辑向量。 TRUE 索引 fct 的值不是最高级别的位置,FALSE 索引相反,NA 索引 NAs。例如,假设 fct 看起来像这样

     > x <- c("apple", "banana", NA)
     > fct <- droplevels(factor(x, c("apple", "banana", "pear")))
     > fct
     [1] apple  banana <NA>  
     Levels: apple banana
    

    那么,你可以看到

     > as.integer(fct) != length(levels(fct))
     [1]  TRUE FALSE    NA 
    
  4. 总而言之,就是把NA_character_赋值给不等于最高层的值,但保持NA不变。

    [<-(x, as.integer(fct) != length(levels(fct)), NA_character_)