📝

Rの日時データ #3 - CSVの読み書き

2022/12/18に公開約9,600字

はじめに

Rの日時データ[1]に関する記事#3です。

今回の#3は、CSVファイルからの読み書きについて紹介します。

(長いので結論を読みたい方はまとめを先にご覧ください)

CSVファイルを読み書きするパッケージ3つ

CSVファイルを読み書きするパッケージは、base, readr, data.tableがあります。

  • base: read.csv, write.csv
  • readr: read_csv, write_csv
  • data.table: fread, fwrite

各パッケージの比較はreadrライブラリのドキュメントに記載がありますので、参照ください。

https://readr.tidyverse.org/index.html#alternatives

本稿では、base, readr, data.tableの3つのパッケージついて、

  • read/write
  • 文字列に明示的にタイムゾーン情報があるか(あり(ISO8601形式) or なし)

によって、以下の12に分けて説明します。

read/write タイムゾーン情報 base readr data.table
read あり 1 2 3
read なし 4 5 6
write あり 7 8 9
write なし 10 11 12

read

タイムゾーン情報あり

以下のようなISO8601拡張形式を含むcsvファイルを読み出す場合です。

datetime
2021-08-08T02:00:00+09:00

1. base::read.csv

文字列として読み出されます。

> tf <- tempfile()
> writeLines("datetime\n2021-08-08T02:00:00+09:00", tf)
> df <- read.csv(tf)
> df$datetime
[1] "2021-08-08T02:00:00+09:00"
> class(df$datetime)
[1] "character"

適宜日時オブジェクト(POSIXct型のオブジェクト)に変換する必要があります。

> df$dt <- lubridate::ymd_hms(df$datetime)
> df$dt
[1] "2021-08-07 17:00:00 UTC"
> class(df$dt)
[1] "POSIXct" "POSIXt" 

2. readr::read_csv

日時オブジェクト(POSIXct型のオブジェクト)として読み出されます。

> df <- readr::read_csv("datetime\n2021-08-08T02:00:00+09:00")
> df$datetime
[1] "2021-08-07 17:00:00 UTC"
>

正確にパースされていますが、タイムゾーンは必ずUTCが設定されています

3. data.table::fread

動作としては、readr::read_csv と全く同じです。

> df <- data.table::fread("datetime\n2021-08-08T02:00:00+09:00")
> df$datetime
[1] "2021-08-07 17:00:00 UTC"
>

タイムゾーン情報なし

以下のようなタイムゾーンが無い日時文字列を含むcsvファイルを読み出す場合です。

datetime
2021-08-08 02:00:00

4. base::read.csv

文字列として読み出されます。

> tf <- tempfile()
> writeLines("datetime\n2021-08-08 02:00:00", tf)
> df <- read.csv(tf)
> df$datetime
[1] "2021-08-08 02:00:00"
> class(df$datetime)
[1] "character"
>

適宜日時オブジェクト(POSIXct型のオブジェクト)に変換する必要があります。

5. readr::read_csv

日時オブジェクト(POSIXct型のオブジェクト)として読み出されます。

> df <- readr::read_csv("datetime\n2021-08-08 02:00:00")
> df$datetime
[1] "2021-08-08 02:00:00 UTC"
> class(df$datetime)
[1] "POSIXct" "POSIXt" 
> 

必ずUTCの時刻として読み出されるので、UTC以外のタイムゾーンの場合には、lubridate::force_tz関数で変換する必要があります。

> df$dt <- lubridate::force_tz(df$datetime, tzone = "Asia/Tokyo")
> df$dt
[1] "2021-08-08 02:00:00 JST"
>

6. data.table::fread

動作としては、readr::read_csv と全く同じです。

必ずUTCの時刻として読み出されるので、UTC以外のタイムゾーンの場合には、lubridate::force_tz関数で変換する必要があります。

> df <- data.table::fread("id,datetime\n1,2021-08-08 02:00:00")
> df$datetime
[1] "2021-08-08 02:00:00 UTC"
> class(df$datetime)
[1] "POSIXct" "POSIXt"
> df$dt <- lubridate::force_tz(df$datetime, tzone = "Asia/Tokyo")
> df$dt
[1] "2021-08-08 02:00:00 JST"
>

write

以下のようなdata.frameを書き込みする場合です。

> library(lubridate)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> dt
[1] "2021-08-08 02:00:00 JST"
> class(dt)
[1] "POSIXct" "POSIXt" 
> x <- data.frame(id = "a", timestamp = dt)
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> x$timestamp
[1] "2021-08-08 02:00:00 JST"
> class(x$timestamp)
[1] "POSIXct" "POSIXt"

タイムゾーン情報あり

7. base::write.csv

日時オブジェクトを文字列に変換してから書き込む必要があります。

> library(lubridate)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> x <- data.frame(id = "a", timestamp = dt)
> x$timestamp <- format(x$timestamp, "%Y-%m-%dT%H:%M:%S+09:00", tz="Asia/Tokyo")
> x$timestamp
[1] "2021-08-08T02:00:00+09:00"
> class(x$timestamp)
[1] "character"
> write.csv(x, "base.csv")
> print(read_file("base.csv"))
[1] "\"\",\"id\",\"timestamp\"\n\"1\",\"a\",\"2021-08-08T02:00:00+09:00\"\n"
> 

以下のように、日時オブジェクトのまま書き込むとISO8601形式にならないので注意が必要です。

