python - ¿Tiene iterrows problemas de rendimiento?
iterrows pandas (4)
He notado un rendimiento muy bajo al usar iterrows de pandas.
¿Es esto algo experimentado por otros? ¿Es específico para 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 es causada cuando se mezclan los dtypes en el marco de datos, sin embargo, el ejemplo simple a continuación muestra que está allí incluso cuando se usa un tipo de dtipo (float64). Esto lleva 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 aplicar son mucho más rápidas? Imagino que también debe haber una iteración fila por fila allí.
No puedo entender cómo no usar iterrows en mi caso (esto lo guardaré para una pregunta futura). Por lo tanto, apreciaría saber si ha sido capaz de evitar esta iteración. Estoy haciendo cálculos basados en datos en marcos de datos separados. ¡Gracias!
--- Editar: la versión simplificada de lo que quiero ejecutar se ha agregado debajo ---
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]
En general, iterrows
solo debe usarse en casos muy específicos. Este es el orden general de precedencia para la ejecució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)
Usar una rutina de cython personalizada suele ser muy complicada, así que salteemos eso por ahora.
1) La vectorización SIEMPRE SIEMPRE es la primera y la mejor opción. Sin embargo, hay un pequeño conjunto de casos que no se pueden vectorizar de forma obvia (en su mayoría implica una recurrencia). Además, en un marco más pequeño, puede ser más rápido hacer otros métodos.
3) Aplicar implica generalmente se puede hacer por un iterador en el espacio de Cython (esto se hace internamente en pandas) (este es un) caso.
Esto depende de lo que esté pasando dentro de la expresión de aplicación. por ejemplo, df.apply(lambda x: np.sum(x))
se ejecutará con bastante rapidez (por supuesto, df.sum(1)
es aún mejor). Sin embargo, algo así como: df.apply(lambda x: x[''b''] + 1)
se ejecutará en el espacio python, y por lo tanto es más lento.
4) itertuples
no itertuples
los datos en una serie, simplemente los devuelve como una tupla
5) iterrows
HACE caja los datos en una serie. A menos que realmente lo necesite, use otro método.
6) actualizar un marco vacío una sola fila a la vez. He visto que este método se usa demasiado. Es de lejos el más lento. Es probable que sea un lugar común (y razonablemente rápido para algunas estructuras de python), pero un DataFrame hace un buen número de comprobaciones en la indexación, por lo que siempre será muy lento actualizar una fila a la vez. Mucho mejor para crear nuevas estructuras y concat
.
Esta es la manera de resolver su problema. Esto está todo 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
Las operaciones vectoriales en Numpy y pandas son mucho más rápidas que las operaciones escalares en Python vainilla por varias razones:
Búsqueda de tipo amortizado
Python es un lenguaje de tipado dinámico, 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; este ahorro solo es una de las mayores ganancias.
Mejor almacenamiento en caché
La iteración sobre una matriz C es apta para caché y, por lo tanto, es muy rápida. Un panda DataFrame es una "tabla orientada a columnas", lo que significa que cada columna es realmente solo una matriz. Entonces, las acciones nativas que puede realizar en un DataFrame (como sumar todos los elementos en una columna) tendrán pocas fallas en la caché.
Más oportunidades para el paralelismo
Una simple matriz C se puede operar a través de instrucciones SIMD. Algunas partes de Numpy permiten SIMD, dependiendo de su CPU y proceso de instalación. Los beneficios para el paralelismo no serán tan dramáticos como el tipado estático 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ápidos 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 incorporadas).
Otra opción es usar to_records()
, que es más rápido que itertuples
y iterrows
.
Pero para su caso, hay mucho espacio para otros tipos de mejoras.
Aquí está mi última versión optimizada
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:
- Use
groupby
para evitar comparaciones repetidas de valores. - Use
to_records
para acceder a los objetos numpy.records en bruto. - No opere en DataFrame hasta que haya compilado todos los datos.