Obtenez les lignes qui ont le nombre maximal de groupes en utilisant groupby

245

Comment puis-je trouver toutes les lignes dans une trame de données pandas qui ont la valeur maximale pour la countcolonne, après le regroupement par ['Sp','Mt']colonnes?

Exemple 1: le dataFrame suivant, que je regroupe ['Sp','Mt']:

   Sp   Mt Value   count
0  MM1  S1   a      **3**
1  MM1  S1   n      2
2  MM1  S3   cb     5
3  MM2  S3   mk      **8**
4  MM2  S4   bg     **10**
5  MM2  S4   dgd      1
6  MM4  S2  rd     2
7  MM4  S2   cb      2
8  MM4  S2   uyi      **7**

Sortie attendue: obtenez les lignes de résultat dont le nombre est maximum entre les groupes, comme:

0  MM1  S1   a      **3**
1 3  MM2  S3   mk      **8**
4  MM2  S4   bg     **10** 
8  MM4  S2   uyi      **7**

Exemple 2: cette trame de données, que je regroupe ['Sp','Mt']:

   Sp   Mt   Value  count
4  MM2  S4   bg     10
5  MM2  S4   dgd    1
6  MM4  S2   rd     2
7  MM4  S2   cb     8
8  MM4  S2   uyi    8

Pour l'exemple ci-dessus, je veux obtenir toutes les lignes où countest égal à max, dans chaque groupe, par exemple:

MM2  S4   bg     10
MM4  S2   cb     8
MM4  S2   uyi    8
jojo12
la source
Dans quel format se trouve votre bloc de données?
David Robinson
2
Je ne comprends pas. Qu'est-ce qu'un groupe exactement? Pourquoi commence la deuxième ligne du résultat 1 3?
Jo So
stackoverflow.com/questions/18879782/… Peut être utile
J_Arthur
1
Cette réponse est la solution la plus rapide que j'ai pu trouver: stackoverflow.com/a/21007047/778533
tommy.carstensen
Semblable à cette question, quelqu'un pourrait-il répondre à cette question: stackoverflow.com/questions/62069465/… Merci.
ds_Abc

Réponses:

326
In [1]: df
Out[1]:
    Sp  Mt Value  count
0  MM1  S1     a      3
1  MM1  S1     n      2
2  MM1  S3    cb      5
3  MM2  S3    mk      8
4  MM2  S4    bg     10
5  MM2  S4   dgd      1
6  MM4  S2    rd      2
7  MM4  S2    cb      2
8  MM4  S2   uyi      7

In [2]: df.groupby(['Mt'], sort=False)['count'].max()
Out[2]:
Mt
S1     3
S3     8
S4    10
S2     7
Name: count

Pour obtenir les indices du DF d'origine, vous pouvez faire:

In [3]: idx = df.groupby(['Mt'])['count'].transform(max) == df['count']

In [4]: df[idx]
Out[4]:
    Sp  Mt Value  count
0  MM1  S1     a      3
3  MM2  S3    mk      8
4  MM2  S4    bg     10
8  MM4  S2   uyi      7

Notez que si vous avez plusieurs valeurs max par groupe, toutes seront retournées.

Mettre à jour

Sur une grêle marie chance que c'est ce que le PO demande:

In [5]: df['count_max'] = df.groupby(['Mt'])['count'].transform(max)

In [6]: df
Out[6]:
    Sp  Mt Value  count  count_max
0  MM1  S1     a      3          3
1  MM1  S1     n      2          3
2  MM1  S3    cb      5          8
3  MM2  S3    mk      8          8
4  MM2  S4    bg     10         10
5  MM2  S4   dgd      1         10
6  MM4  S2    rd      2          7
7  MM4  S2    cb      2          7
8  MM4  S2   uyi      7          7
Zelazny7
la source
@ Zelazny7, existe-t-il un moyen d'adopter cette réponse pour appliquer au regroupement par une colonne, puis en regardant 2 colonnes et en faire un maximum pour obtenir une plus grande des deux? Je ne peux pas faire fonctionner ça. Ce que j'ai actuellement est: def Greater (Merge, maximumA, maximumB): a = Merge [maximumA] b = Merge [maximumB] return max (a, b) Merger.groupby ("Search_Term"). Apply (Greater, "Ratio_x "," Ratio_y ")
mathlover
3
@ Zelazny7 J'utilise la seconde idxapproche. Mais, je ne peux me permettre qu'un seul maximum pour chaque groupe (et mes données ont quelques duplicata max). existe-t-il un moyen de contourner ce problème avec votre solution?
3pitt
en fait, cela ne fonctionne pas pour moi. Je ne peux pas suivre le problème, car la trame de données si vous quittez gros, mais la solution de @Rani fonctionne bien
Ladenkov Vladislav
Salut Zealzny, Si je veux prendre le top 3 de la ligne maximale au lieu d'une valeur max, comment puis-je modifier votre code?
Zephyr
transformLa méthode peut avoir des performances de pool lorsque l'ensemble de données est suffisamment grand, obtenez d'abord la valeur maximale puis fusionnez les trames de données sera meilleure.
Woods Chen
170

