read_csv_arrow 中来自 R 中箭头包的时间戳解析问题

Issue with timestamp parsing in read_csv_arrow from arrow package in R

假设有一个名为 ta_sample.csvcsv 文件,如下所示:

"BILL_DT","AMOUNT"
"2015-07-27T18:30:00Z",16000
"2015-07-07T18:30:00Z",6110
"2015-07-26T18:30:00Z",250
"2015-07-22T18:30:00Z",1000
"2015-07-06T18:30:00Z",2640000

阅读以上使用read_csv_arrow并自定义实际生产数据中始终需要的列类型:

library(arrow)
read_csv_arrow(
  "ta_sample.csv",
  col_names = c("BILL_DT", "AMOUNT"),
  col_types = "td",
  skip = 1,
  timestamp_parsers = c("%Y-%m-%dT%H:%M:%SZ"))

结果如下:

# A tibble: 5 x 2
  BILL_DT              AMOUNT
  <dttm>                <dbl>
1 2015-07-28 00:00:00   16000
2 2015-07-08 00:00:00    6110
3 2015-07-27 00:00:00     250
4 2015-07-23 00:00:00    1000
5 2015-07-07 00:00:00 2640000

这里的问题是日期增加了一天而时间消失了。这里值得一提的是data.table::fread()以及readr::read_csv()读对了,eg,

library(readr)
read_csv("ta_sample.csv")
# A tibble: 5 x 2
  BILL_DT              AMOUNT
  <dttm>                <dbl>
1 2015-07-27 18:30:00   16000
2 2015-07-07 18:30:00    6110
3 2015-07-26 18:30:00     250
4 2015-07-22 18:30:00    1000
5 2015-07-06 18:30:00 2640000

使用 strptime 解析 BILL_DT 列中的示例值也可以完美地工作,如下所示:

strptime(c("2015-07-27T18:30:00Z", "2015-07-07T18:30:00Z"), "%Y-%m-%dT%H:%M:%SZ")
[1] "2015-07-27 18:30:00 IST" "2015-07-07 18:30:00 IST"

需要调整 read_csv_arrow 中的哪些参数才能获得与 readr::read_csv() 给出的结果相同的结果?

这里发生了一些事情,但它们都与时区有关 + R + Arrow + 其他包的各个部分如何解释它们。

当 Arrow 读入时间戳时,它会将值视为 UTC。 Arrow 还不能在解析时指定替代时区 [1],因此将这些值存储为无时区(并假定为 UTC)。尽管在这种情况下,由于您拥有的时间戳是 UTC(根据 ISO_8601,末尾的 Z 表示 UTC),它们作为无时区 UTC 时间戳正确存储在 Arrow 中。时间戳的值是相同的(即它们代表相同的 UTC 时间),区别在于它们的显示方式:它们是显示为 UTC 时间还是显示为本地时区。

将时间戳转换为 R 时,将保留时区性:

> from_arrow <- read_csv_arrow(
+   "ta_sample.csv",
+   col_names = c("BILL_DT", "AMOUNT"),
+   col_types = "td",
+   skip = 1,
+   timestamp_parsers = c("%Y-%m-%dT%H:%M:%SZ"))
> 
> attr(from_arrow$BILL_DT, "tzone")
NULL

R 默认显示本地时区中没有 tzone 属性的时间戳(对我来说它当前是 CDT,对你来说它看起来像是 IST)。并且请注意,具有明确时区的时间戳显示在该时区中。

> from_arrow$BILL_DT
[1] "2015-07-27 13:30:00 CDT" "2015-07-07 13:30:00 CDT"
[3] "2015-07-26 13:30:00 CDT" "2015-07-22 13:30:00 CDT"
[5] "2015-07-06 13:30:00 CDT"

如果您想显示 UTC 时间戳,您可以做一些事情:

  1. 显式设置 tzone 属性(或者您可以使用 lubridate::with_tz() 进行相同的操作):
