時系列データを扱う際、データの粒度を変換したいことがよくあります。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 は時系列データの粒度変換に欠かせないメソッドです。ダウンサンプリングでは適切な集計関数を、アップサンプリングでは適切な補間方法を選ぶことが重要です。