Vous pouvez trier le dataFrame par nombre, puis supprimer les doublons. Je pense que c'est plus simple:

df.sort_values('count', ascending=False).drop_duplicates(['Sp','Mt'])
Rani
la source
4
Très agréable! Rapide avec des cadres larges (25k rangées)
Nolan Conaway
2
Pour ceux qui sont quelque peu nouveaux avec Python, vous devrez assigner cela à une nouvelle variable, cela ne change pas la variable df actuelle.
Tyler
1
@Samir ou utilisez inplace = Truecomme argument pourdrop_duplicates
TMrtSmith
5
C'est une excellente réponse lorsque vous n'avez besoin que d'une des lignes avec les mêmes valeurs maximales, mais cela ne fonctionnera pas comme prévu si j'ai besoin de toutes les lignes avec des valeurs maximales.
Woods Chen
1
@WoodsChen, il supprime les doublons de [sp, mt], par conséquent, dans votre exemple, la sortie ne doit être qu'une seule ligne.
Rani
54

Une solution simple serait d'appliquer: la fonction idxmax () pour obtenir des indices de lignes avec des valeurs max. Cela filtrerait toutes les lignes avec une valeur maximale dans le groupe.

In [365]: import pandas as pd

In [366]: df = pd.DataFrame({
'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
'count' : [3,2,5,8,10,1,2,2,7]
})

In [367]: df                                                                                                       
Out[367]: 
   count  mt   sp  val
0      3  S1  MM1    a
1      2  S1  MM1    n
2      5  S3  MM1   cb
3      8  S3  MM2   mk
4     10  S4  MM2   bg
5      1  S4  MM2  dgb
6      2  S2  MM4   rd
7      2  S2  MM4   cb
8      7  S2  MM4  uyi


### Apply idxmax() and use .loc() on dataframe to filter the rows with max values:
In [368]: df.loc[df.groupby(["sp", "mt"])["count"].idxmax()]                                                       
Out[368]: 
   count  mt   sp  val
0      3  S1  MM1    a
2      5  S3  MM1   cb
3      8  S3  MM2   mk
4     10  S4  MM2   bg
8      7  S2  MM4  uyi

### Just to show what values are returned by .idxmax() above:
In [369]: df.groupby(["sp", "mt"])["count"].idxmax().values                                                        
Out[369]: array([0, 2, 3, 4, 8])
Surya
la source
4
Le questionneur ici spécifié "I want to get ALL the rows where count equals max in each group", tout en étant idxmax Return[s] index of first occurrence of maximum over requested axis"selon la documentation (0,21).
Max Power
1
C'est une excellente solution, mais pour un problème différent
Carlos Souza
33

Après avoir essayé la solution suggérée par Zelazny sur un DataFrame relativement grand (~ 400k lignes), je l'ai trouvé très lent. Voici une alternative que j'ai trouvée pour exécuter des ordres de grandeur plus rapidement sur mon ensemble de données.

df = pd.DataFrame({
    'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4', 'MM4'],
    'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
    'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
    'count' : [3,2,5,8,10,1,2,2,7]
    })

df_grouped = df.groupby(['sp', 'mt']).agg({'count':'max'})

df_grouped = df_grouped.reset_index()

df_grouped = df_grouped.rename(columns={'count':'count_max'})

df = pd.merge(df, df_grouped, how='left', on=['sp', 'mt'])

df = df[df['count'] == df['count_max']]
landewednack
la source
1
en effet, c'est beaucoup plus rapide. la transformation semble lente pour un grand ensemble de données.
goh
1
Pouvez-vous ajouter des commentaires pour expliquer ce que fait chaque ligne?
tommy.carstensen
fwiw: J'ai trouvé que la solution plus élégante de @ Zelazny7 a pris beaucoup de temps à exécuter pour mon ensemble de ~ 100K lignes, mais celle-ci a fonctionné assez rapidement. (J'utilise un 0.13.0 désormais obsolète, ce qui pourrait expliquer la lenteur).
Roland
2
Mais cela df[df['count'] == df['count_max']]fera perdre des lignes NaN, ainsi que les réponses ci-dessus.
Qy Zuo
Je suggère fortement d'utiliser cette approche, pour les trames de données plus volumineuses, il est beaucoup plus rapide d'utiliser .appy () ou .agg ().
Touya D. Serdan
18

