Spec-Zone.ru › pandas 2

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

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

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.

Предупреждение

Некоторые агрегации окон, 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(["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_values, min_periods, center, closed и step автоматически передаются в 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 [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

Rolling apply

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

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

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

  2. Движок будет компилировать цикл 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 [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, индексы которого — даты. В случае одного DataFrame аргумент pairwise можно опустить:

Примечание

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

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

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]

Взвешенный интервал

Аргумент win_type в .rolling генерирует взвешенные интервалы, которые часто используются в фильтрации и спектральном анализе. win_type должен быть строкой, соответствующей функции окна scipy.signal window function. Для использования этих окон необходимо установить 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\) напрямую, часто проще думать либо о протяжённости, центре масс (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\).

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

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

Вы также можете указать halflife в терминах преобразуемого в timedelta единицы измерения, чтобы указать количество времени, необходимое для того, чтобы наблюдение уменьшилось вдвое, при одновременном указании последовательности 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=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/2.2.2/user_guide/window.html

Spec-Zone.ru

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