Подтвердить что ты не робот

Как работают pandas подвижные объекты?

Изменить: Я скомпрометировал этот вопрос, учитывая, что он, вероятно, слишком запутан для начала. Мяч вопроса выделен жирным шрифтом ниже.

Я хотел бы узнать больше об объекте, который создается при использовании DataFrame.rolling или Series.rolling:

print(type(df.rolling))
<class 'pandas.core.window.Rolling'>

Некоторые предпосылки: рассмотрим часто используемую альтернативу с np.as_strided. Этот фрагмент кода сам по себе не важен, но его результатом является моя контрольная точка при задании этого вопроса.

def rwindows(a, window):
    if a.ndim == 1:
        a = a.reshape(-1, 1)
    shape = a.shape[0] - window + 1, window, a.shape[-1]
    strides = (a.strides[0],) + a.strides
    windows = np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
    return np.squeeze(windows)

Здесь rwindows возьмет 1d или 2d ndarray и построит скользящие "блоки", равные указанному размеру окна (как показано ниже). Как объект .rolling сравнивается с выходом ndarray ниже? Является ли он итератором с определенными атрибутами, хранящимися для каждого блока? Или что-то еще? Я пробовал играть с завершением табуляции на объекте с помощью атрибутов/методов, таких как __dict__ и _get_index(), и они не очень мне говорят. Я также видел метод _create_blocks в pandas - он вообще похож на метод strided?

# as_strided version

a = np.arange(5)
print(rwindows(a, 3))           # 1d input
[[0 1 2]
 [1 2 3]
 [2 3 4]]

b = np.arange(10).reshape(5,2)
print(rwindows(b, 4))           # 2d input
[[[0 1]
  [2 3]
  [4 5]
  [6 7]]

 [[2 3]
  [4 5]
  [6 7]
  [8 9]]]

Часть 2, дополнительный кредит

Использование подхода NumPy выше (реализация OLS здесь) обусловлена ​​тем, что func внутри pandas.core.window.Rolling.apply должен

выдает одно значение из входных данных ndarray * args и ** kwargs передается функции

Таким образом, аргумент не может быть другим подвижным объектом. То есть.

def prod(a, b):
    return a * b
df.rolling(3).apply(prod, args=((df + 2).rolling(3),))
-----------------------------------------------------------------------
...
TypeError: unsupported operand type(s) for *: 'float' and 'Rolling'

Итак, это действительно от того, где мой вопрос выше. Почему передаваемая функция должна использовать массив NumPy и создавать одно скалярное значение, и что это связано с компоновкой объекта .rolling?

4b9b3361

Ответ 1

Я предлагаю вам взглянуть на исходный код, чтобы понять, что происходит. В частности, я предлагаю вам взглянуть на функции rolling в generic.py и window.py. Оттуда вы можете посмотреть Window класс, который используется, если вы укажете тип окна или значение по умолчанию rolling class. Последний наследует от _Rolling_and_Expanding и в конечном итоге _Rolling и _Window.

Тем не менее, я дам свои два цента: Pandas 'весь механизм катания полагается на функцию numpy apply_along_axis. В частности, здесь используется здесь pandas. Он используется в сочетании с windows.pyx cython module. В вашей серии выходим агрегированное окно. Для типичных функций агрегации он обрабатывает их для вас эффективно, но для пользовательских (с использованием apply()) он использует roll_generic() в windows.pyx.

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

Источником путаницы может быть то, что вы думаете о подвижном объекте как о кадре данных. (Вы назвали катящийся объект df в своем последнем фрагменте кода). Это действительно не так. Это объект, который может создавать dataframes, применяя агрегации по логике окна, в которой он находится.

Лямбда, которую вы поставляете, применяется для каждой ячейки вашего нового фрейма. Он занимает окно назад (вдоль каждого столбца) в вашем старом фрейме данных и объединяет его в одну ячейку в новом фреймворке данных. Агрегация может быть такой, как sum, mean, что-то обычное, что вы сделали и т.д., Над некоторым размером окна, скажем 3. Вот несколько примеров:

a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])
df.rolling(3).mean().dropna()

... что также можно сделать:

df.rolling(3).apply(np.mean).dropna()

... и производит:

     a
2  3.0
3  6.0
4  9.0

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

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

df.rolling(3).apply(lambda x: np.sqrt(x.dot(x))).dropna()

если вы не знакомы с лямбда-функциями, это то же самое, что:

def euclidean_dist(x):
    return np.sqrt(x.dot(x))

df.rolling(3).apply(euclidean_dist).dropna()

... yielding:

          a
2  2.236068
3  3.741657
4  5.385165

Чтобы убедиться, что мы можем вручную проверить, что np.sqrt(0**2 + 1**2 + 2**2) действительно 2.236068.

