pandas で時系列データをリサンプリングする

時系列データを扱う際、データの粒度を変換したいことがよくあります。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')
ffill(前方補間)

直前の値をそのまま使う。階段状のデータになる。

interpolate(線形補間)

前後の値から線形に補間する。滑らかなデータになる。

時刻のオフセット

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