在 R 中跨不同类型的列应用 ifelse() 时保留列类型

Preserving column types when applying ifelse() across columns of different types in R

这似乎是一个相当简单的任务,但在研究了 ifelse()dplyr::if_else() 的文档以及关于应用 ifelse() 到数据框中的多列。

我的目标: 我有以下数据框,其中包含不同数据类型的列。在每一行上,如果列“有效”指示错误,我想将前 3 列中的值重置为 NA。

问题: 我使用 dplyr::across()ifelse() 来更改我想要的值,但是日期列 date 和因子列 team 被强制转换为数字(如下面的 reprex 所示),这是不可取的。我知道 dplyr::if_else() 保留数据类型,但它也不适用于不同数据类型的列。

我知道 tdf[tdf$valid == FALSE, !grepl("valid", names(tdf))] <- NA 可以实现我的目标,但我更喜欢 tidyverse 方法,我可以在我的数据清理管道中使用它。非常感谢!

library(dplyr)

tdf <- tibble(
  date = c(as.Date("2021-12-10"), as.Date("2021-12-11")),
  team = factor(1:2, labels = c("T1", "T2")),
  score = 3:4,
  valid = c(TRUE, FALSE)
)

tdf
#> # A tibble: 2 x 4
#>   date       team  score valid
#>   <date>     <fct> <int> <lgl>
#> 1 2021-12-10 T1        3 TRUE 
#> 2 2021-12-11 T2        4 FALSE

tdf %>% mutate(across(-valid, ~ ifelse(valid, ., NA)))
#> # A tibble: 2 x 4
#>    date  team score valid
#>   <dbl> <int> <int> <lgl>
#> 1 18971     1     3 TRUE 
#> 2    NA    NA    NA FALSE

reprex package (v2.0.1)

于 2021-12-10 创建

使用 case_when 中的默认 (TRUE) 选项,其中 returns NA 基于类型

library(dplyr)
tdf %>%
    mutate(across(-valid, ~ case_when(valid ~ .)))

-输出

# A tibble: 2 × 4
  date       team  score valid
  <date>     <fct> <int> <lgl>
1 2021-12-10 T1        3 TRUE 
2 NA         <NA>     NA FALSE

或者另一种选择是 replace

tdf %>% 
   mutate(across(-valid, ~ replace(., !valid, NA)))
# A tibble: 2 × 4
  date       team  score valid
  <date>     <fct> <int> <lgl>
1 2021-12-10 T1        3 TRUE 
2 NA         <NA>     NA FALSE

根据?ifelse

The mode of the result may depend on the value of test (see the examples), and the class attribute (see oldClass) of the result is taken from test and may be inappropriate for the values selected from yes and no.

Sometimes it is better to use a construction such as

(tmp <- yes; tmp[!test] <- no[!test]; tmp)

, possibly extended to handle missing values in test.

这是一个未解决的问题:

@akrun 给出了很好的解释以及如何解决您的具体问题!

但万一你想保留 ifelse:

Fabian Werner 在 2015 年使用自定义 safe.ifelse[为您的具体情况提供了唯一可行的解​​决方案 (带日期和因素) =27=]

How to prevent ifelse() from turning Date objects into numeric objects

safe.ifelse <- function(cond, yes, no) {
  class.y <- class(yes)
  if (class.y == "factor") {
    levels.y = levels(yes)
  }
  X <- ifelse(cond,yes,no)
  if (class.y == "factor") {
    X = as.factor(X)
    levels(X) = levels.y
  } else {
    class(X) <- class.y
  }
  return(X)
}

tdf %>% mutate(across(-valid, ~ safe.ifelse(valid, ., NA)))

  date       team  score valid
  <date>     <fct> <int> <lgl>
1 2021-12-10 T1        3 TRUE 
2 NA         NA       NA FALSE