> library(lubridate)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> dt
[1] "2021-08-08 02:00:00 JST"
> class(dt)
[1] "POSIXct" "POSIXt" 
> x <- data.frame(id = "a", timestamp = dt)
> write.csv(x, "base.csv")
> print(read_file("base.csv"))
[1] "\"\",\"id\",\"timestamp\"\n\"1\",\"a\",2021-08-08 02:00:00\n"
> 

8. readr::write_csv

日時オブジェクトをそのまま書き込むと、ISO8601形式かつUTCになります。

> library(lubridate)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> x <- data.frame(id = "a", timestamp = dt)
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> readr::write_csv(x, "readr.csv")
> print(read_file("readr.csv"))
[1] "id,timestamp\na,2021-08-07T17:00:00Z\n"
>

UTC以外にするには日時オブジェクトを文字列に変換してから書き込む必要があります。

> x$timestamp <- format(x$timestamp, "%Y-%m-%dT%H:%M:%S+09:00", tz="Asia/Tokyo")
> x$timestamp
[1] "2021-08-08T02:00:00+09:00"
> class(x$timestamp)
[1] "character"
> readr::write_csv(x, "readr.csv")
> print(read_file("readr.csv"))
[1] "id,timestamp\na,2021-08-08T02:00:00+09:00\n"
> 

9. data.table::fwrite

動作としては、readr::write_csv と全く同じです。ISO8601形式かつUTCになりました。

> library(lubridate)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> x <- data.frame(id = "a", timestamp = dt)
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> data.table::fwrite(x, "dtable.csv")
> print(read_file("dtable.csv"))
[1] "id,timestamp\na,2021-08-07T17:00:00Z\n"
>

こちらも同様に、UTC以外にするには日時オブジェクトを文字列に変換してから書き込む必要があります。

> x$timestamp <- format(x$timestamp, "%Y-%m-%dT%H:%M:%S+09:00", tz="Asia/Tokyo")
> x$timestamp
[1] "2021-08-08T02:00:00+09:00"
> class(x$timestamp)
[1] "character"
> data.table::fwrite(x, "dtable.csv")
> print(read_file("dtable.csv"))
[1] "id,timestamp\na,2021-08-08T02:00:00+09:00\n"
> 

タイムゾーン情報なし

以下いずれのパッケージの場合でも、日時オブジェクトを文字列に変換してから書き込む必要があります。

10. base::write.csv

> library(lubridate)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> x <- data.frame(id = "a", timestamp = dt)
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> x$timestamp
[1] "2021-08-08 02:00:00 JST"
> class(x$timestamp)
[1] "POSIXct" "POSIXt" 
> x$timestamp <- format(x$timestamp, "%Y-%m-%d %H:%M:%S", tz="Asia/Tokyo")
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> x$timestamp
[1] "2021-08-08 02:00:00"
> class(x$timestamp)
[1] "character"
> write.csv(x, "base.csv")
> print(read_file("base.csv"))
[1] "\"\",\"id\",\"timestamp\"\n\"1\",\"a\",\"2021-08-08 02:00:00\"\n"
> 

11. readr::write_csv

> library(lubridate)
> library(readr)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> x <- data.frame(id = "a", timestamp = dt)
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> x$timestamp
[1] "2021-08-08 02:00:00 JST"
> class(x$timestamp)
[1] "POSIXct" "POSIXt" 
> x$timestamp <- format(x$timestamp, "%Y-%m-%d %H:%M:%S", tz="Asia/Tokyo")
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> x$timestamp
[1] "2021-08-08 02:00:00"
> class(x$timestamp)
[1] "character"
> readr::write_csv(x, "readr.csv")
> print(read_file("readr.csv"))
[1] "id,timestamp\na,2021-08-08 02:00:00\n"
> 

12. data.table::fwrite

> library(lubridate)
> library(data.table)
> dt <- with_tz(ymd_hms("2021-08-08T02:00:00+09:00"), tz = "Asia/Tokyo")
> x <- data.frame(id = "a", timestamp = dt)
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> x$timestamp
[1] "2021-08-08 02:00:00 JST"
> class(x$timestamp)
[1] "POSIXct" "POSIXt" 
> x$timestamp <- format(x$timestamp, "%Y-%m-%d %H:%M:%S", tz="Asia/Tokyo")
> x
  id           timestamp
1  a 2021-08-08 02:00:00
> x$timestamp
[1] "2021-08-08 02:00:00"
> class(x$timestamp)
[1] "character"
> data.table::fwrite(x, "dtable.csv")
> print(read_file("dtable.csv"))
[1] "id,timestamp\na,2021-08-08 02:00:00\n"
> 

まとめ

長い記事になりましたが、雑にまとめると各パッケージは以下のような動作になります。

base: read/writeとも文字列として扱う
readr,data.table: read/writeとも日時オブジェクトをそのまま扱う

個人的には、CSVファイルの読み書きで日時データの曖昧さを回避可能な方法は、以下の組み合わせだと思っているところです。

逆に注意が必要なのは、7. base::write.csvのケースで、特に注意せずbase::write.csvを使って書き込みすると、タイムゾーン情報のない2021-08-08 02:00:00の形式で書き込まれてしまい、別途ドキュメント等でカラムの仕様を提示する必要があります。

脚注
  1. 「日時データ」や「日時オブジェクト」といった用語は、野間口謙太郎, 菊池泰樹 訳 「統計学:Rを用いた入門書 第2版」共立出版 2016 pp.348-349 を参考にしています。 ↩︎

GitHubで編集を提案

Discussion

ログインするとコメントできます