juliaで前処理大全 10.日時型

juliaで前処理大全その9です。今回は日時型を取り扱います。

日時型、日付型への変換

Q 日時型、日付型への変換

ホテルの予約レコードから、予約日時の列を日時型、日付型へ変換する問題です。 標準パッケージのDates.jlを用いることで簡単に実装できます。 一度、文字列をDateTime型に変換し、その後、Date型・Time型に変換を行います。 文字列をDateTime型に変換する際に、 ``dateformat``を指定するのがポイントでしょう。

using DataFrames, CSV, Chain, Downloads, Dates
reserve_url =  "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/reserve.csv"

reserve_df = @chain reserve_url begin
                Downloads.download
                CSV.File
                DataFrame
                transform(:reserve_datetime=>ByRow(d->DateTime(d, dateformat"yyyy-mm-dd H:M:S"))=>:reserve_datetime)
                transform(:reserve_datetime=>ByRow(Date)=>:reserve_date,
                          :reserve_datetime=>ByRow(Time)=>:reserve_time)
            end
first(reserve_df, 10) |> println
10×11 DataFrame
 Row │ reserve_id  hotel_id  customer_id  reserve_datetime     checkin_date  checkin_time  checkout_date  people_num  total_price  reserve_date  reserve_time
     │ String7     String7   String7      Dates.DateTime       Dates.Date    Dates.Time    Dates.Date     Int64       Int64        Dates.Date    Dates.Time
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ r1          h_75      c_1          2016-03-06T13:09:42  2016-03-26    10:00:00      2016-03-29              4        97200  2016-03-06    13:09:42
   2 │ r2          h_219     c_1          2016-07-16T23:39:55  2016-07-20    11:30:00      2016-07-21              2        20600  2016-07-16    23:39:55
   3 │ r3          h_179     c_1          2016-09-24T10:03:17  2016-10-19    09:00:00      2016-10-22              2        33600  2016-09-24    10:03:17
   4 │ r4          h_214     c_1          2017-03-08T03:20:10  2017-03-29    11:00:00      2017-03-30              4       194400  2017-03-08    03:20:10
   5 │ r5          h_16      c_1          2017-09-05T19:50:37  2017-09-22    10:30:00      2017-09-23              3        68100  2017-09-05    19:50:37
   6 │ r6          h_241     c_1          2017-11-27T18:47:05  2017-12-04    12:00:00      2017-12-06              3        36000  2017-11-27    18:47:05
   7 │ r7          h_256     c_1          2017-12-29T10:38:36  2018-01-25    10:30:00      2018-01-28              1       103500  2017-12-29    10:38:36
   8 │ r8          h_241     c_1          2018-05-26T08:42:51  2018-06-08    10:00:00      2018-06-09              1         6000  2018-05-26    08:42:51
   9 │ r9          h_217     c_2          2016-03-05T13:31:06  2016-03-25    09:30:00      2016-03-27              3        68400  2016-03-05    13:31:06
  10 │ r10         h_240     c_2          2016-06-25T09:12:22  2016-07-14    11:00:00      2016-07-17              4       320400  2016-06-25    09:12:22

年/月/日/時刻/分/秒/曜日への変換

各日時要素の取り出し

DateTime型から各日時要素の取り出しもDates.jlの関数で実装されています。 任意の文字列に変換するにはDates.format関数を利用する必要があります。(exportされていないので、パッケージの指定が必要です。)

dayname関数はlocaleを指定できるようになっていますが、英語にしか対応していないようです。 公式ドキュメントを見るとフランス語バージョンが書いてあってので、必要な場合は勝手に拡張しろということなのでしょう。

@chain reserve_df begin
    select(:reserve_datetime=>ByRow(year)=>:year,
           :reserve_datetime=>ByRow(month)=>:month,
           :reserve_datetime=>ByRow(day)=>:wday,
           :reserve_datetime=>ByRow(d->dayname(d,locale="english"))=>:weekday,
           :reserve_datetime=>ByRow(hour)=>:hour,
           :reserve_datetime=>ByRow(minute)=>:minute,
           :reserve_datetime=>ByRow(second)=>:second,
           :reserve_datetime=>ByRow(d->Dates.format(d, "yyyy-mm-ss H:M:S"))=>:format_str,
    )
    first(10)
    println
end
10×8 DataFrame
 Row │ year   month  wday   weekday    hour   minute  second  format_str
     │ Int64  Int64  Int64  String     Int64  Int64   Int64   String
