Pandas 滑動視窗

滑動視窗(Window)操作可以對資料的子集進行計算,常用於時間序列分析,例如計算移動平均、累計值等。

rolling():滑動視窗

基本用法

import pandas as pd

df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=7),
    'value': [10, 15, 12, 18, 20, 16, 22]
})

# 3 天移動平均
df['ma_3'] = df['value'].rolling(window=3).mean()
print(df)
        date  value      ma_3
0 2024-01-01     10       NaN
1 2024-01-02     15       NaN
2 2024-01-03     12  12.333333
3 2024-01-04     18  15.000000
4 2024-01-05     20  16.666667
5 2024-01-06     16  18.000000
6 2024-01-07     22  19.333333

前兩個值是 NaN,因為不足 3 個資料點。

常用聚合方法

# 移動總和
df['sum_3'] = df['value'].rolling(3).sum()

# 移動最大值
df['max_3'] = df['value'].rolling(3).max()

# 移動最小值
df['min_3'] = df['value'].rolling(3).min()

# 移動標準差
df['std_3'] = df['value'].rolling(3).std()

min_periods 參數

設定最小資料點數:

# 至少有 1 個資料點就計算
df['ma_3'] = df['value'].rolling(window=3, min_periods=1).mean()
        date  value       ma_3
0 2024-01-01     10  10.000000
1 2024-01-02     15  12.500000
2 2024-01-03     12  12.333333
...

center 參數

將結果放在視窗中心:

df['ma_3_center'] = df['value'].rolling(window=3, center=True).mean()

基於時間的視窗

df = df.set_index('date')

# 7 天視窗
df['ma_7d'] = df['value'].rolling('7D').mean()

expanding():擴展視窗

從第一筆資料開始累計計算:

df = pd.DataFrame({
    'value': [10, 15, 12, 18, 20]
})

# 累計平均
df['expanding_mean'] = df['value'].expanding().mean()
print(df)
   value  expanding_mean
0     10           10.00
1     15           12.50
2     12           12.33
3     18           13.75
4     20           15.00

常用累計方法

df['cumsum'] = df['value'].expanding().sum()   # 累計總和
df['cummax'] = df['value'].expanding().max()   # 累計最大值
df['cummin'] = df['value'].expanding().min()   # 累計最小值
df['cumcount'] = df['value'].expanding().count()  # 累計計數

也可以用內建方法:

df['cumsum'] = df['value'].cumsum()
df['cummax'] = df['value'].cummax()
df['cummin'] = df['value'].cummin()

ewm():指數加權移動

指數加權移動(Exponentially Weighted Moving)給予近期資料更大的權重:

df = pd.DataFrame({
    'value': [10, 15, 12, 18, 20]
})

# 指數加權移動平均
df['ewm'] = df['value'].ewm(span=3).mean()
print(df)

ewm 參數

# span:指定衰減期
df['ewm'] = df['value'].ewm(span=3).mean()

# alpha:直接指定平滑係數
df['ewm'] = df['value'].ewm(alpha=0.5).mean()

# halflife:半衰期
df['ewm'] = df['value'].ewm(halflife=2).mean()

自訂視窗函數

使用 apply() 套用自訂函數:

def custom_func(x):
    return x.max() - x.min()

df['range'] = df['value'].rolling(3).apply(custom_func)

多欄位操作

df = pd.DataFrame({
    'A': [1, 2, 3, 4, 5],
    'B': [10, 20, 30, 40, 50]
})

# 對所有欄位計算
result = df.rolling(3).mean()

按群組的滑動視窗

df = pd.DataFrame({
    'city': ['Taipei', 'Taipei', 'Taipei', 'Tokyo', 'Tokyo', 'Tokyo'],
    'value': [10, 15, 12, 20, 25, 22]
})

# 每個城市分別計算移動平均
df['ma'] = df.groupby('city')['value'].transform(lambda x: x.rolling(2).mean())

實際應用

股票技術指標

df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=20),
    'close': [100, 102, 101, 103, 105, 104, 106, 108, 107, 110,
              109, 111, 113, 112, 114, 116, 115, 117, 119, 118]
})

# 移動平均線
df['MA5'] = df['close'].rolling(5).mean()
df['MA10'] = df['close'].rolling(10).mean()

# 布林通道
df['MA20'] = df['close'].rolling(20).mean()
df['std20'] = df['close'].rolling(20).std()
df['upper'] = df['MA20'] + 2 * df['std20']
df['lower'] = df['MA20'] - 2 * df['std20']

計算變化率

# 日變化率
df['daily_change'] = df['close'].pct_change()

# 5 日累計變化率
df['change_5d'] = df['close'].pct_change(periods=5)

平滑資料

# 使用指數加權移動平均平滑雜訊
df['smoothed'] = df['value'].ewm(span=5).mean()

異常值偵測

# 使用滑動標準差偵測異常
df['rolling_mean'] = df['value'].rolling(10).mean()
df['rolling_std'] = df['value'].rolling(10).std()
df['z_score'] = (df['value'] - df['rolling_mean']) / df['rolling_std']
df['is_anomaly'] = abs(df['z_score']) > 3