Deja de hacer sopa de variables: una guía para el encadenamiento de métodos en Pandas
1. El problema de la ‘sopa de variables’
Todos hemos pasado por eso. Abres un nuevo Jupyter notebook, cargas un CSV y empiezas a limpiar los datos. Primero eliminas algunos valores faltantes y lo llamas df2. Luego filtras los valores atípicos y lo llamas df_filtered. Para cuando llegas a la etapa de análisis, estás trabajando con df_final_v3_fixed.
A esto lo llamo la “sopa de variables”. Tu script se convierte en un cementerio de DataFrames intermedios, y no puedes saber qué cambió ni cuándo. Cambia una regla de filtrado en medio del notebook, y a menudo tendrás que reiniciar todo el kernel solo para asegurarte de que df4 no use accidentalmente la versión anterior de df3.
A continuación, un ejemplo típico de este estilo procedural:
import pandas as pd
import numpy as np
# Creating a dummy dataset of store sales
data = {
'store_id': [1, 2, 3, 4, 5],
'revenue': [100, 250, np.nan, 400, 150],
'region': ['North', 'South', 'North', 'East', 'West']
}
df = pd.DataFrame(data)
# The Variable Soup approach
df1 = df.dropna(subset=['revenue'])
df1['revenue_scaled'] = df1['revenue'] * 1.1
df2 = df1[df1['revenue_scaled'] > 150]
df_final = df2.sort_values('revenue_scaled', ascending=False)
print(df_final)
# Output shows 2 rows. The logic is scattered across four different variable names.
El código funciona, pero leerlo es una cacería de pistas. ¿Quieres saber por qué se eliminó una fila? Tienes que rastrear hacia atrás a través de df2, df1 y df. También desperdicia memoria, ya que Python mantiene vivas todas esas copias intermedias. ¿Qué tal si tu código se leyera como una receta en su lugar?
2. La intuición: pensar en pipelines
Piensa en tus datos como un auto en una línea de ensamblaje. El chasis comienza en bruto y sin terminar. A medida que avanza por la línea, cada estación añade su pieza — una coloca el motor, otra pinta las puertas, otra instala los asientos.
El encadenamiento de métodos es simplemente poner esas estaciones en orden. No hay que sacar el auto de la línea, estacionarlo en un garaje (df1) y luego arrastrarlo de vuelta para la siguiente parada. El auto simplemente sigue avanzando.
Queremos que nuestro código diga: “Toma estos datos, luego elimina las filas vacías, luego calcula el impuesto, luego filtra los resultados.” La parte difícil de este cambio es dejar atrás el impulso de nombrar cada paso. No necesitas llamarlo “Auto Con Puertas” y “Auto Con Puertas y Ruedas”. Solo necesitas el auto terminado.
3. Tu primera cadena: el poder de los paréntesis
Poner todo en una sola línea puede convertirse rápidamente en un desastre. La solución es simple: paréntesis. Envuelve toda la operación en (), y Python te permite poner cada paso en su propia línea.
También vamos a dejar de usar df['col'] = ... y recurrir a .assign() en su lugar. Esto mantiene los datos fluyendo a través de la cadena sin romperla. Vamos a refactorizar nuestro ejemplo de “Sopa de Variables” en un pipeline limpio:
# The Method Chaining approach
clean_df = (
df
.dropna(subset=['revenue'])
.assign(revenue_scaled = lambda x: x['revenue'] * 1.1)
.query("revenue_scaled > 150")
.sort_values('revenue_scaled', ascending=False)
)
print(clean_df)
# The result is exactly the same, but the logic is a single, readable block.
El encadenamiento de métodos (el estilo anterior) brilla cuando:
- Cada paso es una transformación pura (devuelve un DataFrame nuevo, sin efectos secundarios).
- Quieres que todo el pipeline se lea como una receta declarativa: “toma los datos, luego limpia, luego enriquece, luego filtra.”
- Quieres evitar contaminar el espacio de nombres del notebook con
df1,df2,df3, … lo que desperdicia memoria y hace poco claro qué versión es la “actual.”
Las variables intermedias (df1 = df.dropna(...), df2 = df1[...]) brillan cuando:
- Necesitas inspeccionar o depurar un estado intermedio específico en un depurador — puedes poner un punto de interrupción en la línea donde se define
df2y examinarlo directamente. En una cadena, no puedes fácilmente “saltar al medio” de una sola expresión. - Necesitas reutilizar un resultado intermedio en dos ramas posteriores diferentes (p. ej.,
df_cleanalimenta tanto un gráfico como un modelo). El encadenamiento te obliga a duplicar los pasos iniciales o a extraer una variable de todos modos. - Tus pasos tienen efectos secundarios (mutar un DataFrame in situ) que hacen ambiguo el orden de operaciones en una cadena.
Regla práctica: Usa encadenamiento para pipelines lineales y principalmente de lectura. Extrae una variable con nombre en el momento en que necesites ramificar, reutilizar o depurar profundamente un estado intermedio. El problema de la “sopa de variables” no se debe a usar variables intermedias — se debe a usar variables intermedias sin nombre, sin documentación y numeradas en serie. Un df_clean o sales_long bien nombrado es perfectamente aceptable; el enemigo es df1 hasta df7.
¿Qué está pasando realmente aquí? El punto al inicio de cada línea señala lo que viene a continuación. .query() filtra los datos de manera más limpia que los voluminosos corchetes. Nota el lambda x dentro de .assign() — una pequeña función que dice “toma el dataframe tal como existe en este paso y usa su columna de ingresos.” Vale la pena destacar: esto importa porque la columna de ingresos podría haber cambiado en un paso anterior.
4. El método .pipe(): cuando las funciones integradas no son suficientes
A veces Pandas no tiene un botón integrado para lo que necesitas. Tal vez tengas una forma específica de calcular impuestos regionales, o una rutina compleja de limpieza de texto. Normalmente eso rompe tu cadena — te detendrías, ejecutarías tu función y luego empezarías de nuevo.
Ahí es donde entra .pipe(). Piensa en ello como una estación personalizada en la línea de ensamblaje. Introduces el dataframe en una función y mantienes la cadena en movimiento.
Esto confunde a los principiantes porque parece metaprogramación. Pero la idea es sencilla: pasar el resultado del paso anterior a tu herramienta personalizada.
def apply_tax(dataframe, tax_rate):
# A custom function that adds a tax column
dataframe['total_cost'] = dataframe['revenue_scaled'] * (1 + tax_rate)
return dataframe
final_chain = (
df
.dropna(subset=['revenue'])
.assign(revenue_scaled = lambda x: x['revenue'] * 1.1)
.pipe(apply_tax, tax_rate=0.05) # Custom step integrated seamlessly
.query("total_cost > 160")
)
print(final_chain)
# We added a custom calculation without ever creating a 'df_temp' variable.
5. Depurando la cadena: ‘¿Pero cómo veo qué está pasando?’
El mayor temor de la gente respecto a las cadenas es que son difíciles de depurar. Si el resultado se ve incorrecto, ¿cómo saber qué paso lo rompió?
No tienes que perder la visibilidad. El truco más fácil es el método de “comentar” (comment out) — ya que cada paso está en su propia línea, comenta los pasos posteriores para ver el estado de los datos en cualquier punto intermedio de la cadena.
Un enfoque más limpio es una función de registro (logging). Aquí se muestra lo que ocurre cuando usas una función auxiliar para inspeccionar los datos a mitad de la cadena:
def log_shape(dataframe, label="Step"):
print(f"{label}: {dataframe.shape[0]} rows remaining")
return dataframe
debugged_df = (
df
.pipe(log_shape, "Start")
.dropna(subset=['revenue'])
.pipe(log_shape, "After Dropna")
.assign(revenue_scaled = lambda x: x['revenue'] * 1.1)
.query("revenue_scaled > 500") # This will likely filter everything out
.pipe(log_shape, "After Query")
)
# The output will show:
# Start: 5 rows remaining
# After Dropna: 4 rows remaining
# After Query: 0 rows remaining
# Now we know exactly where our data disappeared!
6. Resumen: del caos a la maestría
Tu yo del futuro te lo agradecerá por cambiar de la Sopa de Variables al encadenamiento de métodos. Abre un notebook dentro de seis meses y no estarás rastreando df1 hasta df10. Simplemente leerás la receta.
Aquí tienes tu lista de verificación para un Pandas más limpio:
- Usa paréntesis: Envuelve el código en
()para permitir cadenas de múltiples líneas. - Usa .assign(): Evita
df['col'] = ...para mantener el flujo de los datos. - Usa .query(): Más legible que el indexado booleano.
- Usa .pipe(): Para lógica personalizada y funciones auxiliares de depuración.
- Reconoce cuándo detenerte: Una cadena de 20 líneas se puede dividir en dos fragmentos más pequeños y bien nombrados.
Aquí tienes una última cadena que integra todo:
# The final, clean, professional pipeline
master_df = (
df
.rename(columns=str.lower)
.dropna(subset=['revenue'])
.pipe(log_shape, "Initial Clean")
.assign(
is_high_value = lambda x: x['revenue'] > 200,
tax_amount = lambda x: x['revenue'] * 0.08
)
.query("region != 'West'")
.sort_values('revenue', ascending=False)
)
print(master_df)
# The effect is clear: one readable block that transforms raw data into insights.
Abre tu notebook más desordenado. Convierte una de esas Sopas de Variables en un pipeline limpio y elegante. Feliz limpieza.
Comprueba tu comprensión
Las preguntas a continuación pasan de un simple recuerdo a un diseño abierto, siguiendo aproximadamente la Taxonomía de Bloom.
Recordar
¿Qué permite hacer .pipe() que no es posible con los métodos integrados de Pandas como .query() o .assign()?
Comprender
Con tus propias palabras, explica por qué el artículo usa lambda x: x['revenue'] * 1.1 dentro de .assign() en lugar de hacer referencia directamente a la columna original df['revenue'].
Aplicar
Usando el patrón de depuración log_shape del artículo, si insertaras una llamada .pipe(log_shape, "After Filter") justo después de un paso .query("revenue > 1000") en el conjunto de datos post-dropna de 4 filas (ingresos 100, 250, 400, 150), ¿qué recuento de filas imprimiría?
Analizar
El artículo dice que la “Sopa de variables” hace que sea difícil saber por qué se eliminó una fila, lo que requiere que “rastrees a través de df2, df1 y df”. Recorre paso a paso cómo el patrón .pipe(log_shape, ...) resuelve este mismo problema de depuración sin reintroducir el desorden de variables intermedias que la cadena fue diseñada para evitar.
Evaluar El elemento 5 de la lista de verificación del artículo dice que “si tu cadena tiene 20 líneas de largo, está bien dividirla en dos fragmentos más pequeños y bien nombrados”. Critica la falta de una regla más clara aquí: ¿qué señal específica (no solo el recuento de líneas) te indicaría que una cadena se ha vuelto difícil de razonar y debería dividirse, frente a una que es larga pero aún perfectamente clara?
Crear
Diseña un refactor de encadenamiento de métodos para un script de “Sopa de variables” que: cargue un dataframe, elimine las filas donde email es nulo, añada una columna domain extraída del correo electrónico, filtre solo los dominios .com, y ordene por fecha de registro. Escribe la cadena usando .dropna(), .assign(), y .query()/.sort_values(), siguiendo el estilo del artículo.
Artículos relacionados
- ¿Por qué mi código de Pandas es tan lento? Una guía práctica — El encadenamiento de métodos se combina naturalmente con las operaciones vectorizadas; este artículo cubre el aspecto del rendimiento al elegir el idiom adecuado de Pandas.
- DuckDB para científicos de datos: análisis SQL sin el warehouse — Si tus cadenas de Pandas se vuelven demasiado largas, el enfoque SQL-on-DataFrames de DuckDB puede expresar a menudo el mismo pipeline de manera más concisa.
Referencias y lecturas adicionales
- Documentación de Pandas:
.pipe()— encadenamiento de métodos con funciones definidas por el usuario - Documentación de Pandas:
.assign()— asignar nuevas columnas a un DataFrame
Aplica lo que aprendiste
Resumen. Estás implementando un pipeline de preparación de características en Pandas como microservicio. Un compañero de equipo escribió el pipeline de métodos encadenados a continuación siguiendo el estilo del artículo — paréntesis, .assign(), .pipe(), .query() — pero el equipo de modelos reporta dos síntomas: (1) el pipeline elimina una fila que claramente tiene ingresos válidos, y (2) el total_cost resultante para la fila de ingresos de 150 es ~475 en lugar del ~178 esperado. Usa la técnica de depuración de paso del artículo .pipe(log_shape, …) para localizar dónde se rompe la cadena, luego corrige el error y escribe un breve informe de incidente.
Pipeline con errores.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'store_id': [1, 2, 3, 4, 5],
'revenue': [100, 250, np.nan, 400, 150],
'region': ['North', 'South', 'North', 'East', 'West']
})
def compute_tax(df, rate=0.08):
df['tax_amount'] = df['revenue_scaled'] * rate
df['total_cost'] = df['revenue_scaled'] + df['tax_amount']
return df
features = (
df
.dropna(subset=['revenue'])
.reset_index(drop=True)
.assign(revenue_scaled=df['revenue'] * 1.1) # ← planted bug
.pipe(compute_tax, rate=0.08)
.query("total_cost > 160")
)
# Expected: 3 rows survive (revenues 250, 400, 150 → total_cost ≈ 297, 475, 178)
# Actual: 2 rows; the revenue-400 row vanished, the revenue-150 row has total_cost ≈ 475
Entregable. El código del pipeline corregido más un informe de incidente de 100–150 palabras que nombre la causa raíz, la solución y la técnica de depuración que usaste para aislarlo.
Rúbrica (todos los elementos deben pasar):
- Encuentra el error. Identifica que
.assign(revenue_scaled=df['revenue'] * 1.1)hace referencia aldfa nivel de módulo original en lugar del DataFrame tal como existe en ese punto de la cadena. Después de.dropna()+.reset_index(drop=True), el índice[0,1,2,3,4]deldforiginal ya no coincide con el índice[0,1,2,3]del DataFrame actual; pandas alinea por índice, inyectando el NaN original del índice 2 en el índice 2 actual (la fila de ingresos de 400) y colocando 440 donde debería ir 165. - Aplica la solución. Reemplaza
df['revenue'] * 1.1conlambda x: x['revenue'] * 1.1, para que.assign()lea desde el estado actual de la cadena — el patrón exacto que prescribe el artículo. - Usa la técnica de depuración del artículo. Inserta
.pipe(log_shape, …)(o un paso equivalente que imprima la forma y devuelva el DataFrame) antes y después de.assign()para mostrar que 4 filas entran perorevenue_scaledcontiene un NaN, localizando el paso sin introducir variables intermedias. - El informe es claro y conciso (100–150 palabras). Nombra la causa raíz (desalineación de índices por hacer referencia al DataFrame original después de
reset_index), la solución (lambda x:) y el método de depuración utilizado.
Esta traducción fue generada automáticamente y puede contener errores. Si el idioma inglés es tu preferencia, puedes leer el artículo original en inglés .
Artículos relacionados
- Ingeniería en Python En revisión
¿Por qué falla mi pipeline de datos? Una guía amigable para el perfilado de memoria en Python
Aprende a diagnosticar y solucionar los bloqueos por MemoryError en Python en tus pipelines de datos usando memory_profiler, Fil y chunking para gestionar grandes volúmenes de datos con memoria RAM limitada.
- Ingeniería en Python En revisión
¿Por qué mi código de Pandas es tan lento? Una guía práctica para la vectorización
Aprende por qué los ciclos fila por fila hacen que Pandas sea dolorosamente lento y cómo las operaciones vectorizadas, np.select y groupby ofrecen una aceleración de 10,000x con cambios mínimos en el código.
- Ingeniería en Python En revisión
Generadores de Python: Cómo procesar conjuntos de datos masivos sin colapsar tu computadora
Aprende cómo los generadores de Python y la palabra clave yield te permiten procesar en flujo conjuntos de datos masivos con un consumo de memoria constante, evitando el MemoryError sin tener que cargar todo en la RAM.
- Aprendizaje Automático En revisión
¿Qué es la validación cruzada y cómo evitar hacerla mal?
Aprende a hacer validación cruzada de la manera correcta: evita el sobreajuste, previene la fuga de datos con pipelines, lee la desviación estándar y maneja las series de tiempo correctamente.
¿Buscas otra cosa?
Busca en todos los artículos por título, resumen o tema.