181 votes

GroupBy pandas DataFrame et sélection de la valeur la plus courante

J'ai un cadre de données avec trois colonnes de chaînes. Je sais que la seule valeur de la troisième colonne est valable pour chaque combinaison des deux premières. Pour nettoyer les données, je dois grouper le cadre de données par les deux premières colonnes et sélectionner la valeur la plus courante de la troisième colonne pour chaque combinaison.

Mon code :

import pandas as pd
from scipy import stats

source = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'], 
                  'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
                  'Short name' : ['NY','New','Spb','NY']})

print source.groupby(['Country','City']).agg(lambda x: stats.mode(x['Short name'])[0])

La dernière ligne de code ne fonctionne pas, elle indique "Key error 'Short name'" et si j'essaie de regrouper uniquement par ville, j'obtiens un AssertionError. Que puis-je faire pour corriger cela ?

6voto

NunodeSousa Points 684

Formellement, la réponse correcte est la solution @eumiro. Le problème de la solution @HYRY est que lorsque vous avez une séquence de nombres comme [1,2,3,4], la solution est fausse, c'est-à-dire que vous n'avez pas la solution @eumiro. mode . Exemple :

>>> import pandas as pd
>>> df = pd.DataFrame(
        {
            'client': ['A', 'B', 'A', 'B', 'B', 'C', 'A', 'D', 'D', 'E', 'E', 'E', 'E', 'E', 'A'], 
            'total': [1, 4, 3, 2, 4, 1, 2, 3, 5, 1, 2, 2, 2, 3, 4], 
            'bla': [10, 40, 30, 20, 40, 10, 20, 30, 50, 10, 20, 20, 20, 30, 40]
        }
    )

Si vous calculez comme @HYRY vous obtenez :

>>> print(df.groupby(['client']).agg(lambda x: x.value_counts().index[0]))
        total  bla
client            
A           4   30
B           4   40
C           1   10
D           3   30
E           2   20

Ce qui est clairement faux (voir le A valeur qui devrait être 1 et non 4 ) car il ne peut pas gérer les valeurs uniques.

Ainsi, l'autre solution est correcte :

>>> import scipy.stats
>>> print(df.groupby(['client']).agg(lambda x: scipy.stats.mode(x)[0][0]))
        total  bla
client            
A           1   10
B           4   40
C           1   10
D           3   30
E           2   20

6voto

not a robot Points 1674

Utilice DataFrame.value_counts pour une solution rapide

Les 3 premières réponses ici :

  • source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode)
  • source.groupby(['Country','City']).agg(lambda x:x.value_counts().index[0])
  • source.groupby(['Country','City']).agg(lambda x: stats.mode(x)[0])

sont incroyablement lents pour les grands ensembles de données.

Solution à l'aide de collections.Counter est beaucoup plus rapide (20-40x plus rapide que les 3 premières méthodes)

  • source.groupby(['Country', 'City'])['Short name'].agg(lambda srs: Counter(list(srs)).most_common(1)[0][0])

mais toujours très lent.

Les solutions de abw333 et Josh Friedlander sont beaucoup plus rapides (~10x plus rapide que la méthode utilisant Counter ). Ces solutions peuvent être encore optimisées en utilisant value_counts à la place ( DataFrame.value_counts est disponible depuis pandas 1.1.0.). value_counts trie la sortie dans l'ordre décroissant par défaut, il n'est donc pas nécessaire d'appeler la fonction sort_values donc le code est un peu plus court.

source.value_counts(['Country', 'City', 'Short name']).reset_index(name='Count').drop_duplicates(['Country', 'City'])

Pour que la fonction prenne en compte les NaNs comme dans la fonction de Josh Friedlander, il suffit de désactiver dropna paramètre :

source.value_counts(['Country', 'City', 'Short name'], dropna=False).reset_index(name='Count').drop_duplicates(['Country', 'City'])

En utilisant la configuration d'abw333, si nous testons la différence de temps d'exécution, pour un DataFrame avec 1 million de lignes, value_counts est marginalement plus rapide que la solution d'abw333

scale_test_data = [[random.randint(1, 100),
                    str(random.randint(100, 900)), 
                    str(random.randint(0,2))] for i in range(1000000)]
source = pd.DataFrame(data=scale_test_data, columns=['Country', 'City', 'Short name'])

%timeit u = source.value_counts(['Country', 'City', 'Short name']).reset_index(name='Count').drop_duplicates(['Country', 'City'])
# 709 ms ± 3.88 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit v = mode(source, ['Country', 'City'], 'Short name', 'count')
# 728 ms ± 5.49 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

5voto

irene Points 514

Si vous ne voulez pas inclure les valeurs NaN en utilisant Counter est beaucoup plus rapide que pd.Series.mode o pd.Series.value_counts()[0] :

def get_most_common(srs):
    x = list(srs)
    my_counter = Counter(x)
    return my_counter.most_common(1)[0][0]

df.groupby(col).agg(get_most_common)

devrait fonctionner. Cela échouera si vous avez des valeurs NaN, car chaque NaN sera compté séparément.

2voto

skicavs Points 61

Si vous voulez une autre approche pour le résoudre qui ne dépende pas de l'expérience de l'autre value_counts o scipy.stats vous pouvez utiliser le Counter collection

from collections import Counter
get_most_common = lambda values: max(Counter(values).items(), key = lambda x: x[1])[0]

Ce qui peut être appliqué à l'exemple ci-dessus comme suit

src = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'], 
              'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
              'Short_name' : ['NY','New','Spb','NY']})

src.groupby(['Country','City']).agg(get_most_common)

0voto

Dimitri Points 11

Une approche un peu plus maladroite mais plus rapide pour les grands ensembles de données consiste à obtenir les comptes pour une colonne d'intérêt, à trier les comptes du plus haut au plus bas, puis à dé-dupliquer sur un sous-ensemble pour ne retenir que les cas les plus importants. L'exemple de code est le suivant :

>>> import pandas as pd
>>> source = pd.DataFrame(
        {
            'Country': ['USA', 'USA', 'Russia', 'USA'], 
            'City': ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
            'Short name': ['NY', 'New', 'Spb', 'NY']
        }
    )
>>> grouped_df = source\
        .groupby(['Country','City','Short name'])[['Short name']]\
        .count()\
        .rename(columns={'Short name':'count'})\
        .reset_index()\
        .sort_values('count', ascending=False)\
        .drop_duplicates(subset=['Country', 'City'])\
        .drop('count', axis=1)
>>> print(grouped_df)
  Country              City Short name
1     USA          New-York         NY
0  Russia  Sankt-Petersburg        Spb

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