視窗化操作#

pandas 包含一組精簡的 API,用於執行視窗操作,這是一種對值滑動分割執行聚合的運算。API 的功能類似於 groupby API,其中 SeriesDataFrame 使用必要的參數呼叫視窗方法,然後再呼叫聚合函數。

In [1]: s = pd.Series(range(5))

In [2]: s.rolling(window=2).sum()
Out[2]: 
0    NaN
1    1.0
2    3.0
3    5.0
4    7.0
dtype: float64

視窗由從目前觀察回溯視窗長度組成。以上結果可透過取用以下視窗分割資料的總和得出

In [3]: for window in s.rolling(window=2):
   ...:     print(window)
   ...: 
0    0
dtype: int64
0    0
1    1
dtype: int64
1    1
2    2
dtype: int64
2    2
3    3
dtype: int64
3    3
4    4
dtype: int64

概觀#

pandas 支援 4 種類型的視窗運算

  1. 滾動視窗:在值上的一般固定或可變滑動視窗。

  2. 加權視窗:由 scipy.signal 函式庫提供的加權非矩形視窗。

  3. 擴展視窗:在值上累積的視窗。

  4. 指數加權視窗:在值上累積並指數加權的視窗。

概念

方法

傳回物件

支援基於時間的視窗

支援鏈式 groupby

支援表格方法

支援線上運算

滾動視窗

rolling

pandas.typing.api.Rolling

是(1.3 版起)

加權視窗

rolling

pandas.typing.api.Window

擴展視窗

expanding

pandas.typing.api.Expanding

是(1.3 版起)

指數加權視窗

ewm

pandas.typing.api.ExponentialMovingWindow

是(1.2 版開始)

是(1.3 版起)

如上所述,某些運算支援根據時間偏移量指定視窗

In [4]: s = pd.Series(range(5), index=pd.date_range('2020-01-01', periods=5, freq='1D'))

In [5]: s.rolling(window='2D').sum()
Out[5]: 
2020-01-01    0.0
2020-01-02    1.0
2020-01-03    3.0
2020-01-04    5.0
2020-01-05    7.0
Freq: D, dtype: float64

此外,某些方法支援將 groupby 運算與視窗化運算串聯,這會先依據指定的鍵對資料進行分組,然後對每個群組執行視窗化運算。

In [6]: df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a'], 'B': range(5)})

In [7]: df.groupby('A').expanding().sum()
Out[7]: 
       B
A       
a 0  0.0
  2  2.0
  4  6.0
b 1  1.0
  3  4.0

注意

視窗化運算目前僅支援數字資料(整數和浮點數),且永遠會傳回 float64 值。

警告

某些視窗化聚合、meansumvarstd 方法可能會因為底層視窗化演算法累積總和而導致數值不精確。當值與幅度不同時 \(1/np.finfo(np.double).eps\),這會導致截斷。必須注意的是,大值可能會影響不包含這些值的視窗。 Kahan 總和 用於計算滾動總和,以盡可能保持準確性。

1.3.0 版新功能。

某些視窗操作也支援建構函式中的 method='table' 選項,該選項會對整個 DataFrame 執行視窗操作,而不是一次單一欄或列。這可能會為具有許多欄或列的 DataFrame(搭配對應的 axis 參數)提供有用的效能優勢,或在視窗操作期間使用其他欄。僅當在對應的方法呼叫中指定 engine='numba' 時,才能使用 method='table' 選項。

例如,可以透過指定一組獨立的權重欄,使用 apply() 計算 加權平均數

In [8]: def weighted_mean(x):
   ...:     arr = np.ones((1, x.shape[1]))
   ...:     arr[:, :2] = (x[:, :2] * x[:, 2]).sum(axis=0) / x[:, 2].sum()
   ...:     return arr
   ...: 

In [9]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [10]: df.rolling(2, method="table", min_periods=0).apply(weighted_mean, raw=True, engine="numba")  # noqa: E501
Out[10]: 
          0         1    2
0  1.000000  2.000000  1.0
1  1.800000  2.000000  1.0
2  3.333333  2.333333  1.0
3  1.555556  7.000000  1.0

1.3 版的新增功能。

某些視窗操作在建構視窗物件後,也支援 online 方法,該方法會傳回一個新的物件,支援傳入新的 DataFrameSeries 物件,以使用新值繼續視窗計算(即線上計算)。

