窗口操作#

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. 指数加权窗口:对值进行累加和指数加权的窗口。

概念

方法

返回的对象

支持基于时间的窗口

支持链式分组方式

支持表法

支持在线操作

滚动窗

rolling

Rolling

是(从1.3版开始)

不是的

加权窗口

rolling

Window

不是的

不是的

不是的

不是的

展开窗口

expanding

Expanding

不是的

是(从1.3版开始)

不是的

指数加权窗

ewm

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 价值。

警告

一些窗口聚合, mean, sum, var and std methods may suffer from numerical imprecision due to the underlying windowing algorithms accumulating sums. When values differ with magnitude \(1/np.finfo(np.double).eps\) this results in truncation. It must be noted, that large values may have an impact on windows, which do not include these values. Kahan summation 用于计算滚动总和,以尽可能保持准确性。

1.3.0 新版功能.

一些窗口化操作还支持 method='table' 选项,该选项对整个 DataFrame 而不是一次只有一列或一行。这可以为用户提供有用的性能优势 DataFrame 具有许多列或行(具有对应的 axis 参数)或在窗口操作期间使用其他列的能力。这个 method='table' 选项仅在以下情况下才能使用 engine='numba' 在相应的方法调用中指定。

例如,一个 weighted mean 计算可以用以下公式计算 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
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/compat/_optional.py:139, in import_optional_dependency(name, extra, errors, min_version)
    138 try:
--> 139     module = importlib.import_module(name)
    140 except ImportError:

File /usr/lib/python3.10/importlib/__init__.py:126, in import_module(name, package)
    125         level += 1
--> 126 return _bootstrap._gcd_import(name[level:], package, level)

File <frozen importlib._bootstrap>:1050, in _gcd_import(name, package, level)

File <frozen importlib._bootstrap>:1027, in _find_and_load(name, import_)

File <frozen importlib._bootstrap>:1004, in _find_and_load_unlocked(name, import_)

ModuleNotFoundError: No module named 'numba'

During handling of the above exception, another exception occurred:

ImportError                               Traceback (most recent call last)
Input In [10], in <cell line: 1>()
----> 1 df.rolling(2, method="table", min_periods=0).apply(weighted_mean, raw=True, engine="numba")  # noqa:E501

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/window/rolling.py:1893, in Rolling.apply(self, func, raw, engine, engine_kwargs, args, kwargs)
   1872 @doc(
   1873     template_header,
   1874     create_section_header("Parameters"),
   (...)
   1891     kwargs: dict[str, Any] | None = None,
   1892 ):
-> 1893     return super().apply(
   1894         func,
   1895         raw=raw,
   1896         engine=engine,
   1897         engine_kwargs=engine_kwargs,
   1898         args=args,
   1899         kwargs=kwargs,
   1900     )

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/window/rolling.py:1345, in RollingAndExpandingMixin.apply(self, func, raw, engine, engine_kwargs, args, kwargs)
   1341         apply_func = generate_numba_apply_func(
   1342             func, **get_jit_arguments(engine_kwargs, kwargs)
   1343         )
   1344     else:
-> 1345         apply_func = generate_numba_table_func(
   1346             func, **get_jit_arguments(engine_kwargs, kwargs)
   1347         )
   1348 elif engine in ("cython", None):
   1349     if engine_kwargs is not None:

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/window/numba_.py:209, in generate_numba_table_func(func, nopython, nogil, parallel)
    177 @functools.lru_cache(maxsize=None)
    178 def generate_numba_table_func(
    179     func: Callable[..., np.ndarray],
   (...)
    182     parallel: bool,
    183 ):
    184     """
    185     Generate a numba jitted function to apply window calculations table-wise.
    186 
   (...)
    207     Numba function
    208     """
--> 209     numba_func = jit_user_function(func, nopython, nogil, parallel)
    210     if TYPE_CHECKING:
    211         import numba

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/util/numba_.py:91, in jit_user_function(func, nopython, nogil, parallel)
     89     import numba
     90 else:
---> 91     numba = import_optional_dependency("numba")
     93 if numba.extending.is_jitted(func):
     94     # Don't jit a user passed jitted function
     95     numba_func = func

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/compat/_optional.py:142, in import_optional_dependency(name, extra, errors, min_version)
    140 except ImportError:
    141     if errors == "raise":
--> 142         raise ImportError(msg)
    143     else:
    144         return None

ImportError: Missing optional dependency 'numba'.  Use pip or conda to install numba.

1.3 新版功能.

某些窗口化操作还支持 online 在构造窗口对象后,该窗口对象返回支持传入新的 DataFrameSeries 对象继续使用新值进行窗口化计算(即联机计算)。

这个新窗口对象上的方法必须首先调用Aggregation方法来“准备”在线计算的初始状态。然后,新的 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()
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/compat/_optional.py:139, in import_optional_dependency(name, extra, errors, min_version)
    138 try:
--> 139     module = importlib.import_module(name)
    140 except ImportError:

