note.nkmk.me

pandasで時系列データの曜日や月、四半期、年ごとの合計や平均を算出

Date: 2018-02-01 / tags: Python, pandas, 時系列データ

pandas.DataFrameのインデックスをDatetimeIndexにすると、日付や時刻など日時の情報を持つ時系列データを処理するのに便利。

DatetimeIndexは属性として曜日や月、四半期、年などの情報を取得できるので、それらを利用して時系列データの期間ごとの合計や平均を算出したりすることが可能。さらに、それらを組み合わせて、例えば四半期毎の曜日別の合計などを算出することもできる。

DatetimeIndexについてのドキュメントは以下。

インデックスをdatetime64型の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'>
スポンサーリンク

曜日: weekday, dayofweek, weekday_name

DatetimeIndexの属性weekdaydayofweekで、月曜日が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')

weekday_nameだと英語表記で取得できる。

print(df.index.weekday_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()メソッドで算出できる。

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

特定の曜日だけでなく、すべての曜日ごとの合計や平均を算出したい場合は、マルチインデックスの仕組みを使うと便利。

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

年: 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

月: 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

四半期: 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

曜日、月、四半期、年を複合して処理

マルチインデックスの列を増やすことで、曜日、月、四半期、年を複合して処理することもできる。

例として、年と曜日で分類してみる。

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
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事