此新視窗物件上的方法必須先呼叫聚集方法來「啟動」線上運算的初始狀態。然後,新的 DataFrameSeries 物件可以在 update 參數中傳遞,以繼續視窗運算。

In [11]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [12]: df.ewm(0.5).mean()
Out[12]: 
          0         1         2
0  1.000000  2.000000  0.600000
1  1.750000  2.750000  0.450000
2  2.615385  3.615385  0.276923
3  3.550000  4.550000  0.562500
In [13]: online_ewm = df.head(2).ewm(0.5).online()

In [14]: online_ewm.mean()
Out[14]: 
      0     1     2
0  1.00  2.00  0.60
1  1.75  2.75  0.45

In [15]: online_ewm.mean(update=df.tail(1))
Out[15]: 
          0         1         2
3  3.307692  4.307692  0.623077

所有視窗運算都支援 min_periods 參數,它決定視窗必須具有的非 np.nan 值的最小數量;否則,產生的值為 np.nan。對於基於時間的視窗,min_periods 預設為 1,對於固定視窗,window 預設為 1

In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])

In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]: 
0    NaN
1    1.0
2    3.0
3    3.0
4    2.0
5    3.0
dtype: float64

In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]: 
0    NaN
1    NaN
2    3.0
3    3.0
4    NaN
5    NaN
dtype: float64

# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]: 
0   NaN
1   NaN
2   NaN
3   NaN
4   NaN
5   NaN
dtype: float64

此外,所有視窗運算都支援 aggregate 方法,用於傳回應用於視窗的多個聚集的結果。

In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})

In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]: 
      A                    B                
    sum mean       std   sum  mean       std
0   0.0  0.0       NaN  10.0  10.0       NaN
1   1.0  0.5  0.707107  21.0  10.5  0.707107
2   3.0  1.0  1.000000  33.0  11.0  1.000000
3   6.0  1.5  1.290994  46.0  11.5  1.290994
4  10.0  2.0  1.581139  60.0  12.0  1.581139

滾動視窗#

通用滾動視窗支援將視窗指定為固定數量的觀察值或基於偏移量的可變數量的觀察值。如果提供基於時間的偏移量,則對應的基於時間的索引必須是單調的。

