複数の関数を複数のgroupby列に適用する


221

ドキュメントには、キーとして、出力列名で辞書を使用して、一度にGROUPBYオブジェクト上で複数の機能を適用する方法を示しています。

In [563]: grouped['D'].agg({'result1' : np.sum,
   .....:                   'result2' : np.mean})
   .....:
Out[563]: 
      result2   result1
A                      
bar -0.579846 -1.739537
foo -0.280588 -1.402938

ただし、これはSeries groupbyオブジェクトでのみ機能します。そして、dictが同様にgroupby DataFrameに渡されると、キーは、関数が適用される列名であると想定します。

私がしたいことは、複数の関数を複数の列に適用することです(ただし、特定の列は複数回操作されます)。また、一部の関数はgroupbyオブジェクトの他の列に依存します(sumif関数など)。私の現在の解決策は、列ごとに行き、上記のコードのようなことをし、他の行に依存する関数にラムダを使用することです。しかし、これには長い時間がかかります(groupbyオブジェクトを反復処理するには長い時間がかかると思います)。一度の実行でgroupbyオブジェクト全体を反復処理するように変更する必要がありますが、これをパンダに幾分きれいに実行する組み込みの方法があるかどうか疑問に思っています。

たとえば、私は次のようなことを試しました

grouped.agg({'C_sum' : lambda x: x['C'].sum(),
             'C_std': lambda x: x['C'].std(),
             'D_sum' : lambda x: x['D'].sum()},
             'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)

しかし、予想どおり、KeyErrorが発生します(DataFrameからagg呼び出された場合、キーは列である必要があるため)。

私がやりたいことを行うための組み込みの方法はありますか、またはこの機能が追加される可能性がありますか、それとも手動でgroupbyを反復処理する必要があるだけですか?

ありがとう


2
2017年以降にこの質問に回答する場合は、以下回答を参照して、複数の列を一緒に集計する慣用的な方法を確認してください。現在選択されている回答には複数の非推奨があります。つまり、groupbyの結果の列の名前を変更するために辞書の辞書を使用することはできなくなりました。
Ted Petrou 2017年

回答:


282

現在受け入れられている回答の後半は古く、2つの非推奨事項があります。まず最も重要なのは、辞書の辞書をagggroupbyメソッドに渡すことができなくなったことです。次に、を使用しないでください.ix

2つの別々の列を同時にapply操作する場合は、DataFrameを適用された関数に暗黙的に渡すメソッドを使用することをお勧めします。上と同じデータフレームを使用しましょう

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.418500  0.030955  0.874869  0.145641      0
1  0.446069  0.901153  0.095052  0.487040      0
2  0.843026  0.936169  0.926090  0.041722      1
3  0.635846  0.439175  0.828787  0.714123      1

列名から集計関数にマッピングされたディクショナリは、集計を実行するための完全に優れた方法です。

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': lambda x: x.max() - x.min()})

              a                   b         c         d
            sum       max      mean       sum  <lambda>
group                                                  
0      0.864569  0.446069  0.466054  0.969921  0.341399
1      1.478872  0.843026  0.687672  1.754877  0.672401

醜いラムダ列名が気に入らない場合は、通常の関数を使用して、次の__name__ように特別な属性にカスタム名を指定できます。

def max_min(x):
    return x.max() - x.min()

max_min.__name__ = 'Max minus Min'

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': max_min})

              a                   b         c             d
            sum       max      mean       sum Max minus Min
group                                                      
0      0.864569  0.446069  0.466054  0.969921      0.341399
1      1.478872  0.843026  0.687672  1.754877      0.672401

applyシリーズの使用と返却

ここで、相互作用する必要のある複数の列がある場合agg、を使用できません。これは、Seriesを暗黙的に集計関数に渡します。applyグループ全体をDataFrameとして使用すると、関数に渡されます。

すべての集計のシリーズを返す単一のカスタム関数を作成することをお勧めします。Seriesインデックスを新しい列のラベルとして使用します。

def f(x):
    d = {}
    d['a_sum'] = x['a'].sum()
    d['a_max'] = x['a'].max()
    d['b_mean'] = x['b'].mean()
    d['c_d_prodsum'] = (x['c'] * x['d']).sum()
    return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])

