web-dev-qa-db-ja.com

Python pandas-groupbyの後に行をフィルタリングする

たとえば、次の表があります。

index,A,B
0,0,0
1,0,8
2,0,8
3,1,0
4,1,5

Aでグループ化した後:

0:
index,A,B
0,0,0
1,0,8
2,0,8

1:
index,A,B
3,1,5
4,1,3

Bの数がグループの列Bのすべての行の最大値よりも小さい場合、各グループから行を削除する必要があります。さて、私はこの問題を英語に翻訳して定式化するのに問題があるので、ここに例があります:

グループ0の列Bの行の最大値:8

したがって、インデックス0の行を削除し、インデックス1および2の行を保持したい

グループ1の列Bの行の最大値:5

したがって、インデックス4の行を削除し、インデックス3の行を保持したい

pandasフィルター関数を使用しようとしましたが、問題は、グループ内のすべての行を一度に操作していることです。

data = <example table>
grouped = data.groupby("A")
filtered = grouped.filter(lambda x: x["B"] == x["B"].max())

理想的に必要なのは、グループ内のすべての行を反復処理するいくつかのフィルターです。

手伝ってくれてありがとう!

追伸グループ内の行のみを削除し、DataFrameオブジェクトを返さない方法もありますか?

37
jirinovo

applyオブジェクトでgroupbyを使用するだけです。これをもう少し明確にするために、サンプルデータを変更しました。

import pandas
from io import StringIO

csv = StringIO("""index,A,B
0,1,0.0
1,1,3.0
2,1,6.0
3,2,0.0
4,2,5.0
5,2,7.0""")

df = pandas.read_csv(csv, index_col='index')
groups = df.groupby(by=['A'])
print(groups.apply(lambda g: g[g['B'] == g['B'].max()]))

どのプリント:

         A  B
A index      
1 2      1  6
2 4      2  7
43
Paul H

編集:私は.transformグループをメソッドで使用してこれを行うためのはるかにきちんとした方法を学びました:

def get_max_rows(df):
    B_maxes = df.groupby('A').B.transform(max)
    return df[df.B == B_maxes] 

B_maxesは、元のdfと同じインデックスが付けられたシリーズで、各BグループのAの最大値が含まれています。多くの関数を変換メソッドに渡すことができます。それらが同じ長さのスカラーまたはベクトルとして出力されたら、私は思います。 'median'のような一般的な関数名として一部の文字列を渡すこともできます。これは、「A」が結果のインデックスにならないという点で、Paul Hの方法とは少し異なりますが、後で簡単に設定できます。

import numpy as np
import pandas as pd
df_lots_groups = pd.DataFrame(np.random.Rand(30000, 3), columns = list('BCD')
df_lots_groups['A'] = np.random.choice(range(10000), 30000)

%timeit get_max_rows(df_lots_groups)
100 loops, best of 3: 2.86 ms per loop

%timeit df_lots_groups.groupby('A').apply(lambda df: df[ df.B == df.B.max()])
1 loops, best of 3: 5.83 s per loop

編集:

以下は、有効な比較演算子と有効なgroupbyメソッドを使用してグループから行を選択できるようにする抽象化です。

def get_group_rows(df, group_col, condition_col, func=max, comparison='=='):
    g = df.groupby(group_col)[condition_col]
    condition_limit = g.transform(func)
    df.query('condition_col {} @condition_limit'.format(comparison))

したがって、たとえば、各AグループのB値の中央値より上にあるすべての行が必要な場合は、

get_group_rows(df, 'A', 'B', 'median', '>')

いくつかの例:

%timeit get_group_rows(df_lots_small_groups, 'A', 'B', 'max', '==')
100 loops, best of 3: 2.84 ms per loop
%timeit get_group_rows(df_lots_small_groups, 'A', 'B', 'mean', '!=')
100 loops, best of 3: 2.97 ms per loop
19
JoeCondron

次に、他の例を示します。idxmax()と.loc()を使用したgroupby操作の後に最大値で行をフィルタリングする

In [465]: import pandas as pd

In [466]:   df = pd.DataFrame({
               'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2'],
               'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4'], 
               'value' : [3,2,5,8,10,1]     
                })

In [467]: df
Out[467]: 
   mt   sp  value
0  S1  MM1      3
1  S1  MM1      2
2  S3  MM1      5
3  S3  MM2      8
4  S4  MM2     10
5  S4  MM2      1

### Here, idxmax() finds the indices of the rows with max value within groups,
### and .loc() filters the rows using those indices :
In [468]: df.loc[df.groupby(["mt"])["value"].idxmax()]                                                                                                                           
Out[468]: 
   mt   sp  value
0  S1  MM1      3
3  S3  MM2      8
4  S4  MM2     10
5
Surya

これらの答えはすべて良いですが、私は次のことを望みました:

(DataframeGroupby object) --> filter some rows out --> (DataframeGroupby object)

肩をすくめて、思ったより難しくて面白そうだ。したがって、この1つのライナーは私が望んだことを達成しますが、おそらく最も効率的な方法ではありません:)

gdf.apply(lambda g: g[g['team'] == 'A']).reset_index(drop=True).groupby(gdf.grouper.names) 

作業コードの例:

import pandas as pd

def print_groups(gdf): 
    for name, g in gdf: 
        print('\n'+name) 
        print(g)

df = pd.DataFrame({'name': ['sue', 'jim', 'ted', 'moe'],
                   'team': ['A', 'A', 'B', 'B'], 
                   'fav_food': ['tacos', 'steak', 'tacos', 'steak']})                               

gdf = df.groupby('fav_food')                                                                                                                                           
print_groups(gdf)                                                                                                                                                      

    steak
        name team fav_food
    1  jim    A    steak
    3  moe    B    steak

    tacos
        name team fav_food
    0  sue    A    tacos
    2  ted    B    tacos

fgdf = gdf.apply(lambda g: g[g['team'] == 'A']).reset_index(drop=True).groupby(gdf.grouper.names)                                                                      
print_groups(fgdf)                                                                                                                                                     

    steak
      name team fav_food
    0  jim    A    steak

    tacos
      name team fav_food
    1  sue    A    tacos
0
Briford Wylie