In [22]: times = ['2020-01-01', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-29']

In [23]: s = pd.Series(range(5), index=pd.DatetimeIndex(times))

In [24]: s
Out[24]: 
2020-01-01    0
2020-01-03    1
2020-01-04    2
2020-01-05    3
2020-01-29    4
dtype: int64

# Window with 2 observations
In [25]: s.rolling(window=2).sum()
Out[25]: 
2020-01-01    NaN
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    7.0
dtype: float64

# Window with 2 days worth of observations
In [26]: s.rolling(window='2D').sum()
Out[26]: 
2020-01-01    0.0
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    4.0
dtype: float64

對於所有支援的聚集函數,請參閱 滾動視窗函數

置中視窗#

預設情況下,標籤設定在視窗的右邊緣,但可以使用 center 關鍵字,以便將標籤設定在中心。

In [27]: s = pd.Series(range(10))

In [28]: s.rolling(window=5).mean()
Out[28]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [29]: s.rolling(window=5, center=True).mean()
Out[29]: 
0    NaN
1    NaN
2    2.0
3    3.0
4    4.0
5    5.0
6    6.0
7    7.0
8    NaN
9    NaN
dtype: float64

這也可以應用於類日期的索引。

1.3.0 版新功能。

In [30]: df = pd.DataFrame(
   ....:     {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
   ....: )
   ....: 

In [31]: df
Out[31]: 
            A
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4

In [32]: df.rolling("2D", center=False).mean()
Out[32]: 
              A
2020-01-01  0.0
2020-01-02  0.5
2020-01-03  1.5
2020-01-04  2.5
2020-01-05  3.5

In [33]: df.rolling("2D", center=True).mean()
Out[33]: 
              A
2020-01-01  0.5
2020-01-02  1.5
2020-01-03  2.5
2020-01-04  3.5
2020-01-05  4.0

滾動視窗端點#

滾動視窗計算中包含區間端點可以使用 closed 參數指定

行為

'right'

關閉右端點

'left'

關閉左端點

'both'

關閉兩個端點

'neither'

開啟端點

例如,在許多問題中,開啟右端點很有用,因為這樣可以避免當前資訊對過去資訊造成汙染。這允許滾動視窗計算「到那個時間點為止」的統計資料,但不包含那個時間點。

In [34]: df = pd.DataFrame(
   ....:     {"x": 1},
   ....:     index=[
   ....:         pd.Timestamp("20130101 09:00:01"),
   ....:         pd.Timestamp("20130101 09:00:02"),
   ....:         pd.Timestamp("20130101 09:00:03"),
   ....:         pd.Timestamp("20130101 09:00:04"),
   ....:         pd.Timestamp("20130101 09:00:06"),
   ....:     ],
   ....: )
   ....: 

In [35]: df["right"] = df.rolling("2s", closed="right").x.sum()  # default

In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()

In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()

In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()

In [39]: df
Out[39]: 
                     x  right  both  left  neither
2013-01-01 09:00:01  1    1.0   1.0   NaN      NaN
2013-01-01 09:00:02  1    2.0   2.0   1.0      1.0
2013-01-01 09:00:03  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:04  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:06  1    1.0   2.0   1.0      NaN

自訂視窗滾動#

除了接受整數或偏移量作為 window 參數外,rolling 也接受 BaseIndexer 子類別,讓使用者可以定義自訂方法來計算視窗邊界。 BaseIndexer 子類別需要定義 get_window_bounds 方法,該方法會傳回兩個陣列的元組,第一個陣列是視窗的起始索引,第二個陣列是視窗的結束索引。此外,num_valuesmin_periodscenterclosedstep 會自動傳遞給 get_window_bounds,而定義的方法必須始終接受這些參數。

例如,如果我們有以下 DataFrame

In [40]: use_expanding = [True, False, True, False, True]

In [41]: use_expanding
Out[41]: [True, False, True, False, True]

In [42]: df = pd.DataFrame({"values": range(5)})

In [43]: df
Out[43]: 
   values
0       0
1       1
2       2
3       3
4       4

並且我們想要使用 use_expandingTrue 的擴展視窗,否則視窗大小為 1,我們可以建立以下 BaseIndexer 子類別

In [44]: from pandas.api.indexers import BaseIndexer

In [45]: class CustomIndexer(BaseIndexer):
   ....:      def get_window_bounds(self, num_values, min_periods, center, closed, step):
   ....:          start = np.empty(num_values, dtype=np.int64)
   ....:          end = np.empty(num_values, dtype=np.int64)
   ....:          for i in range(num_values):
   ....:              if self.use_expanding[i]:
   ....:                  start[i] = 0
   ....:                  end[i] = i + 1
   ....:              else:
   ....:                  start[i] = i
   ....:                  end[i] = i + self.window_size
   ....:          return start, end
   ....: 

In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)

In [47]: df.rolling(indexer).sum()
Out[47]: 
   values
0     0.0
1     1.0
2     3.0
3     3.0
4    10.0

您可以在 這裡 查看 BaseIndexer 子類別的其他範例

這些範例中值得注意的一個子類別是 VariableOffsetWindowIndexer,它允許對非固定偏移量(例如 BusinessDay)進行滾動運算。

In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer

In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))

In [50]: offset = pd.offsets.BDay(1)

In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)

In [52]: df
Out[52]: 
            0
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4
2020-01-06  5
2020-01-07  6
2020-01-08  7
2020-01-09  8
2020-01-10  9

In [53]: df.rolling(indexer).sum()
Out[53]: 
               0
2020-01-01   0.0
2020-01-02   1.0
2020-01-03   2.0
2020-01-04   3.0
2020-01-05   7.0
2020-01-06  12.0
2020-01-07   6.0
2020-01-08   7.0
2020-01-09   8.0
2020-01-10   9.0

對於某些問題,未來知識可供分析。例如,當每個資料點都是從實驗中讀取的完整時間序列,而任務是提取基礎條件時,就會發生這種情況。在這些情況下,執行前瞻性滾動視窗運算會很有用。 FixedForwardWindowIndexer 類別可供此目的使用。這個 BaseIndexer 子類別實作封閉固定寬度的前瞻性滾動視窗,我們可以使用它如下所示

In [54]: from pandas.api.indexers import FixedForwardWindowIndexer

In [55]: indexer = FixedForwardWindowIndexer(window_size=2)

In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]: 
               0