df.groupby('group').apply(f)

         a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.864569  0.446069  0.466054     0.173711
1      1.478872  0.843026  0.687672     0.630494

MultiIndexesが好きな場合でも、次のようなSeriesを返すことができます。

    def f_mi(x):
        d = []
        d.append(x['a'].sum())
        d.append(x['a'].max())
        d.append(x['b'].mean())
        d.append((x['c'] * x['d']).sum())
        return pd.Series(d, index=[['a', 'a', 'b', 'c_d'], 
                                   ['sum', 'max', 'mean', 'prodsum']])

df.groupby('group').apply(f_mi)

              a                   b       c_d
            sum       max      mean   prodsum
group                                        
0      0.864569  0.446069  0.466054  0.173711
1      1.478872  0.843026  0.687672  0.630494

3
シリーズを返す関数を使用するパターンが大好きです。とてもきちんとしています。
Stephen McAteer 2017

2
これは、複数の列入力を介してデータフレームを同時に集計する唯一の方法です(上記のc_dの例)
Blake

2
結果に戸惑いましたが、aグループ内の合計を取ると、0これはそうではありません0.418500 + 0.446069 = 0.864569か?同じことが他のセルにも当てはまり、数値が加算されていないように見えます。それは、後続の例で使用されたわずかに異なる基礎データフレームである可能性がありますか?
スラックライン2018年

私は頻繁にgroupbyで.size()を使用してレコード数を確認しています。agg:dictメソッドを使用してこれを行う方法はありますか?特定のフィールドをカウントできることは理解していますが、フィールドに依存しないカウントが望ましいと思います。
Chris Decker

1
@slacklineはい。私はそれをテストしましたが、うまくいきます。テッドはフレームを数回作成したばかりで、乱数生成によって作成されたため、実際にデータを生成するためのdfデータは、計算で最終的に使用されるものとは異なりました
Lucas H

166

最初の部分では、キーの列名の辞書と値の関数のリストを渡すことができます。

In [28]: df
Out[28]:
          A         B         C         D         E  GRP
0  0.395670  0.219560  0.600644  0.613445  0.242893    0
1  0.323911  0.464584  0.107215  0.204072  0.927325    0
2  0.321358  0.076037  0.166946  0.439661  0.914612    1
3  0.133466  0.447946  0.014815  0.130781  0.268290    1

In [26]: f = {'A':['sum','mean'], 'B':['prod']}

In [27]: df.groupby('GRP').agg(f)
Out[27]:
            A                   B
          sum      mean      prod
GRP
0    0.719580  0.359790  0.102004
1    0.454824  0.227412  0.034060

更新1:

集計関数はSeriesで機能するため、他の列名への参照は失われます。これを回避するために、完全なデータフレームを参照し、ラムダ関数内のグループインデックスを使用してインデックスを付けることができます。

ここにハックの回避策があります:

In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()}

In [69]: df.groupby('GRP').agg(f)
Out[69]:
            A                   B         D
          sum      mean      prod  <lambda>
GRP
0    0.719580  0.359790  0.102004  1.170219
1    0.454824  0.227412  0.034060  1.182901

ここで、結果の「D」列は合計された「E」値で構成されます。

更新2:

ここに私があなたが尋ねるすべてをするだろうと思う方法があります。最初にカスタムのラムダ関数を作成します。以下では、gはグループを参照しています。集約すると、gはシリーズになります。を渡すg.indexdf.ix[]、dfから現在のグループが選択されます。次に、列Cが0.5未満かどうかをテストします。返されたブール系列が渡されg[]、基準を満たす行のみが選択されます。

In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum()

In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}}

