Groupby en los pandas python: Fast Way

Quiero mejorar el tiempo de un groupby en pandas python. Tengo este codigo

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

El objective es contar cuántos contratos tiene un cliente en un mes y agregar esta información en una nueva columna ( Nbcontrats ).

  • Client : código de cliente
  • Month : mes de extracción de datos
  • Contrat : número de contrato

Quiero mejorar el tiempo. A continuación, solo trabajo con un subconjunto de mis datos reales:

 %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) 

¿Cómo puedo mejorar el tiempo de ejecución?

Con el método DataFrameGroupBy.size :

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

La mayor parte del trabajo consiste en volver a asignar el resultado a una columna del DataFrame de origen.

Aquí hay una manera de proceder:

  • Corte las columnas relevantes ( ['Client', 'Month'] ) del dataframe de entrada en una matriz NumPy. Esta es principalmente una idea centrada en el rendimiento, ya que usaríamos las funciones NumPy más adelante, que están optimizadas para trabajar con matrices NumPy.

  • Convierta los datos de las dos columnas de ['Client', 'Month'] en un solo arreglo 1D , que sería un índice lineal equivalente considerando los elementos de las dos columnas como pares. Por lo tanto, podemos suponer que los elementos de 'Client' representan los índices de fila, mientras que los elementos de 'Month' son los índices de columna. Esto es como pasar de 2D a 1D . Pero, el problema sería decidir la forma de la cuadrícula 2D para realizar dicho mapeo. Para cubrir todos los pares, una suposición segura sería asumir una cuadrícula 2D cuyas dimensiones son una más que la máxima en cada columna debido a la indexación basada en 0 en Python. Así, obtendríamos índices lineales.

  • A continuación, etiquetamos cada índice lineal en función de su singularidad entre otros. Creo que esto correspondería a las claves obtenidas con grouby en grouby lugar. También necesitamos obtener recuentos de cada grupo / clave única a lo largo de toda la matriz 1D. Finalmente, la indexación en los conteos con esas tags debe asignar para cada elemento los conteos respectivos.

¡Esa es toda la idea al respecto! Aquí está la implementación:

 # 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 (ie 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] 

Prueba de tiempo de ejecución

1) Definir funciones:

 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) Verificar resultados:

 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) Finalmente los cronometra:

 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