2020-01-01   1.0
2020-01-02   3.0
2020-01-03   5.0
2020-01-04   7.0
2020-01-05   9.0
2020-01-06  11.0
2020-01-07  13.0
2020-01-08  15.0
2020-01-09  17.0
2020-01-10   9.0

我們也可以透過使用切片、套用滾動聚合,然後翻轉結果來達成此目的,如下面的範例所示

In [57]: df = pd.DataFrame(
   ....:     data=[
   ....:         [pd.Timestamp("2018-01-01 00:00:00"), 100],
   ....:         [pd.Timestamp("2018-01-01 00:00:01"), 101],
   ....:         [pd.Timestamp("2018-01-01 00:00:03"), 103],
   ....:         [pd.Timestamp("2018-01-01 00:00:04"), 111],
   ....:     ],
   ....:     columns=["time", "value"],
   ....: ).set_index("time")
   ....: 

In [58]: df
Out[58]: 
                     value
time                      
2018-01-01 00:00:00    100
2018-01-01 00:00:01    101
2018-01-01 00:00:03    103
2018-01-01 00:00:04    111

In [59]: reversed_df = df[::-1].rolling("2s").sum()[::-1]

In [60]: reversed_df
Out[60]: 
                     value
time                      
2018-01-01 00:00:00  201.0
2018-01-01 00:00:01  101.0
2018-01-01 00:00:03  214.0
2018-01-01 00:00:04  111.0

滾動套用#

函數 apply() 採用額外的 func 參數,並執行一般性的滾動運算。 func 參數應為單一函數,可從 ndarray 輸入產生單一值。 raw 指定視窗是否轉換為 Series 物件 (raw=False) 或 ndarray 物件 (raw=True)。

In [61]: def mad(x):
   ....:     return np.fabs(x - x.mean()).mean()
   ....: 

In [62]: s = pd.Series(range(10))

In [63]: s.rolling(window=4).apply(mad, raw=True)
Out[63]: 
0    NaN
1    NaN
2    NaN
3    1.0
4    1.0
5    1.0
6    1.0
7    1.0
8    1.0
9    1.0
dtype: float64

Numba 引擎#

此外,如果 apply() 已安裝為選用相依性,則可利用 Numba。可透過指定 engine='numba'engine_kwargs 參數(raw 也必須設為 True)來使用 Numba 執行 apply 聚合。請參閱 使用 Numba 增強效能 以了解參數和效能考量的一般用法。

Numba 將在兩個例程中套用

  1. 如果 func 是標準 Python 函數,引擎將 JIT 傳遞的函數。 func 也可以是 JITed 函數,在這種情況下,引擎不會再次 JIT 函數。

  2. 引擎將 JIT 套用函數至每個視窗的 for 迴圈。

engine_kwargs 參數是關鍵字參數的字典,將傳遞至 numba.jit 裝飾器。這些關鍵字參數將套用至傳遞函數(如果是標準 Python 函數)和每個視窗的 apply for 迴圈。

1.3.0 版新功能。

meanmedianmaxminsum 也支援 engineengine_kwargs 參數。

二元視窗函數#

cov()corr() 可以計算兩個 Series 或任何 DataFrame/SeriesDataFrame/DataFrame 的移動視窗統計資料。以下是每種情況的行為

  • 兩個 Series:計算配對的統計資料。

  • DataFrame/Series:計算 DataFrame 的每一欄與傳遞的 Series 的統計資料,因此會傳回一個 DataFrame。

  • DataFrame/DataFrame:預設計算符合欄位名稱的統計資料,傳回一個 DataFrame。如果傳入關鍵字引數 pairwise=True,則計算每一對欄位的統計資料,傳回一個 DataFrame,其 MultiIndex 的值是相關日期(請參閱 下一節)。

例如

In [64]: df = pd.DataFrame(
   ....:     np.random.randn(10, 4),
   ....:     index=pd.date_range("2020-01-01", periods=10),
   ....:     columns=["A", "B", "C", "D"],
   ....: )
   ....: 

In [65]: df = df.cumsum()

In [66]: df2 = df[:4]

In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]: 
              A    B    C    D
2020-01-01  NaN  NaN  NaN  NaN
2020-01-02 -1.0  1.0 -1.0  1.0
2020-01-03  1.0  1.0  1.0 -1.0
2020-01-04 -1.0  1.0  1.0 -1.0

計算滾動成對共變數和相關係數#