In [97]: df.groupby('GRP').agg(f)
Out[97]:
            A                   B         D
          sum      mean      prod   my name
GRP
0    0.719580  0.359790  0.102004  0.204072
1    0.454824  0.227412  0.034060  0.570441

興味深いことに、{funcname: func}カスタム名を保持するために、リストではなく値としてdictを渡すこともできます。しかし、どちらの場合でも、lambda他の列を使用するを渡すことはできません(lambda x: x['D'][x['C'] < 3].sum()上記のように、「KeyError: 'D'」)。それが可能かどうか、何か考えはありますか?
beardc 2013年

私はそれを正確に実行しようとしていましたが、エラーが発生しますKeyError: 'D'
Zelazny7

かっこいいdf['A'].ix[g.index][df['C'] < 0].sum()です。しかし、これはかなり厄介になり始めています-読みやすさのために、手動ループが望ましいと思います。さらに、agg引数に(の代わりに<lambda>)優先する名前を付ける方法があるかどうかはわかりません。私は誰かがもっと簡単な方法を知っているかもしれないという希望を
差し控え

3
列の値にdictを渡す{'D': {'my name':lambda function}}と、内部のdictキーが列名になります。
Zelazny7 2013年

1
:私はパンダは今、複数の機能をグループ化によってデータフレームに適用されるサポートされていることを信じていpandas.pydata.org/pandas-docs/stable/...
IANS

22

Ted Petrouの回答の(主に美学に関する)代替案として、少しコンパクトなリストを好んだ。それを受け入れることを考慮しないでください、それはテッドの答えに加えてコード/データに関するはるかに詳細なコメントです。Python /パンダは私の最初/最高ではありませんが、これはよく読むことがわかりました:

df.groupby('group') \
  .apply(lambda x: pd.Series({
      'a_sum'       : x['a'].sum(),
      'a_max'       : x['a'].max(),
      'b_mean'      : x['b'].mean(),
      'c_d_prodsum' : (x['c'] * x['d']).sum()
  })
)

          a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.530559  0.374540  0.553354     0.488525
1      1.433558  0.832443  0.460206     0.053313

私はそれがdplyrパイプとdata.tableチェーンされたコマンドをより思い出させます。彼らが優れていると言うのではなく、私にとってはより身近なものです。(私は確かに、多くの人defにとって、これらのタイプの操作に対してより形式化された関数を使用することの好みを認識しています。これは単なる代替手段であり、必ずしも優れているわけではありません。)


Tedと同じ方法でデータを生成しました。再現性のためにシードを追加します。

import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.374540  0.950714  0.731994  0.598658      0
1  0.156019  0.155995  0.058084  0.866176      0
2  0.601115  0.708073  0.020584  0.969910      1
3  0.832443  0.212339  0.181825  0.183405      1

2
私はこの答えが一番好きです。これはR.でパイプをdplyrに似ている
仁懐市

18

Pandas >= 0.25.0、名前付き集約

パンダのバージョン以降0.25.0、辞書ベースの集計と名前変更から離れ、を受け入れる名前付き集計に移行していますtuple。これで、集計と名前の変更をより情報的な列名に同時に行うことができます。

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]

          a         b         c         d  group
0  0.521279  0.914988  0.054057  0.125668      0
1  0.426058  0.828890  0.784093  0.446211      0
2  0.363136  0.843751  0.184967  0.467351      1
3  0.241012  0.470053  0.358018  0.525032      1

GroupBy.agg名前付き集計で適用:

df.groupby('group').agg(
             a_sum=('a', 'sum'),
             a_mean=('a', 'mean'),
             b_mean=('b', 'mean'),
             c_sum=('c', 'sum'),
             d_range=('d', lambda x: x.max() - x.min())
)

          a_sum    a_mean    b_mean     c_sum   d_range
group                                                  
0      0.947337  0.473668  0.871939  0.838150  0.320543
1      0.604149  0.302074  0.656902  0.542985  0.057681

