【发布时间】:2016-04-03 22:42:17
【问题描述】:
考虑这个示例数据:
set.seed(1234567)
mydf <- data.frame(var1 = runif(10), var2 = c(runif(5), rep(NA, 5)))
不幸的是,这个EXAMPLE向量化函数会在其中一个参数为NA时触发错误
myfn <- function(x, y){
sum(x:y)
}
myfn <- Vectorize(myfn)
现在,在dplyr 链的中间,我需要使用myfn 创建一个新变量。这个新变量 (var3) 仅在 var1 和 var2 不是 NA 时定义。
所以对于类似情况最常见的解决方案是使用ifelse。像这样。
mydf %>%
mutate(var3 = ifelse(
test = is.na(var2),
yes = NA,
no = myfn(var1, var2)))
但这在我的情况下不起作用,因为无论如何ifelse 实际上将整个向量var1 和var2 传递给myfn 而不仅仅是当test 是FALSE 时的子向量。这一切都会中断,因为myfn 会在收到NA 时中断。
那么,聪明的dplyr 解决方案是什么? (我可以在不使用dplyr 的情况下为此想到许多解决方案,但我只对dplyr 友好的解决方案感兴趣)
我突然想到 filter 可以提供帮助,并且确实可以使用非常易读的 dplyry 代码
mydf %>%
filter(!is.na(var2)) %>%
mutate(var3 = myfn(var1, var2))
var1 var2 var3
1 0.56226084 0.62588794 0.56226084
2 0.72649850 0.24145251 0.72649850
3 0.91524985 0.03768974 0.91524985
4 0.02969437 0.51659297 0.02969437
5 0.76750970 0.81845788 0.76750970
但是我必须将其保存在一个临时对象中,然后在原始数据中创建 var3 全部使用 NA 并将所有内容重新放在同一数据中(因为据我所知 @987654351 @ 有些人有 suggested 不存在,...,但)。
所以只是为了说明我想要的输出,这段代码产生了它(根本不使用dplyr):
mydf$var3 <- NA
index <- !is.na(mydf$var2)
mydf$var3[index] <- myfn(mydf$var1[index], mydf$var2[index])
mydf
> mydf
var1 var2 var3
1 0.56226084 0.62588794 0.56226084
2 0.72649850 0.24145251 0.72649850
3 0.91524985 0.03768974 0.91524985
4 0.02969437 0.51659297 0.02969437
5 0.76750970 0.81845788 0.76750970
6 0.48005398 NA NA
7 0.08837960 NA NA
8 0.86294587 NA NA
9 0.49660306 NA NA
10 0.85350403 NA NA
编辑:
我接受了@krlmlr 的解决方案,因为它正是我所寻找的:清晰、易读且简洁的代码,可轻松集成到dplyr 链中。对于我的示例,此解决方案如下所示。
mydf %>%
rowwise %>%
mutate(var3 = if(is.na(var2)) NA else myfn(var1, var2))
但是,正如@krlmlr 在他的回答中指出的那样,逐行操作在性能方面是有代价的。对于小型数据集或单次操作,它可能并不重要,但对于较大的数据集或重复操作数百万次,它可能是相当可观的。为了说明,这里是使用microbenchmark 和三个解决方案(base、dyplr 和 data.table)的比较,它们应用于一个更大的数据集(不是海量或其他任何东西,只有 1000 行而不是我原来的示例中的 10 行)。
library(data.table)
library(dplyr)
set.seed(1234567)
mydf <- data.frame(var1 = runif(1000), var2 = c(runif(500), rep(NA, 500)))
myfn <- function(x, y){
sum(x:y)
}
myfn <- Vectorize(myfn)
using_base <- function(){
mydf$var3 <- NA
index <- !is.na(mydf$var2)
mydf$var3[index] <- myfn(mydf$var1[index], mydf$var2[index])
}
using_dplyr <- function(){
mydf <- mydf %>%
rowwise %>%
mutate(var3 = if(is.na(var2)) NA else myfn(var1, var2))
}
using_datatable <- function(){
setDT(mydf)[!is.na(var2), var3 := myfn(var1, var2)]
}
library(microbenchmark)
mbm <- microbenchmark(
using_base(), using_dplyr(), using_datatable(),
times = 1000)
library(ggplot2)
autoplot(mbm)
如您所见,使用rowwise 的dplyr 解决方案比其base 和data.table 的竞争对手慢得多。
【问题讨论】:
-
您的函数只是将非
NA值从var1复制到var3,这是有意的吗? -
这是一个示例函数。那不是我的实际功能。这只是一个示例,在这里提供一个简短的可重现代码来说明问题
-
如何修复你的函数,使其在收到 NA 时不会中断?