web-dev-qa-db-ja.com

Pandas groupby apply vs特定の機能を持つ変換

どの関数がgroupby + transform操作に受け入れられるのかわかりません。多くの場合、推測、テスト、何かが機能するまで元に戻すだけですが、ソリューションが機能するかどうかを決定する体系的な方法があるはずです。

最小限の例を示します。最初にgroupbyapply + setを使用しましょう:

df = pd.DataFrame({'a': [1,2,3,1,2,3,3], 'b':[1,2,3,1,2,3,3], 'type':[1,0,1,0,1,0,1]})

g = df.groupby(['a', 'b'])['type'].apply(set)

print(g)

a  b
1  1    {0, 1}
2  2    {0, 1}
3  3    {0, 1}

これは正常に機能しますが、元のデータフレームの新しい列でグループごとに計算されたsetが必要です。だから私はtransformを使用してみます:

df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
---> 23 df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

TypeError: int() argument must be a string, a bytes-like object or a number, not 'set'

これは、Pandas v0.19.0で見られるエラーです。v0.23.0では、TypeError: 'set' type is unordered。もちろん、明確に定義されたインデックスをマップして、結果を得ることができます。

g = df.groupby(['a', 'b'])['type'].apply(set)
df['g'] = df.set_index(['a', 'b']).index.map(g.get)

print(df)

   a  b  type       g
0  1  1     1  {0, 1}
1  2  2     0  {0, 1}
2  3  3     1  {0, 1}
3  1  1     0  {0, 1}
4  2  2     1  {0, 1}
5  3  3     0  {0, 1}
6  3  3     1  {0, 1}

しかし、transformの利点は、そのような明示的なマッピングを避けることだと思いました。どこで私は間違えましたか?

11
jpp

そもそも、これらの機能は非常に意味のあるものであるため、これらの機能を使用する際に直感の余地があると思います。

最初の結果では、実際に値をtransformではなく、aggregateしようとしています(これはあなたが意図した方法)。

しかし、コードに入ると、transformのドキュメントは、

グループチャンクと同じサイズか、グループチャンクのサイズにブロードキャスト可能な結果を​​返します。

するとき

df.groupby(['a', 'b'])['type'].transform(some_func)

実際には各グループの各pd.Seriesオブジェクトを変換するsome_func関数を使用して新しいオブジェクトになります。ただし、この新しいオブジェクトは、グループと同じサイズにする必要があります[〜#〜] or [〜#〜]チャンクのサイズにブロードキャスト可能である必要があります。

したがって、Tupleまたはlistを使用してシリーズを変換する場合、基本的にオブジェクトを変換します

0    1
1    2
2    3
dtype: int64

[1,2,3]

ただし、これらの値はそれぞれのインデックスに割り当てられているため、transform操作に違いがないことがわかります。 .iloc[0]からpd.Series値を持つ行は、変換リストから[1,2,3][0]値を持つようになります(タプルにも同じことが当てはまります)など。orderingおよびsizeここでは重要です。そうしないと、グループを台無しにしてしまうと変換が機能しなくなるからです(これがまさにsetこの場合、使用する適切な関数ではありません)。


引用されたテキストの2番目の部分には、「グループチャンクのサイズにブロードキャスト可能」とあります。

つまり、pd.Seriesをすべての行で使用できるオブジェクトに変換することもできます。例えば

df.groupby(['a', 'b'])['type'].transform(lambda k: 50)

動作します。どうして? 50は反復可能ではありませんが、最初のpd.Seriesのすべての位置でこの値を繰り返し使用することでbroadcastableです。


Setを使用してapplyできるのはなぜですか?

applyメソッドには、結果にsizeのこの制約がないためです。実際にはthreeの異なる結果タイプがあり、expandreduceまたはbroadcast結果。変換ではreduceできないことに注意してください*

デフォルト(result_type=None)では、最終的な戻り値の型は、適用された関数の戻り値の型から推測されます。 result_type:{'expand'、 'reduce'、 'broadcast'、None}、デフォルトなしこれらはaxis=1(列)の場合にのみ機能します:

  1. 「展開」:リストのような結果が列に変換されます。

  2. 'reduce':リストのような結果を展開するのではなく、可能であればシリーズを返します。これは「展開」の反対です。

  3. 「ブロードキャスト」:結果はDataFrameの元の形状にブロードキャストされ、元のインデックスと列は保持されます。

12
rafaelc

変換の結果は特定のタイプに制限されます。 [たとえば、listsetSeriesなどにすることはできません。-不正確、コメントありがとうございます@RafaelC]これは文書化されていないと思いますが、groupby.pyseries.pyのソースコードを調べるときこれらのタイプの制限を見つけることができます。

groupbyから ドキュメント

transformメソッドは、グループ化されるオブジェクトと同じ(同じサイズ)インデックスが付けられたオブジェクトを返します。変換関数は以下を行う必要があります。

  • グループチャンクと同じサイズ、またはグループチャンクのサイズにブロードキャスト可能な結果(たとえば、スカラー、grouped.transform(ラムダx:x.iloc [-1]))。
  • グループチャンクで列ごとに操作します。変換は、chunk.applyを使用して最初のグループチャンクに適用されます。

  • グループチャンクでインプレース操作を実行しません。グループチャンクは不変として扱う必要があり、グループチャンクを変更すると予期しない結果が生じる可能性があります。たとえば、fillnaを使用する場合、inplaceはFalseでなければなりません(grouped.transform(lambda x:x.fillna(inplace = False)))。

  • (オプション)グループチャンク全体で動作します。これがサポートされている場合、2番目のチャンクから始まる高速パスが使用されます。

免責事項:別のエラーが発生しました(pandasバージョン0.23.1):

df['g'] = df.groupby(['a', 'b'])['type'].transform(set)
File "***/lib/python3.6/site-packages/pandas/core/groupby/groupby.py", line 3661, in transform
s = klass(res, indexer)        s = klass(res, indexer)
File "***/lib/python3.6/site-packages/pandas/core/series.py", line 242, in __init__
"".format(data.__class__.__name__))
TypeError: 'set' type is unordered

更新

グループをセットに変換した後、pandasSeriesにブロードキャストできません。これは、順序付けられていない(およびグループチャンクとは異なる次元を持っている)ためです。リストに強制すると、グループチャンクと同じサイズになり、行ごとに1つの値のみを取得します。答えは、コンテナにラップすることです。したがって、オブジェクトの結果のサイズは1になり、pandasはそれをブロードキャストできます。

df['g'] = df.groupby(['a', 'b'])['type'].transform(lambda x: np.array(set(x)))
print(df)

   a  b  type       g
0  1  1     1  {0, 1}
1  2  2     0  {0, 1}
2  3  3     1  {0, 1}
3  1  1     0  {0, 1}
4  2  2     1  {0, 1}
5  3  3     0  {0, 1}
6  3  3     1  {0, 1}

np.arrayをコンテナとして選択した理由series.py(205:206行目)は、さらにチェックすることなくこのタイプを渡すためです。したがって、この動作は将来のバージョンでも保持されると考えています。

3
igrinis