Reshape: melt y pivot

python
pandas
eda
Cómo cambiar entre formato wide y long en pandas. melt() para pasar a long, pivot() y pivot_table() para volver a wide, y por qué long format es casi siempre lo que quieres antes de un análisis o un gráfico.

Wide y long: la distinción que importa

Los mismos datos pueden representarse de dos formas:

Wide (ancho, una columna por variable medida):

region   2022   2023   2024
Norte    1500   1700   1850
Sur      2100   2200   2400

Long (largo, una fila por observación):

region   año   ventas
Norte    2022  1500
Norte    2023  1700
Norte    2024  1850
Sur      2022  2100
Sur      2023  2200
Sur      2024  2400

Los mismos datos, dos formatos. La diferencia es dónde está la información:

  • En wide, los años son columnas. Cómodo para mirar como una tabla.
  • En long, los años son una variable más (año es una columna).

Regla práctica: para análisis y visualización, long format es casi siempre lo que quieres. matplotlib, seaborn, plotly esperan datos en long format. Los groupby funcionan limpiamente sobre long. Si tus datos vienen en wide, normalmente lo primero es pasarlos a long.

Para presentación final (reports, tablas para humanos), wide es más legible.

Esto es exactamente la distinción que tidyr llama pivot_longer (wide → long) y pivot_wider (long → wide).

melt(): de wide a long

melt() es el equivalente de pivot_longer de tidyr.

import pandas as pd

wide = pd.DataFrame({
    "region": ["Norte", "Sur"],
    "2022": [1500, 2100],
    "2023": [1700, 2200],
    "2024": [1850, 2400],
})

long = wide.melt(
    id_vars="region",
    var_name="año",
    value_name="ventas",
)
#   region   año  ventas
# 0  Norte  2022    1500
# 1    Sur  2022    2100
# 2  Norte  2023    1700
# 3    Sur  2023    2200
# 4  Norte  2024    1850
# 5    Sur  2024    2400

Argumentos clave:

  • id_vars: columnas que se mantienen como están (no se pivotan). Aquí, region no es una métrica, es un identificador.
  • value_vars: columnas que se colapsan. Si lo omites, son todas las que no están en id_vars.
  • var_name: nombre de la nueva columna que contendrá los nombres de las columnas pivotadas.
  • value_name: nombre de la nueva columna que contendrá los valores.

Versión más explícita:

wide.melt(
    id_vars=["region"],
    value_vars=["2022", "2023", "2024"],
    var_name="año",
    value_name="ventas",
)

Útil cuando solo quieres pivotar algunas columnas y dejar otras como id_vars o ignoradas.

pivot(): de long a wide

pivot() es el equivalente de pivot_wider de tidyr.

wide_otra_vez = long.pivot(
    index="region",
    columns="año",
    values="ventas",
)
# año     2022  2023  2024
# region
# Norte   1500  1700  1850
# Sur     2100  2200  2400

Argumentos:

  • index: qué columna(s) forman las filas del wide.
  • columns: qué columna se expande en columnas.
  • values: qué columna rellena las celdas.

Resultado: un DataFrame con region como índice y los años como columnas. Si quieres region como columna normal: .reset_index().

pivot_table(): pivot con agregación

pivot() falla si hay combinaciones duplicadas de index × columns, porque no sabe qué valor poner en la celda. Si tienes datos repetidos (varias ventas por región-año), necesitas pivot_table():

df = pd.DataFrame({
    "region":     ["Norte", "Norte", "Sur", "Sur"],
    "año":        [2024, 2024, 2024, 2024],
    "trimestre":  ["Q1", "Q2", "Q1", "Q2"],
    "ventas":     [800, 950, 1100, 1300],
})

df.pivot_table(
    index="region",
    columns="año",
    values="ventas",
    aggfunc="sum",
)

aggfunc es la función de agregación cuando hay duplicados. Por defecto mean, pero puede ser sum, count, np.median, lo que sea.

