287 votes

colonnes pandas GroupBy avec des valeurs NaN (manquantes)

J'ai un DataFrame avec de nombreuses valeurs manquantes dans les colonnes que je souhaite regrouper :

import pandas as pd
import numpy as np
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})

In [4]: df.groupby('b').groups
Out[4]: {'4': [0], '6': [2]}

voir que Pandas a abandonné les lignes avec des valeurs cibles NaN. (Je veux inclure ces lignes !)

Comme j'ai besoin de nombreuses opérations de ce type (de nombreuses colonnes ont des valeurs manquantes), et que j'utilise des fonctions plus compliquées que les médianes (typiquement des forêts aléatoires), je veux éviter d'écrire des morceaux de code trop compliqués.

Des suggestions ? Dois-je écrire une fonction pour cela ou existe-t-il une solution simple ?

349voto

coldspeed Points 111053

Pandas >= 1.1

Depuis pandas 1.1, vous avez un meilleur contrôle sur ce comportement, Les valeurs NA sont désormais autorisées dans le groupe en utilisant dropna=False :

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

# Example from the docs
df

   a    b  c
0  1  2.0  3
1  1  NaN  4
2  2  1.0  3
3  1  2.0  2

# without NA (the default)
df.groupby('b').sum()

     a  c
b        
1.0  2  3
2.0  2  5

# with NA
df.groupby('b', dropna=False).sum()

     a  c
b        
1.0  2  3
2.0  2  5
NaN  1  4

178voto

Andy Hayden Points 38010

C'est mentionné dans la section Données manquantes des docs :

Les groupes NA dans GroupBy sont automatiquement exclus. Ce comportement est conforme à R

Une solution consiste à utiliser un caractère de remplacement avant d'effectuer le groupby (par exemple, -1) :

In [11]: df.fillna(-1)
Out[11]: 
   a   b
0  1   4
1  2  -1
2  3   6

In [12]: df.fillna(-1).groupby('b').sum()
Out[12]: 
    a
b    
-1  2
4   1
6   3

Cela dit, cela semble être un hack assez terrible... peut-être devrait-il y avoir une option pour inclure NaN dans groupby (voir ce problème github - qui utilise le même hack placeholder).

Cependant, comme décrit dans une autre réponse , "Depuis la version 1.1 de pandas, vous avez un meilleur contrôle sur ce comportement, les valeurs NA sont maintenant autorisées dans le groupe en utilisant dropna=False. "

52voto

M. Kiewisch Points 513

Ancien sujet, si quelqu'un se heurte encore à ce problème, une autre solution consiste à convertir via .astype(str) en chaîne de caractères avant de regrouper. Cela permettra de conserver les NaN.

df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()

    a
b   
4   1
6   3
nan 2

13voto

Je ne peux pas ajouter de commentaire à M. Kiewisch car je n'ai pas assez de points de réputation (j'en ai seulement 41 mais j'ai besoin de plus de 50 pour commenter).

Quoi qu'il en soit, je souhaite simplement souligner que la solution de M. Kiewisch ne fonctionne pas telle quelle et qu'elle peut nécessiter des ajustements supplémentaires. Considérons par exemple

>>> df = pd.DataFrame({'a': [1, 2, 3, 5], 'b': [4, np.NaN, 6, 4]})
>>> df
   a    b
0  1  4.0
1  2  NaN
2  3  6.0
3  5  4.0
>>> df.groupby(['b']).sum()
     a
b
4.0  6
6.0  3
>>> df.astype(str).groupby(['b']).sum()
      a
b
4.0  15
6.0   3
nan   2

qui montre que pour le groupe b=4.0, la valeur correspondante est 15 au lieu de 6. Ici, il s'agit simplement de concaténer 1 et 5 comme des chaînes de caractères au lieu de les additionner comme des nombres.

8voto

Grant Langseth Points 1337

Toutes les réponses fournies jusqu'à présent entraînent un comportement potentiellement dangereux car il est tout à fait possible de sélectionner une valeur fictive qui fait en réalité partie de l'ensemble de données. Cela est de plus en plus probable à mesure que vous créez des groupes avec de nombreux attributs. En d'autres termes, cette approche ne permet pas toujours de généraliser.

Une solution moins compliquée est d'utiliser pd.drop_duplicates() pour créer un index unique des combinaisons de valeurs, chacune avec son propre ID, puis de regrouper sur cet ID. C'est plus verbeux mais cela permet de faire le travail :

def safe_groupby(df, group_cols, agg_dict):
    # set name of group col to unique value
    group_id = 'group_id'
    while group_id in df.columns:
        group_id += 'x'
    # get final order of columns
    agg_col_order = (group_cols + list(agg_dict.keys()))
    # create unique index of grouped values
    group_idx = df[group_cols].drop_duplicates()
    group_idx[group_id] = np.arange(group_idx.shape[0])
    # merge unique index on dataframe
    df = df.merge(group_idx, on=group_cols)
    # group dataframe on group id and aggregate values
    df_agg = df.groupby(group_id, as_index=True)\
               .agg(agg_dict)
    # merge grouped value index to results of aggregation
    df_agg = group_idx.set_index(group_id).join(df_agg)
    # rename index
    df_agg.index.name = None
    # return reordered columns
    return df_agg[agg_col_order]

Notez que vous pouvez maintenant simplement faire ce qui suit :

data_block = [np.tile([None, 'A'], 3),
              np.repeat(['B', 'C'], 3),
              [1] * (2 * 3)]

col_names = ['col_a', 'col_b', 'value']

test_df = pd.DataFrame(data_block, index=col_names).T

grouped_df = safe_groupby(test_df, ['col_a', 'col_b'],
                          OrderedDict([('value', 'sum')]))

Cela renverra le bon résultat sans avoir à se soucier de l'écrasement de données réelles prises pour des valeurs fictives.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X