特別講義DS Ch17 時系列分析(執筆中)
1 時系列回帰
前の節で質的データの処理を扱ったように,データの分析ではデータの種類に応じて手法を選択する必要があります.
代表的な時間に関するデータ
点過程データ
データの発生時刻が記録されたデータを点過程(Point Process)データと言います. 代表的なものに購入された商品に関して,購入時点の日付とともに記録したPOS(Point of Sales)データなどがあります.
- コンビニPOSデータの例
| 商品名 | 単価 (円) | 個数 | 購入日時 |
|---|---|---|---|
| おにぎり | 120 | 2 | 2024-10-01 08:15 |
| サンドイッチ | 350 | 1 | 2024-10-01 12:30 |
| ペットボトル水 | 100 | 3 | 2024-10-01 14:45 |
| カップラーメン | 220 | 1 | 2024-10-02 11:00 |
| チョコレート | 150 | 2 | 2024-10-02 15:20 |
時系列データ
決まった時間単位毎に観測項目を集計したデータ時系列(Time Series)データといいます.
例えば, 上の点過程データを日毎,月ごとなど決まった時間単位で集計することで下のような時系列データとなります.
- 日ごとに集計した時系列データの例
| 日付 | 売上合計 (円) | 購入点数 | 購入回数 |
|---|---|---|---|
| 2024-10-01 | 1060 | 6 | 3 |
| 2024-10-02 | 520 | 3 | 2 |
1.1 データの準備
以下,時系列回帰などの練習用データを準備します.
今回は,実データとして千葉商科大学の電力データを利用し,電力消費量から曜日や祝日を予測/判別してみましょう. 千葉商科大学では, 自然エネルギー100%大学の取り組みに関連して使用したエネルギーを電力の種別ごとに計測しています. 今回は2021年5月の1号館のデータを利用してみます.
ダウンロードしたデータを作業ディレクトリのデータフォルダに配置したら,いくつかの前処理を施します. まず, 元のデータを確認してみましょう.
import pandas as pd
df = pd.read_csv('./data/energy_may_1.csv')
print(df)
"""
Date 1-1L-1-1-1F西/1(照明):kWh ... 1号館動力盤1_T相/76_T(その他動力):kWh 1-太陽光発電量/82(発電):kWh
0 2021/05/01 0:00 0.000 ... 1.605610 0.0
1 2021/05/01 1:00 0.000 ... 1.555380 0.0
2 2021/05/01 2:00 0.000 ... 1.617734 0.0
3 2021/05/01 3:00 0.000 ... 1.612538 0.0
4 2021/05/01 4:00 0.000 ... 1.569237 0.0
.. ... ... ... ... ...
739 2021/05/31 19:00 0.175 ... 1.978000 0.0
740 2021/05/31 20:00 0.115 ... 1.844633 0.0
741 2021/05/31 21:00 0.000 ... 1.806529 0.0
742 2021/05/31 22:00 0.000 ... 1.811724 0.0
743 2021/05/31 23:00 0.000 ... 1.872346 0.0
"""データが読み込めたようなのでまずは,列の情報を編集してみましょう. CSVファイルのヘッダーを確認してみると以下のようになっています.
#列を確認する
cols = list(df.columns)[1:] #Dateを除外
[print(c) for c in cols]
"""
1-1L-1-1-1F西/1(照明):kWh
1-1L-1-2-1F西/2(照明):kWh
1-1L-1-3-1F西/3(照明):kWh
1-1L-1-4-1F西/4(照明):kWh
1-1L-1-5-1F西/5(照明):kWh
1-1L-1-6-1F西/6(照明):kWh
1-1L-1-7-1F~3Fロビー/7(照明):kWh
1-1L-1-8-1F西/8(照明):kWh
1-1L-1-9-1F西/9(照明):kWh
1-1L-1-10-1F西/10(照明):kWh
1-1L-2-11-1F東/11(照明):kWh
1-1L-2-12-1F東/12(照明):kWh
1-1L-2-13-1F東/13(照明):kWh
1-1L-2-14-1F東/14(照明):kWh
1-1L-2-15-1F東/15(照明):kWh
1-1L-2-16-1F東/16(照明):kWh
1-1L-2-17-1F東/17(照明):kWh
1-1L-11-18-1101教室/18(照明):kWh
1-1L-11-19-1101教室/19(照明):kWh
1-1L-11-20-1101教室/20(照明):kWh
1-1L-11-21-1101教室/21(照明):kWh
1-1L-11-22-1101教室/22(照明):kWh
1-1L-11-23-1101教室/23(照明):kWh
1-1L-12-24-1102教室/24(照明):kWh
1-1L-12-25-1102教室/25(照明):kWh
1-1L-13-26-1103教室/26(照明):kWh
1-1L-13-27-1103教室/27(照明):kWh
1-1L-14-28-1104教室/28(照明):kWh
1-1L-14-29-1104教室/29(照明):kWh
1-1L-14-30-1104教室/30(照明):kWh
1-1L-14-31-1104教室/31(照明):kWh
1-1L-14-32-1104教室/32(照明):kWh
1-1L-14-33-1104教室/33(照明):kWh
1-2L-1-34-2F西/34(照明):kWh
1-2L-1-35-2F西/35(照明):kWh
1-2L-1-36-2F西/36(照明):kWh
1-2L-1-37-2F西/37(照明):kWh
1-2L-1-38-2F西 1201共同研究室1_2照明/38(照明):kWh
1-2L-1-39-2F西/39(照明):kWh
1-2L-1-40-2F西/40(照明):kWh
1-2L-1-41-2F西 共同研究室3~6照明/41(照明):kWh
1-2L-2-42-2F東/42(照明):kWh
1-2L-2-43-2F東/43(照明):kWh
1-2L-2-44-2F東/44(照明):kWh
1-2L-2-45-1202~1204照明/45(照明):kWh
1-2L-2-46-2F東/46(照明):kWh
1-2L-2-47-2F東/47(照明):kWh
1-2L-2-48-1205~1207照明/48(照明):kWh
1-2L-H-49-1208教室/49(照明):kWh
1-2L-I-50-1209教室/50(照明):kWh
1-2L-J-51-1210教室/51(照明):kWh
1-2L-K-52-1211教室/52(照明):kWh
1-2L-L-53-1212教室/53(照明):kWh
1-3L-1-54-3F東/54(照明):kWh
1-3L-1-55-3F東/55(照明):kWh
1-3L-1-56-3F東 ラボスクウェア1_2照明/56(照明):kWh
1-3L-1-57 共同研究室7~9/57(照明):kWh
1-3L-1-58 ラボスクウェア1_2照明/58(照明):kWh
1-3L-1-59 3F東/59(照明):kWh
1-3L-1-60 3F東/60(照明):kWh
1-3L-1-61 共同研究室10~12/61(照明):kWh
1-3L-2-62 3F西/62(照明):kWh
1-3L-2-63 3F西/63(照明):kWh
1-3L-2-64 3F西 ラボスクウェア3_4照明/64(照明):kWh
1-3L-2-65 3F西/65(照明):kWh
1-3L-2-66 共同研究室13西照明/66(照明):kWh
1-3L-2-67 ラボスクウェア3_4照明/67(照明):kWh
1-3L-2-68 3F西/68(照明):kWh
1-3L-2-69 3F西/69(照明):kWh
1-3L-2-70 3F西/70(照明):kWh
1-3L-2-71 3F西 共同研究室13東照明/71(照明):kWh
1号館電灯盤1_R相/73(主幹):kWh
1号館電灯盤2_R相/74(主幹):kWh
1号館電灯盤3_R相/75(主幹):kWh
1号館動力盤1_R相/76(その他動力):kWh
1-GHP-77/77GHP(空調熱源):m3
1号館電灯盤1_T相/73_T(照明等その他):kWh
1号館電灯盤2_T相/74_T(照明等その他):kWh
1号館電灯盤3_T相/75_T(照明等その他):kWh
1号館動力盤1_T相/76_T(その他動力):kWh
1-太陽光発電量/82(発電):kWh
"""
#利用対象部分の抽出
print(list(set([c[c.find('(') : c.find(')')+1] for c in cols])))
# >>> ['(発電)', '(照明)', '(空調熱源)', '(照明等その他)', '(その他動力)', '(主幹)']このデータでは1号館を「1F西/1,1F西/2」など分電盤毎に分割し,かつ電力の利用対象を「照明,主幹,空調熱源,照明等その他,その他動力,発電」の5種類に分けて記録しています. このうち「照明等その他」はコンセントなどの電源の利用を表しています. この分類は分析の練習用としては細かすぎるので,「照明,電源,動力,空調」の4つにまとめてみます.
# 列の整形
# 場所は無視して,照明(Lighting),電源(Others),動力(Power),空調(Air),だけに分ける
# '照明'を含む列の抽出
df['Lighting'] = sum([df[x] for x in df.filter(like='(照明)').columns])
# ''を含む列の抽出
df['Others'] = sum([df[x] for x in df.filter(like='(照明等その他)').columns])
# '動力'を含む列の抽出
df['Power'] = sum([df[x] for x in df.filter(like='(その他動力)').columns])
# ‘空調'を含む列の抽出
df['Air'] = sum([df[x] for x in df.filter(like='(空調熱源)').columns])
# 結合した列以外を削除
df = df[['Date','Lighting', 'Others', 'Power', 'Air']]
# totalを追加
df['Total'] = sum(df[x] for x in ['Lighting', 'Others', 'Power', 'Air'])
print(df)
"""
Date Lighting Others Power Air Total
0 2021/05/01 0:00 0.000 6.518 2.876935 0 9.394935
1 2021/05/01 1:00 0.000 6.490 2.835366 0 9.325366
2 2021/05/01 2:00 0.000 6.558 2.982589 0 9.540589
3 2021/05/01 3:00 0.000 6.444 2.932360 0 9.376360
4 2021/05/01 4:00 0.000 6.724 2.864810 0 9.588810
.. ... ... ... ... ... ...
739 2021/05/31 19:00 6.773 10.792 3.574950 0 21.139950
740 2021/05/31 20:00 3.743 8.120 3.257986 0 15.120986
741 2021/05/31 21:00 0.118 6.636 3.230273 0 9.984273
742 2021/05/31 22:00 0.000 6.148 3.083049 0 9.231049
743 2021/05/31 23:00 0.000 6.854 3.183508 0 10.037508
"""一般的に,大学のイベントは時間割に沿って行われるため,電気使用量は曜日によって異なります.
そこで,曜日や祝日の情報を追加して情報量を増やしてみましょう.
datetime型は,.weekday属性で曜日が取得できます. .weekdayは0が月曜日,6が日曜日の数値で返ってくるので,分かりやすく文字列に変更します.
また,土日をHoliday列で1とする,Holidayフラグも作成します.
# 曜日の取得
print('曜日の取得')
import datetime as dt
# 日付をインデックスに変更
print(df.index) #現在は,行数がインデックスになっている
# RangeIndex(start=0, stop=744, step=1)
print('-'*10)
#indexを日付に変更
df.set_index('Date', inplace=True)
print(df.index) # インデックスが変更できたことが確認できる
"""
Index(['2021/05/01 0:00', '2021/05/01 1:00', '2021/05/01 2:00',
'2021/05/01 3:00', '2021/05/01 4:00', '2021/05/01 5:00',
'2021/05/01 6:00', '2021/05/01 7:00', '2021/05/01 8:00',
'2021/05/01 9:00',
...
'2021/05/31 14:00', '2021/05/31 15:00', '2021/05/31 16:00',
'2021/05/31 17:00', '2021/05/31 18:00', '2021/05/31 19:00',
'2021/05/31 20:00', '2021/05/31 21:00', '2021/05/31 22:00',
'2021/05/31 23:00'],
dtype='object', name='Date', length=744)
"""
print('-'*10)
# 現在は日付と時間がただの文字列なので,datatime型に変換する
df.index = pd.to_datetime(df.index)
print(df.index)
"""
DatetimeIndex(['2021-05-01 00:00:00', '2021-05-01 01:00:00',
'2021-05-01 02:00:00', '2021-05-01 03:00:00',
'2021-05-01 04:00:00', '2021-05-01 05:00:00',
'2021-05-01 06:00:00', '2021-05-01 07:00:00',
'2021-05-01 08:00:00', '2021-05-01 09:00:00',
...
'2021-05-31 14:00:00', '2021-05-31 15:00:00',
'2021-05-31 16:00:00', '2021-05-31 17:00:00',
'2021-05-31 18:00:00', '2021-05-31 19:00:00',
'2021-05-31 20:00:00', '2021-05-31 21:00:00',
'2021-05-31 22:00:00', '2021-05-31 23:00:00'],
dtype='datetime64[ns]', name='Date', length=744, freq=None)
"""
print('-'*10)
# 曜日を入れる
df['weekday'] = df.index.weekday
dayOfWeek = { 0:'Mon'
, 1:'Tue'
, 2:'Wed'
, 3:'Thu'
, 4:'Fri'
, 5:'Sat'
, 6:'Sun'}
df['weekday'] = df['weekday'].map(dayOfWeek)
# 曜日を平日かどうかで分ける
df['Holiday'] = 0
for i in df.index:
if 'Sat' == df.at[i,'weekday']:
df.at[i,'Holiday'] = 1
elif 'Sun' == df.at[i,'weekday']:
df.at[i,'Holiday'] = 1
else:
df.at[i,'Holiday'] = 0
print(df)
"""
Lighting Others Power Air Total weekday Holiday
Date
2021-05-01 00:00:00 0.000 6.518 2.876935 0 9.394935 Sat 1
2021-05-01 01:00:00 0.000 6.490 2.835366 0 9.325366 Sat 1
2021-05-01 02:00:00 0.000 6.558 2.982589 0 9.540589 Sat 1
2021-05-01 03:00:00 0.000 6.444 2.932360 0 9.376360 Sat 1
2021-05-01 04:00:00 0.000 6.724 2.864810 0 9.588810 Sat 1
... ... ... ... ... ... ... ...
2021-05-31 19:00:00 6.773 10.792 3.574950 0 21.139950 Mon 0
2021-05-31 20:00:00 3.743 8.120 3.257986 0 15.120986 Mon 0
2021-05-31 21:00:00 0.118 6.636 3.230273 0 9.984273 Mon 0
2021-05-31 22:00:00 0.000 6.148 3.083049 0 9.231049 Mon 0
2021-05-31 23:00:00 0.000 6.854 3.183508 0 10.037508 Mon 0
"""1日の中でも授業時間とそうでない時間によって,消費電力の傾向は異なりそうです. 授業時間割を授業前(before_class),1~5時限,昼休み(break),授業後(after_class)として設定してみましょう.
ここまでの作業ができたら,energy_may_hour.csvとして保存してみます.
#授業時間割の情報を追加する
#2020年から
#https://www.cuc.ac.jp/dpt_grad_sch/graduate_sch/accounting/hours/index.html
#1時限 9:00~10:45
#2時限 10:55~12:40
#昼休み 12:40~13:30
#3時限 13:30~15:15
#4時限 15:25~17:10
#5時限 17:20~19:05
def time2CTime(x):
if 9 <= x.hour <= 11:
return '1'
elif 11 < x.hour <= 12:
return '2'
elif 12 < x.hour <= 13:
return 'break'
elif 13 < x.hour <= 15:
return '3'
elif 15 < x.hour <= 17:
return '4'
elif 17 < x.hour <= 20:
return '5'
else:
return 'after_class'
df['period'] = df.index.map(lambda x : time2CTime(x))
print(df)
"""
Lighting Others Power Air Total weekday Holiday period
Date
2021-05-01 00:00:00 0.000 6.518 2.876935 0 9.394935 Sat 1 after_class
2021-05-01 01:00:00 0.000 6.490 2.835366 0 9.325366 Sat 1 after_class
2021-05-01 02:00:00 0.000 6.558 2.982589 0 9.540589 Sat 1 after_class
2021-05-01 03:00:00 0.000 6.444 2.932360 0 9.376360 Sat 1 after_class
2021-05-01 04:00:00 0.000 6.724 2.864810 0 9.588810 Sat 1 after_class
... ... ... ... ... ... ... ... ...
2021-05-31 19:00:00 6.773 10.792 3.574950 0 21.139950 Mon 0 5
2021-05-31 20:00:00 3.743 8.120 3.257986 0 15.120986 Mon 0 5
2021-05-31 21:00:00 0.118 6.636 3.230273 0 9.984273 Mon 0 after_class
2021-05-31 22:00:00 0.000 6.148 3.083049 0 9.231049 Mon 0 after_class
2021-05-31 23:00:00 0.000 6.854 3.183508 0 10.037508 Mon 0 after_class
"""
#保存
df.to_csv('data/energy_may_hour.csv',encoding='utf-8-sig')今回は1時間毎に集計したデータを時系列分析に用いますが,次の章などで日ごとに集計したデータを利用するので,日毎の集計データも作成しておきましょう.
datetime型ではdateによって日付が取得できます.
# 1時間ごとになっているので,一日ごとに集約
df['Day'] = df.index.date
df = df.set_index(df.index.date) # 時間の部分を削除
print(df)
"""
Lighting Others Power Air Total Day
2021-05-01 0.000 6.518 2.876935 0 9.394935 2021-05-01
2021-05-01 0.000 6.490 2.835366 0 9.325366 2021-05-01
2021-05-01 0.000 6.558 2.982589 0 9.540589 2021-05-01
2021-05-01 0.000 6.444 2.932360 0 9.376360 2021-05-01
2021-05-01 0.000 6.724 2.864810 0 9.588810 2021-05-01
... ... ... ... ... ... ...
2021-05-31 6.773 10.792 3.574950 0 21.139950 2021-05-31
2021-05-31 3.743 8.120 3.257986 0 15.120986 2021-05-31
2021-05-31 0.118 6.636 3.230273 0 9.984273 2021-05-31
2021-05-31 0.000 6.148 3.083049 0 9.231049 2021-05-31
2021-05-31 0.000 6.854 3.183508 0 10.037508 2021-05-31
[744 rows x 6 columns]
"""同じ日付によってまとめて集計するような作業はfor文などでもできますが,pandasのgroupbyメソッドが便利です. groupby('列名')で指定した列名の同じデータごとにデータを区切ってくれます.
更に, .agg({'列名':'処理方法'})を追記することでグループごとにデータを集計します.
処理方法は文字列で指定し, sum(合計),mean(平均),first(最初の値)などが利用できます.
集計したデータはenergy_may_day.csvとして保存します.
# 一日単位で集計
df = df.groupby('Day').agg({'Lighting':'sum'
,'Others':'sum'
,'Power':'sum'
,'Air':'sum'
,'Total':'sum'
,'weekday':'first'
,'Holiday':'first'})
df.index = pd.to_datetime(df.index)
print(df)
"""
Lighting Others Power Air Total weekday Holiday
Day
2021-05-01 57.8590 164.026 150.925638 1 373.810638 Sat 1
2021-05-02 61.3290 170.661 153.542773 0 385.532773 Sun 1
2021-05-03 52.5590 153.860 162.455036 0 368.874036 Mon 0
2021-05-04 27.1570 142.110 190.401666 0 359.668666 Tue 0
2021-05-05 14.9370 141.365 96.532341 0 252.834341 Wed 0
2021-05-06 151.5430 252.266 163.735893 0 567.544893 Thu 0
2021-05-07 137.5760 265.376 90.321215 4 497.273215 Fri 0
2021-05-08 96.8080 202.206 159.786805 4 462.800805 Sat 1
2021-05-09 67.1230 173.335 181.672136 4 426.130136 Sun 1
2021-05-10 124.4350 234.587 187.283113 1 547.305113 Mon 0
2021-05-11 150.7050 271.478 85.611779 1 508.794779 Tue 0
2021-05-12 115.1820 231.200 142.734788 1 490.116788 Wed 0
2021-05-13 137.8290 283.480 80.014654 1 502.323654 Thu 0
2021-05-14 125.5200 235.470 176.655242 4 541.645242 Fri 0
2021-05-15 100.0060 207.944 174.805418 6 488.755418 Sat 1
2021-05-16 62.9970 189.768 101.143069 1 354.908069 Sun 1
2021-05-17 134.4200 253.678 85.963373 4 478.061373 Mon 0
2021-05-18 139.2750 275.532 85.630823 6 506.437823 Tue 0
2021-05-19 111.7410 254.229 79.302779 3 448.272779 Wed 0
2021-05-20 134.6820 258.456 96.874422 0 490.012422 Thu 0
2021-05-21 134.7470 270.402 91.587337 15 511.736337 Fri 0
2021-05-22 85.1020 214.783 89.615409 6 395.500409 Sat 1
2021-05-23 66.4360 176.641 140.140173 4 387.217173 Sun 1
2021-05-24 140.0200 237.016 151.007045 11 539.043045 Mon 0
2021-05-25 133.2290 239.769 183.644077 14 570.642077 Tue 0
2021-05-26 111.7810 220.848 167.229426 11 510.858426 Wed 0
2021-05-27 135.5230 265.379 79.695086 1 481.597086 Thu 0
2021-05-28 132.4820 247.760 141.465190 4 525.707190 Fri 0
2021-05-29 131.5140 226.797 129.222198 12 499.533198 Sat 1
2021-05-30 70.8040 188.536 139.632687 5 403.972687 Sun 1
2021-05-31 123.7695 231.010 164.885098 6 525.664598 Mon 0
"""
df.to_csv('data/energy_may_day.csv',encoding='utf-8-sig')作成したデータは,それぞれ時間ごと,日ごととなります. 以下,これらのデータを利用して,時系列解析を実施していきます.