创建使用`dplyr`
考虑这个例子仅数据为数据的子集定义的新的变量:创建使用`dplyr`
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
而Y码确实工作
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
但后来我不得不把它保存在一个临时对象,然后创建在所有NA
原始数据var3
,并把所有回一起在相同的数据(因为据我所知unfilter
一些有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
竞争对手慢得多。
如果你原来的功能没有矢量化,不能与某些输入应付,有一个在使用Vectorize()
向量化它没有性能优势。相反,使用dplyr::rowwise()
按行操作行:
iris %>%
rowwise %>%
mutate(x = if (Sepal.Length < 5) 1 else NA) %>%
ungroup
注意,使用if
这里是绝对安全的,因为输入长度为1
感谢,'rowwise'是一个好主意。但我只看到它与'do'一起使用。我会尽力让你知道它是怎么回事。 ...,同时,我只是复制粘贴你的代码来查看输出,但它引发了这个错误“错误:不兼容的类型,期待一个数字向量”,...,我还没有调查那里发生了什么(稍后我会这样做),但是如果你在阅读之前很高兴听到可能导致错误的内容 – elikesprogramming
为避免错误,请使用“NA_real_”而不是“NA”。 – eipi10
@elikesprogramming:我正在使用dplyr的开发版本,错误不会发生在这里。否则,'NA_real_'是一个安全选项。 – krlmlr
你可能会考虑使用data.table
,因为dplyr
目前不支持in-place mutation,这是你似乎正在寻找。
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
谢谢@mtoto,是的,这样的部分替换是'data.table'的一个很好的特性。我只是在用'dplyr'来寻找类似的东西,因为即使我是'data.table'性能的粉丝,并没有太多关于它的语法(一种模糊和难以理解的东西......,在这种情况下,尽管;对于这种特殊情况,代码也非常清晰,但在某些情况下,基于data.table的解决方案的代码很难阅读) – elikesprogramming
你可以在完整的行运行函数,然后绑定回与NA
行(尽管这比if
更迂回... else
方法):
mydf %>% filter(complete.cases(.)) %>%
mutate(var3 = myfn(var1, var2)) %>%
bind_rows(mydf %>% filter(!complete.cases(.)))
var1 var2 var3 (dbl) (dbl) (dbl) 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不锈钢管用途:
a)用临时变量
mutate(mydf, temp = !(is.na(var1) | is.na(var2)),
var3 = replace(NA, temp, myfn(var1[temp], var2[temp])),
temp = NULL)
# 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
b)用包装函数(不改变原有myfn
):
myfn2 <- function(x, y) {
i <- !(is.na(x) | is.na(y))
res <- rep(NA, length(x))
res[i] <- myfn(x[i], y[i])
res
}
mutate(mydf, var3 = myfn2(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
这是采取乞求宽恕的pythonic style而不是请求许可的极好的例子。
可以与tryCatch
解决这个和避免条件测试共:
myfn <- function(x, y){
tryCatch(sum(x:y), error = function(e) NA)
}
然后
myfn <- Vectorize(myfn)
mydf %>%
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
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
补遗
当然,这是一个好主意,只能通过NA右侧类型的错误,这是
> tryCatch(sum(NA:NA), error = function(e) print(str(e)))
List of 2
$ message: chr "NA/NaN argument"
$ call : language NA:NA
- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
NULL
感谢@jaimedash使用'tryCatch'的好主意,虽然我会在'dplyr'链中执行它,而不是在函数中部分原因是该函数并不像我发布的这个示例那么简单,尽管我可以编写一个包装函数来尝试捕获错误,但我不是这种包装的粉丝) – elikesprogramming
避免重写函数是有意义的。将'tryCatch'直接放在链中作为内联包装的想法似乎很酷,但是当我尝试例如'mydf%>%mutate(var3 = tryCatch(myfn(var1,var2),error = function(e)NA) )'那么var3全是NA。如何使它工作? (PS'rowwise'也没有帮助) – jaimedash
我还没有尝试过,但我认为用'rowwise'内联'tryCatch'可以工作。如果没有'rowwise',我猜它不应该起作用,因为再次,整个向量被传递给函数,'tryCatch'将会出错,并且让NA返回。无论如何,'rowwise'解决方案肯定使用'do'而不是'mutate'(这最后一个可能只适用于'dplyr'的开发版本?)。在下面他自己的答案中查看@Psidom的评论,他在那里提供的代码有效。 – elikesprogramming
你的函数只复制从'var1'非'NA'值到'var3',是意? – mtoto
它是一个示例函数。这不是我的实际功能。这只是一个例子,在这里提供一个简短的可重现代码来说明问题 – elikesprogramming
如何修复你的函数,使它在收到NA时不会中断? – krlmlr