pandas の rolling で移動平均・移動標準偏差を計算する

時系列データの分析では、ノイズを除去したりトレンドを把握したりするために「移動平均」をよく使います。pandas の rolling メソッドを使えば、移動平均だけでなく移動標準偏差や移動合計なども簡単に計算できます。

基本的な移動平均

まず、株価のようなサンプルデータを作成します。

import pandas as pd
import numpy as np

# ランダムウォークで株価っぽいデータを作成
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=100, freq='D')
price = 100 + np.random.randn(100).cumsum()

df = pd.DataFrame({
    'date': dates,
    'price': price
}).set_index('date')

print(df.head())

7 日間の移動平均を計算してみます。

# 7日間の移動平均
df['ma7'] = df['price'].rolling(window=7).mean()

print(df.head(10))
#                price        ma7
# date                           
# 2024-01-01  100.4967        NaN
# 2024-01-02  100.3582        NaN
# 2024-01-03  101.0055        NaN
# 2024-01-04  102.5293        NaN
# 2024-01-05  102.2953        NaN
# 2024-01-06  102.0614        NaN
# 2024-01-07  103.6359  101.7689
# 2024-01-08  102.8686  102.1077
# ...

最初の 6 行が NaN になっているのは、7 日分のデータが揃うまで計算できないためです。

最小データ数の指定

NaN を減らしたい場合は、min_periods パラメータで計算に必要な最小データ数を指定します。

# 最低3日分のデータがあれば計算
df['ma7_min3'] = df['price'].rolling(window=7, min_periods=3).mean()

print(df.head(10))
#                price        ma7  ma7_min3
# date                                      
# 2024-01-01  100.4967        NaN       NaN
# 2024-01-02  100.3582        NaN       NaN
# 2024-01-03  101.0055        NaN  100.6201  # 3日分で計算開始
# 2024-01-04  102.5293        NaN  101.0974
# ...

移動標準偏差

移動平均と同様に、移動標準偏差も計算できます。価格のボラティリティ(変動性)を測るのによく使われます。

# 7日間の移動標準偏差
df['std7'] = df['price'].rolling(window=7).std()

# ボリンジャーバンド風の上下限
df['upper'] = df['ma7'] + 2 * df['std7']
df['lower'] = df['ma7'] - 2 * df['std7']

rolling で使える主な集計関数をまとめました。

メソッド説明
mean()移動平均
std()移動標準偏差
var()移動分散
sum()移動合計
min()移動最小値
max()移動最大値
median()移動中央値

加重移動平均

単純移動平均(SMA)は全てのデータ点に同じ重みを与えますが、直近のデータにより大きな重みを与える「加重移動平均」を計算したいこともあります。

# 線形加重移動平均(Linear Weighted Moving Average)
def weighted_mean(x):
    weights = np.arange(1, len(x) + 1)
    return np.sum(weights * x) / np.sum(weights)

df['lwma7'] = df['price'].rolling(window=7).apply(weighted_mean)

指数加重移動平均(EMA)は rolling ではなく ewm メソッドを使います。

# 指数加重移動平均(EMA)
df['ema7'] = df['price'].ewm(span=7).mean()
単純移動平均(SMA)

全てのデータ点に同じ重みを与える。安定しているがトレンド変化への反応が遅い。

指数加重移動平均(EMA)

直近のデータほど重みが大きい。トレンド変化に敏感に反応する。

センタリング

デフォルトの rolling は「後方ウィンドウ」、つまり現在と過去のデータを使って計算します。「中央ウィンドウ」を使いたい場合は center=True を指定します。

# 後方ウィンドウ(デフォルト):過去7日
df['ma7_back'] = df['price'].rolling(window=7).mean()

# 中央ウィンドウ:前後3日 + 当日
df['ma7_center'] = df['price'].rolling(window=7, center=True).mean()

中央ウィンドウは将来のデータも使うため、リアルタイムの予測には使えませんが、過去データの分析や可視化では有用です。

時間ベースのウィンドウ

window には整数だけでなく、時間を表す文字列も指定できます。これは不規則な時系列データで特に便利です。

# 不規則な間隔のデータ
irregular = pd.DataFrame({
    'timestamp': pd.to_datetime([
        '2024-01-01 09:00',
        '2024-01-01 09:15',
        '2024-01-01 10:30',
        '2024-01-01 11:00',
        '2024-01-01 14:00',
    ]),
    'value': [100, 102, 105, 103, 110]
}).set_index('timestamp')

# 過去2時間のデータで計算
irregular['ma_2h'] = irregular['value'].rolling('2h').mean()
print(irregular)
#                      value     ma_2h
# timestamp                           
# 2024-01-01 09:00:00    100  100.0000
# 2024-01-01 09:15:00    102  101.0000
# 2024-01-01 10:30:00    105  102.3333
# 2024-01-01 11:00:00    103  103.3333
# 2024-01-01 14:00:00    110  110.0000  # 2時間以内のデータなし

時間ベースのウィンドウを使う場合、インデックスは DatetimeIndex で、昇順にソートされている必要があります。

複数列への適用

DataFrame 全体に rolling を適用することもできます。

df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=30, freq='D'),
    'price_a': np.random.randn(30).cumsum() + 100,
    'price_b': np.random.randn(30).cumsum() + 50
}).set_index('date')

# 全列に7日移動平均を適用
ma = df.rolling(window=7).mean()
ma.columns = [f'{col}_ma7' for col in ma.columns]

# 元データと結合
df = pd.concat([df, ma], axis=1)

実践的な例:ボリンジャーバンド

テクニカル分析でよく使われるボリンジャーバンドを計算してみます。

import pandas as pd
import numpy as np

# データ準備
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=100, freq='D')
df = pd.DataFrame({
    'close': 100 + np.random.randn(100).cumsum()
}, index=dates)

# ボリンジャーバンド(20日、±2σ)
window = 20
df['sma'] = df['close'].rolling(window=window).mean()
df['std'] = df['close'].rolling(window=window).std()
df['upper'] = df['sma'] + 2 * df['std']
df['lower'] = df['sma'] - 2 * df['std']

# バンド幅(ボラティリティの指標)
df['bandwidth'] = (df['upper'] - df['lower']) / df['sma']

print(df.tail())

rolling は時系列分析の基本ツールです。移動平均でトレンドを把握し、移動標準偏差でボラティリティを測定することで、データの特性をより深く理解できます。