在財務資料分析和其他領域,通常會計算時間序列集合的共變異數和相關矩陣。通常也會對移動視窗共變異數和相關矩陣感興趣。這可透過傳遞 pairwise 關鍵字參數來完成,在 DataFrame 輸入的情況下,將會產生一個 MultiIndexed DataFrame,其 index 是有問題的日期。在單一 DataFrame 參數的情況下,甚至可以省略 pairwise 參數

注意

會忽略遺失值,且每個項目都是使用成對的完整觀察值計算。

假設遺失資料是隨機遺失,這會產生一個共變異數矩陣的估計值,且該估計值是無偏的。然而,對於許多應用程式來說,此估計值可能無法接受,因為估計的共變異數矩陣不保證為正半定的。這可能會導致估計相關係數的絕對值大於 1,和/或不可逆的共變異數矩陣。請參閱 共變異數矩陣的估計 以取得更多詳細資料。

In [68]: covs = (
   ....:     df[["B", "C", "D"]]
   ....:     .rolling(window=4)
   ....:     .cov(df[["A", "B", "C"]], pairwise=True)
   ....: )
   ....: 

In [69]: covs
Out[69]: 
                     B         C         D
2020-01-01 A       NaN       NaN       NaN
           B       NaN       NaN       NaN
           C       NaN       NaN       NaN
2020-01-02 A       NaN       NaN       NaN
           B       NaN       NaN       NaN
...                ...       ...       ...
2020-01-09 B  0.342006  0.230190  0.052849
           C  0.230190  1.575251  0.082901
2020-01-10 A -0.333945  0.006871 -0.655514
           B  0.649711  0.430860  0.469271
           C  0.430860  0.829721  0.055300

[30 rows x 3 columns]

加權視窗#

.rolling 中的 win_type 參數會產生加權視窗,這通常用於濾波和頻譜估計中。 win_type 必須是對應於 scipy.signal 視窗函數 的字串。必須安裝 Scipy 才能使用這些視窗,且 Scipy 視窗方法所採用的補充參數必須在聚合函數中指定。

In [70]: s = pd.Series(range(10))

In [71]: s.rolling(window=5).mean()
Out[71]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [72]: s.rolling(window=5, win_type="triang").mean()
Out[72]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

# Supplementary Scipy arguments passed in the aggregation function
In [73]: s.rolling(window=5, win_type="gaussian").mean(std=0.1)
Out[73]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

有關所有支援的聚合函數,請參閱 加權視窗函數

擴充視窗#

擴充視窗會產生聚合統計資料的值,其中包含截至時間點的所有資料。由於這些計算是滾動統計資料的特殊情況,因此它們在 pandas 中的實作方式,使得以下兩個呼叫等效

In [74]: df = pd.DataFrame(range(5))

In [75]: df.rolling(window=len(df), min_periods=1).mean()
Out[75]: 
     0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0

In [76]: df.expanding(min_periods=1).mean()
Out[76]: 
     0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0

有關所有支援的聚合函數,請參閱 擴充視窗函數

指數加權視窗#

指數加權視窗類似於擴充視窗,但每個先前的點會相對於目前的點以指數方式加權降低。

一般而言,加權移動平均值會計算為

\[y_t = \frac{\sum_{i=0}^t w_i x_{t-i}}{\sum_{i=0}^t w_i},\]

其中 \(x_t\) 是輸入,\(y_t\) 是結果,而 \(w_i\) 是權重。

有關所有支援的聚合函數,請參閱 指數加權視窗函數

EW 函數支援兩種指數權重變體。預設值 adjust=True 使用權重 \(w_i = (1 - \alpha)^i\),其結果為

\[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ... + (1 - \alpha)^t x_{0}}{1 + (1 - \alpha) + (1 - \alpha)^2 + ... + (1 - \alpha)^t}\]

當指定 adjust=False 時,移動平均值會計算為

\[\begin{split}y_0 &= x_0 \\ y_t &= (1 - \alpha) y_{t-1} + \alpha x_t,\end{split}\]

這等於使用權重

\[\begin{split}w_i = \begin{cases} \alpha (1 - \alpha)^i & \text{如果 } i < t \\ (1 - \alpha)^i & \text{如果 } i = t. \end{cases}\end{split}\]

注意

