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
Suggerisco di aggiungere il tag numpy. Ricordo @Divakar che ha trovato soluzioni più veloci di groupby con np.einsum. – ayhan
@ayhan, intendi [questa] (http://stackoverflow.com/a/36810721/5741205) soluzione? – MaxU
@MaxU Sì, era un caso molto specifico (facilmente rimodellato). Una soluzione generale potrebbe non essere così semplice. – ayhan