使用dplyr将特定值设置为NA

79
我正在尝试用dplyr找出一个简单的方法来处理这样的情况(数据集=dat,变量=x)。
dat$x[dat$x<0]=NA

应该很简单,但这是我目前能做到的最好的了。有没有更简单的方法?
dat = dat %>% mutate(x=ifelse(x<0,NA,x))
5个回答

124

您可以使用replace函数,它比ifelse更快:

dat <-  dat %>% mutate(x = replace(x, x<0, NA))

您可以使用which参数,并为replace提供一个索引,以进一步加快速度:

dat <- dat %>% mutate(x = replace(x, which(x<0L), NA))

在我的电脑上,这将时间缩短了三分之一,请看下面。

这里是不同答案的简单比较,当然只是指示性的:

set.seed(24)
dat <- data.frame(x=rnorm(1e6))
system.time(dat %>% mutate(x = replace(x, x<0, NA)))
       User      System     elapsed
       0.03        0.00        0.03 
system.time(dat %>% mutate(x=ifelse(x<0,NA,x)))
       User      System     elapsed
       0.30        0.00        0.29 
system.time(setDT(dat)[x<0,x:=NA])
       User      System     elapsed
       0.01        0.00        0.02 
system.time(dat$x[dat$x<0] <- NA)
       User      System     elapsed
       0.03        0.00        0.03 
system.time(dat %>% mutate(x = "is.na<-"(x, x < 0)))
       User      System     elapsed
       0.05        0.00        0.05 
system.time(dat %>% mutate(x = NA ^ (x < 0) * x))
       User      System     elapsed
       0.01        0.00        0.02 
system.time(dat %>% mutate(x = replace(x, which(x<0), NA)))
       User      System     elapsed
       0.01        0.00        0.01 

(我正在使用dplyr_0.3.0.2和data.table_1.9.4)


由于我们总是对基准测试非常感兴趣,特别是在讨论data.table-vs-dplyr的过程中,我提供了另一个基准测试,使用microbenchmark和akrun的数据测试了三个答案。请注意,我修改了dplyr1,将其更新为我的答案:

set.seed(285)
dat1 <- dat <- data.frame(x=sample(-5:5, 1e8, replace=TRUE), y=rnorm(1e8))
dtbl1 <- function() {setDT(dat)[x<0,x:=NA]}
dplr1 <- function() {dat1 %>% mutate(x = replace(x, which(x<0L), NA))}
dplr2 <- function() {dat1 %>% mutate(x = NA ^ (x < 0) * x)}
microbenchmark(dtbl1(), dplr1(), dplr2(), unit='relative', times=20L)
#Unit: relative
#    expr      min       lq   median       uq      max neval
# dtbl1() 1.091208 4.319863 4.194086 4.162326 4.252482    20
# dplr1() 1.000000 1.000000 1.000000 1.000000 1.000000    20
# dplr2() 6.251354 5.529948 5.344294 5.311595 5.190192    20

也许Akrun愿意更新他的回答,他似乎正在运行这两个包的最新版本。 - talat
这似乎是一个基本方法语法上要简单得多的情况。 - Glen
我无法重现你的基准测试!data.table更快。 - M--

20

dplyr中最自然的方法是使用na_if函数。

对于一个变量:

dat %<>% mutate(x = na_if(x, x < 0))

对于所有的变量:

dat %<>% mutate_all(~ na_if(., . < 0))

如果希望替换特定的值,而不是所有变量的范围:

dat %<>% mutate_all(na_if, 0)

请注意,我正在使用magrittr包中的%<>%运算符。


1
谢谢,好知道!我认为当我第一次提出问题时,这个函数还没有可用。 - Glen
9
na_if(x, y) doesn't seem to work in this example where y is a condition that contains x. Compare: quakes %>% mutate(depth = na_if(depth, depth > 610)) doesn't mutate anything, but the following does: quakes %>% mutate(depth = replace(depth, depth > 610)) - Agile Bean
寻找一个示例,根据某些其他条件将一个变量设置为NA...这个页面似乎都没有做到这一点? - Markm0705
@AgileBean的示例需要在末尾加上, NA,使其变为:quakes %>% mutate(depth = replace(depth, depth > 610, NA)) - tassones
这个答案完全错误。na_if函数将第一个参数与第二个参数进行比较,如果它们相等,则返回NA。它的第二个参数不接受列表索引。请参阅na_if文档 - undefined

