医療職からデータサイエンティストへ

統計学、機械学習に関する記事をまとめています。

日付・時刻処理の決定版!lubridateの使い方

Rで時系列データなどを集計、処理したい場合どうしてますか?

Rの基本パッケージにも時系列データを扱うための関数は用意されています。

しかし、これがまたわかりにくい.... POSIXctだの、POSIXltだの、私はなんども調べては忘れ、また調べ、また忘れを繰り返していました。

今回ご紹介する{lubridate}パッケージは、そんな時系列データ処理を簡単に行うことができるパッケージです!

{lubridate}に出会った時、いままでの苦悩を優しく慰められたような、そんな気分でした。Rでの時系列処理に困ったときは是非この記事を読んで慰められてください。

{lubridate}とは?

まず、このパッケージの名前ですが少し長いですよね?これは私の想像ですが、英語単語"lubricate"から由来しているのではないかと思っています。

"lubricate"の意味は、「潤滑にする、スベスベにする」です。つまり、いままで複雑だった時系列処理を滑らかに行うことができる、そんな意味が込められているのではないでしょうか。

実際このパッケージは、処理を滑らかに、まさにスベスベに行えます!

さっそくその使い方を見ていきましょう。

日付・時刻への変換(ymd_hms)

まずは一番基本的な使い方であり、一番よく使うであろう文字列を日時として読み込むための方法です。 これはとても直感的で、y,m,d,h,m,sの六つを並べるだけです。 それぞれ

  • y : year (年)
  • m : month (月)
  • d : day(日)
  • h : hour(時)
  • m : minutes(分)
  • s : seconds(秒)

を表しています。

> ymd("2017-1-19")
[1] "2017-01-19"

> mdy("1-19-2017")
[1] "2017-01-19"

> hms("09:08:30")
[1] "9H 8M 30S"

> ymd_hms("2019-01-19 08:30:10")
[1] "2019-01-19 08:30:10 UTC"

> ymd_hm("2019-01-19 08:30",tz="Japan")
[1] "2019-01-19 08:30:00 JST"

日付と時刻の両方を扱う関数の場合はtzでタイムゾーンを指定することができます。指定できるタイムゾーンはOlsonNames()で確認できます。(ちなみにOlsonとは人の名前で、タイムゾーン作成に貢献した人らしいです。)

{lubridate}のすごいところは、文字列がそれぽかったら自動的に判断して、変換してくれます。

> #区切りなし
> ymd_hms("190103 103040")
[1] "2019-01-03 10:30:40 UTC"
> 
> #異なる区切り文字
> ymd_hms("19/01/03 10-30-40")
[1] "2019-01-03 10:30:40 UTC"
> 
> #英語表記の月
> mdy_hms("Jan,3,19 10:30:40")
[1] "2019-01-03 10:30:40 UTC"

などなどかなり特殊な形式でも変換してくれます。

どこまで耐えられるか試してみましたが、うまく変換されない文字列を探す方が大変でした^^;

そのほかの使い方として今日の日付や今現在の日時を取得することも可能です。

> today()
[1] "2019-02-06"
> now()
[1] "2019-02-06 20:26:38 JST"

また、作成したPOSIXクラスから日時にアクセスするには

  • date()
  • year()
  • month()
  • yday() (day of the year)
  • wday() (day of the week)
  • hour()
  • minute()
  • second().

などの関数を使います。

> da <- ymd_hms("2019-02-05 04:50:20")
> date(da)
[1] "2019-02-05"
> year(da)
[1] 2019
> month(da)
[1] 2
> yday(da)
[1] 36
> wday(da)
[1] 3

year()やmonth()は分かりますが、yday()、wdayは少し分かりにくいですね。

yday()は1月1日からの日数、wday()はその週の始まりからの日数です。 2019年2月5日は火曜日なので、日曜から数えて3日です。

f:id:h-wadsworth02:20190207225044j:plain

