Valor escalar de los pandas obteniendo y configurando: ix o iat?

Estoy tratando de averiguar cuándo usar diferentes métodos de selección en pandas DataFrame. En particular, estoy buscando acceder a valores escalares. A menudo escucho ix siendo generalmente recomendado. Pero en la documentación de pandas se recomienda usar at e iat para un rápido acceso de valor escalar:

Since indexing with [] must handle a lot of cases (single-label access, slicing, boolean indexing, etc.), it has a bit of overhead in order to figure out what you're asking for. If you only want to access a scalar value, the fastest way is to use the Since indexing with [] must handle a lot of cases (single-label access, slicing, boolean indexing, etc.), it has a bit of overhead in order to figure out what you're asking for. If you only want to access a scalar value, the fastest way is to use the and iat methods, which are implemented on all of the data structures. at and iat methods, which are implemented on all of the data structures.

Por lo tanto, asumiría que debería ser más rápido para obtener y configurar celdas individuales. Sin embargo, después de algunas pruebas, encontramos que ix sería comparable o más rápido para leer celdas, mientras que iat mucho más rápido para asignar valores a las celdas.

¿Está este comportamiento documentado en alguna parte? ¿Es siempre el caso y por qué sucede esto? ¿Tiene que ver algo con volver a ver o copiar? Apreciaría si alguien pudiera aclarar esta pregunta y explicar qué se recomienda para obtener y establecer los valores de las celdas y por qué.

Aquí hay algunas pruebas que usan pandas (versión 0.15.2).

Solo para asegurarme de que este comportamiento no sea un error de esta versión, también lo probé en 0.11.0. No ix being much faster for getting, and iat for setting individual cells los resultados, pero la tendencia es exactamente la misma: ix being much faster for getting, and iat for setting individual cells .

 import pandas as pd import numpy as np df = pd.DataFrame(np.random.rand(1000,2),columns = ['A','B']) idx = 0 timeit for i in range(1000): df.ix[i,'A'] = 1 timeit for i in range(1000): df.iat[i,idx] = 2 >> 10 loops, best of 3: 92.6 ms per loop >> 10 loops, best of 3: 21.7 ms per loop timeit for i in range(1000): tmp = df.ix[i,'A'] timeit for i in range(1000): tmp = df.iat[i,idx] >> 100 loops, best of 3: 5.31 ms per loop >> 10 loops, best of 3: 19.4 ms per loop 

Pandas hace algunas cosas bastante interesantes con las clases de indexación . No creo que sea capaz de describir una forma sencilla de saber cuál usar, pero puedo dar una idea de la implementación.

DataFrame#ix es un _IXIndexer que no declara su propio __getitem__ o __setitem__ . Estos dos métodos son importantes porque controlan cómo se accede a los valores con Pandas. Dado que _IXIndexer no declara estos métodos, en su lugar se utilizan las súper clases _NDFrameIndexer .

Continuar investigando el _NDFrameIndexer del _NDFrameIndexer muestra que es relativamente simple y en algunos casos envuelve la lógica encontrada en el get_value de get_value . Entonces, __getitem__ es casi tan rápido como get_value para algunos escenarios.

_NDFrameIndexer de __setitem__ es una historia diferente. Al principio parece simple, pero el segundo método al que llama es _setitem_with_indexer que realiza una cantidad considerable de trabajo para la mayoría de los escenarios.

Esta información sugiere que las llamadas para obtener valores con ix están limitadas por get_value en el mejor de los casos y las llamadas para establecer valores con ix requerirán una explicación básica.

Ahora, para DataFrame#iat que es un _iAtIndexer que tampoco declara su propio __getitem__ o __setitem__ por lo que _ScalarAccessIndexer la implementación de su súper clase _ScalarAccessIndexer .

_ScalarAccessIndexer tiene una implementación sencilla de __getitem__ pero requiere un bucle para convertir la clave al formato adecuado. El bucle adicional agrega un tiempo de procesamiento adicional antes de llamar a get_value .

_ScalarAccessIndexer también tiene una implementación __setitem__ bastante simple que convierte la clave que requieren los parámetros set_value antes de establecer el valor.

Esta información sugiere que las llamadas para obtener valores utilizando iat están limitadas por get_value y también por un bucle for . Los valores de configuración con iat están limitados principalmente por las llamadas a set_value . Así que obtener valores con iat tiene un poco de sobrecarga, mientras que configurarlos tiene una sobrecarga más pequeña.

TL; DR

Creo que está utilizando el acceso correcto para un índice Int64Index basado en la documentación, pero no creo que eso signifique que sea el más rápido. El mejor rendimiento se puede encontrar utilizando get_value y set_value directamente, pero requieren un conocimiento adicional sobre cómo se implementan los Pandas DataFrames.

Notas

Vale la pena señalar que la documentación en Pandas menciona que get_value y set_value están en desuso, lo que creo que estaba destinado a ser iget_value .

Ejemplos

Con el fin de mostrar la diferencia en el rendimiento utilizando unos pocos indexadores (incluyendo llamadas directamente a get_value y set_value ), hice este script:

example.py :

 import timeit def print_index_speed(stmnt_name, stmnt): """ Repeatedly run the statement provided then repeat the process and take the minimum execution time. """ setup = """ import pandas as pd import numpy as np df = pd.DataFrame(np.random.rand(1000,2),columns = ['A','B']) idx = 0 """ minimum_execution_time = min( timeit.Timer(stmnt, setup=setup).repeat(5, 10)) print("{stmnt_name}: {time}".format( stmnt_name=stmnt_name, time=round(minimum_execution_time, 5))) print_index_speed("set ix", "for i in range(1000): df.ix[i, 'A'] = 1") print_index_speed("set at", "for i in range(1000): df.at[i, 'A'] = 2") print_index_speed("set iat", "for i in range(1000): df.iat[i, idx] = 3") print_index_speed("set loc", "for i in range(1000): df.loc[i, 'A'] = 4") print_index_speed("set iloc", "for i in range(1000): df.iloc[i, idx] = 5") print_index_speed( "set_value scalar", "for i in range(1000): df.set_value(i, idx, 6, True)") print_index_speed( "set_value label", "for i in range(1000): df.set_value(i, 'A', 7, False)") print_index_speed("get ix", "for i in range(1000): tmp = df.ix[i, 'A']") print_index_speed("get at", "for i in range(1000): tmp = df.at[i, 'A']") print_index_speed("get iat", "for i in range(1000): tmp = df.iat[i, idx]") print_index_speed("get loc", "for i in range(1000): tmp = df.loc[i, 'A']") print_index_speed("get iloc", "for i in range(1000): tmp = df.iloc[i, idx]") print_index_speed( "get_value scalar", "for i in range(1000): tmp = df.get_value(i, idx, True)") print_index_speed( "get_value label", "for i in range(1000): tmp = df.get_value(i, 'A', False)") 

Salida:

 set ix: 0.9918 set at: 0.06801 set iat: 0.08606 set loc: 1.04173 set iloc: 1.0021 set_value: 0.0452 **set_value**: 0.03516 get ix: 0.04827 get at: 0.06889 get iat: 0.07813 get loc: 0.8966 get iloc: 0.87484 get_value: 0.04994 **get_value**: 0.03111