這些方程式有時會以 \(\alpha' = 1 - \alpha\) 來表示,例如

\[y_t = \alpha' y_{t-1} + (1 - \alpha') x_t.\]

以上兩個變體之間的差異是因為我們處理的是具有有限歷史的序列。考慮一個具有無限歷史的序列,其中 adjust=True

\[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...} {1 + (1 - \alpha) + (1 - \alpha)^2 + ...}\]

請注意,分母是一個幾何級數,其首項等於 1,公比為 \(1 - \alpha\),我們有

\[\begin{split}y_t &= \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...} {\frac{1}{1 - (1 - \alpha)}}\\ &= [x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...] \alpha \\ &= \alpha x_t + [(1-\alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...]\alpha \\ &= \alpha x_t + (1 - \alpha)[x_{t-1} + (1 - \alpha) x_{t-2} + ...]\alpha\\ &= \alpha x_t + (1 - \alpha) y_{t-1}\end{split}\]

這與上述 adjust=False 表達式相同,因此顯示了這兩個變體在無限級數中的等價性。當 adjust=False 時,我們有 \(y_0 = x_0\)\(y_t = \alpha x_t + (1 - \alpha) y_{t-1}\)。因此,有一個假設,即 \(x_0\) 不是一個普通值,而是一個指數加權矩,直到那個點的無限級數。

必須有 \(0 < \alpha \leq 1\),儘管可以傳遞 \(\alpha\),但通常更容易考慮 EW 矩的跨度質心 (com)半衰期

\[\begin{split}\alpha = \begin{cases} \frac{2}{s + 1}, & \text{跨度}\ s \geq 1\\ \frac{1}{1 + c}, & \text{質心}\ c \geq 0\\ 1 - \exp^{\frac{\log 0.5}{h}}, & \text{半衰期}\ h > 0 \end{cases}\end{split}\]

必須準確指定 EW 函數中的跨度質心半衰期alpha 之一

  • 跨度對應於通常稱為“N 日 EW 移動平均值”。

  • 質心具有更物理的解釋,可以用跨度來考慮:\(c = (s - 1) / 2\)

  • 半衰期是使指數權重減半的時間段。

  • Alpha 直接指定平滑因子。

您也可以指定 halflife,以可轉換為時間差單位的單位,以指定觀察值衰減到其值的一半所需的時間,同時也指定 times 的序列。

In [77]: df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})

In [78]: df
Out[78]: 
     B
0  0.0
1  1.0
2  2.0
3  NaN
4  4.0

In [79]: times = ["2020-01-01", "2020-01-03", "2020-01-10", "2020-01-15", "2020-01-17"]

In [80]: df.ewm(halflife="4 days", times=pd.DatetimeIndex(times)).mean()
Out[80]: 
          B
0  0.000000
1  0.585786
2  1.523889
3  1.523889
4  3.233686

使用下列公式計算具有時間輸入向量的指數加權平均值

\[y_t = \frac{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda} x_{t-i}}{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda}},\]

ExponentialMovingWindow 也有 ignore_na 參數,它決定中間的空值如何影響權重的計算。當 ignore_na=False(預設值),權重是根據絕對位置計算的,因此中間的空值會影響結果。當 ignore_na=True,權重是透過忽略中間的空值來計算的。例如,假設 adjust=True,如果 ignore_na=False,則 3, NaN, 5 的加權平均值將計算為

\[\frac{(1-\alpha)^2 \cdot 3 + 1 \cdot 5}{(1-\alpha)^2 + 1}.\]

而如果 ignore_na=True,則加權平均值將計算為

\[\frac{(1-\alpha) \cdot 3 + 1 \cdot 5}{(1-\alpha) + 1}.\]

var()std()cov() 函數有一個 bias 參數,指定結果應包含有偏差或無偏差的統計資料。例如,如果 bias=Trueewmvar(x) 會計算為 ewmvar(x) = ewma(x**2) - ewma(x)**2;而如果 bias=False(預設值),有偏差的變異數統計資料會根據去偏差因子進行縮放

\[\frac{\left(\sum_{i=0}^t w_i\right)^2}{\left(\sum_{i=0}^t w_i\right)^2 - \sum_{i=0}^t w_i^2}.\]

(對於 \(w_i = 1\),這會簡化為一般的 \(N / (N - 1)\) 因數,其中 \(N = t + 1\)。)有關更多詳細資訊,請參閱維基百科上的 加權樣本變異數