2016-05-27 19 views
5

Ho un dataframe che appare più o meno in questo modo:Selezione elementi top n di ogni gruppo in panda groupby

>>> data 
    price currency  
id     
2 1050  EU 
5 1400  EU 
4 1750  EU 
8 4000  EU 
7  630  GBP 
1 1000  GBP 
9 1400  GBP 
3 2000  USD 
6 7000  USD 

ho bisogno di ottenere una nuova dataframe con n prodotti top-valutati per ogni moneta, dove n dipende sulla moneta ed è dato in un altro dataframe:

>>> select_number 
      number_to_select 
currency  
GBP   2 
EU   2 
USD   1 

Se dovessi selezionare lo stesso numero di elementi top-prezzo, ho potuto raggruppare i dati per valuta con pandas.groupby e quindi utilizzare il metodo head di un oggetto raggruppato.

Tuttavia, head accetta solo un numero, non un array o qualche espressione.

Naturalmente, posso scrivere un ciclo for, ma questo sarebbe un modo molto imbarazzante e inefficiente per farlo.

Come si può fare in modo corretto?

+0

si prega di [questo] (http://stackoverflow.com/a/36702926/5741205) rispondi al numero – MaxU

+0

@MaxU la risposta copre il numero variabile di articoli principali? – IanS

+0

@IanS, hai ragione, ho aggiunto una risposta – MaxU

risposta

8

È possibile utilizzare:

data = pd.DataFrame({'id': {0: 2, 1: 5, 2: 4, 3: 8, 4: 7, 5: 1, 6: 9, 7: 3, 8: 6}, 'price': {0: 1050, 1: 1400, 2: 1750, 3: 4000, 4: 630, 5: 1000, 6: 1400, 7: 2000, 8: 7000}, 'currency': {0: 'EU', 1: 'EU', 2: 'EU', 3: 'EU', 4: 'GBP', 5: 'GBP', 6: 'GBP', 7: 'USD', 8: 'USD'}}) 
select_number = pd.DataFrame({'number_to_select': {'USD': 1, 'GBP': 2, 'EU': 2}}) 
print (data) 
    currency id price 
0  EU 2 1050 
1  EU 5 1400 
2  EU 4 1750 
3  EU 8 4000 
4  GBP 7 630 
5  GBP 1 1000 
6  GBP 9 1400 
7  USD 3 2000 
8  USD 6 7000 

print (select_number) 
    number_to_select 
EU     2 
GBP     2 
USD     1 

soluzione con la mappatura da dict:

d = select_number.to_dict() 
d1 = d['number_to_select'] 
print (d1) 
{'USD': 1, 'EU': 2, 'GBP': 2} 

print (data.groupby('currency').apply(lambda dfg: dfg.nlargest(d1[dfg.name],'price')) 
      .reset_index(drop=True)) 

    currency id price 
0  EU 8 4000 
1  EU 4 1750 
2  GBP 9 1400 
3  GBP 1 1000 
4  USD 6 7000 

Solution2:

01.235.164,106174 millions
print (data.groupby('currency') 
      .apply(lambda dfg: (dfg.nlargest(select_number 
            .loc[dfg.name, 'number_to_select'], 'price'))) 
      .reset_index(drop=True)) 

    id price currency 
0 8 4000  EU 
1 4 1750  EU 
2 9 1400  GBP 
3 1 1000  GBP 
4 6 7000  USD 

Spiegazione:

Credo che per il debug è la miglior funzione d'uso f con print:

def f(dfg): 
    #dfg is DataFrame 
    print (dfg) 
    #name of group 
    print (dfg.name) 
    #select value from select_number 
    print (select_number.loc[dfg.name, 'number_to_select']) 
    #return top rows per groups 
    print (dfg.nlargest(select_number.loc[dfg.name, 'number_to_select'], 'price')) 
    return (dfg.nlargest(select_number.loc[dfg.name, 'number_to_select'], 'price')) 

print (data.groupby('currency').apply(f)) 
currency id price 
0  EU 2 1050 
1  EU 5 1400 
2  EU 4 1750 
3  EU 8 4000 
    currency id price 
0  EU 2 1050 
1  EU 5 1400 
2  EU 4 1750 
3  EU 8 4000 
EU 
2 
    currency id price 
3  EU 8 4000 
2  EU 4 1750 
    currency id price 
4  GBP 7 630 
5  GBP 1 1000 
6  GBP 9 1400 
GBP 
2 
    currency id price 
6  GBP 9 1400 
5  GBP 1 1000 
    currency id price 
7  USD 3 2000 
8  USD 6 7000 
USD 
1 
    currency id price 
8  USD 6 7000 

      currency id price 
currency      
EU  3  EU 8 4000 
     2  EU 4 1750 
GBP  6  GBP 9 1400 
     5  GBP 1 1000 
USD  8  USD 6 7000 
3

Ecco una soluzione:

select_number = select_number['number_to_select'] # easier to select from series 

df.groupby('currency').apply(
    lambda dfg: dfg.nlargest(select_number[dfg.name], columns='price') 
) 

Edit - ho avuto la mia risposta da jezrael's answer: ho sostituito con dfg.currency.iloc[0]dfg.name.

Seconda modifica - Come indicato nei commenti, select_number è un dataframe, quindi prima lo converto in una serie.

MaxU e jezrael, grazie per i vostri commenti!

+0

prova a mescolare il DF ('df = df.sample (len (df))') ed esegui la tua query. Immagino che avresti bisogno di ordinare il DF in anticipo – MaxU

+0

@MaxU grazie, ho aggiunto 'nlargest', che sono abbastanza sicuro è più efficiente di ordinare tutto – IanS

+0

Per me restituisce' KeyError: 'EU''. Puoi controllarlo? Non so perché, ma ho ancora lo stesso errore (ho provato anche dfg.name). – jezrael

1

si può fare in questo modo:

df['rn'] = (df.sort_values(['price'], ascending=False) 
       .groupby('currency').cumcount() + 1 
) 

qry = (select_number 
     .reset_index() 
     .astype(str) 
     .apply(lambda x: '((currency=="{0[0]}") & (rn<={0[1]}))'.format(x), axis=1) 
     .str.cat(sep=' | ') 
) 

print(df.query(qry)) 

uscita

In [147]: df.query(qry) 
Out[147]: 
    price currency rn 
id 
4 1750  EU 2 
8 4000  EU 1 
1 1000  GBP 2 
9 1400  GBP 1 
6 7000  USD 1 

Spiegazione:

rn - è una colonna helper - row_number per partizione/gruppo, in ordine decrescente di price (all'interno di quel gruppo)

qry - è generato dinamicamente interrogazione

In [149]: qry 
Out[149]: '((currency=="EU") & (rn<=2)) | ((currency=="GBP") & (rn<=2)) | ((currency=="USD") & (rn<=1))'