また、month()とwday()は、labelsという引数をTRUEにすると、月や曜日のfactor型で結果が返ってきます。

> month(date,label = T)
[1]  2
12 Levels:  1 <  2 <  3 <  4 < ... < 12
> wday(date,label = T)
[1]7 Levels:<<<< ... <

これはグラフを書く時にも便利ですね!

日付・時刻の作成(make_datetime)

日付や時間がバラバラになっているとき、make_dateやmake_datetimeを使うと、連結して時刻データに変換してくれます。ここでの注意点は、文字ではなく数値で引数を渡す必要があることです。

> make_datetime(2019,2,9,22,10,10)
[1] "2019-02-09 22:10:10 UTC"

逆に言えば、数値で指定できるのでランダムな日時を生成することもできます。

randmonth <- sample(1:12,replace = T,5)
randday <- sample(1:28,replace = T,5)
randhour <- sample(0:23,replace = T,5)
randminutes <- sample(0:59,replace = T,5)

make_datetime(2019,randmonth,randday,randhour,randminutes)

[1] "2019-09-07 01:56:00 UTC"
[2] "2019-09-06 17:34:00 UTC"
[3] "2019-08-25 08:00:00 UTC"
[4] "2019-10-22 22:18:00 UTC"
[5] "2019-03-13 14:27:00 UTC"

時系列データ作成にもどうぞ!

日付・時刻の計算(Period,Duration,Interval)

さて、ここからは日付や時刻を足したり、引いたりする方法を紹介します。

{lubrigate}にはperiod,duration,intervalという3つの概念があります。おそらく一番よく使うのはperiodですが、他も知っておいて損はないので、是非読んでみてください。

Period

periodは直感的に分かりやすく、1年や1ヶ月といった期間を作成することができます。periodには以下のような関数があります。

  • years()
  • months()
  • weeks
  • hours()
  • minutes()
  • seconds()

この関数たちに数値を渡すことで、足したり、引いたりできる期間を作成してくれます。

> years(1)
[1] "1y 0m 0d 0H 0M 0S"

> months(2)
[1] "2m 0d 0H 0M 0S"
 
> days(10)+hours(10)
[1] "10d 10H 0M 0S"
 
> days(10)-minutes(48)
[1] "10d 0H -48M 0S"

#掛け算もできます 
> seconds(50)*10
[1] "500S"

もちろん、POSIXクラスにも適応できて

> da <- ymd_hms("2019-02-01 10:20:30")

> #tomorrow
> da+days(1)
[1] "2019-02-02 10:20:30 UTC"

> #last year
> da-years(1)
[1] "2018-02-01 10:20:30 UTC"

> #after 1 hour
> da+hours(1)
[1] "2019-02-01 11:20:30 UTC"

> #before 30 minutes
> da-minutes(30)
[1] "2019-02-01 09:50:30 UTC"

こんな使い方ができます。

Duration

さて、このdurationでもperiodと同じように期間を作成できるのですが、全て”秒数”で作成されるところがポイントです。関数はperiodの関数にdをつけて、

  • dyears()
  • dweeks()
  • dhours()
  • dminutes()
  • dseconds()

となります。実際にやってみましょう。

> dyears(1)
[1] "31536000s (~52.14 weeks)"

> dweeks(2)
[1] "1209600s (~2 weeks)"

> dseconds(50)*10
[1] "500s (~8.33 minutes)"

#割り算もできる
> dhours(1)/dminutes(1)
[1] 60

全て秒数で生成されているのが分かりますね。dmonthsがないのは、月によって日数が違い、秒数で表すことができないからだと思われます。また、秒数で情報を保持しているため、割り算をすることもできます。

こちらも先ほどと同じように、POSIXクラスに適応してみましょう。

> #tomorrow
> da+ddays(1)
[1] "2019-02-02 10:20:30 UTC"

> #last year
> da-dyears(1)
[1] "2018-02-01 10:20:30 UTC"

> #after 1 hour
> da+dhours(1)
[1] "2019-02-01 11:20:30 UTC"

