I have a data.table like so
dt <- data.table(x=as.Date(c("2014-1-1", "2015-1-1", "2016-1-1")), y=as.Date(c(NA, "2015-6-1", NA)))
dt
x y
1: 2014-01-01 <NA>
2: 2015-01-01 2015-06-01
3: 2016-01-01 <NA>
Run Code Online (Sandbox Code Playgroud)
I want to add a column z which is equal to y where y is not NA, and x otherwise.
dt[, z:=ifelse(is.na(y), x, y)]
dt
x y z
1: 2014-01-01 <NA> 16071
2: 2015-01-01 2015-06-01 16587
3: 2016-01-01 <NA> 16801
Run Code Online (Sandbox Code Playgroud)
But for some reason the above statement casts z to numeric. If I try to convert it to a date with as.Date I get an error
dt[, z:=as.Date(ifelse(is.na(y), x, y))]
Error in as.Date.numeric(ifelse(is.na(y), x, y)) : 'origin' must be supplied
Run Code Online (Sandbox Code Playgroud)
What gives and how do I accomplish what I'm trying to do?
这个老问题现在已经被浏览了一万多次。
虽然它有一个公认的答案,但我觉得这个问题值得
data.table解决方案,Date失败ifelse()和replace()方法返回错误的结果。data.table 方法使用data.table,ifelse()和replace()可以写成两个链式赋值操作,其中第二个使用子集:
dt[, z := y][is.na(z), z := x][]
Run Code Online (Sandbox Code Playgroud)
Run Code Online (Sandbox Code Playgroud)x y z 1: 2014-01-01 <NA> 2014-01-01 2: 2015-01-01 2015-06-01 2015-06-01 3: 2016-01-01 <NA> 2016-01-01
第一个赋值操作z通过复制列来创建一个新y列。第二分配操作修改z 代替通过复制的内容x列只有那些行,其中z是NA。
或者,我们可以复制xfirst 并z用非NA y值替换值:
dt <- copy(dt_orig) # use a fresh copy of dt
dt[, z := x][!is.na(y), z := y][]
Run Code Online (Sandbox Code Playgroud)
如果 中有很多NA值,后者可能更有效y。
replace()方法中的错误弗兰克建议使用C8H10N4O2 在编辑他的答案时使用它replace()而不是ifelse()它。不幸的是,这两个代码不仅会生成警告,而且只会返回错误的结果:
dt <- copy(dt_orig) # use a fresh copy of dt
# C8H10N4O2's version
dt[, z := replace(y, is.na(y), x)][]
dt <- copy(dt_orig) # use a fresh copy of dt
# Frank's version
dt[, z := replace(y, which(is.na(y)), x)][]
Run Code Online (Sandbox Code Playgroud)
Run Code Online (Sandbox Code Playgroud)x y z 1: 2014-01-01 <NA> 2014-01-01 2: 2015-01-01 2015-06-01 2015-06-01 3: 2016-01-01 <NA> 2015-01-01 Warning message: In NextMethod(.Generic) : number of items to replace is not a multiple of replacement length
第z3 行中的值已从x第 2 行中复制,这是错误的。相反,它应该是从第 3 行复制的。
这里发生了什么?帮助页面上replace(x, list, values)说
replace在替换值x与给定的指标list在给出的values。
在我们的示例中,list获取行索引1, 3而values获取2014-01-01, 2015-01-01, 2016-01-01。不同的长度是警告消息的原因。并且很明显的是,第二索引在list该第3行是通过在第二值替换values其2015-01-01。
正确使用replace()requires 子集x:
dt <- copy(dt_orig) # use a fresh copy of dt
dt[, z := replace(y, is.na(y), x[is.na(y)])][]
Run Code Online (Sandbox Code Playgroud)
这产生
Run Code Online (Sandbox Code Playgroud)x y z 1: 2014-01-01 <NA> 2014-01-01 2: 2015-01-01 2015-06-01 2015-06-01 3: 2016-01-01 <NA> 2016-01-01
没有任何警告。
Date失败ifelse()帮助页面上ifelse(test, yes, no)有一个很长的警告部分,它开始
结果的模式可能取决于
test[...]的值,并且结果的类属性 [...] 取自test和 ,可能不适用于从yes和 中选择的值no。有时最好使用诸如
Run Code Online (Sandbox Code Playgroud)(tmp <- yes; tmp[!test] <- no[!test]; tmp)
将此建议应用于我们的示例
dt <- copy(dt_orig) # use a fresh copy of dt
dt[, z := {tmp <- x; tmp[!is.na(y)] <- y[!is.na(y)]; tmp}][]
Run Code Online (Sandbox Code Playgroud)
我们确实得到
Run Code Online (Sandbox Code Playgroud)x y z 1: 2014-01-01 <NA> 2014-01-01 2: 2015-01-01 2015-06-01 2015-06-01 3: 2016-01-01 <NA> 2016-01-01
library(data.table) # version 1.11.4 used
dt_orig <-data.table(x = as.Date(c("2014-1-1", "2015-1-1", "2016-1-1")),
y = as.Date(c(NA, "2015-6-1", NA)))
Run Code Online (Sandbox Code Playgroud)
由于现在有 5 种不同的方法可用,我想知道最快的方法是什么。运行时间可能取决于行数,也取决于 中NA值的份额y。
因此,包中的press()函数bench用于研究这两个参数对基准测试结果的影响。
bm <- bench::press(
n_rows = c(100, 1E4, 1E6),
na_share = c(0.1, 0.5, 0.9),
{
dt_bm <- data.table(x = as.Date("1970-01-01") + seq_len(n_rows),
y = as.Date("2970-01-01") + seq_len(n_rows))
set.seed(1L)
dt_bm[sample(seq_len(n_rows), na_share * n_rows), y := NA]
bench::mark(
ifelse = copy(dt_bm)[, z := as.Date(ifelse(is.na(y), x, y), origin="1970-01-01")][],
replace = copy(dt_bm)[, z := replace(y, is.na(y), x[is.na(y)])][],
tmp = copy(dt_bm)[, z := {tmp <- x; tmp[!is.na(y)] <- y[!is.na(y)]; tmp}][],
copy_y = copy(dt_bm)[, z := y][is.na(z), z := x][],
copy_x = copy(dt_bm)[, z := x][!is.na(y), z := y][]
)
}
)
library(ggplot2)
autoplot(bm) + theme_bw()
Run Code Online (Sandbox Code Playgroud)