2015-01-08 15 views
57

Voglio raggruppare il mio dataframe di due colonne e quindi ordinare i risultati aggregati all'interno dei gruppi.panda raggruppa in gruppi

In [167]: 
df 

Out[167]: 
count job source 
0 2 sales A 
1 4 sales B 
2 6 sales C 
3 3 sales D 
4 7 sales E 
5 5 market A 
6 3 market B 
7 2 market C 
8 4 market D 
9 1 market E 

In [168]: 
df.groupby(['job','source']).agg({'count':sum}) 

Out[168]: 
      count 
job  source 
market A 5 
     B 3 
     C 2 
     D 4 
     E 1 
sales A 2 
     B 4 
     C 6 
     D 3 
     E 7 

Vorrei ora ordinare la colonna di conteggio in ordine discendente all'interno di ciascun gruppo. E poi prendi solo le prime tre righe. Per ottenere qualcosa di simile:

  count 
job  source 
market A 5 
     D 4 
     B 3 
sales E 7 
     C 6 
     B 4 

risposta

52

Che cosa si vuole fare è in realtà un nuovo groupby (sul risultato del primo groupby): sort e prendere i primi tre elementi per gruppo.

Partendo dal risultato della prima groupby:

In [60]: df_agg = df.groupby(['job','source']).agg({'count':sum}) 

gruppo Abbiamo dal primo livello dell'indice:

In [63]: g = df_agg['count'].groupby(level=0, group_keys=False) 

Poi vogliamo ordinare ('ordine') ogni gruppo e prendere i primi tre elementi:

In [64]: res = g.apply(lambda x: x.order(ascending=False).head(3)) 

Tuttavia, per questo, v'è una funzione di scorciatoia per fare questo, 012.372.:

In [65]: g.nlargest(3) 
Out[65]: 
job  source 
market A   5 
     D   4 
     B   3 
sales E   7 
     C   6 
     B   4 
dtype: int64 
+0

Ci sarebbe un modo per riassumere tutto ciò che non è contenuto nei primi tre risultati per gruppo e aggiungerli a un gruppo di sorgenti chiamato "altro" per ogni lavoro? – JoeDanger

+7

'order' è deprecato usare' sort_values' invece –

40

Si potrebbe anche solo farlo in una volta sola, facendo il tipo prima e usando la testa per prendere i primi 3 di ogni gruppo.

In[34]: df.sort_values(['job','count'],ascending=False).groupby('job').head(3) 

Out[35]: 
    count  job source 
4  7 sales  E 
2  6 sales  C 
1  4 sales  B 
5  5 market  A 
8  4 market  D 
6  3 market  B 
+2

'groupby' garantisce che l'ordine sia preservato? –

+8

Sembra che lo faccia; da [la documentazione di groupby] (http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.groupby.html): __groupby conserva l'ordine delle righe all'interno di ogni gruppo__ –

6

Ecco altri esempi di prendere top 3 in modo ordinato, e l'ordinamento all'interno dei gruppi:

In [43]: import pandas as pd                                      

In [44]: df = pd.DataFrame({"name":["Foo", "Foo", "Baar", "Foo", "Baar", "Foo", "Baar", "Baar"], "count_1":[5,10,12,15,20,25,30,35], "count_2" :[100,150,100,25,250,300,400,500]}) 

In [45]: df                                           
Out[45]: 
    count_1 count_2 name 
0  5  100 Foo 
1  10  150 Foo 
2  12  100 Baar 
3  15  25 Foo 
4  20  250 Baar 
5  25  300 Foo 
6  30  400 Baar 
7  35  500 Baar 


### Top 3 on sorted order: 
In [46]: df.groupby(["name"])["count_1"].nlargest(3)                                
Out[46]: 
name 
Baar 7 35 
     6 30 
     4 20 
Foo 5 25 
     3 15 
     1 10 
dtype: int64 


### Sorting within groups based on column "count_1": 
In [48]: df.groupby(["name"]).apply(lambda x: x.sort_values(["count_1"], ascending = False)).reset_index(drop=True) 
Out[48]: 
    count_1 count_2 name 
0  35  500 Baar 
1  30  400 Baar 
2  20  250 Baar 
3  12  100 Baar 
4  25  300 Foo 
5  15  25 Foo 
6  10  150 Foo 
7  5  100 Foo 
0

Se non è necessario sommare una colonna, quindi utilizzare la risposta di @ Tvashtar. Se è necessario sommare, è possibile utilizzare la risposta di @joris o questa che è molto simile ad essa.

df.groupby(['job']).apply(lambda x: (x.groupby('source') 
             .sum() 
             .sort_values('count', ascending=False)) 
            .head(3))