─────┼────────────────────────────────────────────────────────────────────────────
   1 │  2016      3      6  Sunday        13       9      42  2016-03-00 13:9:42
   2 │  2016      7     16  Saturday      23      39      55  2016-07-00 23:39:55
   3 │  2016      9     24  Saturday      10       3      17  2016-09-00 10:3:17
   4 │  2017      3      8  Wednesday      3      20      10  2017-03-00 3:20:10
   5 │  2017      9      5  Tuesday       19      50      37  2017-09-00 19:50:37
   6 │  2017     11     27  Monday        18      47       5  2017-11-00 18:47:5
   7 │  2017     12     29  Friday        10      38      36  2017-12-00 10:38:36
   8 │  2018      5     26  Saturday       8      42      51  2018-05-00 8:42:51
   9 │  2016      3      5  Saturday      13      31       6  2016-03-00 13:31:6
  10 │  2016      6     25  Saturday       9      12      22  2016-06-00 9:12:22

日時差への変換

日時差の計算

予約日時とチェックイン日時の差を求め、それを年単位、月単位、日単位から秒単位まで差を求めるお題です。 年や月単位であれば、先にDateTime型から年や月を取得して変換することになります。 DateTime型の差分はMillisecond型になるため、日、時間、分、秒単位にするには少し工夫が必要です。

t1 = DateTime(2022, 1, 12, 12, 23, 34) 
t2 = DateTime(2021, 12, 24, 23, 34, 45)
dt = t1 - t2
println(dt)
1601329000 milliseconds

日単位であれば、Dates.days関数が単位変換に利用可能です。秒・分・時間に関しては自分で関数を作る必要があります。 Dates.days関数の実装を参考に秒・分・時間について変換する関数を作ってみました。

tosecond(dt::Millisecond) = div(Dates.value(dt), 1000)
tominute(dt::Millisecond) = div(Dates.value(dt), 60000)
tohour(dt::Millisecond) = div(Dates.value(dt), 3600000)
@show tohour(dt)

tohour(dt) = 444
それではやってみます。

@chain reserve_df begin
    transform([:checkin_date, :checkin_time]=>ByRow(+)=>:checkin_datetime)
    select(
        [:checkin_datetime, :reserve_datetime]=>ByRow((x, y)->year(x)-year(y))=>:diff_year,
        [:checkin_datetime, :reserve_datetime]=>ByRow((x, y)->12year(x)+month(x)-12year(y)-month(y))=>:diff_month,
        [:checkin_datetime, :reserve_datetime]=>ByRow((x, y)->Dates.days(x-y))=>:diff_days,
        [:checkin_datetime, :reserve_datetime]=>ByRow((x, y)->tohour(x-y))=>:diff_hours,
        [:checkin_datetime, :reserve_datetime]=>ByRow((x, y)->tominute(x-y))=>:diff_minutes,
        [:checkin_datetime, :reserve_datetime]=>ByRow((x, y)->tosecond(x-y))=>:diff_seconds,
        )
    first(10)
    println
end
10×6 DataFrame
 Row │ diff_year  diff_month  diff_days  diff_hours  diff_minutes  diff_seconds
     │ Int64      Int64       Int64      Int64       Int64         Int64
─────┼──────────────────────────────────────────────────────────────────────────
   1 │         0           0         19         476         28610       1716618
   2 │         0           0          3          83          5030        301805
   3 │         0           1         24         598         35936       2156203
   4 │         0           0         21         511         30699       1841990
   5 │         0           0         16         398         23919       1435163
   6 │         0           1          6         161          9672        580375
   7 │         1           1         26         647         38871       2332284
   8 │         0           1         13         313         18797       1127829
   9 │         0           0         19         475         28558       1713534
  10 │         0           1         19         457         27467       1648058

日時型の増減

日時の増減処理

日時のデータを1日、1時間、1分、1秒増やすという問題です。 DateTime型の列のそれぞれの行について、Period型のデータを足すと実現可能です。 可読性も高く、Awesomeでしょう。

@chain reserve_df begin
    select(
        :reserve_datetime => ByRow(d->d+Day(1)) => :reserve_datetime_1d,
        :reserve_date => ByRow(d->d+Day(1)) => :reserve_date_1d,
        :reserve_datetime => ByRow(d->d+Hour(1)) => :reserve_datetime_1h,
        :reserve_datetime => ByRow(d->d+Minute(1)) => :reserve_datetime_1m,
        :reserve_datetime => ByRow(d->d+Second(1)) => :reserve_datetime_1s
    )
    first(10)
    println
end
10×5 DataFrame
 Row │ reserve_datetime_1d  reserve_date_1d  reserve_datetime_1h  reserve_datetime_1m  reserve_datetime_1s
     │ Dates.DateTime       Dates.Date       Dates.DateTime       Dates.DateTime       Dates.DateTime
