2016-07-01 26 views
6

Voglio migliorare il tempo di un groupby in Python panda. ho questo codice:Groupby in Python Panda: Fast Way

df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len) 

L'obiettivo è quello di contare quanti contratti di un cliente ha in un mese e aggiungere queste informazioni in una nuova colonna (Nbcontrats).

  • Client: codice client
  • Month: mese di estrazione dei dati
  • Contrat: numero di contratto

Voglio migliorare il tempo. Di seguito sto lavorando solo con un sottoinsieme dei miei dati reali:

%timeit df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len) 
1 loops, best of 3: 391 ms per loop 

df.shape 
Out[309]: (7464, 61) 

Come posso migliorare i tempi di esecuzione?

+4

Suggerisco di aggiungere il tag numpy. Ricordo @Divakar che ha trovato soluzioni più veloci di groupby con np.einsum. – ayhan

+1

@ayhan, intendi [questa] (http://stackoverflow.com/a/36810721/5741205) soluzione? – MaxU

+0

@MaxU Sì, era un caso molto specifico (facilmente rimodellato). Una soluzione generale potrebbe non essere così semplice. – ayhan

risposta

2

Con il metodo DataFrameGroupBy.size:

df.set_index(['Client', 'Month'], inplace=True) 
df['Nbcontrats'] = df.groupby(level=(0,1)).size() 
df.reset_index(inplace=True) 

La maggior parte del lavoro va nella assegnando il risultato di nuovo in una colonna del dataframe fonte.

9

Ecco un modo di procedere:

  • Fetta le relative colonne (['Client', 'Month']) dal dataframe input in una matrice NumPy. Questa è principalmente un'idea orientata alle prestazioni, poiché in seguito utilizzeremmo le funzioni NumPy, ottimizzate per funzionare con gli array di NumPy.

  • Convertire i dati di due colonne da ['Client', 'Month'] in un singolo array 1D, che sarebbe un indice lineare equivalente considerando gli elementi dalle due colonne come coppie. Pertanto, possiamo supporre che gli elementi da 'Client' rappresentano gli indici di riga, mentre gli elementi 'Month' sono gli indici di colonna. È come andare da 2D a 1D. Ma il problema sarebbe decidere la forma della griglia 2D per eseguire tale mappatura. Per coprire tutte le coppie, un assunto sicuro assumerebbe una griglia 2D le cui dimensioni sono una più del massimo lungo ciascuna colonna a causa dell'indicizzazione basata su 0 in Python. Quindi, otterremmo indici lineari.

  • Successivamente, taggiamo ciascun indice lineare in base alla loro unicità, tra le altre. Penso che questo corrisponderebbe alle chiavi ottenute con grouby invece. Dobbiamo anche ottenere i conteggi di ciascun gruppo/chiave univoca lungo l'intera lunghezza di tale array 1D. Infine, l'indicizzazione nei conteggi con quei tag dovrebbe mappare per ogni elemento i rispettivi conteggi.

Questa è l'idea completa!Ecco l'implementazione -

# Save relevant columns as a NumPy array for performing NumPy operations afterwards 
arr_slice = df[['Client', 'Month']].values 

# Get linear indices equivalent of those columns 
lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1) 

# Get unique IDs corresponding to each linear index (i.e. group) and grouped counts 
unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True) 

# Index counts with the unique tags to map across all elements with the counts 
df["Nbcontrats"] = counts[unqtags] 

prova Runtime

1) Definire le funzioni:

def original_app(df): 
    df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len) 

def vectorized_app(df): 
    arr_slice = df[['Client', 'Month']].values 
    lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1) 
    unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True) 
    df["Nbcontrats"] = counts[unqtags] 

2) Verificare i risultati:

In [143]: # Let's create a dataframe with 100 unique IDs and of length 10000 
    ...: arr = np.random.randint(0,100,(10000,3)) 
    ...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat']) 
    ...: df1 = df.copy() 
    ...: 
    ...: # Run the function on the inputs 
    ...: original_app(df) 
    ...: vectorized_app(df1) 
    ...: 

In [144]: np.allclose(df["Nbcontrats"],df1["Nbcontrats"]) 
Out[144]: True 

3) Infine loro il tempo:

In [145]: # Let's create a dataframe with 100 unique IDs and of length 10000 
    ...: arr = np.random.randint(0,100,(10000,3)) 
    ...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat']) 
    ...: df1 = df.copy() 
    ...: 

In [146]: %timeit original_app(df) 
1 loops, best of 3: 645 ms per loop 

In [147]: %timeit vectorized_app(df1) 
100 loops, best of 3: 2.62 ms per loop 
+0

È incredibile: è 246 volte più veloce! Potresti aggiungere una breve spiegazione per la soluzione numpy? – MaxU

+0

@MaxU Appena aggiunto qualche spiegazione. Ho fatto del mio meglio lì, in genere lo faccio schifo :) – Divakar

+1

È perfetto - grazie per avermi insegnato a diventare numpy! Sfortunatamente non posso revocarlo più di una volta;) – MaxU