Pandas – Explicación sobre la función de aplicación siendo lenta

La función de aplicación parece funcionar muy lentamente con un gran dataframe (aproximadamente 1 ~ 3 millones de filas).

He comprobado las preguntas relacionadas aquí, como Acelerar la función de aplicación de pandas , y Contar dentro de la función de aplicación de pandas () , parece que la mejor manera de acelerarlo es no usar la función de aplicar 🙂

Para mi caso, tengo dos tipos de tareas que hacer con la función de aplicación.

Primero: aplicar con consulta de búsqueda

f(p_id, p_dict): return p_dict[p_dict['ID'] == p_id]['value'] p_dict = DataFrame(...) # it's another dict works like lookup table df = df.apply(f, args=(p_dict,)) 

Segundo: aplicar con groupby

 f(week_id, min_week_num, p_dict): return p_dict[(week_id - min_week_num < p_dict['WEEK']) & (p_dict['WEEK'] < week_id)].ix[:,2].mean() f_partial = partial(f, min_week_num=min_week_num, p_dict=p_dict) df = map(f, df['WEEK']) 

Supongo que para el primer caso, podría hacerse con la unión del dataframe, mientras que no estoy seguro sobre el costo de los recursos para tal unión en un conjunto de datos grande.

Mi pregunta es:

  1. ¿Hay alguna manera de sustituir aplicar en los dos casos anteriores?
  2. ¿Por qué se aplica tan lento? Para el caso de búsqueda de dict, creo que debería ser O (N), no debería costar mucho, incluso si N es 1 millón.

Related of "Pandas – Explicación sobre la función de aplicación siendo lenta"

Con respecto a su primera pregunta, no puedo decir exactamente por qué esta instancia es lenta. Pero en general, apply no aprovecha la vectorización. Además, Apply devuelve un nuevo objeto Series o DataFrame, por lo que con un DataFrame muy grande, tiene una gran sobrecarga de IO (no puedo garantizar que este sea el caso el 100% del tiempo, ya que Pandas tiene un montón de optimización de implementación interna).

Para su primer método, asumo que está tratando de llenar una columna de “valor” en df usando p_dict como una tabla de búsqueda. Es aproximadamente pd.merge más rápido usar pd.merge :

 import string, sys import numpy as np import pandas as pd ## # Part 1 - filling a column by a lookup table ## def f1(col, p_dict): return [p_dict[p_dict['ID'] == s]['value'].values[0] for s in col] # Testing n_size = 1000 np.random.seed(997) p_dict = pd.DataFrame({'ID': [s for s in string.ascii_uppercase], 'value': np.random.randint(0,n_size, 26)}) df = pd.DataFrame({'p_id': [string.ascii_uppercase[i] for i in np.random.randint(0,26, n_size)]}) # Apply the f1 method as posted %timeit -n1 -r5 temp = df.apply(f1, args=(p_dict,)) >>> 1 loops, best of 5: 832 ms per loop # Using merge np.random.seed(997) df = pd.DataFrame({'p_id': [string.ascii_uppercase[i] for i in np.random.randint(0,26, n_size)]}) %timeit -n1 -r5 temp = pd.merge(df, p_dict, how='inner', left_on='p_id', right_on='ID', copy=False) >>> 1000 loops, best of 5: 826 µs per loop 

Con respecto a la segunda tarea, podemos agregar rápidamente una nueva columna a p_dict que calcula una media donde la ventana de tiempo comienza en min_week_num y termina en la semana para esa fila en p_dict . Esto requiere que p_dict esté ordenado por orden ascendente a lo largo de la columna WEEK . Entonces puedes usar pd.merge nuevo.

Supongo que min_week_num es 0 en el siguiente ejemplo. Pero puede modificar fácilmente rolling_growing_mean para tomar un valor diferente. El método rolling_growing_mean se ejecutará en O (n), ya que realiza un número fijo de operaciones por iteración.

 n_size = 1000 np.random.seed(997) p_dict = pd.DataFrame({'WEEK': range(52), 'value': np.random.randint(0, 1000, 52)}) df = pd.DataFrame({'WEEK': np.random.randint(0, 52, n_size)}) def rolling_growing_mean(values): out = np.empty(len(values)) out[0] = values[0] # Time window for taking mean grows each step for i, v in enumerate(values[1:]): out[i+1] = np.true_divide(out[i]*(i+1) + v, i+2) return out p_dict['Means'] = rolling_growing_mean(p_dict['value']) df_merged = pd.merge(df, p_dict, how='inner', left_on='WEEK', right_on='WEEK')