关于r:使用`dplyr`创建仅为数据子集定义的新变量

create new variable defined only for a subset of the data using `dplyr`

日期:考虑这个exampleP></

1
2
set.seed(1234567)
mydf <- data.frame(var1 = runif(10), var2 = c(runif(5), rep(NA, 5)))

矢量函数的this和that example,不幸的是他/她,当一个触发器安Arguments of the error is NAP></

1
2
3
4
myfn <- function(x, y){
    sum(x:y)
}
myfn <- Vectorize(myfn)

现在,在链的中间dplyrof using need to New myfn创建变量。这个新变种(var3)is only when are not var1定义和var2NA。P></

我知道最常见的溶液ifelseis to使用情况相似。something like this。P></

1
2
3
4
5
mydf %>%
    mutate(var3 = ifelse(
        test = is.na(var2),
        yes = NA,
        no = myfn(var1, var2)))

but this does not work在我的房子,因为ifelse啊真的passes the Whole var1布尔向量var2to not just the myfntestis when FALSE子载体。因为它myfn断裂和断裂在NAreceives每当。P></

我是聪明的,dplyrthis is the solution for?(我认为)解决方案使用多dplyrfor this没有兴趣,但我只是在dplyr友好的解决方案)P></

它可以帮助我occurred to that filter和可读的作品真的很dplyry队列与布尔P></

1
2
3
4
5
6
7
8
9
10
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

但我会保存在这个临时对象中创建,然后在与原始数据的var3NA把背在一起的日期(as the same as是知道原因的unfilterthat some have the suggested does not exist,yet,…)。P></

我只是想说明to the输出队列,这produces恩(不使用在所有的dplyr):P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

编辑:P></

的解决方案,因为它的krlmlr' accepted"就是我寻找:清晰,简明和可读的代码,easily effortlessly集成在dplyr链。example for this我如何看,这样的解决方案。P></

1
2
3
mydf %>%
        rowwise %>%
        mutate(var3 = if(is.na(var2)) NA else myfn(var1, var2))

不管一个人多,As在他的答案krlmlr角@超时操作模式,行行有在条款的成本和性能。它可能不重要集合的日期或时间为小单操作空气日期2010年1月17日,but for millions of the operation sets or语言时代,它是considerable。对了,这是比较使用的解决方案和三microbenchmark(基础和应用,dyplr data.table过空气日期2010年1月17日)在somewhat集(not大规模或什么,只是使用instead of 1000 10在我原来的example)。P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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)

enter image description hereP></

和As You can see,the solution is using rowwisedplyrconsiderably slower比data.tablerivals base和ITS。P></


你可以考虑使用data.table,因为dplyr目前不支持就地突变,这是你似乎在寻找的。

1
2
3
4
5
6
7
8
9
10
11
12
13
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


以下是可以在dplyr管道中使用的其他两个选项:

a)带有临时变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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


如果您的原始函数没有向量化,并且不能处理某些输入,那么使用Vectorize()对其进行向量化时没有性能优势。相反,使用dplyr::rowwise()逐行操作:

1
2
3
4
iris %>%
  rowwise %>%
  mutate(x = if (Sepal.Length < 5) 1 else NA) %>%
  ungroup

注意,这里使用if是完全安全的,因为输入的长度为1。


这是一个采用Python式乞求宽恕而不是请求许可的伟大案例。

您可以使用tryCatch解决此问题,并完全避免条件测试:

1
2
3
myfn <- function(x, y){
  tryCatch(sum(x:y), error = function(e) NA)
}

然后

1
2
3
myfn <- Vectorize(myfn)
mydf %>%
    mutate(var3 = myfn(var1, var2))

给出所需的结果

1
2
3
4
5
6
7
8
9
10
11
         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传递给正确的错误类型,即

1
2
3
4
5
6
> 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


您可以在完整的行上运行该函数,然后用NA绑定这些行(尽管这比if更为迂回)。else方法:

1
2
3
mydf %>% filter(complete.cases(.)) %>%
  mutate(var3 = myfn(var1, var2)) %>%
  bind_rows(mydf %>% filter(!complete.cases(.)))
1
2
3
4
5
6
7
8
9
10
11
12
         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