2
votes

Comment résumer sur différentes combinaisons groupby?

Je compile un tableau des 3 principales cultures par comté. Certains comtés ont les mêmes variétés de cultures dans le même ordre. D'autres comtés ont les mêmes variétés de cultures dans un ordre différent.

apples  melons   grain    8000
pears   carrots  raddish  9200 

Je peux faire un groupby sur Crop1, Crop2 et Crop3 et obtenir la somme de total_pop:

df1_grouped
apples  melons  grain   1500
grain   melons  apples  2000
melons  grain   apples  4500
pears   carrots raddish 6700
raddish pears   carrots 2500


0 commentaires

5 Réponses :


2
votes

Voici une façon de le faire.

Commençons par récupérer les valeurs uniques dans les colonnes, puis réaffectons ces valeurs au DataFrame. Nous effectuerons cette opération sur une copie des données d'origine car vous devrez peut-être conserver les données d'origine.

df.groupby(to_sum).Total_pop.sum()

Crop1    Crop2  Crop3  
apples   grain  melons     8000
carrots  pears  raddish    9200
Name: Total_pop, dtype: int64

Nous pouvons maintenant effectuer notre groupby pour obtenir les résultats souhaités.

df = df1.copy()

to_sum = ['Crop1', 'Crop2', 'Crop3']

df[to_sum] = pd.DataFrame(df.loc[:, to_sum] \
                            .apply(set, axis=1) \
                            .apply(sorted) \
                            .values \
                            .tolist(), columns=to_sum)

print(df)

       County  Crop1    Crop2    Crop3  Total_pop
0      Harney  grain   apples   melons       2000
1       Baker  grain   apples   melons       1500
2     Wheeler  grain   apples   melons       3000
3  Hood River  grain   apples   melons       1500
4       Wasco  pears  carrots  raddish       2000
5      Morrow  pears  carrots  raddish       2500
6       Union  pears  carrots  raddish       2700
7        Lake  pears  carrots  raddish       2000


2 commentaires

cela ne donne pas tout à fait la bonne réponse puisque vous ne l'avez pas triée. J'ai mis à jour une version correcte en fonction de votre réponse!


comment le trions-nous exactement alors?



1
votes

Méthode 1:

Combinez les colonnes crop

>>> df.groupby(grouping_cols).Total_pop.sum()
Crop1    Crop2  Crop3  
apples   grain  melons     8000
carrots  pears  raddish    9200
Name: Total_pop, dtype: int64

pour en faire un tuple trié p >

df = df1.copy()

grouping_cols = ['Crop1', 'Crop2', 'Crop3']

df[grouping_cols] = pd.DataFrame(df.loc[:, grouping_cols] \
                            .apply(set, axis=1) \
                            .apply(sorted)            
                            .values \
                            .tolist(), columns=grouping_cols)

>>> df.head()
       County    Crop1  Crop2    Crop3  Total_pop
0      Harney   apples  grain   melons       2000
1       Baker   apples  grain   melons       1500
2     Wheeler   apples  grain   melons       3000
3  Hood River   apples  grain   melons       1500
4       Wasco  carrots  pears  raddish       2000

puis passez à votre groupe normal par opération

>>> df1_grouped = df1.groupby(['sorted'])['Total_pop'].sum().reset_index()
>>> df1_grouped
                      sorted  Total_pop
0    (apples, grain, melons)       8000
1  (carrots, pears, raddish)       9200

Méthode 2: Une version abrégée basée sur la réponse de aws-apprenti

>>> df1['sorted'] = df1.apply(lambda x : tuple(sorted(x['combined_temp'])),axis=1)
>>> df1.head()
       County   Crop1    Crop2            ...             Total_pop              combined_temp                     sorted
0      Harney   grain   melons            ...                  2000    [grain, melons, apples]    (apples, grain, melons)
1       Baker  melons    grain            ...                  1500    [melons, grain, apples]    (apples, grain, melons)
2     Wheeler  melons    grain            ...                  3000    [melons, grain, apples]    (apples, grain, melons)
3  Hood River  apples   melons            ...                  1500    [apples, melons, grain]    (apples, grain, melons)
4       Wasco   pears  carrots            ...                  2000  [pears, carrots, raddish]  (carrots, pears, raddish)

désormais groupe par groupe par

>>> df1['combined_temp'] = df1.apply(lambda x : list([x['Crop1'],
...                           x['Crop2'],
...                           x['Crop3']]),axis=1)
>>> df1.head()
       County   Crop1    Crop2    Crop3  Total_pop              combined_temp