結果は同じですね!それではperiodとdurationの違いはどこで出てくるかというと、例えばうるう年がある2016年は366日あります。2016年の1月1日に、periodとdurationで作成した1年間を足してみると何が起こるでしょうか。

#period
> ymd("2016-01-01")+years(1)
[1] "2017-01-01"

#duration
> ymd("2016-01-01")+dyears(1)
[1] "2016-12-31"

periodでは、2017年の1月1日になっていますが、durationでは2016年12月31になっています。これは、periodが1年や1ヶ月といった期間で情報を保持しているのに対して、durarionは秒数で情報を保持しており、365日分の秒数しか足せないために起こります。

f:id:h-wadsworth02:20190207225518j:plain

ちなみに2017年では、同じ結果が返ってきます。

> ymd("2017-01-01")+years(1)
[1] "2018-01-01"
> ymd("2017-01-01")+dyears(1)
[1] "2018-01-01"

普段はperiodを使っておけば問題ないですが、正確な長さとして扱いたいときはdurationを使いましょう。

Interval

最後はintervalです。これは開始と終了を設定すると、その期間を作成してくれます。期間を作成するには、interval(開始,終了)と書くか、もしくは開始%--%終了と書きます。

d1 <- ymd("2017/1/1")
d2 <- ymd("2018/1/1")
> interval(d1,d2)
[1] 2017-01-01 UTC--2018-01-01 UTC
> d1%--%d2
[1] 2017-01-01 UTC--2018-01-01 UTC

はい、何のこっちゃって感じですよね。使い方としては、ある日付や期間がその期間に含まれているかどうかを判断したり、期間の長さを表示させたりする時に使います。

{lubridate}には、これを扱う関数がいくつかあって

  • %within% : 日付や期間がある期間に含まれているかどうか
  • int_start/int_end : 期間の始まりと終わり
  • int_overlap : 二つの期間が重なっているか
  • int_length : 期間の長さ(秒数)

などがあります。実際にやってみましょう。

d3 <- ymd("2018/6/1")
d4 <- ymd("2019/1/1")

inter1 <- d1%--%d2
inter2 <- d2%--%d3
inter3 <- d2%--%d4
inter4 <- d1%--%d4

>#d2はinter1に含まれている
> d2 %within% inter1
[1] TRUE

>#inter1はinter2に含まれていない
> inter1 %within% inter2
[1] FALSE

>#inter1はinter4に含まれている
> inter1 %within% inter4
[1] TRUE

>#inter1はinter2は重なっている
> int_overlaps(inter1,inter2)
[1] TRUE

>#inter2はinter3は重なっている
> int_overlaps(inter2,inter3)
[1] TRUE

>inter4の長さ(2年分の秒数)
> int_length(inter4)
[1] 63072000

図でみるとこんな感じです。

f:id:h-wadsworth02:20190207225653j:plain

また、期間をperiodやduraionで割り算することもできます。

> inter4/years(1)
[1] 2
> inter3/days(1)
[1] 365

時系列データからある特定の期間のみ集計したい場合などに使えると思います!

まとめ

今回はRで時系列データを扱うためのパッケージ{lubridate}を紹介しました。

{lubridate}は{dplyr}などデータフレーム加工のパッケージと合わせて使うとさらに便利です! 直感的かつ簡単に日時データを扱えるので、是非使ってみてください!

www.medi-08-data-06.work

※本記事は筆者が個人的に学んだことをまとめた記事なります。数学の記法や詳細な理論、筆者の勘違い等で誤りがあった際はご指摘頂けると幸いです。

参考

ログデータ処理で始めるlubridate入門

Cheatsheets - RStudio

16 Dates and times | R for Data Science

上記サイトの和書版です。データの解析手法そのものよりも、いかに効率よくデータを解析するかが書かれています。Rに慣れ始めた頃に読むのがおすすめです!

Rではじめるデータサイエンス

Rではじめるデータサイエンス