7

鉴于 R 下舍入毫秒的以下问题。我如何解决它以使时间正确?

> options(digits.secs=3)
> as.POSIXlt("13:29:56.061", format='%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.060 UTC"
> as.POSIXlt("13:29:56.062", format='%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.061 UTC"
> as.POSIXlt("13:29:56.063", format='%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.063 UTC"

我注意到这个 URL 提供了背景信息,但没有解决我的问题: Milliseconds puzzle when calling strptime in R

此 URL 也涉及该问题但没有解决它:R xts: .001 毫秒 in index

在这些情况下,我确实看到以下内容:

> x <- as.POSIXlt("13:29:56.061", format='%H:%M:%OS', tz='UTC')
> print(as.numeric(x), digits=20)
[1] 1339075796.0610001087

该 URL 似乎也表明这只是一个显示问题,但我注意到使用"%OS3"没有选项行的语句似乎无法获取正确的位数。

我使用的版本是 Windows 下的 32 位 2.15.0,但这似乎在 R 的其他情况下存在。

请注意,我的原始数据是 CSV 文件中的这些日期时间字符串,我必须找到一种方法将它们从字符串转换为正确的毫秒时间。

4

4 回答 4

5

我没有看到:

> options(digits.secs = 4)
> as.POSIXlt("13:29:56.061", format = '%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.061 UTC"
> as.POSIXlt("13:29:56.062", format = '%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.062 UTC"
> as.POSIXlt("13:29:56.063", format = '%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.063 UTC"
> options(digits.secs = 3)
> as.POSIXlt("13:29:56.061", format = '%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.061 UTC"
> as.POSIXlt("13:29:56.062", format = '%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.062 UTC"
> as.POSIXlt("13:29:56.063", format = '%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.063 UTC"

> sessionInfo()
R version 2.15.0 Patched (2012-04-14 r59019)
Platform: x86_64-unknown-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=en_GB.utf8       LC_NUMERIC=C             
 [3] LC_TIME=en_GB.utf8        LC_COLLATE=en_GB.utf8    
 [5] LC_MONETARY=en_GB.utf8    LC_MESSAGES=en_GB.utf8   
 [7] LC_PAPER=C                LC_NAME=C                
 [9] LC_ADDRESS=C              LC_TELEPHONE=C           
[11] LC_MEASUREMENT=en_GB.utf8 LC_IDENTIFICATION=C      

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base

使用"%OSn"格式字符串,可以强制截断。如果小数秒不能精确地用浮点数表示,那么截断很可能会走错路。如果您发现事情出错了,您也可以明确地四舍五入到您想要的单位或添加您希望操作的分数的一半(在所示的情况下0.0005):

> t1 <- as.POSIXlt("13:29:56.061", format = '%H:%M:%OS', tz='UTC')
> t1
[1] "2012-06-07 13:29:56.061 UTC"
> t1 + 0.0005
[1] "2012-06-07 13:29:56.061 UTC"

(但我说过,我在这里看不到问题。)

后一点是Simon Urbanek 于 2012 年 5 月 30 日在 R-Devel 邮件列表中提出的

于 2012-06-07T12:54:41.533 回答
3

这与在 R 中调用 strptime 时的毫秒难题相同。

你的例子:

> x <- as.POSIXlt("13:29:56.061", format='%H:%M:%OS', tz='UTC')
> print(as.numeric(x), digits=20)
[1] 1339075796.0610001087

不代表问题。 as.numeric(x)在转换为数字之前将您的 POSIXlt 对象转换为 POSIXct,因此您会得到不同的浮点精度舍入误差。

这不是print.POSIXlt(调用format.POSIXlt)的工作方式。 format.POSIXlt单独格式化列表构造的每个元素POSIXlt,因此您需要查看:

print(x$sec, digits=20)
[1] 56.060999999999999943

这个数字在小数点后第三位被截断,所以你看56.060。直接调用就可以看到format

> format(x, "%H:%M:%OS6")
[1] "13:29:56.060999"
于 2012-06-07T14:29:17.727 回答
3

在测试中,我注意到 32 位 R 3.01 仍然存在此问题,这是由于 POSIXlt 日期时间的打印、格式和 as.character 运算符的 32 位实现所特有的浮点数据截断。

基础数据没有存储在导致截断的一种情况(32 位)而不是另一种情况(64 位)的不同类型中,但是用于POSIXlt 类型专门用于将 POSIXlt 数据显示为可显示的字符串。

虽然记录的行为是这些函数截断(忽略)额外的数字(如@Gavin Simpson 所述),但对于 32 位和 64 位版本,情况并非如此。展示; 我们将生成 1000 个不同的时间并执行一些比较操作:

> options(digits.sec=3)
> x = as.POSIXlt("13:29:56.061", format='%H:%M:%OS', tz='UTC')

> for (i in 0:999) {
>     x[i+1] = as.POSIXlt(paste0("13:29:56.",sprintf("%03d",i)),format='%H:%M:%OS',tz='UTC')
> }

> sum(x[2:1000]>x[1:999])
[1] 999

在 32 位和 64 位下,比较运算符是一致的,但是在 32 位下我看到:

> x[1:6]
[1] "2015-10-16 13:29:56.000 UTC" "2015-10-16 13:29:56.000 UTC"
[3] "2015-10-16 13:29:56.002 UTC" "2015-10-16 13:29:56.003 UTC"
[5] "2015-10-16 13:29:56.003 UTC" "2015-10-16 13:29:56.005 UTC"

所以这显然是一个显示问题。查看 POSIXlt 数据类型中的实际数字,尤其是我们可以看到发生了什么的秒数:

> y = (x[1:6]$sec) 
> y
[1] 56.000 56.001 56.002 56.003 56.004 56.005
> trunc(y*1000)/1000
[1] 56.000 56.001 56.002 56.003 56.004 56.005
> trunc((y-floor(y))*1000)/1000
[1] 0.000 0.000 0.002 0.003 0.003 0.005

我建议这是一个应该在底层基础库中修复的错误,但作为临时修复,您可以覆盖“print”、“as.character”和“format”函数以将输出更改为所需的输出例如

format.POSIXlt = function(posix) {
    return(paste0(posix$year+1900,"-",sprintf("%02d",posix$mon+1),"-",sprintf("%02d",posix$mday)," ",
        sprintf("%02d",posix$hour),":",sprintf("%02d",posix$min),":",sprintf("%002.003f",posix$sec)))
    }

print.POSIXlt = function(posix) {
    print(paste0(posix$year+1900,"-",sprintf("%02d",posix$mon+1),"-",sprintf("%02d",posix$mday)," ",
        sprintf("%02d",posix$hour),":",sprintf("%02d",posix$min),":",sprintf("%002.003f",posix$sec)))
    }

as.character.POSIXlt = function(posix) {
    return(paste0(posix$year+1900,"-",sprintf("%02d",posix$mon+1),"-",sprintf("%02d",posix$mday)," ",
        sprintf("%02d",posix$hour),":",sprintf("%02d",posix$min),":",sprintf("%002.003f",posix$sec)))
    }
于 2015-10-16T00:59:57.860 回答
1

毫秒在那里:

 unclass(as.POSIXlt("13:29:56.061", '%H:%M:%OS', tz='UTC'))
 $sec
 [1] 56.061
 ...

(这里不需要调用格式,它是参数的名称,而不是来自其他函数的必需输入)。

否则,我无法重现(在 Windows 64 位 R 2.15.0 上):

options(digits.secs = 3)
as.POSIXlt("13:29:56.061", '%H:%M:%OS', tz='UTC')
[1] "2012-06-07 13:29:56.061 UTC"

sessionInfo()
R version 2.15.0 Patched (2012-05-05 r59321)
Platform: x86_64-pc-mingw32/x64 (64-bit)
...
于 2012-06-07T12:55:56.900 回答