[В вашем первоначальном редактировании, в последнем фрагменте кода, ваш код, вероятно, не работает раньше, чем вы ожидаете. Он не работает перед вызовом df.apply(...). Вы пытаетесь добавить катящийся объект с именем df к номеру 2 до его передачи в df.apply(...). Скользящий объект - это не то, над чем вы делаете операции. Функция агрегации, которую вы предоставили, также не соответствует функции агрегации в целом. a - это список со значениями окна, b будет постоянным дополнительным параметром, в который вы проходите. Он может быть скользящим объектом, если вы хотите, но обычно это не то, что вы хотели бы сделать, Чтобы сделать это более понятным, вот что похоже на то, что вы делали в своем первоначальном редактировании, но работает:

a = np.arange(8)
df = pd.DataFrame(a, columns=['a'])
n = 4
rol = df.rolling(n)

def prod(window_list, constant_rol):
    return window_list.dot(constant_rol.sum().dropna().head(n))

rol.apply(prod, args=(rol,)).dropna()

# [92.0, 140.0, 188.0, 236.0, 284.0]

Это надуманный пример, но я показываю его, чтобы указать, что вы можете передать все, что захотите, в качестве константы, даже для подвижного объекта, который вы используете сами. Динамическая часть - это первый аргумент a в вашем случае или window_list в моем случае. Все определенные окна в виде отдельных списков передаются в эту функцию по очереди.

Основываясь на ваших последующих комментариях, это может быть то, что вы ищете:

import numpy as np
import pandas as pd

n = 3
a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])

def keep(window, windows):
    windows.append(window.copy())
    return window[-1]

windows = list()
df['a'].rolling(n).apply(keep, args=(windows,))
df = df.tail(n)
df['a_window'] = windows

который добавляет массивы/векторы к каждому блоку прокатки, создавая таким образом:

   a         a_window
2  2  [0.0, 1.0, 2.0]
3  3  [1.0, 2.0, 3.0]
4  4  [2.0, 3.0, 4.0]

Обратите внимание, что он работает только, если вы делаете это по столбцу за раз. Если вы хотите сделать некоторую математику в окне, прежде чем хранить ее в keep, это тоже хорошо.

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

Если ваша конечная цель состоит в том, чтобы создать dataframe из отстающих переменных, я бы пошел на использование реальных столбцов, используя shift():

import numpy as np
import pandas as pd

a = np.arange(5)

df = pd.DataFrame(a, columns=['a'])
for i in range(1,3):
    df['a-%s' % i] = df['a'].shift(i)

df.dropna()

...:

   a  a-1  a-2
2  2  1.0  0.0
3  3  2.0  1.0
4  4  3.0  2.0

(Может быть, есть еще более красивый способ сделать это, но он выполняет свою работу.)

Что касается вашей переменной b в вашем первом фрагменте кода, помните, что DataFrames в pandas обычно не обрабатываются как тензоры произвольных измерений/объекта. Вы, вероятно, можете наполнить все, что захотите, но в конечном итоге ожидаются строки, объекты времени, ints и floats. Это могут быть причины, по которым дизайнеры pandas не беспокоились, позволяя прокачивать агрегацию нескалярным значениям. Это даже не похоже, что простая строка разрешена как вывод функции агрегации.

В любом случае, я надеюсь, что это ответ на некоторые из ваших вопросов. Если не сообщите мне, и я попытаюсь помочь вам в комментариях или обновлении.


Заключительная записка о функции _create_blocks() для прокатки объектов.

Функция _create_blocks() обрабатывает переиндексирование и бининг, когда вы используете аргумент freq rolling.

Если вы используете частоту с, скажем, неделями, чтобы freq=W:

import pandas as pd

a = np.arange(50)
df = pd.DataFrame(a, columns=['a'])
df.index = pd.to_datetime('2016-01-01') + pd.to_timedelta(df['a'], 'D')
blocks, obj, index = df.rolling(4, freq='W')._create_blocks(how=None)
for b in blocks:
    print(b)

... затем мы получаем забитые (не прокатные) исходные данные по неделям:

               a
a               
2016-01-03   2.0
2016-01-10   9.0
2016-01-17  16.0
2016-01-24  23.0
2016-01-31  30.0
2016-02-07  37.0
2016-02-14  44.0
2016-02-21   NaN

Обратите внимание, что это не результат агрегированной прокатки. Это просто новые блоки, над которыми он работает. После этого. Мы делаем агрегацию типа sum и получаем:

                a
a                
2016-01-03    NaN
2016-01-10    NaN
2016-01-17    NaN
2016-01-24   50.0
2016-01-31   78.0
2016-02-07  106.0
2016-02-14  134.0
2016-02-21    NaN

..., который проверяется с помощью тестового суммирования: 50 = 2 + 9 + 16 + 23.

Если вы не используете freq в качестве аргумента, он просто возвращает исходную структуру данных:

import pandas as pd
a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])
blocks, obj, index = df.rolling(3)._create_blocks(how=None)

for b in blocks:
    print(b)

... который производит...

            a
a            
2016-01-01  0
2016-01-02  1
2016-01-03  2
2016-01-04  3
2016-01-05  4

... и используется для агрегации скользящего окна.