2016-04-03 104 views
5

DataFrameGroupby.filter方法過濾組,並返回包含傳遞過濾器的行的DataFrame鏈接分組,過濾和聚合

但是,我能做些什麼來獲得一個新的DataFrameGroupBy對象,而不是過濾後的DataFrame

例如,假設我有一個DataFramedf,其中有兩列AB。我想獲得B列的平均值爲A列的每個值,只要有至少5行該組中:

# pandas 0.18.0 
# doesn't work because `filter` returns a DF not a GroupBy object 
df.groupby('A').filter(lambda x: len(x)>=5).mean() 
# works but slower and awkward to write because needs to groupby('A') twice 
df.groupby('A').filter(lambda x: len(x)>=5).reset_index().groupby('A').mean() 
# works but more verbose than chaining 
groups = df.groupby('A') 
groups.mean()[groups.size() >= 5] 

回答

2

下面是一些reproduceable數據:

np.random.seed(0) 

df = pd.DataFrame(np.random.randint(0, 10, (10, 2)), columns=list('AB')) 

>>> df 
    A B 
0 5 0 
1 3 3 
2 7 9 
3 3 5 
4 2 4 
5 7 6 
6 8 8 
7 1 6 
8 7 7 
9 8 1 

樣品過濾器應用程序證明它可以處理數據。

gb = df.groupby('A') 
>>> gb.filter(lambda group: group.A.count() >= 3) 
    A B 
2 7 9 
5 7 6 
8 7 7 

這裏有一些選擇:

1)您也可以先過濾器基於該值數,然後組。

vc = df.A.value_counts() 

>>> df.loc[df.A.isin(vc[vc >= 2].index)].groupby('A').mean() 
      B 
A   
3 4.000000 
7 7.333333 
8 4.500000 

2)執行GROUPBY兩次,前後濾波後:

>>> (df.groupby('A', as_index=False) 
     .filter(lambda group: group.A.count() >= 2) 
     .groupby('A') 
     .mean()) 
      B 
A   
3 4.000000 
7 7.333333 
8 4.500000 

3)鑑於你的第一個GROUPBY返回羣體,你還可以過濾那些:

d = {k: v 
    for k, v in df.groupby('A').groups.items() 
    if len(v) >= 2} # gb.groups.iteritems() for Python 2 

>>> d 
{3: [1, 3], 7: [2, 5, 8], 8: [6, 9]} 

這有點破解,但應該相對高效,因爲您不需要重新組合。

>>> pd.DataFrame({col: [df.ix[d[col], 'B'].mean()] for col in d}).T.rename(columns={0: 'B'}) 
      B 
3 4.000000 
7 7.333333 
8 4.500000 

計時與100K行

np.random.seed(0) 
df = pd.DataFrame(np.random.randint(0, 10, (100000, 2)), columns=list('AB')) 

%timeit df.groupby('A', as_index=False).filter(lambda group: group['A'].count() >= 5).groupby('A').mean() 
100 loops, best of 3: 18 ms per loop 

%%timeit 
vc = df.A.value_counts() 
df.loc[df.A.isin(vc[vc >= 2].index)].groupby('A').mean() 
100 loops, best of 3: 15.7 ms per loop 
+0

但我想要得到每個組的平均值,過濾後。您將獲得整個剩餘數據集的平均值。 – max

+0

我想它會產生錯誤的結果...嘗試另一個數據集,你將有至少兩個不同的值組'A' – MaxU

+0

嗯,我得到不同的結果集與您的_hack_版本... – MaxU

3

你可以這樣來做:

In [310]: df 
Out[310]: 
    a b 
0 1 4 
1 7 3 
2 6 9 
3 4 4 
4 0 2 
5 8 4 
6 7 7 
7 0 5 
8 8 5 
9 8 7 
10 6 1 
11 3 8 
12 7 4 
13 8 0 
14 5 3 
15 5 3 
16 8 1 
17 7 2 
18 9 9 
19 3 2 
20 9 1 
21 1 2 
22 0 3 
23 8 9 
24 7 7 
25 8 1 
26 5 8 
27 9 6 
28 2 8 
29 9 0 

In [314]: r = df.groupby('a').apply(lambda x: x.b.mean() if len(x)>=5 else -1) 

In [315]: r 
Out[315]: 
a 
0 -1.000000 
1 -1.000000 
2 -1.000000 
3 -1.000000 
4 -1.000000 
5 -1.000000 
6 -1.000000 
7 4.600000 
8 3.857143 
9 -1.000000 
dtype: float64 

In [316]: r[r>0] 
Out[316]: 
a 
7 4.600000 
8 3.857143 
dtype: float64 

的一行,返回的數據幀,而不是系列:

df.groupby('a') \ 
    .apply(lambda x: x.b.mean() if len(x)>=5 else -1) \ 
    .to_frame() \ 
    .rename(columns={0:'mean'}) \ 
    .query('mean > 0') 

與100的DF進行Timeit比較。000行:

def maxu(): 
    r = df.groupby('a').apply(lambda x: x.b.mean() if len(x)>=5 else -1) 
    return r[r>0] 

def maxu2(): 
    return df.groupby('a') \ 
      .apply(lambda x: x.b.mean() if len(x)>=5 else -1) \ 
      .to_frame() \ 
      .rename(columns={0:'mean'}) \ 
      .query('mean > 0') 

def alexander(): 
    return df.groupby('a', as_index=False).filter(lambda group: group.a.count() >= 5).groupby('a').mean() 

def alexander2(): 
    vc = df.a.value_counts() 
    return df.loc[df.a.isin(vc[vc >= 5].index)].groupby('a').mean() 

結果:

In [419]: %timeit maxu() 
1 loop, best of 3: 1.12 s per loop 

In [420]: %timeit maxu2() 
1 loop, best of 3: 1.12 s per loop 

In [421]: %timeit alexander() 
1 loop, best of 3: 34.9 s per loop 

In [422]: %timeit alexander2() 
10 loops, best of 3: 66.6 ms per loop 

檢查:

In [423]: alexander2().sum() 
Out[423]: 
b 19220943.162 
dtype: float64 

In [424]: maxu2().sum() 
Out[424]: 
mean 19220943.162 
dtype: float64 

結論:

明顯的贏家是alexander2()功能

@亞歷山大,恭喜!

+0

加一個比賽,但我剛剛上調了我的比賽... – Alexander

+0

@亞歷山大,謝謝!我也喜歡你的回答,所以我也贊成。讓我知道你的新解決方案... – MaxU