Filtrar data utilizando ID == ‘cadena’ en Pandas es algo que debería evitar, ya que el operador scalar_compare provoca cuellos de botella en el rendimiento. Hay muchas formas de eludirlo, por ejemplo particionando su Dataframe en un diccionario utilizando el ID en cuestión como clave.
Introducción | Filtros de cadenas en pandas
La simplificación de la gestión del hardware, aportada por las soluciones cloud, nos empuja cada vez más a alejarnos de los problemas de optimización del código.

Pero la ampliación y aumentar la potencia de cálculo no siempre es la solución ya que conduce a una escalada de costes y la potencia de cálculo no es infinita.
Al plantearme el reto de encajar toda mi preparación data en un simple contenedor, me di cuenta rápidamente de que con el conocimiento de algunos trucos, optimizar su código a veces puede ser tan sencillo como instanciar una instancia mayor.
En este artículo me gustaría volver sobre un único cambio de código que me permitió reducir drásticamente el tiempo dedicado al cálculo de características durante la fase de desarrollo de un modelo de propensión. Este cambio es lo suficientemente común como para aplicarlo a muchas otras situaciones.

No pretende ser la solución más óptima, sino que busca ser una opción rápida para disminuir el tiempo de cálculo de forma eficiente, en el espíritu de la Principio de Pareto.
Contexto
Durante esta misión, me encargué de automatizar el proceso de preparación del data y la predicción de los modelos desarrollados por nuestro equipo de científicos del Data. Para simplificar el flujo de data, el data transaccional se cargaba todos los días y tenía que pasar por un primer Preprocesamiento paso, seguido de un segundo paso de Característica Cómputo antes de llegar al último paso de Predicción del modelo utilizando el modelo entrenado.

El Cálculo de características es la fase que más tiempo tardó en ejecutarse: en efecto, muchas características se computaron a nivel de cliente, lo que dio lugar a la ejecución recurrente de una línea del código que consume un tiempo sorprendente :

Esta única línea se cronometró a 18 ms, lo que significa que con mis 33.717 clientes para evaluar diariamente, gastaba unos 10 min de tiempo de cálculo bruto por característica a nivel de cliente, reducidos a 1 min y 16 segundos por característica gracias a la paralelización de la operación en mis 8 CPU disponibles.
Como trabajábamos con B2B data, era necesario calcular las características a nivel de cliente, ya que un cliente representaba de hecho una empresa con a veces varios pedidos al día.
Experimentación | Filtros de cadenas en pandas
Después de investigar un poco utilizando el %%prun comando mágico, pude identificar la fuente de este cuello de botella de procesamiento : el pandas._libs.ops.scalar_compare operador que estaba poco optimizado en mi versión de Pandas (1.3.1).

Simplemente sustituyendo este “==” por el operador “isin”, lo que no es muy intuitivo ya que estaba comparando una sola cadena, ya he dividido el tiempo de cálculo por 2,5 veces, pasando de 18ms por operación a 7,95ms.

Siguiendo con la búsqueda de optimización, me encontré con un Stackoverflow post impulsando el uso de Tipo categórico para mejorar aún más la operación.

Esta última implementación me permitió dividir el tiempo de computación más de 36 veces. Sin embargo, pude observar un matiz en este truco, ya que el tipo de categoría no se comporta como un str clásico durante todas las operaciones (véase el ejemplo de abajo al utilizar .groupby() en pandas) por lo que tuve que convertirlo de nuevo a str en un momento dado.

Hipótesis
¿Pero por qué? ¿Cómo puede un simple == entre dos cadenas requiere más tiempo que una isin() operación de comparación de listas, o una categórico ¿uno?
Bien, para responder a la primera pregunta necesitaríamos sin duda desenterrar el código que hay detrás de la ejecución de comparar_escalares que utiliza principalmente Cython y compararlo con el código detrás del isin() método.
Afortunadamente, la respuesta a la segunda parte parece ser más intuitiva: al comparar dos valores de cadena, estamos comparando un número infinito de posibilidades juntas, mientras que al comparar dos categorías, la se establece el número de opciones por las diferentes categorías únicas que existen. Parece mucho más fácil comparar dos entidades cuando nuestro número de opciones es fijo.
Mi sed de optimización seguía sin saciarse, así que decidí dar un paso atrás en el método actual. Se me ocurrió un nuevo enfoque: particionar mi Dataframe en un diccionario que luego utilizaré para filtrar a mis clientes cuando calcule mis características.
En términos de código, esto se tradujo simplemente en las siguientes líneas:
Construir ese diccionario me costó 32 segundos de tiempo de cálculo, pero utilizando este Dataframe particionado pude ahora filtrar mi data en unos pocos nanosegundos.

Conclusión | Filtros de cadenas en pandas
Tras pasar un par de horas en la fase de experimentación, quedé satisfecho con el resultado :

El tiempo de cálculo inicial por filtrado del cliente se dividía ahora 348 000 veces, pasando de 18ms a 51,7ns, o de 10min a 2,65ms por característica calculada en mi caso, teniendo en cuenta el tiempo empleado en la partición.

Inmediatamente, el impacto de este pequeño cambio me permitió reducir el tiempo de cálculo de mi fase completa de cálculo de características en 90%, de 40’49” a 7’27”. Utilizando un método de estimación de CO2eq que detallaré en mi próximo artículo, esta modificación ahorró al menos 170$/año + 22kgCO2/año y potencialmente mucho más con la creciente lista de clientes y el despliegue del proyecto en otros países.

BLOG