File /usr/lib/python3.10/importlib/__init__.py:126, in import_module(name, package)
    125         level += 1
--> 126 return _bootstrap._gcd_import(name[level:], package, level)

File <frozen importlib._bootstrap>:1050, in _gcd_import(name, package, level)

File <frozen importlib._bootstrap>:1027, in _find_and_load(name, import_)

File <frozen importlib._bootstrap>:1004, in _find_and_load_unlocked(name, import_)

ModuleNotFoundError: No module named 'numba'

During handling of the above exception, another exception occurred:

ImportError                               Traceback (most recent call last)
Input In [14], in <cell line: 1>()
----> 1 online_ewm.mean()

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/window/ewm.py:1013, in OnlineExponentialMovingWindow.mean(self, update, update_times, *args, **kwargs)
   1011         result_kwargs["name"] = self._selected_obj.name
   1012     np_array = self._selected_obj.astype(np.float64).to_numpy()
-> 1013 ewma_func = generate_online_numba_ewma_func(
   1014     **get_jit_arguments(self.engine_kwargs)
   1015 )
   1016 result = self._mean.run_ewm(
   1017     np_array if is_frame else np_array[:, np.newaxis],
   1018     update_deltas,
   1019     self.min_periods,
   1020     ewma_func,
   1021 )
   1022 if not is_frame:

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/window/online.py:33, in generate_online_numba_ewma_func(nopython, nogil, parallel)
     31     import numba
     32 else:
---> 33     numba = import_optional_dependency("numba")
     35 @numba.jit(nopython=nopython, nogil=nogil, parallel=parallel)
     36 def online_ewma(
     37     values: np.ndarray,
   (...)
     44     ignore_na: bool,
     45 ):
     46     """
     47     Compute online exponentially weighted mean per column over 2D values.
     48 
     49     Takes the first observation as is, then computes the subsequent
     50     exponentially weighted mean accounting minimum periods.
     51     """

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/compat/_optional.py:142, in import_optional_dependency(name, extra, errors, min_version)
    140 except ImportError:
    141     if errors == "raise":
--> 142         raise ImportError(msg)
    143     else:
    144         return None

ImportError: Missing optional dependency 'numba'.  Use pip or conda to install numba.

In [15]: online_ewm.mean(update=df.tail(1))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [15], in <cell line: 1>()
----> 1 online_ewm.mean(update=df.tail(1))

File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/window/ewm.py:993, in OnlineExponentialMovingWindow.mean(self, update, update_times, *args, **kwargs)
    991 if update is not None:
    992     if self._mean.last_ewm is None:
--> 993         raise ValueError(
    994             "Must call mean with update=None first before passing update"
    995         )
    996     result_from = 1
    997     result_kwargs["index"] = update.index

ValueError: Must call mean with update=None first before passing update

