web-dev-qa-db-ja.com

Pandasリストの一貫性のない動作を変換する

期待どおりに機能するサンプルスニペットがあります。

import pandas as pd

df = pd.DataFrame(data={'label': ['a', 'b', 'b', 'c'], 'wave': [1, 2, 3, 4], 'y': [0,0,0,0]})
df['new'] = df.groupby(['label'])[['wave']].transform(Tuple)

結果は次のとおりです。

  label  wave  y     new
0     a     1  0    (1,)
1     b     2  0  (2, 3)
2     b     3  0  (2, 3)
3     c     4  0    (4,)

変換でTupleの代わりにset, frozenset, dictを指定すると、同様に機能しますが、listを指定すると、完全に予期しない結果が得られます。

df['new'] = df.groupby(['label'])[['wave']].transform(list)

  label  wave  y  new
0     a     1  0    1
1     b     2  0    2
2     b     3  0    3
3     c     4  0    4

期待される結果を得るための回避策があります:

df['new'] = df.groupby(['label'])[['wave']].transform(Tuple)['wave'].apply(list)

  label  wave  y     new
0     a     1  0     [1]
1     b     2  0  [2, 3]
2     b     3  0  [2, 3]
3     c     4  0     [4]

可変性/不変性(リスト/タプル)について考えましたが、セット/フローズンセットでは一貫しています。

問題は、なぜこのように機能するのかということです。

16
Quant Christo

以前に同様の問題に遭遇したことがあります。私が考える根本的な問題は、リストの要素の数がグループのレコードの数と一致すると、リストの各要素がグループのレコードにマップされるようにリストをアンパックしようとすることです。

たとえば、リストのlenが各グループの長さと一致するため、これによりリストがアンパックされます。

df.groupby(['label'])[['wave']].transform(lambda x: list(x))
    wave
0   1
1   2
2   3
3   4

ただし、リストの長さが各グループと同じでない場合は、目的の動作が得られます。

df.groupby(['label'])[['wave']].transform(lambda x: list(x)+[0])

    wave
0   [1, 0]
1   [2, 3, 0]
2   [2, 3, 0]
3   [4, 0]

これは、リストの展開機能の副作用だと思います。

6
Allen

DataFramesは主に2Dデータを処理するように設計されているため、スカラー値の代わりに配列を含めると、このような警告に遭遇する可能性があります。

pd.DataFrame.trasnformは元々.aggの上に実装されています:

# pandas/core/generic.py
@Appender(_shared_docs["transform"] % dict(axis="", **_shared_doc_kwargs))
def transform(self, func, *args, **kwargs):
    result = self.agg(func, *args, **kwargs)
    if is_scalar(result) or len(result) != len(self):
        raise ValueError("transforms cannot produce " "aggregated results")

    return result

ただし、transformは常に、本質的に入力である自分自身と同じ長さを持つ必要があるDataFrameを返します。

DataFrame.agg関数を実行すると、正常に機能します。

df.groupby('label')['wave'].agg(list)
label
a       [1]
b    [2, 3]
c       [4]
Name: wave, dtype: object

この問題は、transformが同じ長さのSeriesを返そうとすると発生します。

groupbyからのスライスであるself要素を変換し、これを再度連結するプロセスでは、リストは@Allenと同じ長さのインデックスにアンパックされます。

ただし、それらが揃わない場合は、解凍しないでください。

df.groupby(['label'])[['wave']].transform(lambda x: list(x) + [1])
    wave
0   [1, 1]
1   [2, 3, 1]
2   [2, 3, 1]
3   [4, 1]

この問題の回避策はtransformを回避することです:

df = pd.DataFrame(data={'label': ['a', 'b', 'b', 'c'], 'wave': [1, 2, 3, 4], 'y': [0,0,0,0]})
df = df.merge(df.groupby('label')['wave'].agg(list).rename('new'), on='label')
df
    label   wave    y   new
0   a         1     0   [1]
1   b         2     0   [2, 3]
2   b         3     0   [2, 3]
3   c         4     0   [4]
3
iDrwish

それはパンダのバグだと思います。 the github ページでチケットを開けますか?

listが_.transform_への引数として正しく処理されていないため、最初はそうであると考えましたが、そうした場合:

_def create_list(obj):
    print(type(obj))
    return obj.to_list()

df.groupby(['label'])[['wave']].transform(create_list)
_

同じ予期しない結果になります。ただし、aggメソッドを使用すると、直接機能します。

_df.groupby(['label'])['wave'].agg(list)
Out[179]: 
label
a       [1]
b    [2, 3]
c       [4]
Name: wave, dtype: object
_

これが意図された動作であるとは思えません。

ところでまた、グループ化されたシリーズおよびグループ化されたデータフレームにタプルを適用した場合に表示される、異なる動作が疑わしいこともわかりました。例えば。 transformがDataFrameではなくシリーズに適用される場合、結果もリストを含むシリーズではなく、intsを含むシリーズです(_[['wave']]_を覚えておくと、 columed dataframe transform(Tuple)実際に返されたタプル):

_df.groupby(['label'])['wave'].transform(Tuple)
Out[177]: 
0    1
1    2
2    3
3    4
Name: wave, dtype: int64
_

aggの代わりにtransformを使用してこれをもう一度行うと、_['wave']_と_[['wave']]_の両方で機能します

私のテストでは、ubuntu X86_64システムでバージョン0.25.0を使用していました。

3
jottbe