¿Por qué la velocidad de Pandas .loc en Pandas depende de la inicialización de DataFrame? ¿Cómo hacer que MultiIndex .loc sea lo más rápido posible?

Estoy tratando de mejorar el rendimiento de un código. Yo uso Pandas 0.19.2 y Python 3.5.

Acabo de darme cuenta de que la escritura .loc en un montón de valores a la vez tiene una velocidad muy diferente dependiendo de la inicialización del dataframe.

¿Alguien puede explicar por qué y decirme cuál es la mejor inicialización? Me permitiría acelerar mi código.

Aquí hay un ejemplo de juguete. Creo marcos de datos ‘similares’.

import pandas as pd import numpy as np ncols = 1000 nlines = 1000 columns = pd.MultiIndex.from_product([[0], [0], np.arange(ncols)]) lines = pd.MultiIndex.from_product([[0], [0], np.arange(nlines)]) #df has multiindex df = pd.DataFrame(columns = columns, index = lines) #df2 has mono-index, and is initialized a certain way df2 = pd.DataFrame(columns = np.arange(ncols), index = np.arange(nlines)) for i in range(ncols): df2[i] = i*np.arange(nlines) #df3 is mono-index and not initialized df3 = pd.DataFrame(columns = np.arange(ncols), index = np.arange(nlines)) #df4 is mono-index and initialized another way compared to df2 df4 = pd.DataFrame(columns = np.arange(ncols), index = np.arange(nlines)) for i in range(ncols): df4[i] = i 

Luego los cronometro:

 %timeit df.loc[(0, 0, 0), (0, 0)] = 2*np.arange(ncols) 1 loop, best of 3: 786 ms per loop The slowest run took 69.10 times longer than the fastest. This could mean that an intermediate result is being cached. %timeit df2.loc[0] = 2*np.arange(ncols) 1000 loops, best of 3: 275 µs per loop %timeit df3.loc[0] = 2*np.arange(ncols) 10 loops, best of 3: 31.4 ms per loop %timeit df4.loc[0] = 2*np.arange(ncols) 10 loops, best of 3: 63.9 ms per loop 

¿He hecho algo malo? ¿Por qué df2 funciona mucho más rápido que los demás? En realidad, en el caso de múltiples índices, es mucho más rápido configurar los elementos uno por uno utilizando .at. Implementé esta solución en mi código, pero no estoy contento con eso, creo que debe haber una solución mejor. Preferiría mantener mis bonitos marcos de datos de múltiples índices, pero si realmente necesito ir mono-índice, lo haré.

 def mod(df, arr, ncols): for j in range(ncols): df.at[(0, 0, 0),(0, 0, j)] = arr[j] return df %timeit mod(df, np.arange(ncols), ncols) The slowest run took 10.44 times longer than the fastest. This could mean that an intermediate result is being cached. 100 loops, best of 3: 14.6 ms per loop 

Una diferencia que veo aquí es que (efectivamente) ha inicializado df2 & df4 con dtype = int64 pero df & df3 con dtype = object. Podría inicializar con valores reales vacíos como este para df2 & df4:

 #df has multiindex df = pd.DataFrame(np.empty([ncols,nlines]), columns = columns, index = lines) #df3 is mono-index and not initialized df3 = pd.DataFrame(np.empty([ncols,nlines]), columns = np.arange(ncols), index = np.arange(nlines)) 

También puede agregar dtype=int para inicializar como enteros en lugar de reales, pero eso no parece importar tanto como la velocidad.

Obtengo un tiempo mucho más rápido que el de df4 (sin diferencia de código), así que para mí es un misterio. De todos modos, con los cambios anteriores en df & df3, los tiempos están cerca de df2 a df4, pero desafortunadamente df sigue siendo bastante lento.

 %timeit df.loc[(0, 0, 0), (0, 0)] = 2*np.arange(ncols) 1 loop, best of 3: 418 ms per loop %timeit df2.loc[:,0] = 2*np.arange(ncols) 10000 loops, best of 3: 185 µs per loop %timeit df3.loc[0] = 2*np.arange(ncols) 10000 loops, best of 3: 116 µs per loop %timeit df4.loc[:,0] = 2*np.arange(ncols) 10000 loops, best of 3: 196 µs per loop 

Editar para añadir:

En cuanto a su problema más grande con el índice múltiple, no lo sé, pero 2 pensamientos:

1) Ampliando el comentario de @ptrj, obtengo un tiempo muy rápido para su sugerencia (casi igual que los métodos de índice simple):

 %timeit df.loc[(0, 0, 0) ] = 2*np.arange(ncols) 10000 loops, best of 3: 133 µs per loop 

Así que de nuevo recibo un tiempo muy diferente al tuyo (?). Y FWIW, cuando desee que toda la fila con loc / iloc se recomienda usar : lugar de dejar en blanco la referencia de la columna:

 timeit df.loc[(0, 0, 0), : ] = 2*np.arange(ncols) 1000 loops, best of 3: 223 µs per loop 

Pero como pueden ver, es un poco más lento, así que no sé qué sugerir aquí. Supongo que generalmente debería hacerlo según lo recomendado por la documentación, pero por otro lado, esta puede ser una diferencia importante en la velocidad para usted.

2) Alternativamente, esto es más bien una fuerza bruta, pero solo puede guardar su índice / columnas, restablecer el índice / columnas para que sean simples, y luego configurar el índice / columnas de nuevo en múltiples. Aunque, eso no es realmente diferente de solo tomar df.values y sospecho que no es tan conveniente para ti.