考虑这个示例数据:
set.seed(1234567) mydf <- data.frame(var1 = runif(10), var2 = c(runif(5), rep(NA, 5)))
这个示例矢量化函数,不幸的是,只要其中一个参数发生错误就会触发错误 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
可以提供帮助,并且确实可以使用非常易读的dplyr
代码
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
并将所有内容放回到相同的数据中(因为据我所知unfilter
,有些人建议不存在,... ,但).
所以只是为了说明我想要的输出,这段代码产生它(根本没用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)
正如您所看到的,dplyr
使用的解决方案rowwise
比它base
和data.table
竞争对手慢得多.
您可以考虑使用data.table
,因为dplyr
目前不支持就地变异,这是您似乎正在寻找的.
library(data.table) setDT(mydf)[!is.na(var2), 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 # 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