2016-05-02 3 views
7

У меня есть рамка данных pandas, и я хочу рассчитать среднее значение проката столбца (после предложения groupby). Однако я хочу исключить NaNs.pandas groupby и roll_apply игнорирование NaNs

Например, если groupby возвращает [2, NaN, 1], результат должен быть 1,5, а в настоящее время он возвращает NaN.

Я попытался следующие, но это не похоже на работу:

df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 3, lambda x: np.mean([i for i in x if i is not np.nan and i!='NaN'])) 

Если бы я даже попробовать это:

df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 3, lambda x: 1) 

Я получаю NaN на выходе, так он должен быть чем-то связанным с тем, как панды работают в фоновом режиме.

Любые идеи?

EDIT: Вот пример кода, что я пытаюсь сделать:

import pandas as pd 
import numpy as np 

df = pd.DataFrame({'var1' : ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], 'value' : [1, 2, 3, np.nan, 2, 3, 4, 1] }) 
print df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 2, lambda x: np.mean([i for i in x if i is not np.nan and i!='NaN'])) 

Результат:

0 NaN 
1 NaN 
2 2.0 
3 NaN 
4 2.5 
5 NaN 
6 3.0 
7 2.0 

в то время как я хотел бы иметь следующее:

0 NaN 
1 NaN 
2 2.0 
3 2.0 
4 2.5 
5 3.0 
6 3.0 
7 2.0 
+1

Просьба представить небольшой воспроизводимый набор кода, чтобы я мог играть с подобной информацией, но у вас нет необходимости составлять ее самостоятельно. – piRSquared

+0

@piRSquared Я просто добавил образец кода. Спасибо – Stergios

ответ

1

Может ли этот результат соответствовать вашим ожиданиям? Я немного изменил ваше решение с помощью параметра min_periods и правого фильтра для nan.

In [164]: df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 2, lambda x: np.mean([i for i in x if not np.isnan(i)]), min_periods=1) 
Out[164]: 
0 1.0 
1 2.0 
2 2.0 
3 2.0 
4 2.5 
5 3.0 
6 3.0 
7 2.0 
dtype: float64 
+0

Это умное использование 'min_period'! – IanS

1

Вот альтернативная реализация без списка понимания, но это также не заполнять первую запись на выходе с np.nan

means = df.groupby('var1')['value'].apply(
    lambda gp: gp.rolling(2, min_periods=1).apply(np.nanmean)) 
8

Как всегда в панд, придерживаясь векторизованных методов (т.е. избегая 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, так как это позволяет вам общность/краткость за счет производительности.

+0

Обратите внимание, что это использует метод 'roll', который доступен только в pandas 18, тогда как OP использует' pd.rolling_apply', поэтому, скорее всего, установлены pandas 17 или ниже. – IanS

+0

@ajcr Это решает мою текущую проблему, но для полной перезаписи требуется полная переписывание, если я хочу применять функции, отличные от среднего (например, стандартное отклонение от проката). Есть ли способ заставить его работать и для других функций? – Stergios

+0

@Stergios: Я расскажу о проблеме еще раз и добавлю еще несколько рекомендаций/предложений к этому ответу позже сегодня/завтра. Разумеется, использование 'apply' иногда является наиболее удобным вариантом и обеспечивает наиболее общее решение (не все функции легко подражать с помощью ускоренных подпрограмм pandas). –