備忘ログ

チラシの裏的備忘録&メモ

Rで日付データを取り扱うときに使う`{base}`の`as.Date()`関数が使いにくいと思ったら`{lubridate}`の`as_date()`をつかうとよいかもしれないという個人的感想

Rで日付データを取り扱うときに使う{base}as.Date()関数が使いにくいと思ったら{lubridate}1as_date()をつかうとよいかもしれないという個人的感想、それだけの話し。ただ、少しだけ「入力した値から出力された値が意図したものになっているか確認って大事だよね」という話もする。

Rで日付データを取り扱うときに、{base}as.Date()(何も特にライブラリを呼び出さかなったときに使われる)を使って文字列等で入力された日付データを日付型に型をかえることが多いと思う。

HIDUKE <- as.Date("2021/4/1")
HIDUKE
## [1] "2021-04-01"
class(HIDUKE)
## [1] "Date"

しかし、このas.Date()は個人的にちょっと使いにくく感じることがある。具体的には、処理できない値を入力したときの挙動が使いにくい。

# 処理できない値だけ入力された場合はエラーを出力しストップする
as.Date("MOJI")
## Error in charToDate(x):  文字列は標準的な曖昧さのない書式にはなっていません
# 1つ目の値が処理できない値のときは、同じくストップする
as.Date(c("MOJI", "2021/4/1"))
## Error in charToDate(x):  文字列は標準的な曖昧さのない書式にはなっていません
# 1つ目の値が処理できる値のときは無警告で処理できない値を`NA`にする
as.Date(c("2021/4/1", "MOJI"))
## [1] "2021-04-01" NA

入力される値の順番によって出力される値が変わる(Error Messageでストップし結果が出ないとかエラーが出ずに結果が出るとか)のは使いづらい。

たとえば、

# "2021/4/1"から"2021/4/5"までの連続する5日間を意図したデータだが、
# 2021/4/4"を誤って"2021/4//4"とタイポしている。
HIDUKE <- c("2021/4/1", "2021/4/2", "2021/4/3", "2021/4//4", "2021/4/5")
# ErrorやWarning Message等なく、as.Date()で処理できる
HIDUKE <- as.Date(HIDUKE)
# 連続する5日間を意図していたのに意図しないNAが作られる。
HIDUKE
## [1] "2021-04-01" "2021-04-02" "2021-04-03" NA           "2021-04-05"

という風に処理の意図としては連続した5日間を日付型に変換したかったのに、無警告でNAが作られてしまう。ErrorやWarning Messageが出ないからと言って正しく意図した処理が行われているわけじゃない可能性があるという点も注意を要する2。これは5日分なのでわかりやすいしチェックもしやすいが、処理する値の数が多い場合だとチェックも大変になるし無警告だとそもそも意図しない処理がなされたのか気づくことすらないかもしれない。

as.Date()のドキュメントのノートに

If it specifies a date incorrectly, reliable implementations will give an error and the date is reported as NA.

と自分で書いているのに、無警告でNAを返すのは割と罠感が強い気がする3

これは、{base}as.numeric()が処理できな値はWarning Message付きでNAを返すのとは対称的。

as.numeric("MOJI")
## Warning: 強制変換により NA が生成されました

## [1] NA
# 複数の値を入力すると処理できない値が`NA`にされる
as.numeric(c("1234", "MOJI"))
## Warning: 強制変換により NA が生成されました

## [1] 1234   NA
as.numeric(c("MOJI", "1234"))
## Warning: 強制変換により NA が生成されました

## [1]   NA 1234

そこで、{lubridate}as_date()を使うと日付型にできる値は日付型に変換し{base}as.numeric()のように処理できない値はNAで返し、かつワーニングメッセージを出力するという処理ができるようになる。しかも、何個処理できない値だったか教えてくれる親切設計。

library(lubridate)
## 
##  次のパッケージを付け加えます: 'lubridate'

##  以下のオブジェクトは 'package:base' からマスクされています: 
## 
##      date, intersect, setdiff, union
as_date("MOJI")
## Warning: All formats failed to parse. No formats found.

## [1] NA
as_date(c("MOJI", "2021/4/1"))
## Warning: 1 failed to parse.

## [1] NA           "2021-04-01"
as_date(c("2021/4/1", "MOJI"))
## Warning: 1 failed to parse.

## [1] "2021-04-01" NA

ただし、{lubridate}as_date(){base}as.Date()よりも微妙な値でも受け入れ、いい感じに処理されることがあるので注意がやや必要。ただ、値を入れる順番によって出力される結果が異なるとかいう困った展開は避けられる。

ちなみに、先にあげたタイポした例はas_date()では次のようになり、

HIDUKE <- c("2021/4/1", "2021/4/2", "2021/4/3", "2021/4//4", "2021/4/5")
as_date(HIDUKE)
## [1] "2021-04-01" "2021-04-02" "2021-04-03" "2021-04-04" "2021-04-05"

頑張ってそれっぽい日付で処理しようとする。タイポがあれば警告付きでNAで返すべき派であれば良くない処理かもしれないが、個人的には寄せられているので良いんじゃないかと思うが好みが分かれると思う。

やはり入れた値と出てきた値が意図した値なのかは適宜確認が必要であることに変わりはなさそう。

ただ、(自分の分かる範囲で)as_date()は変換できない値があってNAを帰す場合はWarning messageを出力するので、as.Date()で無警告にNA返すよりもよい実装だと思う。

蛇足

{base}as.Date()のほうが比較的厳格な形の値しか受け付けないが、割とヘンテコな値を受け付けることがある。

as.Date("2021/4/1/1")
## [1] "2021-04-01"

受け付けたがちょっとこれは4月1日のタイポなのか、1月1日のタイポなのか微妙なので受け付けないでほしいと個人的には思う。

この値はas_date()は受け付けない。

as_date("2021/4/1/1")
## Warning: All formats failed to parse. No formats found.

## [1] NA

しかもちゃんとWarning Message出してくれるのでうれしい。


  1. {lubridate}ってなんて発音するのか、YouTubeとかパッケージの発音まとめたサイトをみてみると‘loobridot’(ルーブリードット)的なのが多そう。

  2. ErrorやWarning Messageが出力されていないからといって正しい(意図した)処理がなされているわけじゃなく、意図した処理がされているかチェックしていくのが大事だよというのに多少つながるかもしれない。Rの{stats}lag()の挙動に関する問題(別に問題でもなんでもない) -備忘ログ的な話。

  3. “Unfortunately some common implementations (such as glibc) are unreliable and guess at the intended meaning.”とは一応書いているが??。