Vous n'aurez peut-être pas besoin de faire avec le groupe en utilisant sort_values+drop_duplicates

df.sort_values('count').drop_duplicates(['Sp','Mt'],keep='last')
Out[190]: 
    Sp  Mt Value  count
0  MM1  S1     a      3
2  MM1  S3    cb      5
8  MM4  S2   uyi      7
3  MM2  S3    mk      8
4  MM2  S4    bg     10

Également presque la même logique en utilisant tail

df.sort_values('count').groupby(['Sp', 'Mt']).tail(1)
Out[52]: 
    Sp  Mt Value  count
0  MM1  S1     a      3
2  MM1  S3    cb      5
8  MM4  S2   uyi      7
3  MM2  S3    mk      8
4  MM2  S4    bg     10
YOBEN_S
la source
Non seulement c'est un ordre de grandeur plus rapide que les autres solutions (au moins pour mon cas d'utilisation), mais cela a l'avantage supplémentaire de simplement être enchaîné dans le cadre de la construction de la trame de données d'origine.
Clay
Je me grattais la tête en pensant sûrement que c'était simple, merci pour votre réponse brillante comme toujours monsieur Wen.
Datanovice
7

Pour moi, la solution la plus simple serait de conserver la valeur lorsque le nombre est égal au maximum. Par conséquent, la commande d'une ligne suivante suffit:

df[df['count'] == df.groupby(['Mt'])['count'].transform(max)]
PAC
la source
4

Utilisation groupbyet idxmaxméthodes:

  1. transférez col dateà datetime:

    df['date']=pd.to_datetime(df['date'])
  2. récupère l'index maxde la colonne date, après groupyby ad_id:

    idx=df.groupby(by='ad_id')['date'].idxmax()
  3. obtenir les données souhaitées:

    df_max=df.loc[idx,]

Sortie [54]:

ad_id  price       date
7     22      2 2018-06-11
6     23      2 2018-06-22
2     24      2 2018-06-30
3     28      5 2018-06-22
blueear
la source
2
df = pd.DataFrame({
'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
'count' : [3,2,5,8,10,1,2,2,7]
})

df.groupby(['sp', 'mt']).apply(lambda grp: grp.nlargest(1, 'count'))
George Liu
la source
2

Réaliser que "appliquer" "nlargest" à un objet groupby fonctionne aussi bien:

Avantage supplémentaire - peut également récupérer les valeurs n supérieures si nécessaire:

In [85]: import pandas as pd

In [86]: df = pd.DataFrame({
    ...: 'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
    ...: 'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
    ...: 'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
    ...: 'count' : [3,2,5,8,10,1,2,2,7]
    ...: })

## Apply nlargest(1) to find the max val df, and nlargest(n) gives top n values for df:
In [87]: df.groupby(["sp", "mt"]).apply(lambda x: x.nlargest(1, "count")).reset_index(drop=True)
Out[87]:
   count  mt   sp  val
0      3  S1  MM1    a
1      5  S3  MM1   cb
2      8  S3  MM2   mk
3     10  S4  MM2   bg
4      7  S2  MM4  uyi
Surya
la source
2

Essayez d'utiliser "nlargest" sur l'objet groupby. L'avantage de l'utilisation de nlargest est qu'il renvoie l'index des lignes à partir desquelles "les éléments nlargest" ont été récupérés. Remarque: nous coupons le deuxième (1) élément de notre indice puisque notre index dans ce cas est constitué de tuples (par exemple (s1, 0)).

df = pd.DataFrame({
'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
'count' : [3,2,5,8,10,1,2,2,7]
})

d = df.groupby('mt')['count'].nlargest(1) # pass 1 since we want the max

df.iloc[[i[1] for i in d.index], :] # pass the index of d as list comprehension

entrez la description de l'image ici

escha
la source
1

J'utilise ce style fonctionnel pour de nombreuses opérations de groupe:

df = pd.DataFrame({
   'Sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4', 'MM4'],
   'Mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
   'Val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
   'Count' : [3,2,5,8,10,1,2,2,7]
})

df.groupby('Mt')\
  .apply(lambda group: group[group.Count == group.Count.max()])\
  .reset_index(drop=True)

    sp  mt  val  count
0  MM1  S1    a      3
1  MM4  S2  uyi      7
2  MM2  S3   mk      8
3  MM2  S4   bg     10

.reset_index(drop=True) vous ramène à l'index d'origine en supprimant l'index de groupe.

joh-mue
la source