404 Not Found

404 Not Found


nginx

【ウィンドウ関数】Pandasのrolling/expanding/ewm:カスタム集計・グループローリング

レッスン17ではrolling/expanding/ewmを紹介しました。本レッスンではさらに深く掘り下げます:カスタムウィンドウ集計、高度な時間ウィンドウ、ewmパラメータの関係、そしてgroupby+rollingの組み合わせです。ウィンドウ関数は定量分析とセンサーデータ処理の中核となるツールです。これをマスターすれば、「スライドするレンズを通してトレンドを見抜く」力を手に入れることができます。

⚠️ 注意: 以下のコードはローカルのPython環境で実行してください。

1. 学習内容



2. キャロルの運動心拍数ウィンドウ分析

(1) 課題:30秒ウィンドウの平均だけでは不十分

キャロルの運動心拍数データには、より豊富なウィンドウ統計が必要です。平均だけでなく、最大値と心拍変動(標準偏差)も必要です:

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
hr = pd.DataFrame({
    'heart_rate': np.random.normal(75, 10, 60).round(0)
}, index=pd.date_range('2024-01-15 08:00', periods=60, freq='30s'))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) 解決策:複数の集計を伴うrolling

▶ サンプル:rolling による複数指標の統計(難易度 ⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
hr = pd.DataFrame({
    'heart_rate': np.random.normal(75, 10, 60).round(0)
}, index=pd.date_range('2024-01-15 08:00', periods=60, freq='30s'))

# 複数の集計を伴うローリング
stats = hr['heart_rate'].rolling(10).agg(['mean', 'std', 'min', 'max'])
stats.columns = ['hr_ma', 'hr_std', 'hr_min', 'hr_max']
print(stats.dropna().head(5).round(1))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。


3. rollingの高度な使い方

(1) 主要なパラメータ

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:rolling パラメータリファレンス(難易度 ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
s = pd.Series(np.random.randn(20).cumsum() + 100)

# min_periods:部分的なウィンドウを許可
print(s.rolling(5, min_periods=2).mean().head(5))

# center:中央揃えウィンドウ(スムージング用)
print(s.rolling(5, center=True).mean().head(5))

# win_type:重み付きウィンドウ(ガウス、三角など)
print(s.rolling(5, win_type='gaussian').mean(std=1.5).head(8))

# on:列を時間として使用(インデックスでない場合)
df = pd.DataFrame({
    'timestamp': pd.date_range('2024-01', periods=20, freq='6h'),
    'value': np.random.randn(20).cumsum()
})
print(df.rolling('12h', on='timestamp')['value'].mean().head(5))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) rollingパラメータ早見表

パラメータ デフォルト 説明
window 必須 ウィンドウサイズ(整数の行数または時間文字列)
min_periods window ウィンドウ内に必要な最小観測数
center False ウィンドウを中央揃え
win_type None 重み付けの種類(gaussian/triangularなど)
on None 列を時間軸として使用(インデックスの代わりに)
closed 'right' ウィンドウのどちら端を含めるか


4. カスタムウィンドウ集計

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:カスタム集計関数(難易度 ⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'value': np.random.randint(10, 100, 20)
})

# カスタム:ウィンドウ内のレンジ(最大 - 最小)
def window_range(arr):
    return arr.max() - arr.min()

df['rolling_range'] = df['value'].rolling(5).apply(window_range, raw=True)

# カスタム:変動係数(標準偏差/平均)
def cv(arr):
    return arr.std() / arr.mean() if arr.mean() != 0 else 0

df['rolling_cv'] = df['value'].rolling(5).apply(cv, raw=True)

# パフォーマンスのためにrolling.applyでraw=Trueを使用
# raw=True:numpy配列を受け取る(高速)
# raw=False:Seriesを受け取る(低速だが柔軟)

print(df.head(10))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
💡 ヒント: カスタム関数でraw=Trueを使用すると約10倍高速になります(Seriesの代わりにNumPy配列を受け取ります)が、インデックス情報にアクセスできなくなります。単純な数値計算にはraw=Trueを、インデックスが必要な場合はraw=Falseを使用してください。



5. expandingの深掘り

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:カスタムexpanding関数(難易度 ⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'score': np.random.randint(60, 100, 15)
})

# 組み込みのexpandingメソッド
df['cummean'] = df['score'].expanding().mean()
df['cumstd'] = df['score'].expanding().std()
df['cummax'] = df['score'].expanding().max()

# min_periodsを指定したexpanding
df['cummean_5'] = df['score'].expanding(min_periods=5).mean()

