Spec-Zone.ru › pandas 1

Операции с окнами

pandas содержит компактный набор API для выполнения операций с окнами — операции, которые выполняют агрегацию над скользящим разделением значений. Функциональность API аналогична API groupby в том, что Series и DataFrame вызывают метод окон с необходимыми параметрами, а затем последовательно вызывают функцию агрегации.

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

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 и std методы могут страдать от числовой неточности из-за лежащих в основе алгоритмов окон, накапливающих суммы. Когда значения отличаются по величине \(1/np.finfo(np.double).eps\), это приводит к усечению. Следует отметить, что большие значения могут повлиять на окна, которые не включают эти значения. Для вычисления скользящих сумм используется алгоритм Кахана, чтобы сохранить точность по возможности максимально.

Введено в версии 1.3.0.

Некоторые операции с окнами также поддерживают параметр method='table' в конструкторе, который выполняет операцию с окном над всей DataFrame, а не над отдельным столбцом или строкой за раз. Это может обеспечить полезное преимущество производительности для DataFrame со многими столбцами или строками (с соответствующим аргументом axis) или возможность использования других столбцов во время операции с окнами. Параметр method='table' может быть использован только, если engine='numba' указан в соответствующем вызове метода.

Например, вычисление взвешенного среднего значения может быть вычислено с помощью 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 после построения объекта операции с окном, который возвращает новый объект, который поддерживает передачу новых DataFrame или Series объектов для продолжения вычисления операции с окном с новыми значениями (т. е. онлайн-вычисления).

Методы в этом новом объекте окон должны сначала вызвать метод агрегации, чтобы «инициализировать» начальное состояние онлайн-вычисления. Затем новые DataFrame или Series объекты могут быть переданы в аргументе 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 для фиксированных окон

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

Это также можно применить к индексам типа даты и времени.

Введено в версии 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_values, min_periods, center, closed автоматически будут переданы в 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_expanding равно True, в противном случае окно размером 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 можно посмотреть здесь

Введено в версии 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, если он установлен в качестве необязательной зависимости. Агрегация apply может быть выполнена с помощью Numba, указав аргументы engine='numba' и engine_kwargs (raw также должен быть установлен в значение True). См. Улучшение производительности с помощью Numba для общего использования аргументов и соображений по производительности.

Numba будет применяться в потенциально двух процедурах:

  1. Если func является стандартной функцией Python, движок JIT -компилирует переданную функцию. func также может быть JIT-функцией, в этом случае движок не будет JIT-компилировать функцию повторно.

  2. Движок JIT-компилирует цикл for, где функция apply применяется к каждому окну.

Аргумент engine_kwargs — словарь ключевых аргументов, которые будут переданы в декоратор numba.jit. Эти ключевые аргументы будут применены к обеим переданным функциям (если это стандартная функция Python) и циклу apply над каждым окном.

Введено в версии 1.3.0.

mean, median, max, min, и sum также поддерживают аргументы engine и engine_kwargs.

Бинарные функции окна

cov() и corr() могут вычислять статистику скользящего окна для двух Series или любой комбинации DataFrame/Series или DataFrame/DataFrame. Вот поведение в каждом случае:

  • две Series: вычислить статистику для пар.

  • DataFrame/Series: вычислить статистику для каждого столбца DataFrame со связанной Series, тем самым возвращая DataFrame.

  • DataFrame/DataFrame: по умолчанию вычисляет статистику для совпадающих имён столбцов, возвращая DataFrame. Если передан ключевой аргумент pairwise=True, вычисляет статистику для каждой пары столбцов, возвращая DataFrame с MultiIndex, значения которого представляют собой даты, о которых идет речь (см. следующий раздел).

Например:

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 можно даже опустить:

Примечание

Пропущенные значения игнорируются, и каждое значение вычисляется с использованием парных полных наблюдений.

Предполагая, что пропущенные данные пропущены случайным образом, это приводит к оценке матрицы ковариаций, которая является несмещенной. Однако для многих приложений эта оценка может быть неприемлемой, поскольку оценочная матрица ковариаций не гарантируется как положительно полуопределенная. Это может привести к тому, что оценочные корреляции будут иметь абсолютные значения больше единицы и/или к необратимой матрице ковариации. См. Оценку матриц ковариаций для получения более подробной информации.

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 в .rolling генерирует взвешенные окна, которые часто используются в фильтрации и спектральном оценивании. win_type должно быть строкой, соответствующей функции окна scipy.signal. Для использования этих окон необходимо установить 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 - \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{if } i < t \\ (1 - \alpha)^i & \text{if } 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\) напрямую, часто проще думать о протяженности, центре массы (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 необходимо точно указать один из протяженности, центра массы, полураспада и альфа:

  • Протяженность соответствует тому, что обычно называется «скользящим средним EW за N дней».

  • Центр массы имеет более физическое толкование и может быть представлен в терминах протяженности: \(c = (s - 1) / 2\).

  • Полураспада — период времени, в течение которого экспоненциальный вес уменьшается вдвое.

  • Альфа напрямую задает коэффициент сглаживания.

Введено в версии 1.1.0.

Также можно указать halflife в терминах единиц, преобразуемых в timedelta, чтобы указать время, необходимое для того, чтобы наблюдение уменьшилось вдвое, когда также задается последовательность 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-\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=True, ewmvar(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\).) См. Взвешенная выборочная дисперсия на Википедии для получения дополнительной информации.

© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/1.5.0/user_guide/window.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API