为什么as.character()在日期列表中返回一个整数?

时间:2018-02-05 21:14:18

标签: r string date

我很惊讶地注意到R中的以下行为:

as.character(c(Sys.Date()))
#> [1] "2018-02-05"

as.character(list(Sys.Date()))
#> [1] "17567"

为什么会这样?也就是说,显然" 17567"是as.integer(Sys.Date)的结果,但我没有遵循为什么as.character(list(Sys.Date()))最终应该调用as.integer()的逻辑。

(通常将字符串视为整数可归咎于未设置 options(stringsAsFactors=FALSE),但这似乎不是这种情况。)

编辑:正如Josh所说,这是由于as.vector的基本行为,但我没有发现更直观:

as.vector(Sys.Date())
#> 17567
as.vector(Sys.Date(), "character")
#> "17567"

为什么呢? (是的,我相信日期在较低级别的内部存储为整数,但在这种情况下,这种强制到一个字面整数而没有警告似乎让我感到惊讶)。

这也以更微妙的方式表现出来:

tbl <- tibble:::as_data_frame(list(col1 = list(Sys.Date(), "stuff")))
df <- as.data.frame(tbl)
df
#>    col1
#> 1 17567
#> 2 stuff

df[1, 1]
#> [[1]]
#> [1] "2018-02-05"

请注意,data.frame的打印方法将日期显示为整数,实际上它是列表列,日期仍然是日期。

目前还不清楚在这种情况下打印方法发生了什么,以及为什么它会显示数据的这种误导性表示。

修改

Date类令人惊讶地脱落的其他示例,暴露了基础数字基类型:

vapply(list(Sys.Date()), I, Sys.Date())
vapply(list(Sys.Date()), lubridate::as_date, Sys.Date())

和我最喜欢的:

unlist(list(Sys.Date()))

看起来Date(和POSIX对象)的向量操作是脆弱的;我们应该关注mode / typeof而不是class来预测向量的行为方式。

3 个答案:

答案 0 :(得分:13)

问题最终与函数as.vector()的行为有关。

当您将as.character()应用于列表时,它会看到类"list"的对象(不是类"Date"之一)。由于列表没有as.character()方法,因此会调度默认方法as.character.default。它做了以下事情:

as.character.default
# function (x, ...) 
# .Internal(as.vector(x, "character"))
# <bytecode: 0x0000000006793e88>
# <environment: namespace:base>

如您所见,它首先通过将数据对象强制转换为向量来准备数据对象。直接在Date对象列表上运行as.vector()反过来显示它是产生整数然后变为字符的强制。

as.vector(list(Sys.Date()), "character")
# [1] "17567"

正如卡尔指出的那样,上面的解释,即使准确,也不是很令人满意。更完整的答案需要查看在.Internal(as.vector(x, "character"))调用执行的C代码中发生的情况。所有相关的C代码都在源文件coerce.c中。

首先是do_asvector(),调用ascommon()调用coerceVector(),调用coerceVectorList(),然后调用coerceToString()coerceToString() examines the "typeof"它处理的元素,在我们的例子中,看到它是“REAL”切换到this code block

case REALSXP:
PrintDefaults();
savedigits = R_print.digits; R_print.digits = DBL_DIG;/* MAX precision */
for (i = 0; i < n; i++) {
//  if ((i+1) % NINTERRUPT == 0) R_CheckUserInterrupt();
    SET_STRING_ELT(ans, i, StringFromReal(REAL(v)[i], &warn));
}
R_print.digits = savedigits;
break;

为什么它使用块为REALSXP类型的对象?因为这是R Date个对象的存储模式(通过执行mode(Sys.Date())typeof(Sys.Date())可以看到)。

带回家是这样的:在上面描述的事件链中,在R函数调用和方法调度领域中,列表的元素不会以某种方式被捕获并被视为"Date"对象。相反,它们作为"list"(又名VECSXP)传递给一系列C函数。在那一点上,它太迟了,因为处理该列表的C函数对其元素的"Date"类一无所知。特别是,最终转换为字符的函数coerceToCharacter()只能看到元素的存储模式,即REAL / numeric / double,并将它们处理为 all 他们是。

答案 1 :(得分:3)

您可以使用

中的函数format来实现您想要的效果
format(Sys.Date(), "%a %b %d")

Sys.Date提供了一个Date对象,有很多方法可以将它转换为字符串,因此需要使用特殊函数。 format会为您提供Date

的字符代表

顺便说一句:如果您只是在控制台上点击Sys.Date(),这将调用内部使用print.Date的{​​{1}},正如您在不带括号的情况下键入format所看到的那样在控制台print.Date

答案 2 :(得分:0)

paste函数是一个很好的解决方法。

paste(rep(lubridate::as_datetime(0), 2))
# [1] "1970-01-01" "1970-01-01"

base::paste将其参数(通过as.character)转换为字符串,并将其连接(由sep给定的字符串分隔)。如果参数是向量,则将它们逐项连接以给出字符向量结果。向量参数将根据需要进行回收,零长度参数将被回收为“”。

例如,如果您有POSIXct对象的列表,

l.ex = list(t1 = lubridate::as_datetime(0), t2 = lubridate::as_datetime(100))
do.call(paste, l.ex)
[1] "1970-01-01 1970-01-01 00:01:40"

使用do.call避免强迫。