# カスタム:expandingによるzスコア
def zscore(arr):
    if len(arr) < 2: return np.nan
    return (arr[-1] - arr.mean()) / arr.std()

df['zscore'] = df['score'].expanding().apply(zscore, raw=True)
print(df.round(2).head(10))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。


6. ewmパラメータの解説

(1) 4つのパラメータの関係

パラメータ 関係 意味
span com = span - 1 N期間のSMAに類似
com α = 1/(1+com) 重心(center of mass)
halflife α = 1 - 0.5^(1/halflife) 重みが半分になる期間数
alpha 直接指定 平滑化係数 0<α≤1

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:ewmパラメータの比較(難易度 ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
s = pd.Series(np.random.randn(30).cumsum() + 100)

# これらはすべて(近似的に)等価
ewm_span5 = s.ewm(span=5).mean()
ewm_com4 = s.ewm(com=4).mean()          # com = span - 1
ewm_hl3 = s.ewm(halflife=3).mean()      # 異なる重み付け
ewm_alpha = s.ewm(alpha=0.333).mean()   # alpha = 2/(span+1)

# SMAとの比較
sma5 = s.rolling(5).mean()

print("SMA vs EWM(最初の10件):")
print(pd.DataFrame({
    'value': s, 'SMA_5': sma5, 'EWM_span5': ewm_span5
}).head(10).round(2))

# ewmの標準偏差(ボラティリティ)
ewm_std = s.ewm(span=10).std()
print(f"\n最新のEWM標準偏差:{ewm_std.iloc[-1]:.2f}")
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。


7. groupby + rolling

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:グループ内のローリングウィンドウ(難易度 ⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# センサーデータ:複数のセンサー、それぞれが独自の時系列を持つ
np.random.seed(42)
df = pd.DataFrame({
    'sensor_id': np.repeat(['T01', 'T02', 'T03'], 20),
    'timestamp': list(pd.date_range('2024-01', periods=20, freq='6h')) * 3,
    'temperature': np.random.normal(20, 3, 60).round(1)
})

# 各センサーグループ内でのローリング
df = df.sort_values(['sensor_id', 'timestamp'])
df['temp_ma'] = df.groupby('sensor_id')['temperature'].transform(
    lambda x: x.rolling(4, min_periods=1).mean()
)

# 代替方法:groupby + rolling(MultiIndexを返す)
result = df.groupby('sensor_id')['temperature'].rolling(4).mean()
print(result.head(12))

# きれいな出力のためにリセット
df['temp_ma2'] = df.groupby('sensor_id')['temperature'].transform(
    lambda x: x.rolling(4).mean().values
)
print(df[df['sensor_id'] == 'T01'].head(6))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。


8. 完全な例:運動心拍数データ分析

(5) ▶ 3つのウィンドウタイプの比較

100%
graph LR
    A[ウィンドウ関数] --> B[rolling 固定ウィンドウ]
    A --> C[expanding 累積ウィンドウ]
    A --> D[ewm 指数加重]
    B --> E[ローカルトレンド / 移動平均]
    C --> F[累積統計 / 開始から現在まで]
    D --> G[直近データを重視 / スムージング]
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:完全なウィンドウ分析パイプライン(難易度 ⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# ============================================
# 総合例:フィットネス心拍数
# ウィンドウ分析パイプライン
# ============================================

# 1. 2時間のワークアウトデータを生成(30秒ごとに1回測定 = 240行)
np.random.seed(42)
timestamps = pd.date_range('2024-01-15 08:00', periods=240, freq='30s')
exercise_type = (['ウォームアップ'] * 30 + ['有酸素'] * 90 + ['筋トレ'] * 60 + ['クールダウン'] * 60)
hr_base = ([70] * 30 + list(np.linspace(70, 140, 90)) + [120] * 60 + list(np.linspace(120, 70, 60)))
heart_rates = [max(50, h + np.random.randn() * 5) for h in hr_base]

df = pd.DataFrame({
    'timestamp': timestamps,
    'exercise': exercise_type,
    'heart_rate': np.round(heart_rates, 0)
})

# 2. Rolling:1分(2回測定)と5分(10回測定)の移動平均
df['hr_ma_1min'] = df['heart_rate'].rolling(2).mean()
df['hr_ma_5min'] = df['heart_rate'].rolling(10).mean()

# 3. Rolling:心拍変動(5分ウィンドウ内の標準偏差)
df['hrv'] = df['heart_rate'].rolling(10).std()

# 4. Expanding:累積平均
df['cum_avg_hr'] = df['heart_rate'].expanding().mean()

# 5. EWM:平滑化されたトレンド
df['hr_ewm'] = df['heart_rate'].ewm(span=10).mean()

# 6. Groupby + rolling:運動タイプごと
df['hr_ma_per_exercise'] = df.groupby('exercise')['heart_rate'].transform(
    lambda x: x.rolling(5, min_periods=1).mean()
)

# 7. 運動ごとのサマリー
print("=== 運動サマリー ===")
summary = df.groupby('exercise')['heart_rate'].agg(['mean', 'max', 'min', 'std']).round(1)
print(summary)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

❓ よくある質問

Q rollingは時間ベースのウィンドウをサポートしますか?
A はい—rolling('7D')は7日間の暦ウィンドウを使用し、rolling(7)は7行のウィンドウを使用します。時間ベースのウィンドウは、均一にサンプリングされていないデータ(週末のスキップや欠測日がある場合)に最適で、行ベースのウィンドウは均一なサンプリングに適しています。時間ベースのウィンドウはインデックスがDatetimeIndexである必要があり、またはonパラメータで時間の列を指定できます。
Q カスタム関数のパフォーマンスはどうですか?
A カスタム関数は組み込み集計より10〜100倍低速です。ウィンドウごとにPythonの関数呼び出しが発生するためです。最適化のヒント:raw=Trueを使用してNumPy配列を渡す(5〜10倍高速)、PythonのループをNumPyの演算に置き換える。組み込み関数はCで実装されていますが、カスタム関数はPythonインタプリタを経由します。組み込み関数で用が足りる場合は、カスタム関数を書くのを避けてください。
Q ewmのspan/halflife/comの関係は何ですか?
A 3つとも数学的には等価で、同じものを異なる方法で表現しているだけです。span=NはN期間のSMAのように振る舞いますが、直近のデータにより大きな重みを置きます。com=span-1は重心です。halflifeは重みが半分になる期間数です。spanが最もよく使われます(最も直感的です)。α=2/(span+1)が平滑化係数です。指定するのは1つだけでよく、複数のパラメータを同時に設定する必要はありません。
Q groupby+rollingで注意すべき点は何ですか?
A 重要なポイントは2つです:① 必ず先にグループキー+時間でsort_valuesしてください。そうしないとウィンドウがグループをまたぐ可能性があります。② transform(lambda x: x.rolling(N).mean())は元のデータフレームと同じ長さの結果を返しますが、groupby+rollingを直接呼び出すとMultiIndexが返されます。大規模なデータセットではtransformの方が便利です。
Q win_typeにはどのようなオプションがありますか?
A scipy.signal.windowsのすべてのウィンドウ関数が使えます:gaussian(ガウス加重)、triang(三角)、blackman、hamming、kaiserなどです。pip install scipyが必要です。win_typeはウィンドウ内のデータに重みを適用し(中央ほど重みが高く、端ほど低い)、等重みウィンドウよりも滑らかな結果が得られます。
Q closedパラメータは何をしますか?
A closedはウィンドウのどちら端を含めるかを制御します。'right'(デフォルト、右端=現在の行を含める)、'left'(左端を含める)、'both'(両端を含める)、'neither'(どちらの端も含めない)。金融では'right'がよく使われ(過去データのみ)、特定の統計シナリオでは'both'や'left'が必要です。
Q expandingとcumsum/cummaxの違いは何ですか?
A expandingはcumsumを一般化したものです。mean/std/カスタム関数をサポートしますが、cumsumは累積和のみを計算します。expanding().sum()はcumsum()と等しく、expanding().max()はcummax()と等しくなります。expandingの方が柔軟ですがやや低速です。単純なケースではcum*系関数の方が簡潔で効率的です。

📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):30日分の気温データを作成してください。rolling(7, min_periods=3)で移動平均と標準偏差を計算し、ewm(span=7)で指数平滑化を計算してください。
  2. 応用(難易度 ⭐⭐):複数センサーの気温データ(3センサー、各20行)を作成してください。groupby+rollingを使って各センサーの5期間移動平均を計算してください。
  3. チャレンジ(難易度 ⭐⭐⭐):240行の運動心拍数データ(4つの運動タイプを含む)をシミュレートしてください。次のパイプラインを完成させてください:rollingによる複数指標(mean/std/max)→ expanding累積平均 → ewm平滑化 → 運動タイプごとのgroupby+rolling → サマリーレポート。

← 前へ:MultiIndex · 次へ:可視化 →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%