2015-01-08 11 views
57

Chcę pogrupować moją ramkę danych za pomocą dwóch kolumn, a następnie posortować zagregowane wyniki w obrębie grup.Grupa pandypoprzez sortowanie w grupach

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 

Chciałbym teraz posortować kolumnę zliczania w porządku malejącym w obrębie każdej z grup. A następnie weź tylko trzy górne rzędy. Aby uzyskać coś takiego:

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

Odpowiedz

52

Co chcesz zrobić, to rzeczywiście kolejny GroupBy (w wyniku pierwszego GroupBy): sort i podjąć pierwsze trzy elementy w każdej grupie.

Począwszy od wyniku pierwszego GroupBy:

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

Group Firma przez pierwszego poziomu indeksu:

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

Następnie chcemy uporządkować ('zamówienie') każdej grupy i weź pierwsze trzy elementy:

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

Jednak do tego jest dostępna funkcja skrótu, nlargest:

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

Czy istnieje sposób podsumowania wszystkiego, co nie jest zawarte w trzech pierwszych wynikach na grupę i dodanie ich do grupy źródłowej o nazwie "inne" dla każdego zadania? – JoeDanger

+7

'order' jest nieaktualne użyj' wartość_zmienienia' zamiast –

40

Można też po prostu to zrobić za jednym razem, wykonując swego pierwszego i używając głowy, aby zrobić pierwsze 3 z każdej grupy.

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

Czy 'groupby' gwarantuje, że zamówienie zostało zachowane? –

+8

Wygląda na to, że tak; z [dokumentacji groupby] (http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.groupby.html): __groupby zachowuje kolejność wierszy w każdej grupie__ –

6

Oto inny przykład biorąc top 3 na posortowanych i sortowanie w grupach:

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

Jeśli nie trzeba podsumować kolumnę, a następnie użyć odpowiedź @ tvashtar użytkownika. Jeśli potrzebujesz sumowania, możesz użyć odpowiedzi @joris lub bardzo podobnej do tej.

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