R+Tidyverse:Tibbles 似乎不存储毫秒

R+Tidyverse: Tibbles don't appear to store milliseconds

我有一个包含很多值的 CSV。其中有这样存储的时间:

1:34.434

使用readr,我把它们拼成一个tibble,然后使用dplyr剪掉所有不需要的行和列。

lapData <- read_csv("sampledata/sampledata.csv")
lapData <- dplyr::select(lapData, 
                         Driver, Lap, Penalty, 'Lap Time', 'Lap type', 
                         'Pressure FR', 'Pressure FL', 'Pressure RR', 'Pressure RL', 
                         'Temp FR', 'Temp FL', 'Temp RR', 'Temp RL',
                         'Road Temp')
lapData <- dplyr::filter(lapData, Driver == "[personal info]")
lapData <- dplyr::filter(lapData, is.na(Penalty))
lapData <- dplyr::filter(lapData, Lap > 1)

然后我使用print(ggplot(data = lapData, mapping = aes(Lap, 'Lap Time')) + geom_line())打印数据,它看起来像这样:

如果不清楚,图形线的顶点将四舍五入为秒值,而不是使用我想要的完整毫秒精度。
如果我然后 print(lapData),我会在 'Lap Time' 下看到以下条目:

我做了一些研究,发现 this,这似乎表明 tibble 打印输出中的精度损失是无关紧要的,完整的数据仍然存储在 tibble 中。然而,情节与这一说法相矛盾。

如何让绘图显示完整的毫秒分辨率?

您的时间格式与 readr 默认要求的时间格式不一致。具体来说,readr1:34.434 解释为 1 小时 34 分钟,而不是 1 分 34.434 秒。

text <- "id,time
1,1:23.456
2,2:34.567
3,3:45.678
"
cat(text, file = "foo.csv")

library("readr")
tt1 <- read_csv("foo.csv")
tt1
## # A tibble: 3 × 2
##      id time  
##   <dbl> <time>
## 1     1 01:23 
## 2     2 02:34 
## 3     3 03:45

as.double(tt1$time) / 60 # numbers of minutes
## [1]  83 154 225

文档建议使用 read_csvcol_types 参数来指定时间格式。通常,这个 有效:

col_types <- list(id = col_integer(), time = col_time(format = "%M:%OS"))

但事实并非如此:

tt2 <- read_csv("foo.csv", col_types = col_types)
## Warning message:                                                                                                   
## One or more parsing issues, see `problems()` for details

tt2
## # A tibble: 3 × 2
##      id time  
##   <int> <time>
## 1     1    NA 
## 2     2    NA 
## 3     3    NA

问题是 readr 的解析器需要用零填充分钟:

parse_time("01:23.456", format = "%M:%OS")
## 00:01:23.456

parse_time("1:23.456", format = "%M:%OS")
## Warning: 1 parsing failure.
## row col         expected   actual
##   1  -- time like %M:%OS 1:23.456
## 
## NA

这让我很惊讶,因为 base R 的解析器没有那个约束:

strptime("1:23.456", format = "%M:%OS")
## [1] "2022-02-11 00:01:23 EST"

一种解决方法是将有问题的列作为字符向量读取,并在

之后通过 strptime 将其强制转换为数字
tt3 <- read_csv("foo.csv", col_types = list(id = col_integer(), time = col_character()))
tt3
## # A tibble: 3 × 2
##      id time    
##   <int> <chr>   
## 1     1 1:23.456
## 2     2 2:34.567
## 3     3 3:45.678

library("dplyr")
tt4 <- tt3 %>% mutate(seconds = with(strptime(time, format = "%M:%OS"), 60 * min + sec))
options(pillar.sigfig = 10L)
tt4
## # A tibble: 3 × 3
##      id time     seconds
##   <int> <chr>      <dbl>
## 1     1 1:23.456  83.456
## 2     2 2:34.567 154.567
## 3     3 3:45.678 225.678

您也可以使用 lubridate 进行强制转换,它有专门的解析器:

library("lubridate")
tt5 <- tt3 %>% mutate(seconds = as.double(as.duration(ms(time))))
identical(tt4, tt5)
## [1] TRUE

为了避免readr的猜测,您可以使用常规的read.csv

r <- read.csv('foo.csv')

接下来,使用纯毫秒可能会更好。为此,我们可以使用 strptime"%M:%OS",其中 returns 漂亮的 "POSIXlt" 格式,我们可以将其放入 with 并使用 minutes 和 seconds.

r$lap_time_ms <- with(strptime(r$lap_time, format="%M:%OS"), 6e4*min + 1e3*sec)

r
#   lap_time lap          x lap_time_ms
# 1 1:29.134   1  0.9292880       89134
# 2 1:26.233   2 -0.3301566       86233
# 3 1:28.033   3 -1.5426225       88033
# 4 1:26.434   4 -0.9961375       86434
# 5 1:26.634   5  1.1610645       86634
# 6 1:27.634   6 -0.2817558       87634

如果您想要 y-axis 格式化为 <secs>:<mins> 格式,请在 range 圈速中使用 sequence,步长以秒为单位。

at <- do.call(seq, c(as.list(range(ceiling(r$lap_time_ms/1e3))*1e3), 1e3))
sq <- at %/% 1e3 |> {\(.) paste(. %/% 60, . %% 60, sep=':')}()

plot(lap_time_ms ~ lap, r, type='l', yaxt='n')
axis(2, at=at, labels=sq)

ggplot2 包也应该可以做到这一点。


数据:

dat <- read.table(header=TRUE, text='lap_time
1:29.134
1:26.233
1:28.033
1:26.434 
1:26.634
1:27.634
') |> transform(lap=1:6, x=rnorm(6))
write.csv(dat, 'foo.csv', row.names=FALSE)