Tibble 默默地改变回收的 difftime 变量

Tibble silently changes recycled difftime variables

如果一个 difftime 变量包含在一个 tibble 中,并且指定的观测值数量等于其他变量,则保持该变量的 class。

tibble::tibble(a = c(1,2), b = as.difftime(c(1,2), units = "hours"))

# A tibble: 2 x 2
      a       b
  <dbl>  <time>
1     1 1 hours
2     2 1 hours

但是,如果 difftime 变量中指定的观测数是另一个变量中观测数的适当因数,因此 difftime 变量被回收,则 class 的变量静默更改为 numeric:

tibble::tibble(a = c(1,2), b = as.difftime(1, units = "hours"))

# A tibble: 2 x 2
      a     b
  <dbl> <dbl>
1     1     1
2     2     1

出现这种行为差异是否因为 tidyverse 鼓励用户使用 lubridate 提供的 periodduration 对象来指定时间,而不是基础 R difftime 对象?或者这是一个意外的错误?

使用 tibble::data_framedplyr::data_frame 时会出现同样的问题,尽管我相信这些在未来可能会被弃用。

需要说明的是,以下调用不会静默更改时间类型变量的 class:

tibble::tibble(a = c(1,2), b = lubridate::as.period("1H"))

# A tibble: 2 x 2
      a            b
  <dbl> <S4: Period>
1     1     1H 0M 0S
2     2     1H 0M 0S

tibble::tibble(a = c(1,2), b = lubridate::as.duration("1H"))

# A tibble: 2 x 2
      a                b
  <dbl>   <S4: Duration>
1     1 3600s (~1 hours)
2     2 3600s (~1 hours)

我不认为 tibble 鼓励使用 lubridate(即使我鼓励你使用它)像类型一样处理日期,但更多的是如何处理日期的问题是回收时在内部创建的矢量。事实上,当您使用 clist 时,您可以重现相同的回收行为。例如使用 c 你将输掉输入:

c(as.difftime(c(1), units = "hours"),1)
### Time differences in hours
### [1] 1 1

但是使用list会保持时差类型:

list(as.difftime(c(1), units = "hours"),2)

# [[1]]
# Time difference of 1 hours
# 
# [[2]]
# [1] 2

list 与 tibble 一起应用,您 "conserve" class 类型:

tibble::tibble(a = c(1,2), 
               b = list(as.difftime(c(1), units = "hours")))

# A tibble: 2 x 2
# a          b
# <dbl>     <list>
#   1     1 <time [1]>
#   2     2 <time [1]>

不过这个以后就很难操作了。在这种情况下最好使用 lubridate

您看到的行为源于创建数据框期间向量回收过程中的一些非常特殊的行为。如您所知,传递给 data.frame 函数的对象应该具有相同的行数。但是 atomic vectors 将在必要时被回收整数次。这就提出了以下问题为什么不起作用:

dff <- data.frame(a=c(1,2), b=as.difftime(1, units="hours"))

上面的代码引发了以下错误:

Error in data.frame(a = c(1, 2), b = as.difftime(1, units = "hours")) : arguments imply differing number of rows: 2, 1

事实证明,这不起作用的原因是 difftime 个对象的向量未被识别为原子向量。您可以检查以下内容:

is.vector(as.difftime(1, units="hours"))

这个returns:

[1] FALSE

因此,当 data.frame 函数尝试回收列 b 时,它首先检查该列是否实际上是一个向量(is.vector)。由于returnsFALSE,回收没有进行;因此错误返回。

所以,接下来的问题是:为什么不直接将 b 列转换为 as.vector

这实际上是个好主意,希望 as.vector 删除所有属性,包括名称,结果向量。您可以通过以下方式看到:

as.vector(as.difftime(1, units="hours"))

returns:

[1] 1

difftime 对象的所有属性在强制过程中都丢失了。这使我认为 tibble::data_frame 函数实际上在生成 data_frame 的过程中的某个地方使用了 as.vector。结果,我们看到以下行为:

data_frame(a=c(1,2), b=as.difftime(1, units="hours"))

returns

# A tibble: 2 x 2
      a     b
  <dbl> <dbl>
1     1     1
2     2     1

估计和@agstudy得出的结论是一样的:要维护difftime对象,可能需要对b列使用list,如下:

tibble::tibble(a = c(1,2), b = list(as.difftime(1, units = "hours")))

我希望这在某种程度上是有用的。