Как всегда в панд, придерживаясь векторизованных методов (т.е. избегая apply
) имеет важное значение для производительности и масштабируемости.
Операция, которую вы хотите сделать, немного затруднительна, так как операции качения по объектам groupby в настоящее время не являются NaN-версией (версия 0.18.1). Таким образом, нам нужно несколько коротких строк кода:
g1 = df.groupby(['var1'])['value'] # group values
g2 = df.fillna(0).groupby(['var1'])['value'] # fillna, then group values
s = g2.rolling(2).sum()/g1.rolling(2).count() # the actual computation
s.reset_index(level=0, drop=True).sort_index() # drop/sort index
Идея заключается в том, чтобы суммировать значения в окне (с помощью sum
), подсчитывать значения NaN (используя count
), а затем разделить, чтобы найти имею в виду. Этот код дает следующий результат, который соответствует вашему желаемому результату:
0 NaN
1 NaN
2 2.0
3 2.0
4 2.5
5 3.0
6 3.0
7 2.0
Name: value, dtype: float64
Testing это на больший DataFrame (около 100000 строк), во время выполнения находилось под 100мсом, значительно быстрее, чем любая основой применять методы, которые я пробовал.
Возможно, стоит проверить различные подходы к фактическим данным, так как на тайминги могут влиять другие факторы, такие как количество групп. Однако довольно уверенно, что векторизованные вычисления победят.
Подход, показанный выше, хорошо подходит для простых расчетов, таких как среднее значение прокатки.Он будет работать для более сложных вычислений (например, стандартного отклонения от проката), хотя реализация более активно.
Общая идея состоит в том, чтобы просмотреть каждую простую процедуру, которая выполняется быстро в pandas (например, sum
), а затем заполнить все нулевые значения идентификационным элементом (например, 0
). Затем вы можете использовать groubpy и выполнить операцию прокатки (например, .rolling(2).sum()
). Затем результат объединяется с выводами других операций.
Например, для реализации groupby NaN-осведомленная дисперсия качения (из которой стандартное отклонение является квадратным корнем) мы должны найти «среднее значение квадратов минус квадрат среднего значения». Вот набросок того, что это может выглядеть следующим образом:
def rolling_nanvar(df, window):
"""
Group df by 'var1' values and then calculate rolling variance,
adjusting for the number of NaN values in the window.
Note: user may wish to edit this function to control degrees of
freedom (n), depending on their overall aim.
"""
g1 = df.groupby(['var1'])['value']
g2 = df.fillna(0).groupby(['var1'])['value']
# fill missing values with 0, square values and groupby
g3 = df['value'].fillna(0).pow(2).groupby(df['var1'])
n = g1.rolling(window).count()
mean_of_squares = g3.rolling(window).sum()/n
square_of_mean = (g2.rolling(window).sum()/n)**2
variance = mean_of_squares - square_of_mean
return variance.reset_index(level=0, drop=True).sort_index()
Обратите внимание, что эта функция не может быть численно устойчивым (возведения в квадрат может привести к переполнению). pandas использует Welford's algorithm внутренне для смягчения этой проблемы.
В любом случае, эта функция, хотя и использует несколько операций, все еще очень быстро. Вот сравнение с более выразительным применять на основе метода, предложенного Yakym Pirozhenko:
>>> df2 = pd.concat([df]*10000, ignore_index=True) # 80000 rows
>>> %timeit df2.groupby('var1')['value'].apply(\
lambda gp: gp.rolling(7, min_periods=1).apply(np.nanvar))
1 loops, best of 3: 11 s per loop
>>> %timeit rolling_nanvar(df2, 7)
10 loops, best of 3: 110 ms per loop
Vectorization в 100 раз быстрее, в этом случае. Разумеется, в зависимости от того, сколько данных у вас есть, вы можете использовать apply
, так как это позволяет вам общность/краткость за счет производительности.
Просьба представить небольшой воспроизводимый набор кода, чтобы я мог играть с подобной информацией, но у вас нет необходимости составлять ее самостоятельно. – piRSquared
@piRSquared Я просто добавил образец кода. Спасибо – Stergios