─────┼─────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ 2016-03-07T13:09:42  2016-03-07       2016-03-06T14:09:42  2016-03-06T13:10:42  2016-03-06T13:09:43
   2 │ 2016-07-17T23:39:55  2016-07-17       2016-07-17T00:39:55  2016-07-16T23:40:55  2016-07-16T23:39:56
   3 │ 2016-09-25T10:03:17  2016-09-25       2016-09-24T11:03:17  2016-09-24T10:04:17  2016-09-24T10:03:18
   4 │ 2017-03-09T03:20:10  2017-03-09       2017-03-08T04:20:10  2017-03-08T03:21:10  2017-03-08T03:20:11
   5 │ 2017-09-06T19:50:37  2017-09-06       2017-09-05T20:50:37  2017-09-05T19:51:37  2017-09-05T19:50:38
   6 │ 2017-11-28T18:47:05  2017-11-28       2017-11-27T19:47:05  2017-11-27T18:48:05  2017-11-27T18:47:06
   7 │ 2017-12-30T10:38:36  2017-12-30       2017-12-29T11:38:36  2017-12-29T10:39:36  2017-12-29T10:38:37
   8 │ 2018-05-27T08:42:51  2018-05-27       2018-05-26T09:42:51  2018-05-26T08:43:51  2018-05-26T08:42:52
   9 │ 2016-03-06T13:31:06  2016-03-06       2016-03-05T14:31:06  2016-03-05T13:32:06  2016-03-05T13:31:07
  10 │ 2016-06-26T09:12:22  2016-06-26       2016-06-25T10:12:22  2016-06-25T09:13:22  2016-06-25T09:12:23

季節への変換

季節への変換

こちらはホテルの予約レコードから予約月を季節に変換する問題です。 数値から季節の文字コードに変換するのは、本と同じですが、合成関数を利用して、簡潔にかけたと思います。

using CategoricalArrays

function to_season(m)
    3≤m<6 ? "spring" : 
    6≤m<9 ? "summer" : 
    9≤m<12 ? "autumn" : "winter" 
end

@chain reserve_df begin
    select(:reserve_datetime => ByRow(to_season∘month) => :reserve_datetime_season)
    transform(:reserve_datetime_season => categorical => :reserve_datetime_season)
    first(10)
    println
end
10×1 DataFrame
 Row │ reserve_datetime_season
     │ CategoricalValue…
─────┼─────────────────────────
   1 │ spring
   2 │ summer
   3 │ autumn
   4 │ spring
   5 │ autumn
   6 │ autumn
   7 │ winter
   8 │ spring
   9 │ spring
  10 │ summer

時間帯への変換

時間帯への変換はコードが記載されていないので割愛します。

平日/休日への変換

休日フラグの付与

こちらは、ホテルの予約レコードと休日のマスターレコードを内部結合するだけです。 技術的に新しいものはありません。

holiday_url =  "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/holiday_mst.csv"

holiday_mst = @chain holiday_url begin
                Downloads.download
                CSV.File
                DataFrame
            end

@chain reserve_df begin
    innerjoin(holiday_mst, on=[:checkin_date => :target_day ])
    first(10)
    println
end
10×13 DataFrame
 Row │ reserve_id  hotel_id  customer_id  reserve_datetime     checkin_date  checkin_time  checkout_date  people_num  total_price  reserve_date  reserve_time  holidayday_flg  nextday_is_holiday_flg
     │ String7     String7   String7      Dates.DateTime       Dates.Date    Dates.Time    Dates.Date     Int64       Int64        Dates.Date    Dates.Time    Bool            Bool
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ r1          h_75      c_1          2016-03-06T13:09:42  2016-03-26    10:00:00      2016-03-29              4        97200  2016-03-06    13:09:42                true                    true
   2 │ r2          h_219     c_1          2016-07-16T23:39:55  2016-07-20    11:30:00      2016-07-21              2        20600  2016-07-16    23:39:55               false                   false
   3 │ r3          h_179     c_1          2016-09-24T10:03:17  2016-10-19    09:00:00      2016-10-22              2        33600  2016-09-24    10:03:17               false                   false
   4 │ r4          h_214     c_1          2017-03-08T03:20:10  2017-03-29    11:00:00      2017-03-30              4       194400  2017-03-08    03:20:10               false                   false
   5 │ r5          h_16      c_1          2017-09-05T19:50:37  2017-09-22    10:30:00      2017-09-23              3        68100  2017-09-05    19:50:37               false                    true
   6 │ r6          h_241     c_1          2017-11-27T18:47:05  2017-12-04    12:00:00      2017-12-06              3        36000  2017-11-27    18:47:05               false                   false
   7 │ r7          h_256     c_1          2017-12-29T10:38:36  2018-01-25    10:30:00      2018-01-28              1       103500  2017-12-29    10:38:36               false                   false
   8 │ r8          h_241     c_1          2018-05-26T08:42:51  2018-06-08    10:00:00      2018-06-09              1         6000  2018-05-26    08:42:51               false                    true
   9 │ r9          h_217     c_2          2016-03-05T13:31:06  2016-03-25    09:30:00      2016-03-27              3        68400  2016-03-05    13:31:06               false                    true
  10 │ r10         h_240     c_2          2016-06-25T09:12:22  2016-07-14    11:00:00      2016-07-17              4       320400  2016-06-25    09:12:22               false                   false

つづく