時系列データを扱う際、データの粒度を変換したいことがよくあります。1 分ごとのデータを 1 時間単位に集約したり、日次データを月次に変換したりする処理を「リサンプリング」と呼びます。pandas の resample メソッドを使えば、こうした変換を簡単に行えます。
基本的なリサンプリング
まず、サンプルデータを作成します。
import pandas as pd import numpy as np # 1分間隔のデータを作成 dates = pd.date_range('2024-01-01', periods=1440, freq='min') df = pd.DataFrame({ 'timestamp': dates, 'value': np.random.randn(1440).cumsum() + 100 }) df = df.set_index('timestamp') print(df.head()) # value # timestamp # 2024-01-01 00:00:00 99.123456 # 2024-01-01 00:01:00 98.765432 # 2024-01-01 00:02:00 99.234567 # ...
このデータを 1 時間単位に集約してみます。
# 1時間ごとの平均値 hourly = df.resample('h').mean() print(hourly.head()) # value # timestamp # 2024-01-01 00:00:00 99.543210 # 2024-01-01 01:00:00 100.234567 # 2024-01-01 02:00:00 101.345678 # ...
resample の引数には頻度を表す文字列を指定します。よく使うものをまとめました。
| 文字列 | 意味 |
|---|---|
| s | 秒 |
| min | 分 |
| h | 時間 |
| D | 日 |
| W | 週 |
| MS | 月初 |
| ME | 月末 |
| YS | 年初 |
| YE | 年末 |
ダウンサンプリングの集計方法
ダウンサンプリング(細かい粒度から粗い粒度への変換)では、複数のデータ点を 1 つに集約する必要があります。mean 以外にもさまざまな集計関数が使えます。
# 各種集計関数 print(df.resample('h').sum()) # 合計 print(df.resample('h').max()) # 最大値 print(df.resample('h').min()) # 最小値 print(df.resample('h').first()) # 最初の値 print(df.resample('h').last()) # 最後の値 print(df.resample('h').count()) # データ数
金融データでよく使う OHLC(始値・高値・安値・終値)も一度に取得できます。
# OHLC(始値・高値・安値・終値) ohlc = df.resample('h').ohlc() print(ohlc.head()) # value # open high low close # timestamp # 2024-01-01 00:00:00 99.1234 102.3456 97.5432 100.2345 # ...
複数の集計を同時に行う
agg メソッドを使えば、複数の集計関数を同時に適用できます。
# 複数の集計を同時に result = df.resample('h').agg({ 'value': ['mean', 'std', 'min', 'max'] }) print(result.head()) # value # mean std min max # timestamp # 2024-01-01 00:00:00 99.5432 1.23456 97.1234 102.3456 # ...
カラムごとに異なる集計関数を指定することもできます。
# 複数カラムの場合 df['volume'] = np.random.randint(100, 1000, len(df)) result = df.resample('h').agg({ 'value': 'mean', 'volume': 'sum' })
アップサンプリングと補間
アップサンプリング(粗い粒度から細かい粒度への変換)では、存在しないデータ点を何らかの方法で埋める必要があります。
# 日次データを作成 daily = pd.DataFrame({ 'date': pd.date_range('2024-01-01', periods=5, freq='D'), 'value': [100, 105, 103, 108, 110] }).set_index('date') # 時間単位にアップサンプリング(まだ NaN) hourly = daily.resample('h').asfreq() print(hourly.head(30)) # value # date # 2024-01-01 00:00:00 100.0 # 2024-01-01 01:00:00 NaN # 2024-01-01 02:00:00 NaN # ...
NaN を埋めるには ffill(前方補間)や bfill(後方補間)を使います。
# 前方補間:直前の値で埋める hourly_ffill = daily.resample('h').ffill() # 後方補間:直後の値で埋める hourly_bfill = daily.resample('h').bfill() # 線形補間 hourly_interp = daily.resample('h').interpolate(method='linear')
直前の値をそのまま使う。階段状のデータになる。
前後の値から線形に補間する。滑らかなデータになる。
時刻のオフセット
resample のデフォルトでは、各期間の開始時点にラベルが付きます。これを変更するには offset パラメータを使います。
# デフォルト:各時間の開始(00分) df.resample('h').mean() # 30分オフセット:各時間の30分を基準にする df.resample('h', offset='30min').mean()
営業日や特定の曜日を基準にする場合は、頻度文字列で指定します。
# 週次(月曜始まり) df.resample('W-MON').mean() # 週次(金曜始まり) df.resample('W-FRI').mean() # 営業日 df.resample('B').mean()
グループ化との組み合わせ
複数のカテゴリを持つデータでは、groupby と resample を組み合わせることで、カテゴリ別の時系列集計ができます。
# カテゴリ付きデータ df = pd.DataFrame({ 'timestamp': pd.date_range('2024-01-01', periods=1000, freq='h'), 'category': np.random.choice(['A', 'B', 'C'], 1000), 'value': np.random.randn(1000) }) df = df.set_index('timestamp') # カテゴリ別に日次集計 result = df.groupby('category').resample('D').mean() print(result.head(10))
resample は時系列データの粒度変換に欠かせないメソッドです。ダウンサンプリングでは適切な集計関数を、アップサンプリングでは適切な補間方法を選ぶことが重要です。