所有窗口化操作都支持 min_periods 参数,该参数指示窗口必须具有的非`np.nan``值的最小数量;否则,结果值为 np.nanmin_periods 对于基于时间的窗口和 window 适用于固定窗

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([np.sum, np.mean, np.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

这也适用于类似DATETIME的索引。

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

自定义滚窗#

1.0 新版功能.

除了接受整数或偏移量作为 window 论点, rolling 还接受一个 BaseIndexer 子类,允许用户定义用于计算窗口边界的自定义方法。这个 BaseIndexer 子类将需要定义一个 get_window_bounds 方法,该方法返回由两个数组组成的元组,第一个是窗口的开始索引,第二个是窗口的结束索引。另外, num_valuesmin_periodscenterclosed ,并将自动传递给 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 [2]: from pandas.api.indexers import BaseIndexer

In [3]: class CustomIndexer(BaseIndexer):
   ...:     def get_window_bounds(self, num_values, min_periods, center, closed):
   ...:         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 [4]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)

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

您可以查看其他示例 BaseIndexer subclasses here

1.1 新版功能.

这些示例中值得注意的一个子类是 VariableOffsetWindowIndexer ,它允许在非固定偏移量上滚动操作,如 BusinessDay

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

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

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

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

In [48]: df
Out[48]: 
            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 [49]: df.rolling(indexer).sum()
Out[49]: 
               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 [50]: from pandas.api.indexers import FixedForwardWindowIndexer

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

In [52]: df.rolling(indexer, min_periods=1).sum()
Out[52]: 
               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 [53]: 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 [54]: df
Out[54]: 
                     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 [55]: reversed_df = df[::-1].rolling("2s").sum()[::-1]

In [56]: reversed_df
Out[56]: 
                     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 [57]: def mad(x):
   ....:     return np.fabs(x - x.mean()).mean()
   ....: 

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

In [59]: s.rolling(window=4).apply(mad, raw=True)
Out[59]: 
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引擎#

1.0 新版功能.

另外, apply() 可以利用 Numba 如果作为可选依赖项安装,则。可以使用Numba执行应用聚合,方法是指定 engine='numba'engine_kwargs 论据 (raw 还必须设置为 True )。看见 enhancing performance with Numba 以了解参数的一般用法和性能注意事项。

Numba可能会在两个例程中应用:

  1. 如果 func is a standard Python function, the engine will JIT 传递的函数。 func 也可以是JITed函数,在这种情况下,引擎不会再次JIT该函数。

  2. 引擎将JIT for循环,其中将Apply函数应用到每个窗口。

这个 engine_kwargs argument is a dictionary of keyword arguments that will be passed into the numba.jit decorator 。这些关键字参数将应用于 both 传递的函数(如果是标准的Python函数)和每个窗口上的应用for循环。

1.3.0 新版功能.

meanmedianmaxmin ,以及 sum 还支持 engineengine_kwargs 争论。

二进制窗函数#

cov() and corr() can compute moving window statistics about two Series or any combination of DataFrame/SeriesDataFrame/DataFrame 。以下是每种情况下的行为:

  • Series :计算配对的统计量。

  • DataFrame/Series :使用传递的Series计算DataFrame的每一列的统计信息,从而返回DataFrame。

  • DataFrame/DataFrame :默认情况下,计算匹配列名的统计信息,返回DataFrame。如果关键字参数 pairwise=True 传递,然后计算每对列的统计信息,并返回 DataFrame 使用一个 MultiIndex 其值是有问题的日期(请参见 the next section )。

例如:

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

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

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

In [63]: df2.rolling(window=2).corr(df2["B"])
Out[63]: 
              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 输入将产生多索引的 DataFrame 谁的 index 都是有问题的日期。在单个DataFrame参数的情况下, pairwise 甚至可以省略参数:

备注

遗漏的值被忽略,并且使用成对的完整观测来计算每个条目。

假设丢失的数据是随机丢失的,这导致对协方差矩阵的估计是无偏的。然而,对于许多应用,这种估计可能是不可接受的,因为估计的协方差矩阵不能保证是半正定的。这可能导致估计的相关性具有大于1的绝对值,和/或不可逆的协方差矩阵。看见 Estimation of covariance matrices 了解更多详细信息。

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

In [65]: covs
Out[65]: 
                     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]

加权窗口#

这个 win_type argument in .rolling generates a weighted windows that are commonly used in filtering and spectral estimation. win_type must be string that corresponds to a scipy.signal window function 。必须安装Scipy才能使用这些窗口,并且必须在聚合函数中指定Scipy窗口方法采用的补充参数。

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

In [67]: s.rolling(window=5).mean()
Out[67]: 
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 [68]: s.rolling(window=5, win_type="triang").mean()
Out[68]: 
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 [69]: s.rolling(window=5, win_type="gaussian").mean(std=0.1)
Out[69]: 
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 [70]: df = pd.DataFrame(range(5))

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

In [72]: df.expanding(min_periods=1).mean()
Out[72]: 
     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-\α)x_{t-1}+(1-\α)^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-\α)y_{t-1}+\αx_t,\end{split}\]

这相当于使用权重

\[\begin{split}W_i=\开始{案例} \Alpha(1-\Alpha)^i&\Text{if}i<t\\ (1-\Alpha)^i&\Text{if}i=t. \结束{案例}\end{split}\]

备注

这些方程式有时用以下形式写成 \(\alpha' = 1 - \alpha\) ,例如

\[Y_t=\α‘y_{t-1}+(1-\α’)x_t。\]

出现上述两种变体之间的差异是因为我们所处理的级数具有有限的历史。考虑一系列无限的历史, adjust=True

\[Y_t=\frac{x_t+(1-\α)x_{t-1}+(1-\α)^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\) 直接来说,通常更容易想到的是 span质心(COM)half-life 一个EW时刻:

\[\begin{split}\Alpha= \开始{案例} \frac{2}{s+1},&\Text{for span}\s\geq 1\\ \FRAC{1}{1+c},&\Text{质心}\c\geq 0\\ 1-\exp^{\frac{\log 0.5}{h}},&\Text{用于半衰期}\h>0 \结束{案例}\end{split}\]

必须准确地指定以下其中之一 span质心half-lifeAlpha 致EW职能部门:

  • Span 与通常所说的“N日EW移动均线”相对应.

  • 质心 有更多的物理解释,可以从跨度的角度来考虑: \(c = (s - 1) / 2\)

  • Half-life 是指数权重减少到一半的时间段。

  • Alpha 直接指定平滑系数。

1.1.0 新版功能.

您还可以指定 halflife 以时间增量可转换单位表示,用于指定观测值衰减到其值的一半所需的时间量,同时还指定 times

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

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

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

In [76]: df.ewm(halflife="4 days", times=pd.DatetimeIndex(times)).mean()
Out[76]: 
          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-\α)^2\CDOT 3+1\CDOT 5}{(1-\α)^2+1}。\]

鉴于如果 ignore_na=True ,加权平均数的计算公式为

\[FRAC{(1-\α)\CDOT 3+1\CDOT 5}{(1-\α)+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\) 。)看见 Weighted Sample Variance 有关更多细节,请访问维基百科。