18
你可以使用is.na<-函数:
dat %>% mutate(x = "is.na<-"(x, x < 0))

或者您可以使用数学运算符:

dat %>% mutate(x = NA ^ (x < 0) * x)

寻找一个示例,根据某些其他条件将一个变量设置为NA...这个页面似乎都没有做到这一点? - Markm0705

11

如果您正在使用data.table,则以下代码更快。

library(data.table)
setDT(dat)[x<0,x:=NA]

基准测试

使用data.table_1.9.5dplyr_0.3.0.9000

library(microbenchmark)
set.seed(285)
dat <- data.frame(x=sample(-5:5, 1e7, replace=TRUE), y=rnorm(1e7))

dtbl1 <- function() {as.data.table(dat)[x<0,x:=NA]}
dplr1 <- function() {dat %>% mutate(x = replace(x, x<0, NA))}

microbenchmark(dtbl1(), dplr1(), unit='relative', times=20L)
#Unit: relative
#expr     min       lq     mean   median       uq      max neval cld
#dtbl1() 1.00000 1.000000 1.000000 1.000000 1.000000 1.000000    20  a 
#dplr1() 2.06654 2.064405 1.927762 1.795962 1.881821 1.885655    20   b

更新的基准测试结果

使用 data.table_1.9.5dplyr_0.4.0。我使用了稍微更大的数据集,并用setDT替换了as.data.table(也包括@Sven Hohenstein的更快的函数)。

set.seed(285)
dat <- data.frame(x=sample(-5:5, 1e8, replace=TRUE), y=rnorm(1e8))
dat1 <- copy(dat)
dtbl1 <- function() {setDT(dat)[x<0,x:=NA]}
dplr1 <- function() {dat1 %>% mutate(x = replace(x, x<0, NA))}
dplr2 <- function() {dat1 %>% mutate(x = NA ^ (x < 0) * x)} 

microbenchmark(dtbl1(), dplr1(), dplr2(), unit='relative', times=20L)
#Unit: relative
#  expr      min       lq     mean   median       uq      max neval cld
#dtbl1() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000    20  a 
#dplr1() 2.523945 2.542412 2.536255 2.579379 2.518336 2.486757    20   b
#dplr2() 1.139216 1.089992 1.088753 1.058653 1.093906 1.100690    20  a 

更新的基准测试2

应 @docendo discimus 的要求,再次使用data.table_1.9.5dplyr_0.4.0来对他的“新”版本的dplyr进行基准测试。

注意:由于@docendo discimus的代码发生了变化,我将`data.table`中的0更改为0L

set.seed(285)
dat <- data.frame(x=sample(-5:5, 1e8, replace=TRUE), y=rnorm(1e8))
dat1 <- copy(dat)
dtbl1 <- function() {setDT(dat)[x<0L, x:= NA]}
dplr1 <- function() {dat1 %>% mutate(x = replace(x, which(x<0L), NA))}
dplr2 <- function() {dat1 %>% mutate(x = NA ^ (x < 0) * x)} 

microbenchmark(dtbl1(), dplr1(), dplr2(), unit='relative', times=20L)
#Unit: relative
#expr      min       lq     mean   median       uq      max neval cld
#dtbl1() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000    20 a  
#dplr1() 2.186055 2.183432 2.142293 2.222458 2.194450 1.442444    20  b 
#dplr2() 2.919854 2.925795 2.852528 2.942700 2.954657 1.904249    20   c

数据

set.seed(24)
dat <- data.frame(x=sample(-5:5, 25, replace=TRUE), y=rnorm(25))

在我的电脑上,dplyr 运行速度比较快。我安装的版本是 data.table_1.9.5dplyr_0.4.0 - Khashaa
data.table_1.9.4dplyr_0.3.0.2的结果与@Akrun相似。然后我升级到了dplyr_0.4.0,但是dplyr仍然比原来快了约两倍。 - Vlo
@akrun,你觉得用最新版本的data.table/dplyr更新一下你的基准测试怎么样?我的回答在底部。 - talat
1
@Arun 感谢您的评论。经过一番思考,我得出结论,这可能是因为我在 dplyr 特定问题中发布了一个 data.table 解决方案。 - akrun
2
@akrun,它也被标记为“r”。他们没有写注释的事实已经说明了很多。这是SO,不是dplyr/data.table论坛。 - Arun
显示剩余4条评论

1

直接在 x 列上使用 replace 并且不使用 mutate 也可以起作用。

dat$x <- replace(dat$x, dat$x<0, NA)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接