He visto muchas respuestas publicadas a preguntas sobre Stack Overflow que involucran el uso del método Pandas apply
. También he visto a usuarios comentar debajo de ellos diciendo que " apply
es lento y debe evitarse".
He leído muchos artículos sobre el tema del rendimiento que explican que apply
es lento. También he visto un descargo de responsabilidad en los documentos sobre cómo apply
es simplemente una función de conveniencia para pasar UDF (parece que no puedo encontrar eso ahora). Entonces, el consenso general es que apply
debe evitarse si es posible. Sin embargo, esto plantea las siguientes preguntas:
- Si
apply
es tan malo, ¿por qué está en la API? - ¿Cómo y cuándo debo hacer que mi código sea
apply
libre? - ¿Alguna vez hay situaciones en las que
apply
es bueno (mejor que otras posibles soluciones)?
python
pandas
performance
apply
cs95
fuente
fuente
returns.add(1).apply(np.log)
vs.np.log(returns.add(1)
es un caso en elapply
que generalmente será ligeramente más rápido, que es el cuadro verde de la parte inferior derecha en el diagrama de jpp a continuación.Respuestas:
apply
, la función de conveniencia que nunca necesitóComenzamos abordando las preguntas en el PO, una por una.
DataFrame.apply
ySeries.apply
son funciones de conveniencia definidas en el objeto DataFrame y Series respectivamente.apply
acepta cualquier función definida por el usuario que aplique una transformación / agregación en un DataFrame.apply
es efectivamente una bala de plata que hace lo que cualquier función panda existente no puede hacer.Algunas de las cosas que se
apply
pueden hacer:axis=1
) o por columnas (axis=0
) en un DataFrameagg
otransform
en estos casos)result_type
argumento)....Entre otros. Para obtener más información, consulte Aplicación de función de fila o columna en la documentación.
Entonces, con todas estas características, ¿por qué es
apply
malo? Es porqueapply
es lento . Pandas no hace suposiciones sobre la naturaleza de su función, por lo que aplica iterativamente su función a cada fila / columna según sea necesario. Además, manejar todas las situaciones anteriores significa que seapply
incurre en una sobrecarga importante en cada iteración. Además,apply
consume mucha más memoria, lo que es un desafío para las aplicaciones limitadas a la memoria.Hay muy pocas situaciones en las que
apply
sea apropiado usar (más sobre eso a continuación). Si no está seguro de si debería usarloapply
, probablemente no debería hacerlo.Abordemos la siguiente pregunta.
Para reformular, aquí hay algunas situaciones comunes en las que querrá deshacerse de las llamadas a
apply
.Datos numéricos
Si está trabajando con datos numéricos, es probable que ya exista una función cython vectorizada que hace exactamente lo que está tratando de hacer (de lo contrario, haga una pregunta en Stack Overflow o abra una solicitud de función en GitHub).
Contraste el rendimiento de
apply
para una operación de adición simple.En cuanto al rendimiento, no hay comparación, el equivalente cythonizado es mucho más rápido. No es necesario un gráfico, porque la diferencia es obvia incluso para los datos de juguetes.
Incluso si habilita el paso de matrices sin procesar con el
raw
argumento, sigue siendo dos veces más lento.Otro ejemplo:
En general, busque alternativas vectorizadas si es posible.
Cadena / Regex
Pandas proporciona funciones de cadena "vectorizadas" en la mayoría de las situaciones, pero hay casos raros en los que esas funciones no ... "se aplican", por así decirlo.
Un problema común es comprobar si un valor de una columna está presente en otra columna de la misma fila.
Esto debería devolver la segunda y tercera fila, ya que "donald" y "minnie" están presentes en sus respectivas columnas de "Título".
Usando aplicar, esto se haría usando
Sin embargo, existe una mejor solución utilizando listas por comprensión.
Lo que hay que tener en cuenta aquí es que las rutinas iterativas resultan ser más rápidas que
apply
, debido a la menor sobrecarga. Si necesita manejar NaNs y dtypes no válidos, puede construir sobre esto usando una función personalizada que luego puede llamar con argumentos dentro de la comprensión de la lista.Para obtener más información sobre cuándo las listas por comprensión deben considerarse una buena opción, consulte mi artículo: Para bucles con pandas: ¿cuándo debería importarme? .
Un error común: columnas de listas explosivas
La gente se siente tentada a consumir
apply(pd.Series)
. Esto es horrible en términos de rendimiento.Una mejor opción es enumerar la columna y pasarla a pd.DataFrame.
Por último,
Aplicar es una función de conveniencia, por lo que no son situaciones en las que la sobrecarga es lo suficientemente insignificante para perdonar. Realmente depende de cuántas veces se llame a la función.
Funciones que están vectorizadas para series, pero no DataFrames
¿Qué sucede si desea aplicar una operación de cadena en varias columnas? ¿Qué sucede si desea convertir varias columnas a fecha y hora? Estas funciones están vectorizadas solo para Series, por lo que deben aplicarse sobre cada columna en la que desee convertir / operar.
Este es un caso admisible para
apply
:Tenga en cuenta que también tendría sentido
stack
, o simplemente usar un bucle explícito. Todas estas opciones son un poco más rápidas que usarapply
, pero la diferencia es lo suficientemente pequeña como para perdonar.Puede presentar un caso similar para otras operaciones, como operaciones de cadena o conversión a categoría.
v / s
Y así...
Conversión de series a
str
:astype
versusapply
Esto parece una idiosincrasia de la API. Usar
apply
para convertir enteros en una serie a cadena es comparable (y a veces más rápido) que usarastype
.El gráfico se trazó utilizando la
perfplot
biblioteca.Con flotadores, veo que
astype
es consistentemente tan rápido como, o un poco más rápido queapply
. Entonces, esto tiene que ver con el hecho de que los datos en la prueba son de tipo entero.GroupBy
operaciones con transformaciones encadenadasGroupBy.apply
no se ha discutido hasta ahora, peroGroupBy.apply
también es una función de conveniencia iterativa para manejar cualquier cosa que lasGroupBy
funciones existentes no lo hagan.Un requisito común es realizar un GroupBy y luego dos operaciones principales, como un "cumsum retrasado":
Necesitaría dos llamadas grupales sucesivas aquí:
Con
apply
, puede acortar esto a una sola llamada.Es muy difícil cuantificar el rendimiento porque depende de los datos. Pero en general,
apply
es una solución aceptable si el objetivo es reducir unagroupby
llamada (porquegroupby
también es bastante caro).Otras advertencias
Aparte de las advertencias mencionadas anteriormente, también vale la pena mencionar que
apply
opera en la primera fila (o columna) dos veces. Esto se hace para determinar si la función tiene efectos secundarios. De lo contrario, esapply
posible que pueda utilizar una vía rápida para evaluar el resultado; de lo contrario, recurre a una implementación lenta.Este comportamiento también se ve en las
GroupBy.apply
versiones de pandas <0.25 (se corrigió para 0.25, consulte aquí para obtener más información ).fuente
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')
seguramente después de la primera iteración será mucho más rápido ya que te estás convirtiendodatetime
a ...datetime
?to_datetime
a las cuerdas es tan rápido como a ...datetime
objetos" ... ¿en serio? Incluí la creación de marcos de datos (costo fijo) en tiempos de bucleapply
vsfor
y la diferencia es mucho menor.No todas las
apply
s son igualesLa siguiente tabla sugiere cuándo considerar
apply
1 . Verde significa posiblemente eficiente; rojo evitar.Algo de esto es intuitivo:
pd.Series.apply
es un bucle de nivel de fila de Python, lo mismo ocurre con el depd.DataFrame.apply
fila (axis=1
). Los usos indebidos de estos son muchos y de gran alcance. La otra publicación los trata con más profundidad. Las soluciones más populares son utilizar métodos vectorizados, listas por comprensión (asume datos limpios) o herramientas eficientes como elpd.DataFrame
constructor (por ejemplo, para evitarapply(pd.Series)
).Si utiliza
pd.DataFrame.apply
filas, especificarraw=True
(cuando sea posible) suele ser beneficioso. En esta etapa,numba
suele ser una mejor opción.GroupBy.apply
: generalmente favorecidoLa repetición de
groupby
operaciones para evitarapply
dañará el rendimiento.GroupBy.apply
generalmente está bien aquí, siempre que los métodos que use en su función personalizada estén vectorizados. A veces, no existe un método Pandas nativo para una agregación grupal que desee aplicar. En este caso, para una pequeña cantidad de gruposapply
con una función personalizada, aún puede ofrecer un rendimiento razonable.pd.DataFrame.apply
columna-sabio: una bolsa mixtapd.DataFrame.apply
column -wise (axis=0
) es un caso interesante. Para una pequeña cantidad de filas frente a una gran cantidad de columnas, casi siempre es caro. Para una gran cantidad de filas en relación con las columnas, el caso más común, a veces puede ver mejoras de rendimiento significativas utilizandoapply
:1 Hay excepciones, pero suelen ser marginales o poco frecuentes. Un par de ejemplos:
df['col'].apply(str)
puede superar ligeramentedf['col'].astype(str)
.df.apply(pd.to_datetime)
trabajar con cadenas no se escala bien con filas frente a unfor
bucle normal .fuente
apply
es significativamente más rápida que mi solución conany
. Tiene alguna idea sobre esto?any
es aproximadamente 100 veces más rápido queapply
. Hice mis primeras pruebas con 2000 filas x 1000 columnas y aquíapply
fue dos veces más rápido queany
Para
axis=1
(es decir, funciones por filas), puede usar la siguiente función en lugar deapply
. Me pregunto por qué este no es elpandas
comportamiento. (No probado con índices compuestos, pero parece ser mucho más rápido queapply
)fuente
zip(df, row[1:])
es suficiente aquí; realmente, en esta etapa, considerenumba
si func es un cálculo numérico. Consulte esta respuesta para obtener una explicación.numba
es más rápido,faster_df_apply
está destinado a personas que solo quieren algo equivalente a, pero más rápido queDataFrame.apply
(que es extrañamente lento).¿Alguna vez hay situaciones en las que
apply
es bueno? Sí a veces.Tarea: decodificar cadenas Unicode.
Actualización
De ninguna manera estaba abogando por el uso de
apply
, solo pensaba que, dadoNumPy
que no puede lidiar con la situación anterior, podría haber sido un buen candidato parapandas apply
. Pero me estaba olvidando de la simple comprensión de la lista gracias al recordatorio de @jpp.fuente
[unidecode.unidecode(x) for x in s]
olist(map(unidecode.unidecode, s))
?apply
, solo pensé que esto podría haber sido un buen caso de uso.