3
votes

Pandas groupby signifie () ne pas ignorer les NaN

Si je calcule la moyenne d'un objet groupby et que dans l'un des groupes il y a un ou des NaN, les NaN sont ignorés. Même lors de l'application de np.mean, il ne renvoie que la moyenne de tous les nombres valides. Je m'attendrais à un comportement de retour de NaN dès qu'un NaN est dans le groupe. Voici un exemple simplifié du comportement

     a
b     
1  1.5
2  NaN

Je souhaite recevoir le résultat suivant:

import pandas as pd
import numpy as np
c = pd.DataFrame({'a':[1,np.nan,2,3],'b':[1,2,1,2]})
c.groupby('b').mean()
     a
b     
1  1.5
2  3.0
c.groupby('b').agg(np.mean)
     a
b     
1  1.5
2  3.0

Je suis conscient que je peux remplacer les NaN au préalable et que je peux probablement écrire ma propre fonction d'agrégation pour renvoyer NaN dès que NaN est dans le groupe. Cette fonction ne serait cependant pas optimisée.

Connaissez-vous un argument pour obtenir le comportement souhaité avec les fonctions optimisées?

Btw, je pense que le comportement souhaité a été implémenté dans une version précédente de pandas.


0 commentaires

6 Réponses :


3
votes

Utilisez l'option skipna -

c.groupby('b').apply(lambda g: g.mean(skipna=False))


0 commentaires

8
votes

Par défaut, pandas ignore les valeurs Nan . Vous pouvez le faire inclure Nan en spécifiant skipna=False:

In [215]: c.groupby('b').agg({'a': lambda x: x.mean(skipna=False)})
Out[215]: 
     a
b     
1  1.5
2  NaN


1 commentaires

oui, mais ce n'est pas vectorisé (lent).



2
votes

Une autre approche consisterait à utiliser une valeur non ignorée par défaut, par exemple np.inf:

>>> c = pd.DataFrame({'a':[1,np.inf,2,3],'b':[1,2,1,2]})
>>> c.groupby('b').mean()
          a
b          
1  1.500000
2       inf


1 commentaires

Avant le calcul de la moyenne, vous pouvez utiliser fillna (np.inf) et après la moyenne, vous pouvez utiliser .replace ([np.inf, -np.inf], np.nan) pour restaurer les valeurs nan.



0
votes

Il existe trois méthodes différentes:

  1. le plus lent :
    method3 = c.groupby('b').sum()
    nan_index = c[c['b'].isna()].index.to_list()
    method3.loc[method3.index.isin(nan_index)] = np.nan
  1. plus rapide que l'application mais plus lent que la somme par défaut :
    c.groupby('b').agg({'a': lambda x: x.mean(skipna=False)})
  1. Plus rapide mais nécessite plus de codes :
    c.groupby('b').apply(lambda g: g.mean(skipna=False))

 entrez la description de l'image ici


0 commentaires

4
votes

Il y a mean (skipna = False) , mais ça ne fonctionne pas

Les méthodes d'agrégation GroupBy (min, max, mean, median, etc.) ont le code skipna >, qui est destiné à cette tâche exacte, mais il semble qu'actuellement (mai 2020) il existe un bug (problème ouvert le mars 2020), qui l'empêche de fonctionner correctement.

Solution rapide

Exemple de travail complet basé sur ces commentaires: @Serge Ballesta , @ RoelAdriaans

>>> import pandas as pd
>>> import numpy as np
>>> c = pd.DataFrame({'a':[1,np.nan,2,3],'b':[1,2,1,2]})
>>> c.fillna(np.inf).groupby('b').mean().replace(np.inf, np.nan)

     a
b     
1  1.5
2  NaN

Pour plus d'informations et des mises à jour, suivez le lien ci-dessus.


0 commentaires

0
votes

J'ai atterri ici à la recherche d'un moyen rapide (vectorisé) de faire cela, mais je ne l'ai pas trouvé. De plus, dans le cas des nombres complexes, groupby se comporte un peu étrangement: il n'aime pas mean () , et avec sum () il convertira les groupes où toutes les valeurs sont NaN en 0 + 0j .

Voici donc ce que j'ai trouvé:

Configuration :

cnt = gb.count()
gb.sum() / cnt

Out[]:
     b    c                   d
a                              
1  1.5  1.5  0.000000+2.000000j
2  3.0  NaN                 NaN

Comportement par défaut :

cnt = gb.count()
gb.sum() * (cnt / cnt)
out

Out[]:
     b    c                   d
a                              
1  3.0  3.0  0.000000+2.000000j
2  3.0  NaN                 NaN

Un seul NaN tue le groupe :

cnt = gb.count()
siz = gb.size()
mask = siz.values[:, None] == cnt.values
gb.sum().where(mask)

Out[]:
     b    c   d
a              
1  3.0  3.0 NaN
2  NaN  NaN NaN

Seulement NaN si toutes les valeurs du groupe sont NaN

gb.sum()

Out[]:
     b    c                   d
a                              
1  3.0  3.0  0.000000+2.000000j
2  3.0  0.0  0.000000+0.000000j

Corollaire: moyenne du complexe :

df = pd.DataFrame({
    'a': [1, 2, 1, 2],
    'b': [1, np.nan, 2, 3],
    'c': [1, np.nan, 2, np.nan],
    'd': np.array([np.nan, np.nan, 2, np.nan]) * 1j,
})
gb = df.groupby('a')


0 commentaires