これらの名前付き集計は好きですが、複数の列でそれらをどのように使用するのかわかりませんか?
Simon Woodhead

良い質問です。これを理解できませんでした。これが可能かどうかは(まだ)疑問です。これのチケットを開けました。私の質問とあなたを更新し続けます。@SimonWoodheadを指摘していただきありがとうございます
Erfan

4

バージョン0.25.0の新機能。

出力列名を制御して列固有の集計をサポートするために、pandasGroupBy.agg()「名前付き集計」と呼ばれる特別な構文を受け入れます。ここで、

  • キーワードは出力列名です
  • 値は、最初の要素が選択する列であり、2番目の要素がその列に適用する集計であるタプルです。Pandasはpandas.NamedAggのnamedtupleにフィールド['column'、 'aggfunc']を提供し、引数を明確にします。通常のように、集計は呼び出し可能または文字列エイリアスにすることができます。
    In [79]: animals = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
       ....:                         'height': [9.1, 6.0, 9.5, 34.0],
       ....:                         'weight': [7.9, 7.5, 9.9, 198.0]})
       ....: 

    In [80]: animals
    Out[80]: 
      kind  height  weight
    0  cat     9.1     7.9
    1  dog     6.0     7.5
    2  cat     9.5     9.9
    3  dog    34.0   198.0

    In [81]: animals.groupby("kind").agg(
       ....:     min_height=pd.NamedAgg(column='height', aggfunc='min'),
       ....:     max_height=pd.NamedAgg(column='height', aggfunc='max'),
       ....:     average_weight=pd.NamedAgg(column='weight', aggfunc=np.mean),
       ....: )
       ....: 
    Out[81]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

pandas.NamedAggは単なる名前付きタプルです。プレーンタプルも許可されます。

    In [82]: animals.groupby("kind").agg(
       ....:     min_height=('height', 'min'),
       ....:     max_height=('height', 'max'),
       ....:     average_weight=('weight', np.mean),
       ....: )
       ....: 
    Out[82]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

追加のキーワード引数は集約関数に渡されません。(column、aggfunc)のペアのみを** kwargsとして渡す必要があります。集約関数に追加の引数が必要な場合は、functools.partial()を使用して部分的に適用します。

名前付き集計は、Series groupby集計にも有効です。この場合、列は選択されていないため、値は単なる関数です。

    In [84]: animals.groupby("kind").height.agg(
       ....:     min_height='min',
       ....:     max_height='max',
       ....: )
       ....: 
    Out[84]: 
          min_height  max_height
    kind                        
    cat          9.1         9.5
    dog          6.0        34.0

3

テッドの答えは驚くべきものです。誰かが興味を持っている場合に備えて、私は結局それの小さいバージョンを使用することになりました。複数の列の値に依存する1つの集計を探す場合に役立ちます。

データフレームを作成する

df=pd.DataFrame({'a': [1,2,3,4,5,6], 'b': [1,1,0,1,1,0], 'c': ['x','x','y','y','z','z']})


   a  b  c
0  1  1  x
1  2  1  x
2  3  0  y
3  4  1  y
4  5  1  z
5  6  0  z

適用によるグループ化と集計(複数の列を使用)

df.groupby('c').apply(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

c
x    2.0
y    4.0
z    5.0

集約によるグループ化と集約(複数の列を使用)

まだ集計を使用できるため、このアプローチが好きです。おそらく人々は、グループで集計を行うときに複数の列を取得するために適用が必要な理由を私に知らせてくれるでしょう。

今では明らかなようですが、groupbyの直後に対象の列を選択しない限り、集計関数内からデータフレームのすべての列にアクセスできます。

選択した列へのアクセスのみ

df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())

選択はすべての魔法の後であるため、すべての列へのアクセス

df.groupby('c').aggregate(lambda x: x[(x['a']>1) & (x['b']==1)].mean())['a']

または同様に

df.groupby('c').aggregate(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

これがお役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.