在对特定行进行条件调整时动态地对多列进行突变


11

我知道这里有几个类似的问题,但是似乎没有一个问题可以解决我遇到的确切问题。

set.seed(4)
df = data.frame(
  Key = c("A", "B", "A", "D", "A"),
  Val1 = rnorm(5),
  Val2 = runif(5),
  Val3 = 1:5
)

我想将其中Key ==“ A”的行的value列的值清零。通过a引用列名grep

cols = grep("Val", names(df), value = TRUE)

通常,在这种情况下,要实现我想要的功能,我将使用data.table以下命令:

library(data.table)
df = as.data.table(df)
df[Key == "A", (cols) := 0]

所需的输出是这样的:

  Key      Val1       Val2 Val3
1   A  0.000000 0.00000000    0
2   B -1.383814 0.55925762    2
3   A  0.000000 0.00000000    0
4   D  1.437151 0.05632773    4
5   A  0.000000 0.00000000    0

但是,这一次我需要dplyr在每个人都在使用的团队项目中使用它。我刚刚提供的数据是说明性的,我的真实数据是> 5m行,其中有16个值列需要更新。我唯一能想到的解决方案是这样使用mutate_at

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

但是,这对我的真实数据而言似乎极其缓慢。我希望找到一个更优雅,更重要的是更快的解决方案。

我已经尝试了许多组合using map,unquoting using !!,using get:=(烦人的可以被:=in data.table掩盖)等。


6
这需要多长时间?df [df $ Key ==“ A”,cols] <-0。我可以看到它很慢,因为您正在调用ifelse并遍历列和行。
StupidWolf

StupidWolf,使用我的数据实际上非常快,同时非常紧凑和优雅。谢谢。如果您愿意,可以随意添加它作为答案。
LiviusI

好吧,我可以向您展示解决该问题的另一种解决方案
。– StupidWolf

Answers:


9

使用此dplyr命令,

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

您实际上在评估语句df $ Key ==“ A”,n次,其中n =您拥有的列数。

一种解决方法是预定义要更改的行:

idx = which(DF$Key=="A")
DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})

@IceCreamToucan正确指出的一种更干净,更好的方法(请参阅下面的注释)是使用replace函数,同时向其传递额外的参数:

DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0)

我们可以对所有这些方法进行测试,我认为dplyr和data.table是可比的。

#simulate data
set.seed(100)
Key = sample(LETTERS[1:3],1000000,replace=TRUE)
DF = as.data.frame(data.frame(Key,matrix(runif(1000000*10),nrow=1000000,ncol=10)))
DT = as.data.table(DF)

cols = grep("[35789]", names(DF), value = TRUE)

#long method
system.time(DF %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(DF$Key == "A", 0, x)))
user  system elapsed 
  0.121   0.035   0.156 

#old base R way
system.time(DF[idx,cols] <- 0)
   user  system elapsed 
  0.085   0.021   0.106 

#dplyr
# define function
func = function(){
       idx = which(DF$Key=="A")
       DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})
}
system.time(func())
user  system elapsed 
  0.020   0.006   0.026

#data.table
system.time(DT[Key=="A", (cols) := 0])
   user  system elapsed 
  0.012   0.001   0.013 
#replace with dplyr
system.time(DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0))
user  system elapsed 
  0.007   0.001   0.008

4
额外的mutate参数将被求值一次并作为参数传递给所提供的函数(类似于lapply),因此您无需显式创建temp变量idx就可以做到这一点df %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan

感谢您指出@IceCreamToucan,我不知道。是的,替换功能甚至更好,并且比我更笨拙。如果您不介意,我会将其包含在答案中?(当然要贷给您)。
StupidWolf

在我的机器上测试之后,看来该replace方法比您原来的idx方法要慢一些。
IceCreamToucan

1
我也认为dplyr::if_else()比基础要快ifelse()
sindri_baldur
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.