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

San*_*San 0 csv r parquet apache-arrow

假设有一个csv文件名为ta_sample.csv

"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
Run Code Online (Sandbox Code Playgroud)

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"))
Run Code Online (Sandbox Code Playgroud)

结果如下:

# 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
Run Code Online (Sandbox Code Playgroud)

这里的问题是日期增加一天并且时间消失。这里值得一提的是,data.table::fread()以及readr::read_csv()正确阅读它,例如,

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
Run Code Online (Sandbox Code Playgroud)

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

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"
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*ane 5

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

\n

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

\n

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

\n
> from_arrow <- read_csv_arrow(\n+   "ta_sample.csv",\n+   col_names = c("BILL_DT", "AMOUNT"),\n+   col_types = "td",\n+   skip = 1,\n+   timestamp_parsers = c("%Y-%m-%dT%H:%M:%SZ"))\n> \n> attr(from_arrow$BILL_DT, "tzone")\nNULL\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
> from_arrow$BILL_DT\n[1] "2015-07-27 13:30:00 CDT" "2015-07-07 13:30:00 CDT"\n[3] "2015-07-26 13:30:00 CDT" "2015-07-22 13:30:00 CDT"\n[5] "2015-07-06 13:30:00 CDT"\n
Run Code Online (Sandbox Code Playgroud)\n

如果您想显示 UTC 时间戳,您可以执行以下操作:

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

这也让人更加困惑,因为基本 Rstrptime()不解析时区(这就是为什么你看到相同的时钟时间,但在上面的示例中使用 IST)。lubridate\'s[2] 解析函数确实尊重这一点,您可以在这里看到区别:

\n
> lubridate::parse_date_time(c("2015-07-27T18:30:00Z", "2015-07-07T18:30:00Z"), "YmdHMS")\n[1] "2015-07-27 18:30:00 UTC" "2015-07-07 18:30:00 UTC"\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
\n

ISO8601 与 UTC 的小时和分钟偏移量。例如 -0800、-08:00 或 -08,均表示比 UTC 晚 8 小时。此格式还与 Z (Zulu) UTC 指示器匹配。因为 base::strptime() 不完全支持 ISO8601,所以该格式被实现为 4 个指令的并集:Ou (Z)、Oz (-0800)、OO (-08:00) 和 Oo (-08)。您可以像使用其他命令一样使用这四个命令,但很少有必要。parse_date_time2() 和 fast_strptime() 支持所有时区格式。\n https://lubridate.tidyverse.org/reference/parse_date_time.html

\n
\n