pandasで時系列データの曜日や月、四半期、年ごとの合計や平均を算出
pandas.DataFrame
のインデックスをDatetimeIndex
にすると、日付や時刻など日時の情報を持つ時系列データを処理するのに便利。
DatetimeIndex
は属性として曜日や月、四半期、年などの情報を取得できるので、それらを利用して時系列データの期間ごとの合計や平均を算出したりできる。さらに、それらを組み合わせて、例えば四半期毎の曜日別の合計などを算出することも可能。
ここでは、以下の内容について説明する。
resample()
による任意の期間の集計- 曜日:
weekday
,dayofweek
,day_name()
- 曜日を指定して行を抽出
- 曜日ごとの合計や平均を算出
- そのほかの属性
- 年:
year
- 四半期:
quarter
- 月:
month
,month_name()
- 週:
week
,weekofyear
- 年:
- 複数の属性(曜日、月、四半期、年など)を組み合わせて処理
インデックスをdatetime64[ns]
型のDatetimeIndex
として設定する方法などについては以下の記事を参照。
例として、以下のcsvデータを使う。
import pandas as pd
df = pd.read_csv('data/src/sample_date.csv', index_col=0, parse_dates=True)
print(df)
# val_1 val_2
# date
# 2017-11-01 65 76
# 2017-11-07 26 66
# 2017-11-18 47 47
# 2017-11-27 20 38
# 2017-12-05 65 85
# 2017-12-12 4 29
# 2017-12-22 31 54
# 2017-12-29 21 8
# 2018-01-03 98 76
# 2018-01-08 48 64
# 2018-01-19 18 48
# 2018-01-23 86 70
print(type(df.index))
# <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
resample()による任意の期間の集計
まず、DatetimeIndex
の属性ではなく、resample()
を使って任意の期間の集計(合計や平均など)を行う方法を示す。
第一引数にW
(週)、M
(月)、Q
(四半期)、Y
(年)などの頻度コードを指定し、さらにsum()
などのメソッドを呼び出す。
print(df.resample('W').sum())
# val_1 val_2
# date
# 2017-11-05 65 76
# 2017-11-12 26 66
# 2017-11-19 47 47
# 2017-11-26 0 0
# 2017-12-03 20 38
# 2017-12-10 65 85
# 2017-12-17 4 29
# 2017-12-24 31 54
# 2017-12-31 21 8
# 2018-01-07 98 76
# 2018-01-14 48 64
# 2018-01-21 18 48
# 2018-01-28 86 70
print(df.resample('M').sum())
# val_1 val_2
# date
# 2017-11-30 158 227
# 2017-12-31 121 176
# 2018-01-31 250 258
print(df.resample('Q').sum())
# val_1 val_2
# date
# 2017-12-31 279 403
# 2018-03-31 250 258
print(df.resample('Y').sum())
# val_1 val_2
# date
# 2017-12-31 279 403
# 2018-12-31 250 258
10D
(10日間)や3W
(3週間)のように数字を組み合わせて期間を指定することもできる。
print(df.resample('10D').sum())
# val_1 val_2
# date
# 2017-11-01 91 142
# 2017-11-11 47 47
# 2017-11-21 20 38
# 2017-12-01 65 85
# 2017-12-11 4 29
# 2017-12-21 52 62
# 2017-12-31 146 140
# 2018-01-10 18 48
# 2018-01-20 86 70
print(df.resample('3W').sum())
# val_1 val_2
# date
# 2017-11-05 65 76
# 2017-11-26 73 113
# 2017-12-17 89 152
# 2018-01-07 150 138
# 2018-01-28 152 182
sum()
のほか、mean()
などのメソッドが使用可能。agg()
で複数のメソッドを同時に適用することもできる。
print(df.resample('Y').mean())
# val_1 val_2
# date
# 2017-12-31 34.875 50.375
# 2018-12-31 62.500 64.500
print(df.resample('Y').agg(['sum', 'mean', 'max', 'min']))
# val_1 val_2
# sum mean max min sum mean max min
# date
# 2017-12-31 279 34.875 65 4 403 50.375 85 8
# 2018-12-31 250 62.500 98 18 258 64.500 76 48
index
の値が、D
の場合は期間の開始日時、W
, M
, Q
, Y
は終了日時となるという違いがあるので注意。
resample()
や頻度コードについての詳細は以下の記事を参照。
曜日: weekday, dayofweek, day_name()
ここからは、DatetimeIndex
の属性やマルチインデックスを利用して集計を行う方法について説明する。まずはresample()
で扱うのが難しい曜日を例とする。
DatetimeIndex
の属性weekday
またはdayofweek
で、月曜日が0
、日曜日が6
となる整数で曜日の情報を取得できる。どちらでも同じ。
print(df.index.weekday)
# Int64Index([2, 1, 5, 0, 1, 1, 4, 4, 2, 0, 4, 1], dtype='int64', name='date')
print(df.index.dayofweek)
# Int64Index([2, 1, 5, 0, 1, 1, 4, 4, 2, 0, 4, 1], dtype='int64', name='date')
day_name()
だと英語表記で取得できる。属性ではなくメソッドなので注意。なお、バージョン0.22
まではweekday_name
属性で曜日の名前を取得可能だったが、0.23
で廃止され、新しくday_name()
が追加された。
print(df.index.day_name())
# Index(['Wednesday', 'Tuesday', 'Saturday', 'Monday', 'Tuesday', 'Tuesday',
# 'Friday', 'Friday', 'Wednesday', 'Monday', 'Friday', 'Tuesday'],
# dtype='object', name='date')
曜日を指定して行を抽出
例えば、月曜日(0
)の行を抽出したい場合は、以下のようにする。
print(df[df.index.weekday == 0])
# val_1 val_2
# date
# 2017-11-27 20 38
# 2018-01-08 48 64
曜日ごとの合計や平均を算出
指定した曜日で抽出したデータの合計や平均などの統計量は、sum()
やmean()
メソッドで算出できる。agg()
でまとめて適用することも可能。
print(df[df.index.weekday == 0].sum())
# val_1 68
# val_2 102
# dtype: int64
print(df[df.index.weekday == 0].mean())
# val_1 34.0
# val_2 51.0
# dtype: float64
print(df[df.index.weekday == 0].agg(['sum', 'mean']))
# val_1 val_2
# sum 68.0 102.0
# mean 34.0 51.0
特定の曜日だけでなく、すべての曜日ごとの合計や平均を算出したい場合は、マルチインデックスの仕組みを使うと便利。
set_index()
で複数の列をインデックスに指定するとマルチインデックスになる。set_index()
については以下の記事を参照。
df_w = df.set_index([df.index.weekday, df.index])
print(df_w)
# val_1 val_2
# date date
# 2 2017-11-01 65 76
# 1 2017-11-07 26 66
# 5 2017-11-18 47 47
# 0 2017-11-27 20 38
# 1 2017-12-05 65 85
# 2017-12-12 4 29
# 4 2017-12-22 31 54
# 2017-12-29 21 8
# 2 2018-01-03 98 76
# 0 2018-01-08 48 64
# 4 2018-01-19 18 48
# 1 2018-01-23 86 70
このままだとインデックス列の名前が重複しているので、index.names
属性を書き換える。
df_w.index.names = ['weekday', 'date']
print(df_w)
# val_1 val_2
# weekday date
# 2 2017-11-01 65 76
# 1 2017-11-07 26 66
# 5 2017-11-18 47 47
# 0 2017-11-27 20 38
# 1 2017-12-05 65 85
# 2017-12-12 4 29
# 4 2017-12-22 31 54
# 2017-12-29 21 8
# 2 2018-01-03 98 76
# 0 2018-01-08 48 64
# 4 2018-01-19 18 48
# 1 2018-01-23 86 70
さらに、sort_index()
でソート。
df_w.sort_index(inplace=True)
print(df_w)
# val_1 val_2
# weekday date
# 0 2017-11-27 20 38
# 2018-01-08 48 64
# 1 2017-11-07 26 66
# 2017-12-05 65 85
# 2017-12-12 4 29
# 2018-01-23 86 70
# 2 2017-11-01 65 76
# 2018-01-03 98 76
# 4 2017-12-22 31 54
# 2017-12-29 21 8
# 2018-01-19 18 48
# 5 2017-11-18 47 47
sum()
をそのまま使うと全体の合計が算出されるが、引数level
でインデックス列の名前を指定すると、そのインデックスごとの合計(この場合は各曜日の合計)が算出される。
print(df_w.sum())
# val_1 529
# val_2 661
# dtype: int64
print(df_w.sum(level='weekday'))
# val_1 val_2
# weekday
# 0 68 102
# 1 181 250
# 2 163 152
# 4 70 110
# 5 47 47
mean()
など、他のメソッドでも同様。
print(df_w.mean(level='weekday'))
# val_1 val_2
# weekday
# 0 34.000000 51.000000
# 1 45.250000 62.500000
# 2 81.500000 76.000000
# 4 23.333333 36.666667
# 5 47.000000 47.000000
agg()
の場合はgroupby()
と合わせて使う。
print(df_w.groupby(level='weekday').agg(['sum', 'mean']))
# val_1 val_2
# sum mean sum mean
# weekday
# 0 68 34.000000 102 51.000000
# 1 181 45.250000 250 62.500000
# 2 163 81.500000 152 76.000000
# 4 70 23.333333 110 36.666667
# 5 47 47.000000 47 47.000000
そのほかの属性
曜日以外の属性をいくつか示す。一覧は公式ドキュメントを参照。
年: year
DatetimeIndex
の属性year
で、西暦の年が取得できる。
print(df.index.year)
# Int64Index([2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2018, 2018, 2018,
# 2018],
# dtype='int64', name='date')
上で説明した曜日の場合と同じ要領で、年ごとの合計や平均などを算出できる。
df_y = df.set_index([df.index.year, df.index])
df_y.index.names = ['year', 'date']
print(df_y)
# val_1 val_2
# year date
# 2017 2017-11-01 65 76
# 2017-11-07 26 66
# 2017-11-18 47 47
# 2017-11-27 20 38
# 2017-12-05 65 85
# 2017-12-12 4 29
# 2017-12-22 31 54
# 2017-12-29 21 8
# 2018 2018-01-03 98 76
# 2018-01-08 48 64
# 2018-01-19 18 48
# 2018-01-23 86 70
print(df_y.sum(level='year'))
# val_1 val_2
# year
# 2017 279 403
# 2018 250 258
四半期: quarter
DatetimeIndex
の属性quarter
で、四半期の番号が取得できる。
df_q = df.set_index([df.index.quarter, df.index])
df_q.index.names = ['quarter', 'date']
print(df_q)
# val_1 val_2
# quarter date
# 4 2017-11-01 65 76
# 2017-11-07 26 66
# 2017-11-18 47 47
# 2017-11-27 20 38
# 2017-12-05 65 85
# 2017-12-12 4 29
# 2017-12-22 31 54
# 2017-12-29 21 8
# 1 2018-01-03 98 76
# 2018-01-08 48 64
# 2018-01-19 18 48
# 2018-01-23 86 70
print(df_q.sum(level='quarter'))
# val_1 val_2
# quarter
# 4 279 403
# 1 250 258
月: month
DatetimeIndex
の属性month
で、月が取得できる。
df_m = df.set_index([df.index.month, df.index])
df_m.index.names = ['month', 'date']
print(df_m)
# val_1 val_2
# month date
# 11 2017-11-01 65 76
# 2017-11-07 26 66
# 2017-11-18 47 47
# 2017-11-27 20 38
# 12 2017-12-05 65 85
# 2017-12-12 4 29
# 2017-12-22 31 54
# 2017-12-29 21 8
# 1 2018-01-03 98 76
# 2018-01-08 48 64
# 2018-01-19 18 48
# 2018-01-23 86 70
print(df_m.sum(level='month'))
# val_1 val_2
# month
# 11 158 227
# 12 121 176
# 1 250 258
月の名前を返すmonth_name()
もある
print(df.index.month_name())
# Index(['November', 'November', 'November', 'November', 'December', 'December',
# 'December', 'December', 'January', 'January', 'January', 'January'],
# dtype='object', name='date')
週: week, weekofyear
DatetimeIndex
の属性week
またはweekofyear
で、週番号を取得できる。
df_w2 = df.set_index([df.index.week, df.index])
df_w2.index.names = ['week', 'date']
print(df_w2)
# val_1 val_2
# week date
# 44 2017-11-01 65 76
# 45 2017-11-07 26 66
# 46 2017-11-18 47 47
# 48 2017-11-27 20 38
# 49 2017-12-05 65 85
# 50 2017-12-12 4 29
# 51 2017-12-22 31 54
# 52 2017-12-29 21 8
# 1 2018-01-03 98 76
# 2 2018-01-08 48 64
# 3 2018-01-19 18 48
# 4 2018-01-23 86 70
print(df_w2.sum(level='week'))
# val_1 val_2
# week
# 44 65 76
# 45 26 66
# 46 47 47
# 48 20 38
# 49 65 85
# 50 4 29
# 51 31 54
# 52 21 8
# 1 98 76
# 2 48 64
# 3 18 48
# 4 86 70
週番号はISO 8601の定義に従っている。1月1日が第1週とは限らないので注意。月曜始まり。
この記法では、ある年における「最初の木曜日を含む週が、その年の第1週である。」と規定されている。「その年の第1週は、1月4日を含む週である。」としても、基準としては等しい。 ISO 8601 - Wikipedia
print(pd.date_range('2017-01-01', '2017-01-07').week)
# Int64Index([52, 1, 1, 1, 1, 1, 1], dtype='int64')
print(pd.date_range('2017-01-01', '2017-01-07').day_name())
# Index(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
# 'Saturday'],
# dtype='object')
複数の属性(曜日、月、四半期、年など)を組み合わせて処理
マルチインデックスの列を増やすことで、複数の属性(曜日、月、四半期、年など)を組み合わせて処理することもできる。
単純な年ごとや月ごとの処理はresample()
のほうが簡単だが、複数の属性を組み合わせた処理はマルチインデックスを利用すると便利。
例として、年と曜日で分類してみる。
df_yw = df.set_index([df.index.year, df.index.weekday, df.index])
df_yw.index.names = ['year', 'weekday', 'date']
df_yw.sort_index(inplace=True)
print(df_yw)
# val_1 val_2
# year weekday date
# 2017 0 2017-11-27 20 38
# 1 2017-11-07 26 66
# 2017-12-05 65 85
# 2017-12-12 4 29
# 2 2017-11-01 65 76
# 4 2017-12-22 31 54
# 2017-12-29 21 8
# 5 2017-11-18 47 47
# 2018 0 2018-01-08 48 64
# 1 2018-01-23 86 70
# 2 2018-01-03 98 76
# 4 2018-01-19 18 48
sum()
やmean()
の引数level
によって、全データの曜日別データを集計したり、年ごとの曜日別データを集計したりできる。
print(df_yw.sum(level='weekday'))
# val_1 val_2
# weekday
# 0 68 102
# 1 181 250
# 2 163 152
# 4 70 110
# 5 47 47
print(df_yw.sum(level=['year', 'weekday']))
# val_1 val_2
# year weekday
# 2017 0 20 38
# 1 95 180
# 2 65 76
# 4 52 62
# 5 47 47
# 2018 0 48 64
# 1 86 70
# 2 98 76
# 4 18 48
マルチインデックスの場合の列の選択、抽出は若干クセがある。詳細は以下の記事を参照。
例をいくつか示す。
print(df_yw.loc[(2017, 1), :])
# val_1 val_2
# date
# 2017-11-07 26 66
# 2017-12-05 65 85
# 2017-12-12 4 29
print(df_yw.xs(1, level='weekday'))
# val_1 val_2
# year date
# 2017 2017-11-07 26 66
# 2017-12-05 65 85
# 2017-12-12 4 29
# 2018 2018-01-23 86 70
print(df_yw.loc[(2017, [0, 4]), :])
# val_1 val_2
# year weekday date
# 2017 0 2017-11-27 20 38
# 4 2017-12-22 31 54
# 2017-12-29 21 8
print(df_yw.loc[pd.IndexSlice[:, [0, 4]], :])
# val_1 val_2
# year weekday date
# 2017 0 2017-11-27 20 38
# 4 2017-12-22 31 54
# 2017-12-29 21 8
# 2018 0 2018-01-08 48 64
# 4 2018-01-19 18 48
元の時系列データの範囲が広い場合は、最初にマルチインデックスの列をたくさん設定しておくと様々な切り口からデータを集計できるので便利。
df_yqmw = df.set_index([df.index.year, df.index.quarter, df.index.month, df.index.weekday, df.index])
df_yqmw.index.names = ['year', 'quarter', 'month', 'weekday', 'date']
df_yqmw.sort_index(inplace=True)
print(df_yqmw)
# val_1 val_2
# year quarter month weekday date
# 2017 4 11 0 2017-11-27 20 38
# 1 2017-11-07 26 66
# 2 2017-11-01 65 76
# 5 2017-11-18 47 47
# 12 1 2017-12-05 65 85
# 2017-12-12 4 29
# 4 2017-12-22 31 54
# 2017-12-29 21 8
# 2018 1 1 0 2018-01-08 48 64
# 1 2018-01-23 86 70
# 2 2018-01-03 98 76
# 4 2018-01-19 18 48
print(df_yqmw.sum(level='month'))
# val_1 val_2
# month
# 11 158 227
# 12 121 176
# 1 250 258
通年の曜日別の傾向と四半期毎の曜日別の傾向などを簡単に分析したりできる。
print(df_yqmw.sum(level='weekday'))
# val_1 val_2
# weekday
# 0 68 102
# 1 181 250
# 2 163 152
# 5 47 47
# 4 70 110
print(df_yqmw.sum(level=['quarter', 'weekday']))
# val_1 val_2
# quarter weekday
# 4 0 20 38
# 1 95 180
# 2 65 76
# 5 47 47
# 4 52 62
# 1 0 48 64
# 1 86 70
# 2 98 76
# 4 18 48
繰り返しになるが、マルチインデックスの場合の列の選択、抽出は若干クセがあるので、詳細は以下の記事を参照。
いくつか例を示しておく。
print(df_yqmw.xs(1, level='weekday'))
# val_1 val_2
# year quarter month date
# 2017 4 11 2017-11-07 26 66
# 12 2017-12-05 65 85
# 2017-12-12 4 29
# 2018 1 1 2018-01-23 86 70
print(df_yqmw.xs((1, 2017), level=('weekday', 'year')))
# val_1 val_2
# quarter month date
# 4 11 2017-11-07 26 66
# 12 2017-12-05 65 85
# 2017-12-12 4 29
print(df_yqmw.loc[pd.IndexSlice[2017, :, :, [0, 4]], :])
# val_1 val_2
# year quarter month weekday date
# 2017 4 11 0 2017-11-27 20 38
# 12 4 2017-12-22 31 54
# 2017-12-29 21 8