特別講義DS Ch17 時系列分析(執筆中)

資料
Published on 2025-10-30 under the tag datascience, statistics, python

Table of Contents

1 時系列回帰

前の節で質的データの処理を扱ったように,データの分析ではデータの種類に応じて手法を選択する必要があります.

データの発生時刻が記録されたデータを点過程(Point Process)データと言います. 代表的なものに購入された商品に関して,購入時点の日付とともに記録したPOS(Point of Sales)データなどがあります.

商品名 単価 (円) 個数 購入日時
おにぎり 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属性で曜日が取得できます. .weekday0月曜日,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文などでもできますが,pandasgroupbyメソッドが便利です. 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')

作成したデータは,それぞれ時間ごと,日ごととなります. 以下,これらのデータを利用して,時系列解析を実施していきます.

1.2 古典的時系列解析

ce0f13b2-4a83-4c1c-b2b9-b6d18f4ee6d2