Muchas personas recurren a .apply() para «evitar bucles» y esperan que sea rápido y sencillo. En la práctica, el « .apply(axis=1) » por filas sigue siendo un bucle a nivel de Python. Puede ser lento con datos de gran tamaño y, en ocasiones, devuelve formas que no esperabas. La solución es sencilla: utiliza operaciones vectorizadas de pandas/NumPy para tareas comunes y reserva .apply() para la lógica que realmente necesite varias columnas.
Esta guía muestra cómo .apply() funciona hoy en día, destaca los errores comunes y proporciona patrones de inserción que son más rápidos y claros.
¿Qué son DataFrame.apply() y Series.apply()?
DataFrame.apply(func, axis=0) Llama a func en cada columna de forma predeterminada. Con axis=1, llama a func en cada fila. Series.apply(func) llama a func en cada elemento (o en todo Series, dependiendo de las firmas en las versiones recientes).
Por filas frente a por columnas:axis=1 significa «por fila». axis=0 (por defecto) significa «por columna».
Reglas de forma de retorno (pandas ≥0,23): Si tu función devuelve un escalar por fila/columna, obtienes un Series. Si devuelve un Series o un dict, pandas expande esas claves en un DataFrame. Si devuelve una lista/arreglo, obtendrás una serie de listas a menos que establezcas result_type='expand'.
Errores más estrictos (pandas ≥2.0): Las longitudes desiguales en las listas ahora generan errores en lugar de producir resultados inconsistentes de forma silenciosa.
Primera opción recomendada: Operaciones vectorizadas
La mayoría de las tareas que implican combinar o transformar columnas son más rápidas y claras con expresiones vectorizadas o métodos integrados. El siguiente ejemplo calcula la diferencia de carreras de un equipo de béisbol sin utilizar .apply().
import pandas as pd
team_stats = pd.DataFrame({"Team":["ARI","ATL","BAL"],"League":["NL","NL","AL"],"Year":[2012,2012,2012],"RunsScored":[734,700,712],"RunsAllowed":[688,600,705],"Wins":[81,94,93],"Games":[162,162,162],"Playoffs":[0,1,1],})# Vectorized: fast and idiomatic
team_stats["RunDiff"]= team_stats["RunsScored"]- team_stats["RunsAllowed"]print(team_stats[["Team","RunsScored","RunsAllowed","RunDiff"]])Con la tecnología de
Row-Wise .apply() cuando realmente lo necesitas
Úsalo .apply(axis=1) cuando tu lógica realmente abarque varias columnas y no sea fácil de vectorizar (por ejemplo, reglas condicionales que dependan de varios campos).
calcular un valor derivado a partir de varias columnas
Este patrón calcula un valor por fila utilizando múltiples entradas. El enfoque vectorizado anterior sigue siendo el preferido, pero esto muestra el uso correcto por filas y las opciones que afectan a la velocidad y al resultado.
defcompute_run_diff(row):# Treat the row as read-only; return a scalarreturn row["RunsScored"]- row["RunsAllowed"]# Row-wise apply (Python-level loop; can be slow on large data)
team_stats["RunDiff_apply"]= team_stats.apply(compute_run_diff, axis=1)Con la tecnología de
Cuando la función es numérica y solo necesita arreglos sin procesar, pasar raw=True omite parte de la sobrecarga de pandas al proporcionar un arreglo NumPy a tu función.
import numpy as np
defcompute_run_diff_raw(values):# values is a NumPy array when raw=True# Order matches the column order we select
rs, ra = values
return rs - ra
team_stats["RunDiff_raw"]= team_stats[["RunsScored","RunsAllowed"]].apply(
compute_run_diff_raw, axis=1, raw=True)Con la tecnología de
Verifica la entrada de la función una vez.
Cuando conecto una nueva función de fila, imprimo una fila una vez para confirmar lo que se está pasando.
defdebug_row(row):# Print the first row onlyif row.name == team_stats.index[0]:print("Example row:", row.to_dict())return0
_ = team_stats.apply(debug_row, axis=1)Con la tecnología de
Caso práctico: Sumas, totales de temporada y marcadores de texto
Dadas las estadísticas de Rays por año, es preferible utilizar métodos integrados y herramientas elementales en lugar de .apply() siempre que sea posible.
rays_by_year = pd.DataFrame({"Year":[2012,2011,2010,2009,2008],"RunsScored":[697,707,802,803,774],"RunsAllowed":[577,614,649,754,671],"Wins":[90,91,96,84,97],"Playoffs":[0,1,1,0,1],}).set_index("Year")Con la tecnología de
obtener sumas de columnas de manera eficiente
Utiliza DataFrame.sum() en lugar de .apply(sum). Es más rápido y más claro.
# Preferred
totals = rays_by_year.sum(axis=0)print(totals)# If you must use apply (not recommended here)
totals_apply = rays_by_year.apply(sum, axis=0)Con la tecnología de
Calcular el total de carreras anotadas en una temporada.
Utiliza aritmética vectorizada en todas las columnas; no .apply() necesidad.
rays_by_year["TotalRuns"]= rays_by_year["RunsScored"]+ rays_by_year["RunsAllowed"]print(rays_by_year[["RunsScored","RunsAllowed","TotalRuns"]].head())Con la tecnología de
Convertir un indicador 0/1 en texto
Prefiero Series.map() o replace() para transformaciones elemento por elemento en una columna.
# Using map with a dict
rays_by_year["PlayoffsText"]= rays_by_year["Playoffs"].map({0:"No",1:"Yes"})# Equivalent with replace
rays_by_year["PlayoffsText2"]= rays_by_year["Playoffs"].replace({0:"No",1:"Yes"})Con la tecnología de
Controla la forma de salida desde .apply()
Cuando tu función devuelva más de un valor por fila, haz que la forma sea explícita. Esto evita sorpresas y problemas con versiones futuras.
Devuelve una lista y la expande en columnas.
Usa result_type='expand' para dividir una lista/arreglo en varias columnas.
defwins_losses(row):# Derive wins and losses as two outputs
wins = row["Wins"]
losses = row["Games"]- row["Wins"]if"Games"in row else np.nan
return[wins, losses]# Example using team_stats, which has "Wins" and "Games"
expanded = team_stats.apply(wins_losses, axis=1, result_type="expand")
expanded.columns =["WinsOut","LossesOut"]
team_stats = pd.concat([team_stats, expanded], axis=1)Con la tecnología de
Devuelve una serie o un diccionario para nombrar columnas automáticamente.
Devolver un Series o dict de cada fila produce un DataFrame con etiquetas de columna coincidentes.
defsummary_row(row):return pd.Series({"IsWinningSeason": row["Wins"]>=90,"RunRatio":(row["RunsScored"]/ row["RunsAllowed"]),})
summary = team_stats.apply(summary_row, axis=1)
team_stats = pd.concat([team_stats, summary], axis=1)Con la tecnología de
Consejos importantes para mejorar el rendimiento
Por filas .apply(axis=1) es conveniente, pero lento en marcos grandes, ya que llama a tu función Python una vez por fila. Estos patrones evitan ese cuello de botella.
Es preferible utilizar operaciones vectorizadas de pandas/NumPy y métodos integrados como sum, mean, clip, where, astypey métodos de cadena/accesor.
Para operaciones numéricas de fila/columna que aceptan arreglos, pasa raw=True para obtener un arreglo NumPy y reducir la sobrecarga de pandas.
Para trabajar elemento por elemento en una sola columna, utiliza Series.map() o métodos vectorizados en lugar de DataFrame.apply(axis=1).
Evita modificar la fila/columna proporcionada dentro de tu función; en su lugar, devuelve un nuevo valor.
Notas sobre la versión que debes conocer (pandas 2.x)
Las últimas versiones de pandas han restringido el comportamiento en torno a .apply() para que los resultados sean más predecibles.
Resultados en forma de lista: En la versión 2.0+, las longitudes de lista que no coinciden generan un error. Utiliza longitudes uniformes y establece result_type='expand' cuando desees varias columnas.
Ampliación de la producción: Al devolver un objeto de tipo Series o dict se expande en un DataFrame con esas claves como columnas (estable desde la versión 0.23).
Series.apply() cambios: El argumento convert_dtype está en desuso. Si necesitas tipos mixtos, primero convierte a object (por ejemplo, s.astype("object").apply(fn)). Las firmas más recientes permiten controlar si fn recibe escalares o una serie en más contextos (por ejemplo,by_row).
.apply() vs. .map() vs. .applymap() vs. .agg()
Elige la API que se adapte a la forma de tu transformación.
Series.map(func_or_dict): Transformación por elementos en una columna. Ideal para búsquedas o funciones sencillas.
DataFrame.apply(func, axis=1): Lógica por filas que requiere varias columnas.
DataFrame.apply(func, axis=0): Lógica por columnas (cada entrada es una serie que representa una columna).
DataFrame.applymap(func): Por elementos sobre cada celda. Úsalo con moderación; los métodos vectorizados son más rápidos.
.agg()/.transform(): Agregaciones y transformaciones por grupos; preferirlos en canalizaciones groupby.
Errores comunes y soluciones rápidas
Estos son los problemas que con mayor frecuencia provocan resultados incorrectos o ralentizaciones, junto con las formas de corregirlos.
Olvidando axis=1 para la lógica por fila: Si tu función recibe de repente columnas en lugar de filas, añade axis=1.
Serie inesperada de listas: Al devolver una lista/arreglo de cada fila, establece result_type='expand' para dividir en columnas.
Código lento por filas: Reemplaza con expresiones vectorizadas o integradas; si no es posible, considera utilizar «raw=True » para reducir la sobrecarga.
Mutación de la fila de entrada: Trata las entradas como de solo lectura y devuelve nuevos valores.
Desviación del tipo de datos: Si tu función a veces devuelve valores no enteros, las columnas enteras pueden convertirse a flotantes u objetos. A continuación, moldea con astype si es necesario.
Conclusión
.apply() Es una herramienta flexible, pero no es un atajo para mejorar el rendimiento. Utiliza operaciones vectorizadas para realizar operaciones aritméticas, agregaciones y transformaciones elemento por elemento en una sola columna. Utiliza un DataFrame.apply(axis=1) o solo cuando tu lógica realmente necesite varias columnas por fila. Cuando lo utilices, controla la forma de salida con result_type, ten en cuenta raw=True para las funciones numéricas y presta atención a dtypes. Estos patrones producen resultados predecibles en los pandas modernos y se adaptan mejor a medida que tus datos crecen.
Explora el análisis de datos con Python. Los DataFrames de Pandas facilitan la manipulación de tus datos, desde la selección o sustitución de columnas e índices hasta la remodelación de tus datos.
Utiliza Python Pandas y selecciona columnas de los DataFrames. ¡Sigue nuestro tutorial con ejemplos de código y aprende hoy mismo distintas formas de seleccionar tus datos!
Importar datos es el primer paso de cualquier proyecto de ciencia de datos. Aprende por qué los científicos de datos actuales prefieren la función pandas read_csv() para hacerlo.
Muchas personas recurren a
.apply()para «evitar bucles» y esperan que sea rápido y sencillo. En la práctica, el «.apply(axis=1)» por filas sigue siendo un bucle a nivel de Python. Puede ser lento con datos de gran tamaño y, en ocasiones, devuelve formas que no esperabas. La solución es sencilla: utiliza operaciones vectorizadas de pandas/NumPy para tareas comunes y reserva.apply()para la lógica que realmente necesite varias columnas.Esta guía muestra cómo
.apply()funciona hoy en día, destaca los errores comunes y proporciona patrones de inserción que son más rápidos y claros.¿Qué son DataFrame.apply() y Series.apply()?
DataFrame.apply(func, axis=0)Llama afuncen cada columna de forma predeterminada. Conaxis=1, llama afuncen cada fila.Series.apply(func)llama afuncen cada elemento (o en todoSeries, dependiendo de las firmas en las versiones recientes).axis=1significa «por fila».axis=0(por defecto) significa «por columna».Series. Si devuelve unSerieso undict, pandas expande esas claves en unDataFrame. Si devuelve una lista/arreglo, obtendrás una serie de listas a menos que establezcasresult_type='expand'.Primera opción recomendada: Operaciones vectorizadas
La mayoría de las tareas que implican combinar o transformar columnas son más rápidas y claras con expresiones vectorizadas o métodos integrados. El siguiente ejemplo calcula la diferencia de carreras de un equipo de béisbol sin utilizar
.apply().Row-Wise .apply() cuando realmente lo necesitas
Úsalo
.apply(axis=1)cuando tu lógica realmente abarque varias columnas y no sea fácil de vectorizar (por ejemplo, reglas condicionales que dependan de varios campos).calcular un valor derivado a partir de varias columnas
Este patrón calcula un valor por fila utilizando múltiples entradas. El enfoque vectorizado anterior sigue siendo el preferido, pero esto muestra el uso correcto por filas y las opciones que afectan a la velocidad y al resultado.
Cuando la función es numérica y solo necesita arreglos sin procesar, pasar
raw=Trueomite parte de la sobrecarga de pandas al proporcionar un arreglo NumPy a tu función.Verifica la entrada de la función una vez.
Cuando conecto una nueva función de fila, imprimo una fila una vez para confirmar lo que se está pasando.
Caso práctico: Sumas, totales de temporada y marcadores de texto
Dadas las estadísticas de Rays por año, es preferible utilizar métodos integrados y herramientas elementales en lugar de
.apply()siempre que sea posible.obtener sumas de columnas de manera eficiente
Utiliza
DataFrame.sum()en lugar de.apply(sum). Es más rápido y más claro.Calcular el total de carreras anotadas en una temporada.
Utiliza aritmética vectorizada en todas las columnas; no
.apply()necesidad.Convertir un indicador 0/1 en texto
Prefiero
Series.map()oreplace()para transformaciones elemento por elemento en una columna.Controla la forma de salida desde .apply()
Cuando tu función devuelva más de un valor por fila, haz que la forma sea explícita. Esto evita sorpresas y problemas con versiones futuras.
Devuelve una lista y la expande en columnas.
Usa
result_type='expand'para dividir una lista/arreglo en varias columnas.Devuelve una serie o un diccionario para nombrar columnas automáticamente.
Devolver un
Seriesodictde cada fila produce un DataFrame con etiquetas de columna coincidentes.Consejos importantes para mejorar el rendimiento
Por filas
.apply(axis=1)es conveniente, pero lento en marcos grandes, ya que llama a tu función Python una vez por fila. Estos patrones evitan ese cuello de botella.sum,mean,clip,where,astypey métodos de cadena/accesor.raw=Truepara obtener un arreglo NumPy y reducir la sobrecarga de pandas.Series.map()o métodos vectorizados en lugar deDataFrame.apply(axis=1).Notas sobre la versión que debes conocer (pandas 2.x)
Las últimas versiones de pandas han restringido el comportamiento en torno a
.apply() para que los resultados sean más predecibles.result_type='expand'cuando desees varias columnas.Seriesodictse expande en un DataFrame con esas claves como columnas (estable desde la versión 0.23).Series.apply()cambios: El argumentoconvert_dtypeestá en desuso. Si necesitas tipos mixtos, primero convierte aobject(por ejemplo,s.astype("object").apply(fn)). Las firmas más recientes permiten controlar sifnrecibe escalares o una serie en más contextos (por ejemplo,by_row)..apply() vs. .map() vs. .applymap() vs. .agg()
Elige la API que se adapte a la forma de tu transformación.
Series.map(func_or_dict): Transformación por elementos en una columna. Ideal para búsquedas o funciones sencillas.DataFrame.apply(func, axis=1): Lógica por filas que requiere varias columnas.DataFrame.apply(func, axis=0): Lógica por columnas (cada entrada es una serie que representa una columna).DataFrame.applymap(func): Por elementos sobre cada celda. Úsalo con moderación; los métodos vectorizados son más rápidos..agg()/.transform(): Agregaciones y transformaciones por grupos; preferirlos en canalizaciones groupby.Errores comunes y soluciones rápidas
Estos son los problemas que con mayor frecuencia provocan resultados incorrectos o ralentizaciones, junto con las formas de corregirlos.
axis=1.result_type='expand'para dividir en columnas.raw=True» para reducir la sobrecarga.astypesi es necesario.Conclusión
.apply()Es una herramienta flexible, pero no es un atajo para mejorar el rendimiento. Utiliza operaciones vectorizadas para realizar operaciones aritméticas, agregaciones y transformaciones elemento por elemento en una sola columna. Utiliza unDataFrame.apply(axis=1)o solo cuando tu lógica realmente necesite varias columnas por fila. Cuando lo utilices, controla la forma de salida conresult_type, ten en cuentaraw=Truepara las funciones numéricas y presta atención adtypes. Estos patrones producen resultados predecibles en los pandas modernos y se adaptan mejor a medida que tus datos crecen.