kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Pandas で時系列データをグループ化して集計できる「Grouper」

Pandasgroupby() 関数を使うと,データセットをグループ化して集計できる.さらに Grouper オブジェクトと組み合わせると,より高機能なグループ化を実現できる.今回は groupby() 関数と Grouper オブジェクトを組み合わせて「時系列データの集計」を試す.最後に関連する resample() 関数も試す.

データセット 🪢

今回使うサンプルデータセットを準備する.まず,Pandas の date_range() 関数を使って 2020/1/1 ~ 2020/12/31 の範囲で1年間の DatetimeIndex を作る.そして DatetimeIndex をインデックス値とした DataFrame を作る.今回は count カラムとして 1 ~ 99 の範囲で乱数を含めておく.乱数は Numpyrandom.randint() 関数を使う.結果的に以下のような DataFrame になる.

dates = pd.date_range(start='2020-01-01', end='2020-12-31')
dates
# DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
#                '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
#                '2020-01-09', '2020-01-10',
#                ...
#                '2020-12-22', '2020-12-23', '2020-12-24', '2020-12-25',
#                '2020-12-26', '2020-12-27', '2020-12-28', '2020-12-29',
#                '2020-12-30', '2020-12-31'],
#               dtype='datetime64[ns]', length=366, freq='D')

df = pd.DataFrame(np.random.randint(1, 100, 366), index=dates, columns=['count'])
df
# 2020-01-01   13
# 2020-01-02   50
# 2020-01-03   44
# 2020-01-04   12
# 2020-01-05   25
# 2020-01-06   82
# 2020-01-07   4
# 2020-01-08   2
# 2020-01-09   27
# 2020-01-10   38
# (中略)

f:id:kakku22:20210511124750p:plain

groupby() 関数と Grouper オブジェクトを組み合わせる 🪢

以下のように DataFrame に対して groupby() 関数と Grouper オブジェクトを組み合わせる.まず pd.Grouper(freq='M')pd.Grouper(freq='Q') を試す.M「月末集計」Q「四半期集計」となる.そして今回は mean() 関数を使って「平均値」を取得する.このように Grouper オブジェクト を使うと簡単に「時系列データの集計」を実現できる.

  • M : month end frequency
  • Q : quarter end frequency
# month end frequency
df.groupby(pd.Grouper(freq='M')).mean()
# 2020-01-31   40.096774
# 2020-02-29   47.655172
# 2020-03-31   46.064516
# 2020-04-30   54.766667
# 2020-05-31   54.354839
# 2020-06-30   49.100000
# 2020-07-31   57.000000
# 2020-08-31   46.387097
# 2020-09-30   41.333333
# 2020-10-31   36.967742
# 2020-11-30   40.266667
# 2020-12-31   57.645161

# quarter end frequency
df.groupby(pd.Grouper(freq='Q')).mean()
# 2020-03-31   44.538462
# 2020-06-30   52.758242
# 2020-09-30   48.315217
# 2020-12-31   45.010870

f:id:kakku22:20210511125429p:plain

また以下のドキュメント(DateOffset objectsOffset aliasesAnchored offsets)を読むと freq に指定できる識別子は他にも多くある.代表的な例を以下に抜粋した.

  • W : weekly frequency
  • SM : semi-month end frequency (15th and end of month)
  • H : hourly frequency
  • W-SUN : weekly frequency (Sundays). Same as ‘W’
  • W-MON : weekly frequency (Mondays)
  • W-TUE : weekly frequency (Tuesdays)
  • etc

pandas.pydata.org

例えば pd.Grouper(freq='W-MON') を指定すると「週次集計」で軸にする「曜日(ここでは月曜日)」を指定できる.デフォルトでは WW-SUN となる.正確に値を計算すると 2020/1/13 (月)32.4285712020/1/7 (火) ~ 2020/1/13 (月) の期間での平均値となるため,計算式としては (4 + 2 + 27 + 38 + 23 + 62 + 71) / 7 = 32.428571 として確認できる.

# weekly frequency (Mondays)
df.groupby(pd.Grouper(freq='W-MON')).mean()
# 2020-01-06   37.666667
# 2020-01-13   32.428571
# 2020-01-20   47.142857
# 2020-01-27   35.714286
# 2020-02-03   37.571429
# 2020-02-10   66.000000
# 2020-02-17   43.285714
# 2020-02-24   48.000000
# (中略)

f:id:kakku22:20210511130035p:plain

Grouper オブジェクトの key パラメータ 🪢

デフォルトの挙動では DataFrame のインデックス値が DatetimeIndexTimedeltaIndex である必要があるため,それ以外の DataFrame だと TypeError: Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'RangeIndex' というエラーが出る.その場合は Grouper オブジェクトの key パラメータを使ってカラムを指定する.

動作確認をするために以下のようなサンプルデータセットを準備する.DataFrame のインデックス値は 0,1,2 ... という数値とし,date カラムに日付を含める.そして pd.Grouper(key='date', freq='M') のように key パラメータを指定すると,期待した平均値となった.

df = pd.DataFrame(
    [
        {'date': pd.Timestamp('2020-01-01'), 'counter': 1},
        {'date': pd.Timestamp('2020-01-02'), 'counter': 2},
        {'date': pd.Timestamp('2020-01-03'), 'counter': 3},
        {'date': pd.Timestamp('2020-02-01'), 'counter': 4},
        {'date': pd.Timestamp('2020-02-02'), 'counter': 5},
        {'date': pd.Timestamp('2020-02-03'), 'counter': 6}
    ]
)
df
# 0    2020-01-01  1
# 1    2020-01-02  2
# 2    2020-01-03  3
# 3    2020-02-01  4
# 4    2020-02-02  5
# 5    2020-02-03  6

df.groupby(pd.Grouper(key='date', freq='M')).mean()
# 2020-01-31   2
# 2020-02-29   5

f:id:kakku22:20210511132345p:plain

関連する resample() 関数も試す 🪢

Pandas のドキュメントを読むと,resample() 関数を使った「時系列データの集計」の例も載っていた.最近読んだ「Pandas ライブラリ活用入門」にも resample() 関数の例が載っていた.

実際に試したところ,Grouper オブジェクトと同じような集計ができた.例えば,以下は同じデータセットに対して「月末集計」をした結果で,平均値は完全に一致していた.また on パラメータを使ってカラムを指定することもできる.今回試した範囲だと Grouper オブジェクトと resample() 関数の機能面での差は確認できなかった.どう使い分けるんだろう?

df.resample('M').mean()
# 2020-01-31   40.096774
# 2020-02-29   47.655172
# 2020-03-31   46.064516
# 2020-04-30   54.766667
# 2020-05-31   54.354839
# 2020-06-30   49.100000
# 2020-07-31   57.000000
# 2020-08-31   46.387097
# 2020-09-30   41.333333
# 2020-10-31   36.967742
# 2020-11-30   40.266667
# 2020-12-31   57.645161

df.resample('Q').mean()
df.resample('W-MON').mean()
df.resample(on='date', rule='M').mean()

f:id:kakku22:20210511132840p:plain

まとめ 🪢

今回は Pandasgroupby() 関数と Grouper オブジェクトを組み合わせて「時系列データの集計」を試した.「月末集計」「四半期集計」そして「週集計」など高機能なグループ化を実現できる.引き続き気になった機能はどんどん試していくぞー👏

関連記事

kakakakakku.hatenablog.com