2016-05-02 10 views
7

私はpandasデータフレームを持っており、(groupby句の後の)列のローリング平均を計算したいと思います。しかし、私はNaNを除外したい。pandas groupbyとrolling_apply NaNを無視する

たとえば、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を使用して)、ウィンドウ内の値を合計(countを使用して)NaN値をカウントして、見つけること割ることです平均。 、実行時には速く、私が試したすべての適用ベースの方法よりも大幅に、100ミリ秒未満であった

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 

テストこの大きなDATAFRAME上(100,000行の周り):このコードは、ご希望の出力に一致する次のような出力を提供します。

タイミングがグループの数などの他の要因の影響を受ける可能性があるため、実際のデータに対するさまざまなアプローチをテストする価値があります。しかし、ベクトル化された計算が勝つことはかなり確かです。


上記のアプローチは、ローリング平均などの簡単な計算ではうまくいきます。実装はより複雑ですが、より複雑な計算(標準偏差のローリングなど)にも役立ちます。

一般的な考え方は、パンダで速い(例えば、sum)単純なルーチンを見て、その後、すべてのヌル値に同一性要素(たとえば、0)を入力します。ゴーピーを使用してローリング操作を実行できます(例:.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() 

注:ここでは、このように見えることができるもののスケッチです。パンダは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 

ベクトル化が100倍高速このケースです。もちろん、どれくらいのデータがあるかによって、applyを使用することに固執したいと思うかもしれません。これは、パフォーマンスを犠牲にして一般性/簡潔さを可能にするからです。

+0

これはパンダ18でのみ利用可能な 'rolling'メソッドを使用していますが、OPでは' pd.rolling_apply'を使用するので、パンダ17以下がインストールされている可能性が最も高いことに注意してください。 – IanS

+0

@ajcrこれは現在の問題を解決しますが、平均以外の関数(ローリング標準偏差など)を適用したい場合は完全な書き換えが必要です。他の機能のためにも機能させる方法はありますか? – Stergios

+0

@Stergios:私は問題をもう少し考えさせ、今日/明後のこの答えにいくつかのガイドライン/提案を追加します。 'apply'を使うのが時には最も便利なオプションであり、最も一般的な解決法を提供することは間違いありません(すべての機能が、パンダの加速ルーチンを模倣するのは簡単ではありません)。 –

関連する問題