Escriba Pandas como un profesional con canalizaciones de encadenamiento de métodos

Lo cerré recientemente e inmediatamente.

No porque estuviera mal. El código funcionó. Los números se comprobaron.

Pero no tenía idea de lo que estaba pasando.

Había variables por todas partes. df1, df2, final_df, final_final. Cada paso tenía sentido de forma aislada, pero en conjunto me sentí como si estuviera trazando un laberinto. Tuve que leer línea por línea sólo para entender lo que ya había hecho.

Y lo curioso es que así es como la mayoría de nosotros comenzamos con los pandas.

Aprendes algunas operaciones. Usted filtra aquí, crea una columna allí, agrupa y agrega. Hace el trabajo. Pero con el tiempo, su código comienza a resultar más difícil de confiar, más difícil de revisar y definitivamente más difícil de compartir.

Ese fue el punto en el que me di cuenta de algo.

La brecha entre los usuarios principiantes e intermedios de Pandas no se trata de conocer más funciones. Se trata de cómo estructuras tus transformaciones.

Hay un patrón que cambia todo silenciosamente una vez que lo ves. Su código se vuelve más fácil de leer. Más fácil de depurar. Más fácil de construir.

Se llama encadenamiento de métodos.

En este artículo, explicaré cómo comencé a usar el encadenamiento de métodos correctamente, junto con asignar() y pipe(), y cómo cambió la forma en que escribo el código Pandas. Si alguna vez ha sentido que sus cuadernos se ensucian a medida que crecen, esto probablemente le resulte útil.

El cambio: lo que los usuarios intermedios de Pandas hacen de manera diferente

Al principio, pensé que mejorar en Pandas significaba aprender más funciones.

Más trucos. Más sintaxis. Más formas de manipular datos.

Pero cuanto más construía, más notaba algo. Las personas que eran realmente buenas con Pandas no necesariamente usaban más funciones que yo. Su código simplemente parecía… diferente.

Limpiador. Más intencional. Más fácil de seguir.

En lugar de escribir código paso a paso con muchas variables intermedias, escribieron transformaciones que fluían entre sí. Podrías leer su código de arriba a abajo y comprender exactamente qué estaba sucediendo con los datos en cada etapa.

Casi me sentí como si estuviera leyendo un cuento.

Fue entonces cuando hice clic para mí. La verdadera actualización no se trata de lo que usas. Se trata de cómo lo estructuras.

En lugar de pensar:

"¿Qué hago junto a este DataFrame?"

Empiezas a pensar:

“¿Qué transformación viene después?”

Ese pequeño cambio lo cambia todo.

Y aquí es donde entra en juego el encadenamiento de métodos.

El encadenamiento de métodos no es sólo una forma más limpia de escribir Pandas. Es una forma diferente de pensar en trabajar con datos. Cada paso toma su DataFrame, lo transforma y lo transmite. Sin variables innecesarias. Nada de saltar.

Simplemente un flujo claro y legible desde los datos sin procesar hasta el resultado final.

En la siguiente sección, le mostraré exactamente cómo se ve esto usando un ejemplo real.

El “antes”: cómo la mayoría de nosotros escribimos pandas

Para concretar esto, digamos que queremos responder una pregunta simple:

¿Qué categorías de productos generan más ingresos cada mes?

Saqué un pequeño conjunto de datos de ventas con detalles de pedidos, categorías de productos, precios y fechas. Nada especial.

importar pandas como pd df = pd.read_csv("sales.csv") print(df.head())

Producción

order_id customer_id categoría de producto cantidad precio fecha_pedido 0 1001 C001 Electrónica portátil 1 1200 2023-01-05 1 1002 C002 Auriculares Electrónica 2 150 2023-01-07 2 1003 C003 Zapatillas de deporte Moda 1 80 2023-01-10 3 1004 C001 Camiseta Moda 3 25 2023-01-12 4 1005 C004 Blender Home 1 60 2023-01-15