> attr(from_arrow$BILL_DT, "tzone") <- "UTC"
> from_arrow$BILL_DT
[1] "2015-07-27 18:30:00 UTC" "2015-07-07 18:30:00 UTC"
[3] "2015-07-26 18:30:00 UTC" "2015-07-22 18:30:00 UTC"
[5] "2015-07-06 18:30:00 UTC"
  1. 您可以在 R 会话中设置时区,这样当 R 去显示它使用 UTC 的时间时(注意:tzone 属性在此处仍未设置,但显示为 UTC,因为会话时区设置为 UTC)
> Sys.setenv(TZ="UTC")
> from_arrow <- read_csv_arrow(
 3.   "ta_sample.csv",
 4.   col_names = c("BILL_DT", "AMOUNT"),
 5.   col_types = "td",
 6.   skip = 1,
 7.   timestamp_parsers = c("%Y-%m-%dT%H:%M:%SZ"))
> from_arrow$BILL_DT
[1] "2015-07-27 18:30:00 UTC" "2015-07-07 18:30:00 UTC"
[3] "2015-07-26 18:30:00 UTC" "2015-07-22 18:30:00 UTC"
[5] "2015-07-06 18:30:00 UTC"
> attr(from_arrow$BILL_DT, "tzone")
NULL
  1. 您可以将数据读入 Arrow table,并在使用 collect() 将数据拉入 R 之前将时间戳转换为在 Arrow 中具有明确的时区。这个 csv -> Arrow table -> data.frame 是幕后发生的事情,所以这里没有额外的转换(除了转换)。如果您正在应用其他转换,那么在箭头 table 上进行操作可能会更有用 + 更高效,尽管它比前两个代码更多。
> library(arrow)
> library(dplyr)
> tab <- read_csv_arrow(
+     "ta_sample.csv",
+     col_names = c("BILL_DT", "AMOUNT"),
+     col_types = "td",
+     skip = 1,
+     as_data_frame = FALSE)
> 
> tab_df <- tab %>% 
+   mutate(BILL_DT_cast = cast(BILL_DT, timestamp(unit = "s", timezone = "UTC"))) %>%
+    collect()
> attr(tab_df$BILL_DT, "tzone")
NULL
> attr(tab_df$BILL_DT_cast, "tzone")
[1] "UTC"
> tab_df
# A tibble: 5 × 3
  BILL_DT              AMOUNT BILL_DT_cast       
  <dttm>                <dbl> <dttm>             
1 2015-07-27 13:30:00   16000 2015-07-27 18:30:00
2 2015-07-07 13:30:00    6110 2015-07-07 18:30:00
3 2015-07-26 13:30:00     250 2015-07-26 18:30:00
4 2015-07-22 13:30:00    1000 2015-07-22 18:30:00
5 2015-07-06 13:30:00 2640000 2015-07-06 18:30:00

这也让人有点困惑,因为基础 R 的 strptime() 不解析时区(这就是为什么你看到相同的时钟时间但在上面的示例中使用 IST)。 lubridate 的 [2] 解析函数 do 遵守这一点,您可以在这里看到不同之处:

> lubridate::parse_date_time(c("2015-07-27T18:30:00Z", "2015-07-07T18:30:00Z"), "YmdHMS")
[1] "2015-07-27 18:30:00 UTC" "2015-07-07 18:30:00 UTC"

[1] 虽然我们有两个与添加此功能相关的问题 https://issues.apache.org/jira/browse/ARROW-12820 and https://issues.apache.org/jira/browse/ARROW-13348

[2] 而且,lubridate 的文档甚至提到了这一点:

ISO8601 signed offset in hours and minutes from UTC. For example -0800, -08:00 or -08, all represent 8 hours behind UTC. This format also matches the Z (Zulu) UTC indicator. Because base::strptime() doesn't fully support ISO8601 this format is implemented as an union of 4 orders: Ou (Z), Oz (-0800), OO (-08:00) and Oo (-08). You can use these four orders as any other but it is rarely necessary. parse_date_time2() and fast_strptime() support all of the timezone formats. https://lubridate.tidyverse.org/reference/parse_date_time.html