Reshape: melt y pivot
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ñoes 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 2400Argumentos clave:
id_vars: columnas que se mantienen como están (no se pivotan). Aquí,regionno es una métrica, es un identificador.value_vars: columnas que se colapsan. Si lo omites, son todas las que no están enid_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 2400Argumentos:
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 unpivot().stack(): mueve un nivel de columnas al índice. Como unmelt().
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
groupbyyagg→ 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_tableal 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, usapivot_table()conaggfunc. 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. meltsinid_varscolapsa todas las columnas, incluido lo que querías mantener. Siempre especificaid_varsexplícito.- Pivotar antes de groupby: a veces se hace al revés. Si tienes datos en wide y quieres agrupar por año, primero
melta long, despuésgroupby. Mucho más limpio que iterar columnas en wide. - Olvidar
aggfuncenpivot_table. El default esmean, nosum. 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.