【问题标题】:Strange behavior involving dates - "origin must be supplied"涉及日期的奇怪行为 - “必须提供原点”
【发布时间】:2015-07-07 16:39:04
【问题描述】:

我有一个这样的data.table

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>

我想添加一列z,它等于 y,其中 y 不为 NA,否则为 x。

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

但由于某种原因,上述语句将 z 转换为数字。如果我尝试将其转换为带有 as.Date 的日期,则会出现错误

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

什么给了我,我如何完成我想做的事?

【问题讨论】:

标签: r date data.table


【解决方案1】:

这个老问题现在已经被浏览了超过一万次。

虽然它有一个公认的答案,但我觉得这个问题值得

  • 正版data.table解决方案,
  • 解释为什么Date 失败并出现ifelse()
  • 为什么replace() 方法会返回错误的结果。

data.table 接近

data.tableifelse()replace() 可以写成两个链式赋值操作,其中第二个使用子集:

dt[, z := y][is.na(z), z := x][]
            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

第一个赋值操作通过复制y 列来创建一个新列z。第二个赋值操作通过将x 列的内容仅复制到zNA 的那些行 来修改z 就地

或者,我们可以先复制x,然后将z 值替换为非NA y 值:

dt <- copy(dt_orig)   # use a fresh copy of dt
dt[, z := x][!is.na(y), z := y][]

如果y 中有许多NA 值,后者可能会更有效。

replace() 方法中的错误

Frank has suggested 使用replace() 而不是ifelse(),后者被C8H10N4O2 in an edit of his answer 拾取。不幸的是,这两个代码不仅会产生警告,而且只是返回错误的结果

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)][]
            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

第 3 行中 z 的值已从第 2 行中的 x 复制,这是错误的。相反,它应该是从第 3 行复制过来的。

这里发生了什么? replace(x, list, values) 上的帮助页面说

replacex 中的值替换为list 中给出的索引,这些索引由values 中给出。

在我们的示例中,list 获取行索引 1, 3,而 values 获取 2014-01-01, 2015-01-01, 2016-01-01。不同的长度是警告消息的原因。很明显,list 中的第二个索引(即第 3 行)被values 中的第二个值替换为2015-01-01

replace() 的正确使用也需要子集x

dt <- copy(dt_orig)   # use a fresh copy of dt
dt[, z := replace(y, is.na(y), x[is.na(y)])][]

产生

            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,可能不适用于从 yesno

有时最好使用诸如

之类的结构
(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}][]

我们确实得到了

            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)))

基准测试

由于现在有 5 种不同的方法可用,我想知道最快的方法是什么。运行时间可能取决于行数,也取决于NA 值在y 中的份额。

因此,bench 包中的 press() 函数用于研究这两个参数对基准测试结果的影响。

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()

【讨论】:

    【解决方案2】:

    当 R 将日期视为整数时,它的 起源是 1970 年 1 月 1 日。

    https://stats.idre.ucla.edu/r/faq/how-does-r-handle-date-values/

    dt[, z:=as.Date(ifelse(is.na(y), x, y), origin="1970-01-01")]
    

    更新: 正如弗兰克建议的那样,这似乎也有效并且似乎不需要非强制:dt[, z:=replace(y, is.na(y), x)]。它会引发警告,因此请谨慎使用。

    【讨论】:

    • 而不是ifelsereplace 在这里很自然。此外,对于帮助文件,您可能需要提供代码以联系他们?dates,而不是链接。
    • @Frank - 感谢您的提示,尽管我没有发现 ?dates 非常有启发性。我也不确定为什么replace 会抛出number of items to replace is not a multiple of replacement length,因为is.na(y)x 的长度都是3。
    • 哦,我的错。我将您的网址误读为 ethz 网址(仅反映内置文档)。替换语法是 replace(y,which(is.na(y)),x) ...不确定这是否能解决您遇到的错误。
    • 使用replace() 的两种变体都返回错误的结果。警告不能被忽视。详情请见here
    【解决方案3】:
    dt[, z:=as.Date(ifelse(is.na(y), x, y),origin="1970-01-01")]
    dt
                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
    

    【讨论】:

      猜你喜欢
      • 2011-02-17
      • 2010-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      • 2018-04-01
      • 1970-01-01
      相关资源
      最近更新 更多