Ahora bien, así es como habría escrito esto no hace mucho:

# Crear una nueva columna para ingresos df["revenue"] = df["quantity"] * df["price"] # Filtrar para pedidos a partir de 2023 df_filtered = df[df["order_date"] >= "2023-01-01"] # Convertir order_date a datetime y extraer mes df_filtered["month"] = pd.to_datetime(df_filtered["order_date"]).dt.to_period("M") # Agrupar por categoría y mes, luego sumar los ingresos agrupados = df_filtered.groupby(["category", "month"])["revenue"].sum() # Convertir la serie nuevamente a resultado de DataFrame = grouped.reset_index() # Ordenar por resultado descendente de ingresos = result.sort_values(by="revenue", ascendente=Falso) imprimir(resultado)

Esto funciona. Obtienes tu respuesta.

categoría ingresos mensuales 1 Electrónica 2023-02 2050 2 Electrónica 2023-03 1590 0 Electrónica 2023-01 1500 8 Hogar 2023-03 225 6 Hogar 2023-01 210 5 Moda 2023-03 205 7 Hogar 2023-02 180 4 Moda 2023-02 165 3 Moda 2023-01 155

Pero hay algunos problemas que comienzan a aparecer a medida que crece su análisis.

En primer lugar, la corriente es difícil de seguir. Debe realizar un seguimiento de df, df_filtered, grouped y result. Cada variable representa un estado ligeramente diferente de los datos.

En segundo lugar, la lógica está dispersa. La transformación se está produciendo paso a paso, pero no de una manera que parezca conectada. Estás uniendo cosas mentalmente mientras lees.

En tercer lugar, es más difícil de reutilizar o probar. Si desea modificar una parte de la lógica, ahora debe rastrear dónde se modifica todo.
Este es el tipo de código que funciona bien hoy… pero se vuelve doloroso cuando vuelves a usarlo una semana después.

Ahora compare eso con cómo se ve la misma lógica cuando comienza a pensar en transformaciones en lugar de pasos.

El “después”: cuando todo hace clic

Ahora resolvamos exactamente el mismo problema nuevamente.

Mismo conjunto de datos. Mismo objetivo.

¿Qué categorías de productos generan más ingresos cada mes?

Así es como se ve cuando empiezas a pensar en transformaciones:

resultado = ( pd.read_csv("sales.csv") # Comenzar con datos sin procesar .assign( # Crear columna de ingresos ingresos=lambda df: df["cantidad"] * df["precio"], # Convertir fecha_pedido a fechahora fecha_pedido=lambda df: pd.to_datetime(df["fecha_pedido"]), # Extraer mes de fecha_pedido mes=lambda df: df["order_date"].dt.to_period("M") ) # Filtrar para pedidos a partir de 2023 en adelante .loc[lambda df: df["order_date"] >= "2023-01-01"] # Agrupar por categoría y mes, luego sumar los ingresos .groupby(["category", "month"], as_index=False)["revenue"] .sum() # Ordenar por ingresos descendente .sort_values(by="ingresos", ascendente=False) ) imprimir(resultado)

Misma salida. Sensación completamente diferente.

categoría ingresos mensuales 1 Electrónica 2023-02 2050 2 Electrónica 2023-03 1590 0 Electrónica 2023-01 1500 8 Hogar 2023-03 225 6 Hogar 2023-01 210 5 Moda 2023-03 205 7 Hogar 2023-02 180 4 Moda 2023-02 165 3 Moda 2023-01 155

Lo primero que notas es que todo fluye. No hay que saltar entre variables ni intentar recordar qué significa df_filtered o grouped.

Cada paso se basa en el anterior.

Comienzas con los datos sin procesar, luego:

crear ingresos convertir fechas extraer el mes filtrar grupo ordenar agregado

Todo en una tubería continua.

Puede leerlo de arriba a abajo y comprender exactamente qué sucede con los datos en cada etapa.

Esa es la parte que más me sorprendió.

No es sólo un código más corto. Es un código más claro.

Y una vez que te acostumbras a esto, volver a lo antiguo te resulta… incómodo.

Aquí suceden un par de cosas que hacen que esto funcione tan bien.

No estamos simplemente encadenando métodos. Estamos utilizando algunas herramientas específicas que hacen que el encadenamiento sea realmente práctico.

En la siguiente sección, los desglosamos.

Rompiendo el patrón

Cuando vi por primera vez este estilo de código Pandas, me pareció un poco intimidante.

Todo estaba encadenado. Sin variables intermedias. Suceden muchas cosas en un espacio pequeño.

Pero una vez que desaceleré y lo rompí en pedazos, empezó a tener sentido.

En realidad, aquí solo hay tres ideas que abarcan todo:

método de encadenamiento asignar() tubería()

Repasémoslos uno por uno.

Encadenamiento de métodos (La Fundación)

En esencia, el encadenamiento de métodos es simple. Cada paso toma un DataFrame, aplica una transformación y devuelve un nuevo DataFrame. Ese nuevo DataFrame pasa inmediatamente al siguiente paso.

Entonces en lugar de esto:

df = paso1(df) df = paso2(df) df = paso3(df)

Tu haces esto:

df = paso1(df).paso2().paso3()

Eso es literalmente todo.

Pero el impacto es mayor de lo que parece.

Te obliga a pensar en términos de flujo. Cada línea se convierte en una transformación. Ya no estás saltando ni almacenando estados temporales. Simplemente estás avanzando.

Es por eso que el código comienza a parecer más legible. Puede seguir la transformación de principio a fin sin tener que tener varias versiones de los datos en su cabeza.

asignar(): mantener todo en el flujo

Este es el que realmente me abrió el encadenamiento.

Antes de esto, cada vez que quería crear una nueva columna, interrumpía el flujo:

df["ingresos"] = df["cantidad"] * df["precio"]

Eso funciona, pero interrumpe el proceso.

asignar() te permite hacer lo mismo sin romper la cadena:

.assign(ingresos=lambda df: df["cantidad"] * df["precio"])

Al principio, la parte lambda df: se sentía rara.

Pero la idea es simple. Estás diciendo:

"Tome el DataFrame actual y utilícelo para definir esta nueva columna".

El beneficio clave es que todo permanece en un solo lugar. Puede ver dónde se crea la columna y cómo se utiliza, todo dentro del mismo flujo.

También fomenta un estilo más limpio donde las transformaciones se agrupan de forma lógica en lugar de estar dispersas por el cuaderno.

pipe(): donde las cosas empiezan a sentirse poderosas

pipe() es el que ignoré al principio.

Pensé: "Ya puedo encadenar métodos, ¿por qué necesito esto?"
Entonces me encontré con un problema.

Algunas transformaciones son demasiado complejas para encajar perfectamente en una cadena.

Tú tampoco:
escribir lógica en línea desordenada
o romper la cadena por completo

Ahí es donde entra en juego pipe().

Le permite pasar su DataFrame a una función personalizada sin interrumpir el flujo.

Por ejemplo:

def filter_high_value_orders(df): return df[df["ingresos"] > 500] df = ( pd.read_csv("sales.csv") .assign(ingresos=lambda df: df["cantidad"] * df["precio"]) .pipe(filter_high_value_orders) )

Ahora tu lógica es más limpia, reutilizable y más fácil de probar

Este es el punto donde las cosas empezaron a parecer diferentes para mí.

En lugar de escribir guiones largos, estaba empezando a crear pequeños pasos de transformación reutilizables.

Y ahí fue cuando hizo clic.

No se trata sólo de escribir código Pandas más limpio. Se trata de escribir código que se amplíe a medida que el análisis se vuelve más complejo.

En la siguiente sección, quiero mostrar cómo esto cambia por completo la forma en que piensa acerca de trabajar con datos.

Pensando en tuberías (la verdadera actualización)

Hasta este punto, podría parecer que acabamos de hacer que el código se viera mejor.

Pero aquí está sucediendo algo más profundo.

Cuando comienza a utilizar el encadenamiento de métodos de manera constante, la forma en que piensa acerca de trabajar con datos comienza a cambiar.

Antes mi enfoque era muy paso a paso.

Miraría un DataFrame y pensaría:

“¿Qué hago ahora?”

Filtrarlo. Modifícalo. Guárdalo. Siga adelante.

Cada paso se sintió un poco desconectado del anterior.

Pero con el encadenamiento de métodos, esa pregunta cambia.

Ahora se convierte en:

“¿Qué transformación viene después?”

Ese cambio es pequeño, pero cambia la forma en que estructura todo.

Dejas de pensar en términos de pasos aislados y empiezas a pensar en términos de un flujo. Un oleoducto. Los datos entran, se transforman etapa por etapa y producen un resultado.

Y el código refleja eso.

Cada línea no se limita a hacer algo. Es parte de una secuencia. Una progresión clara desde los datos sin procesar hasta el conocimiento.

Esto también hace que sea más fácil razonar sobre su código.

Si algo se rompe, no es necesario escanear todo el cuaderno. Puede mirar la tubería y preguntar:

¿Qué transformación podría estar mal? ¿Dónde cambiaron los datos de forma inesperada?

Resulta más fácil de depurar porque la lógica es lineal y visible.

Otra cosa que noté es que naturalmente te empuja hacia mejores hábitos.

Empiezas a escribir transformaciones más pequeñas. Empiezas a nombrar las cosas con más claridad. Empiezas a pensar en reutilizar sin siquiera intentarlo.

Y ahí es donde empieza a sentirse menos como "solo Pandas" y más como crear flujos de trabajo de datos reales.

En este punto, no sólo estás analizando datos.

Estás diseñando cómo fluyen los datos.

Refactorización del mundo real: de lo desordenado a lo limpio

Déjame mostrarte cómo se desarrolla esto realmente.

En lugar de saltar directamente del código desordenado a una cadena perfecta, quiero explicar cómo refactorizaría esto paso a paso. De todos modos, así suele suceder en la vida real.

Paso 1: El punto de partida (desordenado pero funciona)

df = pd.read_csv("sales.csv") # Cargar conjunto de datos # Crear columna de ingresos df["revenue"] = df["quantity"] * df["price"] # Filtrar pedidos desde 2023 en adelante df_filtered = df[df["order_date"] >= "2023-01-01"] # Convertir order_date y extraer mes df_filtered["month"] = pd.to_datetime(df_filtered["order_date"]).dt.to_period("M") # Agrupar por categoría y mes, luego sumar los ingresos agrupados = df_filtered.groupby(["category", "month"])["revenue"].sum() # Convertir a resultado de DataFrame = grouped.reset_index() # Ordenar resultados resultado = resultado.sort_values(por="ingresos", ascendente=Falso)

No hay nada malo aquí. Así es como empezamos la mayoría de nosotros.

Pero ya podemos ver:

demasiadas variables intermedias las transformaciones están dispersas y es más difícil de seguir a medida que crece

Paso 2: Reducir las variables innecesarias

Primero, elimine las variables que realmente no sean necesarias.

df = pd.read_csv("sales.csv") # Cargar conjunto de datos # Crear nuevas columnas por adelantado df["revenue"] = df["cantidad"] * df["price"] df["month"] = pd.to_datetime(df["order_date"]).dt.to_period("M") result = ( # Filtrar filas relevantes df[df["order_date"] >= "2023-01-01"] # Ingresos agregados por categoría y mes .groupby(["category", "month"])["revenue"] .sum() # Convertir a DataFrame .reset_index() # Ordenar resultados .sort_values(by="revenue", ascending=False) )

Ya mejor. Hay menos piezas móviles y está empezando a aparecer algo de flujo.

Paso 3: Introducir el encadenamiento básico

Ahora empezamos a encadenar de forma más deliberada.

result = ( pd.read_csv("sales.csv") # Comenzar con datos sin procesar .assign( # Crear columna de ingresos ingresos=lambda df: df["cantidad"] * df["price"], # Extraer mes de order_date Month=lambda df: pd.to_datetime(df["order_date"]).dt.to_period("M") ) # Filtrar por pedidos recientes .loc[lambda df: df["order_date"] >= "2023-01-01"] # Agrupar y agregar .groupby(["category", "month"])["revenue"] .sum() # Convertir a DataFrame .reset_index() # Ordenar resultados .sort_values(by="revenue", ascending=False) )

En este punto, el flujo es claro, las transformaciones se agrupan de manera lógica y ya no estamos saltando entre variables.

Paso 4: límpielo más

Pequeños ajustes marcan una gran diferencia.

resultado = ( pd.read_csv("sales.csv") # Cargar datos .assign( # Crear ingresos ingresos=lambda df: df["cantidad"] * df["precio"], # Asegúrese de que la fecha_pedido sea la fecha y hora fecha_pedido=lambda df: pd.to_datetime(df["fecha_pedido"]), # Extraiga el mes de la fecha_pedido mes=lambda df: df["order_date"].dt.to_period("M") ) # Filtrar el rango de tiempo relevante .loc[lambda df: df["order_date"] >= "2023-01-01"] # Ingresos agregados .groupby(["category", "month"], as_index=False)["revenue"] .sum() # Ordenar resultados .sort_values(by="revenue", ascendente = Falso) )

Ahora no hay conversiones redundantes, hay agrupaciones más limpias y una estructura más consistente.

Paso 5: Cuando pipe() se vuelve útil

Digamos que la lógica crece. Quizás sólo nos importen las disputas por los altos ingresos.

En lugar de introducir esa lógica en la cadena, la extraemos:

def filter_high_revenue(df): # Mantener solo las filas donde los ingresos superan el umbral return df[df["revenue"] > 500]

Ahora lo conectamos a la tubería:

resultado = ( pd.read_csv("sales.csv") # Cargar datos .assign( # Crear ingresos ingresos=lambda df: df["cantidad"] * df["precio"], # Convertir y extraer características de tiempo order_date=lambda df: pd.to_datetime(df["order_date"]), mes=lambda df: df["order_date"].dt.to_period("M") ) # Aplicar transformación personalizada .pipe(filter_high_revenue) # Filtrar por fecha .loc[lambda df: df["order_date"] >= "2023-01-01"] # Agregar resultados .groupby(["category", "month"], as_index=False)["revenue"] .sum() # Ordenar salida .sort_values(by="revenue", ascendente = Falso) )

Aquí es donde empieza a sentirse diferente. Tu código ya no es sólo un script. Ahora es una secuencia de transformaciones reutilizables.

Lo que me gusta de este proceso es que no es necesario pasar directamente a la versión final.

Puedes evolucionar tu código gradualmente.

Empieza desordenado. Reducir variables. Introducir encadenamiento. Extraiga la lógica cuando sea necesario.

Así es como este patrón realmente se mantiene.

A continuación, hablemos de algunos errores que cometí mientras aprendía esto para que no te encuentres con los mismos problemas.

Errores comunes (yo cometí la mayoría de estos)

Cuando comencé a usar el encadenamiento de métodos, definitivamente me excedí.
Todo se sentía más limpio, así que traté de forzar todo en una cadena. Eso llevó a algún… código cuestionable.

Aquí hay algunos errores que encontré para que usted no tenga que hacerlo.

1. Encadenarlo todo

En algún momento, pensé que cadenas más largas = mejor código.
No es cierto.

# Esto se vuelve difícil de leer muy rápidamente df = ( df .assign(…) .loc[…] .groupby(…) .agg(…) .reset_index() .rename(…) .sort_values(…) .query(…) )

Sí, técnicamente está limpio. Pero ahora está haciendo demasiado en un solo lugar.

Arreglar:

Rompe tu cadena cuando empiece a sentirse densa. Agrupe las transformaciones relacionadas. Divida pasos lógicamente diferentes. Piense primero en la legibilidad, no en la inteligencia.

2. Forzar la lógica en una sola línea

Solía ​​meter lógica compleja en asignar() o loc() sólo para mantener la cadena en marcha.

Eso suele empeorar las cosas.

.assign( ingresos_flag=lambda df: np.where( (df["cantidad"] * df["precio"] > 500) & (df["categoría"] == "Electrónica"), "Alto", "Bajo" ) )

Esto funciona, pero no es muy legible.

Arreglar:

Si la lógica es compleja, extráigala.

def add_revenue_flag(df): df["revenue_flag"] = np.where( (df["cantidad"] * df["price"] > 500) & (df["category"] == "Electrónica"), "Alto", "Bajo" ) return df df = df.pipe(add_revenue_flag)

Limpiador. Más fácil de probar. Más fácil de reutilizar.

3. Ignorar pipe() durante demasiado tiempo

Al principio evité pipe() porque me parecía innecesario. Pero sin él, tocas un techo.

Tú tampoco:
rompe tu cadena
o escribir lógica en línea desordenada

Arreglar:

Utilice pipe() tan pronto como su lógica deje de ser simple. Es lo que convierte su código de un script en algo modular.

4. Perder legibilidad con nombres deficientes

Cuando empiezas a usar funciones personalizadas con pipe(), los nombres son muy importantes.

Malo:
transformación def(df): …

Mejor:
def filtro_altos_ingresos(df): …

Ahora su canalización se lee como una historia:
.pipe(filtro_altos_ingresos)

Ese pequeño cambio hace una gran diferencia.

5. Pensar que se trata de un código más corto

Esto me tomó un tiempo darme cuenta. El encadenamiento de métodos no consiste en escribir menos líneas. Se trata de escribir código que sea más fácil de leer, razonar y volver a consultar más tarde.

A veces la versión encadenada es más larga. Eso está bien. Si es más claro, mejor.

Resumamos esto y vinculémoslo a la idea "intermedia".

Conclusión: subir de nivel tu juego Pandas

Si ha seguido adelante, habrá visto un pequeño cambio con un gran impacto.

Al pensar en transformaciones en lugar de pasos, utilizando el encadenamiento de métodos, asignar() y pipe(), su código deja de ser solo una colección de líneas y se convierte en un flujo claro y legible.

Esto es lo que cambia cuando internalizas este patrón:

Puede leer su código de arriba a abajo sin perderse. Puedes reutilizar las transformaciones fácilmente, haciendo que tus cuadernos sean más modulares. Puede depurar y probar sin rastrear docenas de variables intermedias. Empiezas a pensar en tuberías, no solo en pasos.

Esto es exactamente lo que separa a un principiante de un usuario intermedio de Pandas.

Ya no estás simplemente "haciendo que funcione". Está diseñando su análisis de una manera que se pueda escalar, sea mantenible y se vea bien para cualquiera que lo lea, incluso para usted en el futuro.

Pruébelo usted mismo

Elija un cuaderno desordenado en el que haya estado trabajando y refactorice solo una parte utilizando el encadenamiento de métodos.

Comience con asignar() para columnas nuevas Utilice loc[]para filtrar Introducir pipe() para cualquier lógica personalizada

Se sorprenderá de lo mucho más claro que se vuelve su cuaderno, casi de inmediato.

Eso es todo. Acabas de desbloquear Pandas intermedios.

¿Tu próximo paso? Continúe practicando, cree sus propios canales y observe cómo su pensamiento sobre los datos se transforma junto con su código.