pivot_table también admite múltiples valores y múltiples agregaciones a la vez:

df.pivot_table(
    index="region",
    columns="año",
    values=["ventas"],
    aggfunc=["sum", "mean"],
    margins=True,         # añade fila/columna de totales
)

margins=True añade fila y columna All con totales. Útil para tablas resumen.

Más de una columna pivotada

melt() con varias columnas de id:

df.melt(
    id_vars=["region", "categoria"],
    value_vars=["2022", "2023", "2024"],
    var_name="año",
    value_name="ventas",
)

pivot_table() con varias columnas de índice:

df.pivot_table(
    index=["region", "categoria"],
    columns="año",
    values="ventas",
    aggfunc="sum",
)

El resultado tiene MultiIndex en filas (region × categoria) y columnas planas (año). Aplanar luego con .reset_index().

stack() y unstack(): pivot del MultiIndex

stack y unstack operan sobre el índice. Son la versión “low-level” de pivot:

  • unstack(): mueve el último nivel del índice a las columnas. Como un pivot().
  • stack(): mueve un nivel de columnas al índice. Como un melt().
agg = df.groupby(["region", "año"])["ventas"].sum()
# Series con MultiIndex (region, año)

agg.unstack("año")     # años como columnas (DataFrame wide)
agg.unstack(level=-1)  # último nivel — equivalente

Útil cuando ya tienes un MultiIndex de un groupby y quieres “abrir” un nivel a columnas. Muy idiomático en pandas.

wide_to_long: cuando los nombres tienen estructura

Si tus columnas tienen nombres compuestos (ventas_2022, ventas_2023, clientes_2022…), pd.wide_to_long() los separa:

df = pd.DataFrame({
    "id": [1, 2],
    "ventas_2022": [1500, 2100],
    "ventas_2023": [1700, 2200],
    "clientes_2022": [120, 180],
    "clientes_2023": [130, 190],
})

pd.wide_to_long(df, stubnames=["ventas", "clientes"],
                i="id", j="año", sep="_")

Resultado: filas con (id, año) como índice y columnas ventas y clientes. Equivalente a pivot_longer con names_pattern en tidyr, útil cuando el wide es “denso” con varias métricas.

¿Qué formato uso?

Decisión por situación:

  • Análisis con groupby y agg → long.
  • Gráficos con seaborn o ggplot-like → long (cada estética mapea a una columna).
  • Modelos estadísticos → long para modelos mixtos, time-series. Wide para regresión clásica si las columnas son features.
  • Tablas para informes → wide, normalmente con pivot_table al final.

En la práctica, trabajas en long el 90 % del análisis y solo pivotas a wide al final para presentar.

Trampas habituales

  • pivot() con duplicados falla. Si pasa, usa pivot_table() con aggfunc. El error es críptico (“Index contains duplicate entries”). Cuando lo veas, ya sabes el remedio.
  • MultiIndex tras pivotar queda como índice y columnas multinivel. Si te incomoda, .reset_index() lo aplana en filas. Para columnas, df.columns = ['_'.join(c) for c in df.columns] aplana las tuplas a strings.
  • melt sin id_vars colapsa todas las columnas, incluido lo que querías mantener. Siempre especifica id_vars explícito.
  • Pivotar antes de groupby: a veces se hace al revés. Si tienes datos en wide y quieres agrupar por año, primero melt a long, después groupby. Mucho más limpio que iterar columnas en wide.
  • Olvidar aggfunc en pivot_table. El default es mean, no sum. Si los datos son recuentos, puedes obtener resultados raros sin darte cuenta.

En la siguiente entrega

Tienes reshape, groupby y operaciones. Lo siguiente es combinar DataFrames, joins (merge) y concatenación (concat). Es donde pandas se asemeja más a SQL y donde los gotchas (índices duplicados, joins many-to-many) merecen un tutorial dedicado. Lo siguiente.