He notado un rendimiento muy bajo al usar iterrows de pandas.
¿Es esto algo que otros experimentan? ¿Es específico de iterrows y debería evitarse esta función para datos de cierto tamaño (estoy trabajando con 2-3 millones de filas)?
Esta discusión en GitHub me llevó a creer que se debe a la mezcla de dtypes en el marco de datos, sin embargo, el ejemplo simple a continuación muestra que está ahí incluso cuando se usa un dtype (float64). Esto toma 36 segundos en mi máquina:
import pandas as pd
import numpy as np
import time
s1 = np.random.randn(2000000)
s2 = np.random.randn(2000000)
dfa = pd.DataFrame({'s1': s1, 's2': s2})
start = time.time()
i=0
for rowindex, row in dfa.iterrows():
i+=1
end = time.time()
print end - start
¿Por qué las operaciones vectorizadas como se aplican mucho más rápido? Me imagino que también debe haber alguna iteración fila por fila.
No puedo averiguar cómo no usar iterrows en mi caso (esto lo guardaré para una pregunta futura). Por lo tanto, agradecería que me escuchara si ha podido evitar esta iteración de manera constante. Estoy haciendo cálculos basados en datos en marcos de datos separados. ¡Gracias!
--- Editar: a continuación se agregó una versión simplificada de lo que quiero ejecutar ---
import pandas as pd
import numpy as np
#%% Create the original tables
t1 = {'letter':['a','b'],
'number1':[50,-10]}
t2 = {'letter':['a','a','b','b'],
'number2':[0.2,0.5,0.1,0.4]}
table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)
#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=[0])
#%% Iterate through filtering relevant data, optimizing, returning info
for row_index, row in table1.iterrows():
t2info = table2[table2.letter == row['letter']].reset_index()
table3.ix[row_index,] = optimize(t2info,row['number1'])
#%% Define optimization
def optimize(t2info, t1info):
calculation = []
for index, r in t2info.iterrows():
calculation.append(r['number2']*t1info)
maxrow = calculation.index(max(calculation))
return t2info.ix[maxrow]
fuente
apply
NO está vectorizado.iterrows
es incluso peor, ya que encierra todo (con lo que 'el diferencial de rendimiento'apply
). Solo debe usarloiterrows
en muy pocas situaciones. En mi humilde opinión, nunca. Muestre lo que está haciendo realmenteiterrows
.DatetimeIndex
enTimestamps
(se implementó en el espacio de Python), y esto se ha mejorado mucho en master.Respuestas:
Generalmente,
iterrows
solo debe usarse en casos muy, muy específicos. Este es el orden general de precedencia para la realización de varias operaciones:1) vectorization 2) using a custom cython routine 3) apply a) reductions that can be performed in cython b) iteration in python space 4) itertuples 5) iterrows 6) updating an empty frame (e.g. using loc one-row-at-a-time)
El uso de una rutina Cython personalizada suele ser demasiado complicado, así que omitamos eso por ahora.
1) La vectorización es SIEMPRE, SIEMPRE la primera y mejor opción. Sin embargo, hay un pequeño conjunto de casos (que generalmente involucran una recurrencia) que no pueden vectorizarse de manera obvia. Además, en términos más pequeños
DataFrame
, puede ser más rápido utilizar otros métodos.3)
apply
generalmente puede ser manejado por un iterador en el espacio Cython. Esto lo manejan los pandas internamente, aunque depende de lo que esté sucediendo dentro de laapply
expresión. Por ejemplo,df.apply(lambda x: np.sum(x))
se ejecutará con bastante rapidez, aunque, por supuesto,df.sum(1)
es incluso mejor. Sin embargo, algo comodf.apply(lambda x: x['b'] + 1)
se ejecutará en el espacio de Python y, en consecuencia, es mucho más lento.4)
itertuples
no incluye los datos en un archivoSeries
. Simplemente devuelve los datos en forma de tuplas.5)
iterrows
ENCUENTRA los datos en aSeries
. A menos que realmente lo necesite, utilice otro método.6) Actualización de un marco vacío de una sola fila a la vez. He visto que este método se usa MUY demasiado. Es de lejos el más lento. Probablemente sea un lugar común (y razonablemente rápido para algunas estructuras de Python), pero
DataFrame
realiza una buena cantidad de comprobaciones sobre la indexación, por lo que siempre será muy lento actualizar una fila a la vez. Mucho mejor para crear nuevas estructuras yconcat
.fuente
itertuples
es más rápido queapply
:(pd.DataFrame.apply
suele ser más lento queitertuples
. Además, vale la pena considerar listas por comprensión,map
las mal nombradasnp.vectorize
ynumba
(sin ningún orden en particular) para cálculos no vectorizables , por ejemplo, vea esta respuesta .Las operaciones vectoriales en Numpy y pandas son mucho más rápidas que las operaciones escalares en vanilla Python por varias razones:
Búsqueda de tipos amortizados : Python es un lenguaje escrito dinámicamente, por lo que hay una sobrecarga de tiempo de ejecución para cada elemento en una matriz. Sin embargo, Numpy (y por lo tanto los pandas) realizan cálculos en C (a menudo a través de Cython). El tipo de matriz se determina solo al comienzo de la iteración; solo este ahorro es uno de los mayores logros.
Mejor almacenamiento en caché : la iteración sobre una matriz C es compatible con la caché y, por lo tanto, es muy rápida. Un DataFrame de pandas es una "tabla orientada a columnas", lo que significa que cada columna es en realidad solo una matriz. Por lo tanto, las acciones nativas que puede realizar en un DataFrame (como sumar todos los elementos en una columna) tendrán pocas pérdidas de caché.
Más oportunidades de paralelismo : se puede operar una matriz C simple mediante instrucciones SIMD. Algunas partes de Numpy habilitan SIMD, según su CPU y el proceso de instalación. Los beneficios del paralelismo no serán tan dramáticos como la escritura estática y el mejor almacenamiento en caché, pero siguen siendo una victoria sólida.
Moraleja de la historia: usa las operaciones vectoriales en Numpy y pandas. Son más rápidas que las operaciones escalares en Python por la sencilla razón de que estas operaciones son exactamente lo que un programador de C habría escrito a mano de todos modos. (Excepto que la noción de matriz es mucho más fácil de leer que los bucles explícitos con instrucciones SIMD integradas).
fuente
Esta es la forma de resolver su problema. Todo esto está vectorizado.
In [58]: df = table1.merge(table2,on='letter') In [59]: df['calc'] = df['number1']*df['number2'] In [60]: df Out[60]: letter number1 number2 calc 0 a 50 0.2 10 1 a 50 0.5 25 2 b -10 0.1 -1 3 b -10 0.4 -4 In [61]: df.groupby('letter')['calc'].max() Out[61]: letter a 25 b -1 Name: calc, dtype: float64 In [62]: df.groupby('letter')['calc'].idxmax() Out[62]: letter a 1 b 2 Name: calc, dtype: int64 In [63]: df.loc[df.groupby('letter')['calc'].idxmax()] Out[63]: letter number1 number2 calc 1 a 50 0.5 25 2 b -10 0.1 -1
fuente
Otra opción es usar
to_records()
, que es más rápido que ambositertuples
yiterrows
.Pero para su caso, hay mucho margen para otro tipo de mejoras.
Aquí está mi versión optimizada final
def iterthrough(): ret = [] grouped = table2.groupby('letter', sort=False) t2info = table2.to_records() for index, letter, n1 in table1.to_records(): t2 = t2info[grouped.groups[letter].values] # np.multiply is in general faster than "x * y" maxrow = np.multiply(t2.number2, n1).argmax() # `[1:]` removes the index column ret.append(t2[maxrow].tolist()[1:]) global table3 table3 = pd.DataFrame(ret, columns=('letter', 'number2'))
Prueba de referencia:
-- iterrows() -- 100 loops, best of 3: 12.7 ms per loop letter number2 0 a 0.5 1 b 0.1 2 c 5.0 3 d 4.0 -- itertuple() -- 100 loops, best of 3: 12.3 ms per loop -- to_records() -- 100 loops, best of 3: 7.29 ms per loop -- Use group by -- 100 loops, best of 3: 4.07 ms per loop letter number2 1 a 0.5 2 b 0.1 4 c 5.0 5 d 4.0 -- Avoid multiplication -- 1000 loops, best of 3: 1.39 ms per loop letter number2 0 a 0.5 1 b 0.1 2 c 5.0 3 d 4.0
Código completo:
import pandas as pd import numpy as np #%% Create the original tables t1 = {'letter':['a','b','c','d'], 'number1':[50,-10,.5,3]} t2 = {'letter':['a','a','b','b','c','d','c'], 'number2':[0.2,0.5,0.1,0.4,5,4,1]} table1 = pd.DataFrame(t1) table2 = pd.DataFrame(t2) #%% Create the body of the new table table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=table1.index) print('\n-- iterrows() --') def optimize(t2info, t1info): calculation = [] for index, r in t2info.iterrows(): calculation.append(r['number2'] * t1info) maxrow_in_t2 = calculation.index(max(calculation)) return t2info.loc[maxrow_in_t2] #%% Iterate through filtering relevant data, optimizing, returning info def iterthrough(): for row_index, row in table1.iterrows(): t2info = table2[table2.letter == row['letter']].reset_index() table3.iloc[row_index,:] = optimize(t2info, row['number1']) %timeit iterthrough() print(table3) print('\n-- itertuple() --') def optimize(t2info, n1): calculation = [] for index, letter, n2 in t2info.itertuples(): calculation.append(n2 * n1) maxrow = calculation.index(max(calculation)) return t2info.iloc[maxrow] def iterthrough(): for row_index, letter, n1 in table1.itertuples(): t2info = table2[table2.letter == letter] table3.iloc[row_index,:] = optimize(t2info, n1) %timeit iterthrough() print('\n-- to_records() --') def optimize(t2info, n1): calculation = [] for index, letter, n2 in t2info.to_records(): calculation.append(n2 * n1) maxrow = calculation.index(max(calculation)) return t2info.iloc[maxrow] def iterthrough(): for row_index, letter, n1 in table1.to_records(): t2info = table2[table2.letter == letter] table3.iloc[row_index,:] = optimize(t2info, n1) %timeit iterthrough() print('\n-- Use group by --') def iterthrough(): ret = [] grouped = table2.groupby('letter', sort=False) for index, letter, n1 in table1.to_records(): t2 = table2.iloc[grouped.groups[letter]] calculation = t2.number2 * n1 maxrow = calculation.argsort().iloc[-1] ret.append(t2.iloc[maxrow]) global table3 table3 = pd.DataFrame(ret) %timeit iterthrough() print(table3) print('\n-- Even Faster --') def iterthrough(): ret = [] grouped = table2.groupby('letter', sort=False) t2info = table2.to_records() for index, letter, n1 in table1.to_records(): t2 = t2info[grouped.groups[letter].values] maxrow = np.multiply(t2.number2, n1).argmax() # `[1:]` removes the index column ret.append(t2[maxrow].tolist()[1:]) global table3 table3 = pd.DataFrame(ret, columns=('letter', 'number2')) %timeit iterthrough() print(table3)
La versión final es casi 10 veces más rápida que el código original. La estrategia es:
groupby
para evitar la comparación repetida de valores.to_records
para acceder a objetos numpy.records sin procesar.fuente
Sí, Pandas itertuples () es más rápido que iterrows (). puede consultar la documentación: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html
"Para conservar dtypes mientras se itera sobre las filas, es mejor usar itertuples () que devuelve las tuplas con nombre de los valores y que generalmente es más rápido que iterrows.
fuente
Detalles en este video
Punto de referencia
fuente