0      Harney   grain   melons   apples       2000    [grain, melons, apples]
1       Baker  melons    grain   apples       1500    [melons, grain, apples]
2     Wheeler  melons    grain   apples       3000    [melons, grain, apples]
3  Hood River  apples   melons    grain       1500    [apples, melons, grain]
4       Wasco   pears  carrots  raddish       2000  [pears, carrots, raddish]

mais je personnellement préférez cette réponse en utilisant numpy


5 commentaires

parce que votre réponse originale ne fonctionnait pas sans le tri. (Je l'ai exécuté et vérifié) J'ai changé le .apply (liste) dans votre réponse originale à .apply (trié) pour le faire fonctionner correctement. J'ai également donné du crédit à votre réponse.


sans courir trié sur la colonne, votre réponse ne donnait pas le résultat correct après group by que l'OP voulait sur mon système. peut-être que vous devriez revérifier? Faites donc le changement pour trier les 3 colonnes, en fonction de votre réponse bien sûr.


hé, je n'implique pas que le PO veut que la réponse soit triée. Je voulais dire que la sortie du groupe final par n'était pas correcte quand j'ai suivi exactement ce que vous aviez donné comme réponse. J'ai donc utilisé apply (trié) pour le corriger lorsque j'ai mis à jour ma réponse


continuons cette discussion dans le chat .


Merci pour toutes les réponses. Ils ont été d'une grande aide et éducative pour moi. La méthode 1 ci-dessus a généré les résultats que je recherchais et je l'ai donc sélectionnée.



4
votes

Étant donné que vos données semblent garantir 3 cultures uniques par pays ("Je compile un tableau des 3 principales cultures par comté."), il suffit de trier les valeurs et de les réattribuer.

df1 = pd.concat([df1]*10000, ignore_index=True)

cols = ['Crop1', 'Crop2', 'Crop3']
%timeit df1[cols] = np.sort(df1[cols].to_numpy(), axis=1)
#36.1 ms ± 399 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

to_sum = ['Crop1', 'Crop2', 'Crop3']
%timeit df1[to_sum] = pd.DataFrame(df1.loc[:, to_sum].apply(set, axis=1).apply(list).values.tolist(), columns=to_sum)
#1.41 s ± 51.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Alors pour résumer:

df1.groupby(cols).sum()

#                       Total_pop
#Crop1   Crop2 Crop3             
#apples  grain melons        8000
#carrots pears raddish       9200

L'avantage est que vous évitez Series.apply ou .apply (axis = 1) . Pour les DataFrames plus grands, la différence de performances est perceptible:

import numpy as np

cols = ['Crop1', 'Crop2', 'Crop3']
df1[cols] = np.sort(df1[cols].to_numpy(), axis=1)

       County    Crop1  Crop2    Crop3  Total_pop
0      Harney   apples  grain   melons       2000
1       Baker   apples  grain   melons       1500
2     Wheeler   apples  grain   melons       3000
3  Hood River   apples  grain   melons       1500
4       Wasco  carrots  pears  raddish       2000
5      Morrow  carrots  pears  raddish       2500
6       Union  carrots  pears  raddish       2700
7        Lake  carrots  pears  raddish       2000


0 commentaires

0
votes
      County    Crop1    Crop2    Crop3  Total_pop
      Harney    grain   melons   apples       2000
       Baker   melons    grain   apples       1500
     Wheeler   melons    grain   apples       3000
  Hood River   apples   melons    grain       1500
       Wasco    pears  carrots  raddish       2000
      Morrow  raddish    pears  carrots       2500
       Union    pears  carrots  raddish       2700
        Lake    pears  carrots  raddish       2000

           x      y        z  Total_pop
      apples  grain   melons       8000
     carrots  pears  raddish       9200

0 commentaires

2
votes

np.bincount
pd.Series(dict(zip(map(tuple, u), s))) \
  .rename_axis(['Crop1', 'Crop2', 'Crop3']).reset_index(name='Total_pop')

     Crop1    Crop2   Crop3  Total_pop
0   melons    grain  apples     8000.0
1  carrots  raddish   pears     9200.0

Ou, si vous voulez des colonnes séparées

pd.Series(dict(zip(map(tuple, u), s)))

melons   grain    apples    8000.0
carrots  raddish  pears     9200.0
dtype: float64

Et complètement jolie

i, u = pd.factorize([*map(frozenset, zip(df1.Crop1, df1.Crop2, df1.Crop3))])
s = np.bincount(i, df1.Total_pop)

pd.Series(s, u)

(melons, grain, apples)      8000.0
(carrots, raddish, pears)    9200.0
dtype: float64


0 commentaires