【Python】pandas.Grouper・resample・pandas.date_rangeの処理を比較する
時系列データを扱う際によく使われる、以下の3つの処理を日次・週次・月次(daily, weekly, monthly)で比較してみます!
どこが同じで、どこが違うのかを確認していきます!
【比較対象】
- pandas.Grouper
- resample
- pandas_date_range
pandas.Grouperについてはこちらの記事で紹介しています。
動作環境 MacOS Catalina 10.15.7 Python 3.8.6 pandas 1.2.1 VSCode 1.52.1
VSCode拡張機能のJupyterを使用します。 詳細はこちらを参照ください。
データ準備
2020年のa店舗、b店舗、c店舗の日次来客数のデータを使用します。
import pandas as pd store_visits_df = pd.read_csv("./csv/store_visits_2020.csv")
pandas.Grouper、pandas.resample共に、日付データのカラムは datetime型 にしておく必要があります。
store_visits_df.dtypes
date object store_id object visit_num int64 dtype: object
dateカラムはobject型になっていると以下のエラーが発生します。
TypeError: Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'Index'
以下のようにしてdatetime型に変換しておきましょう。
store_visits_df["date"] = pd.to_datetime(store_visits_df["date"])
また、resampleでは日付カラムをインデックスに設定する必要があります。
store_visits_df = store_visits_df.set_index("date")
日次の処理(daily)
pandas.Grouperの場合
grouper_daily_df = store_visits_df.groupby( pd.Grouper(level="date", freq="D") ).sum() grouper_daily_df
resampleの場合
resample_daily_df = store_visits_df.resample("D").sum()
resample_daily_df
こちらも同じ結果に!
全く同じDataFrameであることを、 assert_frame_equal
を使用して確認します。
from pandas.testing import assert_frame_equal print(assert_frame_equal(grouper_daily_df, resample_daily_df)) > None
assert_frame_equal
についてはこちら ↓
日次では、pandas.Grouperとresampleが同じ処理をしていることがわかりました。
コード的にはresampleの方がシンプルになりますね!
pandas.date_rangeの場合
ちょっと毛色が違いますが、date_rangeも比較してみます。
from datetime import date date_range_daily = pd.date_range( start=date(2020,1,1), end=date(2020,12,31), freq="D" ) date_range_daily
結果はこちら ↓
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')
これもpandas.GrouperとresampleのIndexと比較して、同じであることが確認できました。
list(grouper_daily_df.index) == list(date_range_daily) > True list(resample_daily_df.index) == list(date_range_daily) > True
週次の処理(weekly)
月曜 ~ 日曜の1週間を、月曜表示で扱います。
例 【表示】 2020-01-06 【集計】 2020-01-06 ~ 2020-01-12
pandas.Grouperの場合
grouper_daily_df = store_visits_df.groupby( pd.Grouper(level="date", freq="W-MON", closed="left", label="left") ).sum() grouper_daily_df
月曜始まりの日曜終わりでちゃんとグルーピングできているかチェックします。
store_visits_df.groupby( pd.Grouper(level="date", freq="W-MON", closed="left", label="left") ).get_group("2020-01-06")
2020-01-06(月)から2020-01-12(日)でグルーピングできていました!
resampleの場合
resample_weekly_df = store_visits_df.resample( "W-MON", closed="left", label="left" ).sum() resample_weekly_df
resampleでもpandas.Grouperと同じオプション指定で同じ結果になりました!
念のため、同じDataFrameになったことをチェックします。
print(assert_frame_equal(grouper_weekly_df, resample_weekly_df)) > None
詳細なコードや結果は載せませんが、日曜始まり(freq="W"または"W-SUN")の集計においてもpandas.Grouperとresampleの処理に違いはありませんでした。
pandas.date_rangeの場合
date_range_weekly = pd.date_range( start=date(2020,1,1), end=date(2020,12,31), freq="W-MON" ) date_range_weekly
結果 ↓
DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27', '2020-02-03', '2020-02-10', '2020-02-17', '2020-02-24', '2020-03-02', '2020-03-09', '2020-03-16', '2020-03-23', '2020-03-30', '2020-04-06', '2020-04-13', '2020-04-20', '2020-04-27', '2020-05-04', '2020-05-11', '2020-05-18', '2020-05-25', '2020-06-01', '2020-06-08', '2020-06-15', '2020-06-22', '2020-06-29', '2020-07-06', '2020-07-13', '2020-07-20', '2020-07-27', '2020-08-03', '2020-08-10', '2020-08-17', '2020-08-24', '2020-08-31', '2020-09-07', '2020-09-14', '2020-09-21', '2020-09-28', '2020-10-05', '2020-10-12', '2020-10-19', '2020-10-26', '2020-11-02', '2020-11-09', '2020-11-16', '2020-11-23', '2020-11-30', '2020-12-07', '2020-12-14', '2020-12-21', '2020-12-28'], dtype='datetime64[ns]', freq='W-MON')
pandas.date_rangeでもfreqの指定("W"や"W-MON")は共通でした。
しかし日付リストには違いが出ました。
list(grouper_weekly_df.index) == list(date_range_weekly) > False
差分も出してみます。
set(grouper_weekly_df.index) - set(date_range_weekly) > {Timestamp('2019-12-30 00:00:00', freq='W-MON')}
このことから、pandas.date_rangeは、
start_date
から end_date
が所属する週のリストを出すのではなく、
start_date
から end_date
の中にある週の基準日のリストを出す ことがわかります。
月次の処理(monthly)
月初日を基準に集計します。
例 【表示】 2020-01-01 【集計】 2020-01-01 ~ 2020-01-31
pandas.Grouperの場合
月初日を基準とする場合、freq="MS"とします。
(freq="M" では月末日が基準となります)
grouper_monthly_df = store_visits_df.groupby( pd.Grouper(level="date", freq="MS") ).sum() grouper_monthly_df
グルーピングも確認します。
store_visits_df.groupby( pd.Grouper(level="date", freq="MS") ).get_group("2020-01-01")
resampleの場合
resample_monthly_df = store_visits_df.resample("MS").sum()
resample_monthly_df
月次においても、pandas.GrouperとDataFrameが一致しました!
print(assert_frame_equal(grouper_monthly_df, resample_monthly_df)) > None
pandas.date_rangeの場合
date_range_monthly = pd.date_range( start=date(2020,1,1), end=date(2020,12,31), freq="MS" ) date_range_monthly
結果 ↓
DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01', '2020-05-01', '2020-06-01', '2020-07-01', '2020-08-01', '2020-09-01', '2020-10-01', '2020-11-01', '2020-12-01'], dtype='datetime64[ns]', freq='MS')
pandas.Grouperのインデックスと比較します。
list(grouper_monthly_df.index) == list(date_range_monthly) > True
2020-01-01 ~ 2020-12-31 を指定して実行したので同じ結果になりましたが、週次でエッジケースの処理が異なることを考慮すると月次でも同じことが起きるはずです。
試しに、2020-01-02 ~ 2020-12-31 で処理を行ってみます。
date_range_monthly = pd.date_range( start=date(2020,1,2), end=date(2020,12,31), freq="MS" ) date_range_monthly
結果 ↓
DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01', '2020-05-01', '2020-06-01', '2020-07-01', '2020-08-01', '2020-09-01', '2020-10-01', '2020-11-01', '2020-12-01'], dtype='datetime64[ns]', freq='MS')
やはり 2020-01-01 が抜けましたね。この処理の違いには注意が必要そうです。