How to iterate over rows in a DataFrame in Pandas?
Respuesta: ¡NO * !
La iteración en Pandas es un antipatrón y es algo que solo debe hacer cuando haya agotado todas las demás opciones. No debe usar ninguna función con " iter
" en su nombre por más de unas pocas miles de filas o tendrá que acostumbrarse a esperar mucho .
¿Quieres imprimir un DataFrame? uso DataFrame.to_string()
_
¿Quieres calcular algo? En ese caso, busque métodos en este orden (lista modificada desde aquí ):
- Vectorización
- rutinas Cython
for
Lista de comprensiones ( bucle de vainilla )
DataFrame.apply()
: i) Reducciones que se pueden realizar en Cython, ii) Iteración en el espacio de Python
DataFrame.itertuples()
yiteritems()
DataFrame.iterrows()
iterrows
y itertuples
(ambos recibieron muchos votos en las respuestas a esta pregunta) deben usarse en circunstancias muy raras, como generar objetos de fila/nombres para el procesamiento secuencial, que es realmente lo único para lo que son útiles estas funciones.
Apelar a la autoridad
La página de documentación sobre la iteración tiene un enorme cuadro de advertencia rojo que dice:
Iterating through pandas objects is generally slow. In many cases, iterating manually over the rows is not needed [...].
* En realidad es un poco más complicado que "no". df.iterrows()
es la respuesta correcta a esta pregunta, pero "vectorizar sus operaciones" es la mejor. Admitiré que hay circunstancias en las que no se puede evitar la iteración (por ejemplo, algunas operaciones en las que el resultado depende del valor calculado para la fila anterior). Sin embargo, se necesita cierta familiaridad con la biblioteca para saber cuándo. Si no está seguro de si necesita una solución iterativa, probablemente no la necesite. PD: Para saber más sobre mi razón para escribir esta respuesta, salte hasta el final.
Un buen número de operaciones y cálculos básicos son "vectorizados" por pandas (ya sea a través de NumPy o mediante funciones Cythonized). Esto incluye aritmética, comparaciones, (la mayoría de) reducciones, remodelación (como pivote), uniones y operaciones de agrupación. Consulte la documentación sobre la funcionalidad básica esencial para encontrar un método vectorizado adecuado para su problema.
Si no existe ninguno, siéntase libre de escribir el suyo usando extensiones de Cython personalizadas .
La comprensión de listas debe ser su próximo puerto de escala si 1) no hay una solución vectorizada disponible, 2) el rendimiento es importante, pero no lo suficientemente importante como para pasar por la molestia de citonizar su código, y 3) está tratando de realizar una transformación por elementos en tu código. Existe una buena cantidad de evidencia que sugiere que las listas de comprensión son lo suficientemente rápidas (e incluso a veces más rápidas) para muchas tareas comunes de Pandas.
La fórmula es sencilla,
# Iterating over one column - `f` is some function that processes your data
result = [f(x) for x in df['col']]
# Iterating over two columns, use `zip`
result = [f(x, y) for x, y in zip(df['col1'], df['col2'])]
# Iterating over multiple columns - same data type
result = [f(row[0], ..., row[n]) for row in df[['col1', ...,'coln']].to_numpy()]
# Iterating over multiple columns - differing data type
result = [f(row[0], ..., row[n]) for row in zip(df['col1'], ..., df['coln'])]
Si puede encapsular su lógica comercial en una función, puede usar una lista de comprensión que la llame. Puede hacer que cosas arbitrariamente complejas funcionen a través de la simplicidad y la velocidad del código Python sin procesar.
Advertencias
Las comprensiones de listas asumen que es fácil trabajar con sus datos, lo que significa que sus tipos de datos son consistentes y no tiene NaN, pero esto no siempre se puede garantizar.
- El primero es más obvio, pero cuando se trata de NaN, prefiera los métodos integrados de pandas si existen (porque tienen una lógica de manejo de casos de esquina mucho mejor), o asegúrese de que su lógica comercial incluya la lógica de manejo de NaN adecuada.
- Cuando se trata de tipos de datos mixtos, debe iterar en
zip(df['A'], df['B'], ...)
lugar de df[['A', 'B']].to_numpy()
, ya que este último convierte implícitamente los datos al tipo más común. Como ejemplo, si A es numérico y B es una cadena, to_numpy()
convertirá toda la matriz en una cadena, lo que puede no ser lo que desea. Afortunadamente zip
, hacer ping a sus columnas juntas es la solución más sencilla para esto.
*Su millaje puede variar por las razones descritas en la sección Advertencias anterior.
Un ejemplo obvio
Demostremos la diferencia con un ejemplo simple de agregar dos columnas de pandas A + B
. Esta es una operación vectorizable, por lo que será fácil contrastar el rendimiento de los métodos discutidos anteriormente.
Código de evaluación comparativa, para su referencia . La línea en la parte inferior mide una función escrita en numpandas, un estilo de Pandas que se mezcla fuertemente con NumPy para exprimir el máximo rendimiento. Se debe evitar escribir código numpandas a menos que sepa lo que está haciendo. Apéguese a la API donde pueda (es decir, prefiera vec
sobre vec_numpy
).
Debo mencionar, sin embargo, que no siempre es así de claro. A veces, la respuesta a "cuál es el mejor método para una operación" es "depende de sus datos". Mi consejo es probar diferentes enfoques en sus datos antes de decidirse por uno.
Mi Opinión Personal *
La mayoría de los análisis realizados sobre las diversas alternativas a la familia iter se han realizado a través de la lente del rendimiento. Sin embargo, en la mayoría de las situaciones, normalmente trabajará en un conjunto de datos de tamaño razonable (nada más allá de unos pocos miles o 100 000 filas) y el rendimiento será secundario a la simplicidad/legibilidad de la solución.
Esta es mi preferencia personal al seleccionar un método para usar en un problema.
Para el novato:
Vectorization (when possible); apply()
; List Comprehensions; itertuples()
/iteritems()
; iterrows()
; Cython
Para los más experimentados:
Vectorization (when possible); apply()
; List Comprehensions; Cython; itertuples()
/iteritems()
; iterrows()
La vectorización prevalece como el método más idiomático para cualquier problema que pueda ser vectorizado. ¡Busca siempre vectorizar! En caso de duda, consulte los documentos o busque en Stack Overflow una pregunta existente sobre su tarea en particular.
Tiendo a continuar con lo malo que apply
es en muchas de mis publicaciones, pero reconozco que es más fácil para un principiante comprender lo que está haciendo. Además, hay bastantes casos de uso que se apply
han explicado en esta publicación mía .
Cython ocupa un lugar más bajo en la lista porque requiere más tiempo y esfuerzo para lograrlo correctamente. Por lo general, nunca necesitará escribir código con pandas que exija este nivel de rendimiento que incluso la comprensión de una lista no puede satisfacer.
* Como con cualquier opinión personal, ¡tómelo con mucha sal!
Otras lecturas
* Los métodos de cadena de Pandas están "vectorizados" en el sentido de que se especifican en la serie pero operan en cada elemento. Los mecanismos subyacentes siguen siendo iterativos, porque las operaciones de cadenas son intrínsecamente difíciles de vectorizar.
Por qué escribí esta respuesta
Una tendencia común que noto entre los nuevos usuarios es hacer preguntas del tipo "¿Cómo puedo iterar sobre mi df para hacer X?". Mostrando código que llama iterrows()
mientras se hace algo dentro de un for
bucle. Aquí está el por qué. Un nuevo usuario de la biblioteca que no ha sido introducido al concepto de vectorización probablemente visualizará el código que resuelve su problema iterando sobre sus datos para hacer algo. Sin saber cómo iterar sobre un DataFrame, lo primero que hacen es buscarlo en Google y terminar aquí, en esta pregunta. Luego ven la respuesta aceptada que les dice cómo hacerlo, y cierran los ojos y ejecutan este código sin siquiera preguntarse si la iteración es lo correcto.
El objetivo de esta respuesta es ayudar a los nuevos usuarios a comprender que la iteración no es necesariamente la solución a todos los problemas, y que podrían existir soluciones mejores, más rápidas y más idiomáticas, y que vale la pena invertir tiempo en explorarlas. No estoy tratando de iniciar una guerra de iteración versus vectorización, pero quiero que los nuevos usuarios estén informados cuando desarrollen soluciones a sus problemas con esta biblioteca.