web-dev-qa-db-fra.com

Pandas transforme un comportement incohérent pour la liste

J'ai un extrait d'extrait qui fonctionne comme prévu:

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)

Le résultat est:

  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,)

Cela fonctionne de façon analogue, si au lieu de Tuple en transformation, je donne set, frozenset, dict, mais si je donne list j'obtiens un résultat complètement inattendu:

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

Il existe une solution de contournement pour obtenir le résultat attendu:

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]

J'ai pensé à la mutabilité/immuabilité (liste/Tuple) mais pour set/frozenset c'est cohérent.

La question est pourquoi cela fonctionne de cette façon?

16
Quant Christo

J'ai rencontré un problème similaire auparavant. Je pense que le problème sous-jacent est que lorsque le nombre d'éléments dans la liste correspond au nombre d'enregistrements dans le groupe, il essaie de décompresser la liste afin que chaque élément de la liste mappe vers un enregistrement dans le groupe.

Par exemple, cela entraînera la décompression de la liste, car le len de la liste correspond à la longueur de chaque groupe:

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

Cependant, si la longueur de la liste n'est pas la même que pour chaque groupe, vous obtiendrez le comportement souhaité:

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]

Je pense que c'est un effet secondaire de la fonctionnalité de déballage de la liste.

6
Allen

Étant donné que DataFrames est principalement conçu pour gérer des données 2D, y compris des tableaux au lieu de valeurs scalaires peuvent tomber sur une mise en garde comme celle-ci.

pd.DataFrame.trasnform est à l'origine implémenté au-dessus de .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

Cependant, transform renvoie toujours un DataFrame qui doit avoir la même longueur que self, qui est essentiellement l'entrée.

Lorsque vous faites un .agg sur la fonction DataFrame, cela fonctionne très bien:

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

Le problème est introduit lorsque transform essaie de renvoyer un Series de même longueur.

Dans le processus de transformation d'un élément groupby qui est une tranche de self, puis de concaténation à nouveau, les listes sont décompressées à la même longueur d'index que @Allen mentionné.

Cependant, lorsqu'ils ne s'alignent pas, ne vous décompressez pas:

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]

Une solution de contournement que ce problème pourrait éviter 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

Je pense que c'est un bug chez les pandas. Pouvez-vous ouvrir un ticket sur leur github page s'il vous plaît?

Au début, je pensais que ce pourrait être le cas, car list n'est tout simplement pas traité correctement comme argument de .transform, Mais si je le fais:

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

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

J'obtiens le même résultat inattendu. Si toutefois la méthode agg est utilisée, elle fonctionne directement:

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

Je ne peux pas imaginer que ce soit un comportement voulu.

Btw. Je trouve également le comportement différent suspect, qui apparaît si vous appliquez Tuple à une série groupée et à une trame de données groupée. Par exemple. si transform est appliqué à une série au lieu d'un DataFrame, le résultat n'est pas non plus une série contenant des listes, mais une série contenant ints (rappelez-vous pour [['wave']] qui en crée une -frame dataframe transform(Tuple) en effet retourné des tuples):

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

Si je refais ça avec agg au lieu de transform ça marche pour ['wave'] Et [['wave']]

J'utilisais la version 0.25.0 sur un système ubuntu X86_64 pour mes tests.

3
jottbe