apply, map, applymap: las tres herramientas para la transformación de datos
Al finalizar este tema
apply, map, applymap comprenderá claramente las diferencias entre estas tres funciones, podrá seleccionar la herramienta adecuada según el contexto y entenderá las diferencias de rendimiento.
¿Por qué existen tres?
En pandas, hay tres funciones para transformar datos, lo que puede resultar confuso al principio. Cada una tiene un alcance de aplicación diferente.
| Función | Destino | Unidad de aplicación |
|---|---|---|
map() | Series (1D) | Valor individual |
apply() | Series o DataFrame | Fila o columna completa |
applymap() | DataFrame (2D) | Valor individual |
En una frase: map aplica una función a cada valor de una Series, applymap aplica una función a cada valor de un DataFrame y apply aplica una función a filas o columnas completas.
map: transformación de cada valor en una Series
map() es exclusivo para Series. Aplica una función a cada valor o realiza un mapeo mediante un diccionario.
Mapeo de funciones
import pandas as pd
df = pd.DataFrame({ "name": ["Alice", "Bob", "Carol"], "score": [85.7, 92.3, 78.1]})
# Convierte cada nombre a mayúsculasdf["name"].map(str.upper)# 0 ALICE# 1 BOB# 2 CAROL
# Redondea cada puntuacióndf["score"].map(round)# 0 86# 1 92# 2 78Mapeo de diccionario
grade_map = { "A": "Excellent", "B": "Good", "C": "Average"}
grades = pd.Series(["A", "B", "C", "A", "B"])grades.map(grade_map)# 0 Excellent# 1 Good# 2 Average# 3 Excellent# 4 GoodEl valor que no está en el diccionario será NaN. Esto se utiliza frecuentemente en la práctica para el codificado de categorías.
Con lambda
df["score"].map(lambda x: "Pass" if x >= 80 else "Fail")# 0 Pass# 1 Pass# 2 Failapply — aplicar una función por fila o columna
apply() se puede utilizar tanto en Series como en DataFrames.
Series.apply — similar a map
df["score"].apply(lambda x: round(x, 1))# Mismo resultado que map()En Series, es casi idéntico a map(). La única diferencia es que apply() puede pasar argumentos adicionales.
DataFrame.apply — por columna (o fila)
df = pd.DataFrame({ "math": [85, 92, 78], "english": [90, 88, 95], "science": [88, 91, 82]})
# Media de cada asignatura (columna)df.apply("mean") # axis=0 (predeterminado, por columnas)# math 85.0# english 91.0# science 87.0
# Media de cada estudiante (fila)df.apply("mean", axis=1) # axis=1 (por filas)# 0 87.666667# 1 90.333333# 2 85.000000axis=0 aplica funciones "de arriba hacia abajo" (a cada columna), mientras que axis=1 aplica funciones "de izquierda a derecha" (a cada fila).
Referencia de múltiples columnas por fila
df = pd.DataFrame({ "name": ["Alice", "Bob", "Carol"], "math": [85, 92, 78], "english": [90, 88, 95]})
# Mayor puntuación de cada estudiante entre las dos asignaturasdf.apply(lambda row: max(row["math"], row["english"]), axis=1)# 0 90# 1 92# 2 95En la operación apply a nivel de fila (axis=1), row corresponde al Series de dicha fila. Esto permite realizar cálculos complejos que hacen referencia a varias columnas.
applymap — Transformar todos los valores de un DataFrame
applymap() es exclusivo de DataFrame y aplica una función a cada celda.
df = pd.DataFrame({ "math": [85.7, 92.3, 78.1], "english": [90.2, 88.9, 95.4], "science": [88.1, 91.7, 82.3]})
# Convierte todos los valores en enterosdf.applymap(int)# math english science# 0 85 90 88# 1 92 88 91# 2 78 95 82
# Aplica formato a todos los valoresdf.applymap(lambda x: f"{x:.1f}%")# math english science# 0 85.7% 90.2% 88.1%# 1 92.3% 88.9% 91.7%# 2 78.1% 95.4% 82.3%Nota: En pandas 2.1+,
applymap()se ha integrado enmap(). Puedes realizar la misma tarea conDataFrame.map(). Sin embargo, es importante tener en cuenta que muchas bases de código aún utilizanapplymap().
Comparación de las tres funciones — De un vistazo
import pandas as pd
df = pd.DataFrame({ "A": [1, 2, 3], "B": [4, 5, 6]})
# map: cada valor de Series → valordf["A"].map(lambda x: x * 10) # Series → Series# 0 10# 1 20# 2 30
# apply (Series): similar a mapdf["A"].apply(lambda x: x * 10) # Series → Series (mismo resultado)
# apply (DataFrame, axis=0): por columnasdf.apply(sum) # DataFrame → Series# A 6# B 15
# apply (DataFrame, axis=1): por filasdf.apply(sum, axis=1) # DataFrame → Series# 0 5# 1 7# 2 9
# applymap: cada valor de DataFrame → valordf.applymap(lambda x: x * 10) # DataFrame → DataFrame# A B# 0 10 40# 1 20 50# 2 30 60Rendimiento — operaciones vectoriales primero
apply, map, applymap utilizan bucles de Python internamente. Las operaciones integradas de pandas (operaciones vectoriales) son mucho más rápidas.
import numpy as np
df = pd.DataFrame({"value": range(1_000_000)})
# Lento: apply (bucle de Python)%timeit df["value"].apply(lambda x: x * 2)# ~200ms
# Rápido: operación vectorial (ejecutada en C)%timeit df["value"] * 2# ~2 ms (100 veces más rápido)Reglas: Utilice operaciones vectoriales para aritmética simple, comparaciones y métodos de cadenas. Use apply() únicamente en lógica compleja que no pueda expresarse mediante operaciones vectoriales.
# Mal: ramificación condicional con applydf["label"] = df["value"].apply(lambda x: "high" if x > 500000 else "low")
# Bien: np.where (operación vectorial)df["label"] = np.where(df["value"] > 500000, "high", "low")
# Mal: procesamiento de texto con applydf["upper"] = df["name"].apply(str.upper)
# Bien: accesor str (operación vectorial)df["upper"] = df["name"].str.upper()Patrones prácticos — Transformaciones compuestas
Se utiliza apply cuando las operaciones vectoriales no son posibles:
df = pd.DataFrame({ "name": ["Alice Smith", "Bob Lee", "Carol Park"], "birth": ["1995-03-15", "1988-11-22", "2001-07-08"], "department": ["Sales", "Dev", "HR"]})
def create_employee_id(row): dept_code = row["department"][:2].upper() last_name = row["name"].split()[-1].upper() year = row["birth"][:4] return f"{dept_code}-{last_name}-{year}"
df["emp_id"] = df.apply(create_employee_id, axis=1)print(df["emp_id"])# 0 SA-SMITH-1995# 1 DE-LEE-1988# 2 HR-PARK-2001Tareas que construyen cadenas complejas haciendo referencia a varias columnas: esto es difícil de expresar mediante operaciones vectoriales, por lo que apply(axis=1) es adecuado.
Resumen clave
| Situación | Herramienta |
|---|---|
| Transformar cada valor de una Series | map() o apply() |
| Mapeo de valores con un diccionario | map(dict) |
| Transformar cada celda de un DataFrame | applymap() (o pandas 2.1+ map()) |
| Agregación por columna | apply(func, axis=0) |
| Cálculos complejos por fila | apply(func, axis=1) |
| Aritmética o comparación simple | Operaciones vectoriales (evitar apply) |
Orden para elegir entre las tres funciones: 1) ¿Se puede hacer con operaciones vectoriales? → Operaciones vectoriales. 2) ¿Cada valor de una Series? → map. 3) ¿Cada celda de un DataFrame? → applymap. 4) ¿Lógica compleja por fila/columna? → apply. Recordar este orden permite cuidar tanto el rendimiento como la legibilidad.
Errores frecuentes
| Error | Problema | Solución |
|---|---|---|
df.map(func) (pandas < 2.1) | AttributeError | df.applymap(func) o actualizar pandas |
Devolver un escalar en df.apply(func) | Resultados inesperados | Verificar axis: 0 es columna, 1 es fila |
| Usar apply para aritmética simple | 100 veces más lento | Reemplazar con operaciones vectoriales |
| Claves faltantes en map | Genera NaN | Especificar valor predeterminado con fillna() |