概要
- ggplot2 でx軸が日付・時刻のグラフを描いて,
geom_vline()
を使うときは値の型をDate
かPOSIXct
に統一しないと正常に表示されないという話 - lubridate なんかを使っているとこの辺の違いを忘れがちかもしれない
- 「
as.numeric()
変換で対処する」は今は不要
この問題はかなり昔から知られており, 関連しそうなキーワードで検索するだけでもトップに10年近く前のスタックオーバーフローの質問がヒットする. なので今あえて書く必要はないともいえるが, 昨日 R-Wakalang にこの話が投稿されたので日本語のページがすぐに見つからないと調べるのを打ち切る人が多いのかもしれないので書く意味があるのかもしれない. (実際, 今検索したらスタックオーバーフローの機械翻訳を表示するページばかりヒットして私も見る気が失せてしまった.1 なお以前紹介した Hearly の邦訳『データ可視化入門』にもこの話は書かれていない.)
私の R-wakalang の当初の私の回答もあまり正確でなかったことに注意. さらに言うならば, もう少し動作確認を進めると問題の原因が違うところにある可能性が出てきたので書く必要が増したように感じた.
詳細
話題にのぼったのは, X軸が時刻のとき, geom_vline()
で特定の日付に垂直線を引けない, というもの.
発生する条件は「X軸に指定する変数と, geom_vline()
で指定する日付・時刻が Date
型か POSIXct
型かどちらかに統一されていない」というもの2.
- X軸の変数と
geom_vline()
に与える日付・時刻の型をDate
かPOSIXct
に統一する. - (古い方法) (1) に加えさらに
geom_vline(xintercept = as.numeric(as.Date(...)))
のようにNumeric
型に変換して与える
現時点 (v3.3.5) では, (1) が最も素直なやり方だろう. 両者の型の違いは, 時刻やタイムゾーン情報を含むかどうかである. 古いバージョンでは ggplot2 の issue #1048 やスタックオーバーフローのこの質問に書かれているように単に POSIXct
を使うだけでは不十分で, geom_vline()
内では as.numeric()
が必要だったようだが, 現在はこれは不要になっている.3 しかし何らかの理由でパッケージを更新できないということもありえるので, もしかするとこちらの方法が必要なこともあるかもしれない.
なお, geom_vline
以外で日付の型が一致してない場合はそういう内容のエラー文が表示される.
2021/7/26追記: あまり使うことはなさそうだが, y軸と geom_hline()
の組み合わせでも試したところ, これと同様な挙動だった.
実際に試してみる4. まずは問題が発生しない, いずれも Date
型の場合は, 特別なことを何もしなくとも表示できる.
require(ggplot2) d <- data.frame(date = seq(as.Date("1945-01-01"), as.Date("1965-01-01"), by = "quarter")) d$date_posix <- with(d, as.POSIXct(date, tz = "Asia/Tokyo")) d$x <- 1:NROW(d) + 4 * sin(4 * 1:NROW(d)) ggplot(d, aes(x = date, y = x)) + geom_line() + geom_vline(xintercept = as.Date("1955-04-01"), linetype = 2, color = "red") + geom_vline(xintercept = as.Date("1960-04-01"), linetype = 2, color = "red")
POSIXct
に統一しても表示できる.
ggplot(d, aes(x = date_posix, y = x)) + geom_line() + geom_vline(xintercept = as.POSIXct("1955-04-01"), linetype = 2, color = "red") + geom_vline(xintercept = as.POSIXct("1960-04-01"), linetype = 2, color = "red")
X軸が Date
型で xintercept
に指定する値が POSIXct
型だと表示されない.
ggplot(d, aes(x = date, y = x)) + geom_line() + geom_vline(xintercept = as.Date("1955-04-01"), linetype = 2, color = "red") + geom_vline(xintercept = as.POSIXct("1960-04-01"), linetype = 2, color = "red")
逆も同様.
ggplot(d, aes(x = date_posix, y = x)) + geom_line() + geom_vline(xintercept = as.Date("1955-04-01"), linetype = 2, color = "red") + geom_vline(xintercept = as.POSIXct("1960-04-01"), linetype = 2, color = "red")
ちなみに複数の垂直線を引きたい場合は以下のように aes()
を使うこともできる. この場合も型の統一が必要.
ggplot(d, aes(x = date_posix, y = x)) + geom_line() + geom_vline( aes(xintercept = date_posix), linetype = 2, color = "red", data = data.frame(date_posix = as.POSIXct(c( "1955-04-01", "1960-04-01" ))) )
もう少し細かいことを言うと, geom_vline()
以外の関数では型がそろっていないとエラーで停止する. つまり「geom_vline()
だけ不適切な入力に対して本来エラーを返すべきなのになされていない」という見方もできる.
ggplot(d, aes(x = date, y = x)) + geom_line(aes(color = "Date")) + geom_point(aes(x = date_posix, color = "POSIX"))
## Error: Invalid input: date_trans works with objects of class Date only
as.numeric による古い対処法
as.numeric()
でも Date
と as.POSIXct
の変換はできない. つまり現在の最新バージョンでは as.numeric
は無意味である.
ggplot(d, aes(x = date, y = x)) + geom_line() + geom_vline(xintercept = as.numeric(as.Date("1955-04-01")), linetype = 2, color = "red") + geom_vline(xintercept = as.numeric(as.POSIXct("1960-04-01")), linetype = 2, color = "red")
すでに上げたスタックオーバーフローの質問によると, 古いバージョンでは Date
と POSIXct
いずれも統一した場合でもこれが必要があるらしいが, 今回は実際に確認していない.
補足: lubridate パッケージ使用時の注意点
いろいろな時刻や日付のフォーマットを変換したり演算したりするのに lubridate パッケージが便利だが, 便利さゆえにこの Date
型と POSIXct
型の違いを忘れがちになるのかもしれない. lubridate パッケージはおおむね, 入力で両者の違いを意識せずにすむような作りになっているが, 出力は関数によって Date
型だったり POSIXct
だったりするので注意が必要である. (POSIXlt
型もあるが ggplot2 が対応していないので説明略)
例えば ymd()
, mdy()
などの年月日までの入力を受け付ける関数はデフォルトで Date
型を返すが, タイムゾーンを指定した場合や ymd_h()
, ymd_hm()
, ymd_hms()
などは POSIXct
型を返す5. 後者は Date
型で表現できないから当たり前なのだが, この辺のルールはヘルプをよく読まないと気づかない可能性がある. ちなみに tibble
を使えばヘッダに <date>
, <dttm>
とそれぞれ表示されるので違いに気づきやすい. 最新バージョンでも当初の投稿のような現象がおこるなら, この辺を一度確認してみるといいだろう.
もとの投稿では「
Date
型であるのにうまくいかない」と書いているが, 両者ともにDate
型ならば問題は発生しないはずなので, 古いバージョンを使っている可能性がある. chagelog を流し読みしただけではいつごろ変更があったかはわからなかったが, https://github.com/r-lib/scales/pull/75 と関係があるなら2018年ごろには修正されているようだ.↩︎issue の議論では修正が難しいとコメントされて終わっているが, いつの間にか改善されている. change log を簡単に検索しただけではいつごろ修正されたのか分からなかった↩︎
R 本体のバージョンは 4.1.0, ggplot2 は 3.3.5↩︎
v1.7.10 時点の挙動↩︎