¿Qué es un MultiIndex?
Un MultiIndex (índice jerárquico) es un índice de Pandas con más de un nivel. Permite que cada fila (o columna) quede identificada por una combinación de etiquetas, no por una sola. Esto es especialmente útil para datos agrupados por categorías, datos de panel, series temporales con múltiples frecuencias o resultados de operaciones groupby.
graph TB
DF[DataFrame plano] -->|set_index región, trimestre| MI[DataFrame MultiIndex]
MI -->|loc tupla región, trim| SEL[Selección por niveles]
MI -->|xs nivel=trimestre| XS[Sección transversal]
MI -->|IndexSlice rangos| RNG[Slicing avanzado]
MI -->|swaplevel / reorder_levels| REO[Reordenar niveles]
MI -->|stack / unstack| RES[Pivotar nivel a columnas]
MI -->|sort_index| SRT[Ordenar índice]
MI -->|reset_index| FLA[Volver a DataFrame plano]
GBY[groupby multinivel] --> MI
import pandas as pd
import numpy as np
# DataFrame plano de ventas por región y trimestre
datos = {
"region": ["Norte", "Norte", "Norte", "Norte", "Sur", "Sur", "Sur", "Sur"],
"trimestre": ["Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4"],
"unidades": [120, 135, 150, 110, 90, 105, 98, 115],
"importe": [14400, 16200, 18000, 13200, 10800, 12600, 11760, 13800],
}
df = pd.DataFrame(datos)
print(df)
En este estado, region y trimestre son columnas ordinarias. Convertirlas en un MultiIndex mejora la expresividad del código y habilita operaciones de reestructuración avanzadas.
Crear un MultiIndex con set_index()
La forma más directa de crear un MultiIndex es pasar una lista de columnas a set_index():
df_mi = df.set_index(["region", "trimestre"])
print(df_mi)
# unidades importe
# region trimestre
# Norte Q1 120 14400
# Q2 135 16200
# Q3 150 18000
# Q4 110 13200
# Sur Q1 90 10800
# ...
El índice resultante tiene dos niveles: region (nivel 0) y trimestre (nivel 1). Pandas muestra el primer nivel solo en la primera fila de cada grupo para mejorar la legibilidad.
print(df_mi.index)
# MultiIndex([('Norte', 'Q1'),
# ('Norte', 'Q2'),
# ...
# ('Sur', 'Q4')],
# names=['region', 'trimestre'])
Crear MultiIndex con constructores
También es posible construir un MultiIndex directamente, sin partir de un DataFrame existente.
from_tuples()
# Construir desde una lista de tuplas
indice = pd.MultiIndex.from_tuples(
[("Madrid", 2024), ("Madrid", 2025), ("Barcelona", 2024), ("Barcelona", 2025)],
names=["ciudad", "año"]
)
serie = pd.Series([1200, 1350, 900, 980], index=indice)
print(serie)
from_product()
from_product() genera el producto cartesiano de varias listas. Es la forma más concisa cuando se quieren todas las combinaciones posibles:
ciudades = ["Madrid", "Barcelona", "Valencia"]
años = [2023, 2024, 2025]
indice = pd.MultiIndex.from_product([ciudades, años], names=["ciudad", "año"])
print(indice)
# MultiIndex([('Madrid', 2023),
# ('Madrid', 2024),
# ...
# ('Valencia', 2025)],
# names=['ciudad', 'año'])
Selección con loc y tuplas
Con un MultiIndex, la selección por etiqueta con loc acepta tuplas para indicar el nivel de cada etiqueta:
# Seleccionar todas las filas del nivel "Norte"
print(df_mi.loc["Norte"])
# Seleccionar el valor exacto Norte-Q2
print(df_mi.loc[("Norte", "Q2")])
# Seleccionar con lista en el primer nivel
print(df_mi.loc[["Norte", "Sur"]])
Cuando se selecciona por el primer nivel únicamente, Pandas devuelve un DataFrame con ese nivel eliminado del índice. Es equivalente a
xs("Norte", level="region").
Selección con pd.IndexSlice
Para seleccionar rangos en cualquier nivel de un MultiIndex se usa pd.IndexSlice, que permite una sintaxis más clara que las tuplas anidadas:
idx = pd.IndexSlice
# Todas las regiones, trimestres Q1 y Q2
print(df_mi.loc[idx[:, ["Q1", "Q2"]], :])
# Solo Norte, trimestres Q2 a Q3
print(df_mi.loc[idx["Norte", "Q2":"Q3"], :])
Extracción transversal con xs()
El método xs() (cross-section) permite extraer un subconjunto de datos fijando un valor en cualquier nivel del índice, no necesariamente el primero:
# Extraer todos los datos del trimestre Q3, independientemente de la región
print(df_mi.xs("Q3", level="trimestre"))
# Equivalente con loc usando IndexSlice
print(df_mi.loc[idx[:, "Q3"], :])
xs() es especialmente útil cuando el nivel que se quiere fijar no es el exterior (nivel 0).
Ordenar con sort_index()
Los DataFrames con MultiIndex deben tener el índice ordenado para que las operaciones de corte (slicing) con rangos funcionen correctamente. sort_index() ordena lexicográficamente por todos los niveles:
df_mi_desordenado = df_mi.loc[["Sur", "Norte"]] # orden alterado
df_mi_ordenado = df_mi_desordenado.sort_index()
print(df_mi_ordenado)
Intercambiar y reordenar niveles
swaplevel()
Intercambia dos niveles del índice:
df_swapped = df_mi.swaplevel("region", "trimestre")
print(df_swapped.sort_index())
# Ahora el nivel 0 es "trimestre" y el nivel 1 es "region"
reorder_levels()
Cuando hay más de dos niveles, permite especificar el orden deseado:
# Con tres niveles: zona, region, trimestre
df3 = df_mi.copy()
df3.index = pd.MultiIndex.from_tuples(
[("Iberia", r, t) for r, t in df_mi.index],
names=["zona", "region", "trimestre"]
)
# Reordenar a [region, trimestre, zona]
df3_reordenado = df3.reorder_levels(["region", "trimestre", "zona"])
print(df3_reordenado.head())
unstack() y stack()
Estas dos operaciones transforman el MultiIndex entre formato largo (stack) y formato ancho (unstack):
unstack()
Mueve el nivel más interno del índice de filas a las columnas:
df_ancho = df_mi["importe"].unstack(level="trimestre")
print(df_ancho)
# Q1 Q2 Q3 Q4
# region
# Norte 14400 16200 18000 13200
# Sur 10800 12600 11760 13800
También se puede mover el primer nivel:
df_ancho2 = df_mi["importe"].unstack(level="region")
print(df_ancho2)
stack()
Realiza la operación inversa: mueve las columnas al índice como un nuevo nivel:
df_largo = df_ancho.stack()
print(df_largo)
# region trimestre
# Norte Q1 14400
# Q2 16200
# ...
MultiIndex en columnas
El MultiIndex no se limita al índice de filas; también puede aplicarse a las columnas. Esto es frecuente en el resultado de operaciones pivot_table() o groupby().agg() con múltiples funciones:
df_agg = df.groupby("region")[["unidades", "importe"]].agg(["sum", "mean"])
print(df_agg)
# Columnas: MultiIndex([('importe', 'mean'), ('importe', 'sum'),
# ('unidades', 'mean'), ('unidades', 'sum')], ...)
# Acceder a una columna específica del MultiIndex
print(df_agg[("importe", "sum")])
# Aplanar el MultiIndex de columnas
df_agg.columns = ["_".join(col) for col in df_agg.columns]
print(df_agg.columns)
# Index(['importe_mean', 'importe_sum', 'unidades_mean', 'unidades_sum'], ...)
reset_index(): volver a columnas ordinarias
Para eliminar el MultiIndex y recuperar las columnas como campos normales se usa reset_index():
df_plano = df_mi.reset_index()
print(df_plano)
# region trimestre unidades importe
# 0 Norte Q1 120 14400
# ...
Esto es útil antes de exportar datos o encadenar operaciones que esperan un índice numérico sencillo.
groupby() y MultiIndex en el resultado
Las operaciones groupby() con varias columnas devuelven naturalmente un MultiIndex en el resultado:
resumen = df.groupby(["region", "trimestre"]).agg(
total_importe=("importe", "sum"),
media_unidades=("unidades", "mean")
)
print(resumen)
print(type(resumen.index)) # <class 'pandas.core.indexes.multi.MultiIndex'>
# Seleccionar con loc sobre el resultado de groupby
print(resumen.loc["Norte"])
print(resumen.loc[("Norte", "Q3")])
Caso práctico: análisis de ventas multidimensional
import pandas as pd
import numpy as np
# Datos de ventas por región, producto y trimestre
np.random.seed(42)
regiones = ["Norte", "Sur", "Este", "Oeste"]
productos = ["Laptop", "Tablet", "Móvil"]
trimestres = ["Q1", "Q2", "Q3", "Q4"]
filas = [
(r, p, t)
for r in regiones
for p in productos
for t in trimestres
]
df_ventas = pd.DataFrame(filas, columns=["region", "producto", "trimestre"])
df_ventas["unidades"] = np.random.randint(50, 200, size=len(df_ventas))
df_ventas["precio"] = df_ventas["producto"].map(
{"Laptop": 1200, "Tablet": 450, "Móvil": 700}
)
df_ventas["importe"] = df_ventas["unidades"] * df_ventas["precio"]
# Crear MultiIndex con tres niveles
df_ventas_mi = df_ventas.set_index(["region", "producto", "trimestre"])
# Totales anuales por región y producto (sumando los 4 trimestres)
total_anual = df_ventas_mi.groupby(level=["region", "producto"])["importe"].sum()
print("Total anual por región y producto:")
print(total_anual)
# Tabla pivot: filas=región, columnas=producto, valores=importe anual
tabla = total_anual.unstack(level="producto")
print("\nTabla pivot importe anual:")
print(tabla)
# Mejor trimestre por región
mejor_q = df_ventas_mi.groupby(level=["region", "trimestre"])["importe"].sum()
print("\nImporte por región y trimestre:")
print(mejor_q.unstack(level="trimestre"))
Los MultiIndex son fundamentales para trabajar con datos de panel, resultados de agrupaciones complejas y estructuras matriciales en Pandas. Combinados con stack(), unstack() y xs(), permiten pivotar y extraer información con gran flexibilidad.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Pandas es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Pandas
Explora más contenido relacionado con Pandas y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Crear un MultiIndex a partir de columnas existentes con
set_index()o desde listas conpd.MultiIndex.from_tuples()yfrom_product(). - Seleccionar filas y columnas en DataFrames con MultiIndex usando
loccon tuplas ypd.IndexSlice. - Operar con
xs()para extraer secciones transversales de un MultiIndex. - Reordenar y renombrar niveles del índice con
swaplevel(),reorder_levels()yrename_axis(). - Agregar datos agrupados con
groupby()generando resultados con MultiIndex. - Transformar MultiIndex con
stack(),unstack()yreset_index(). - Ordenar un DataFrame con MultiIndex usando
sort_index().
Cursos que incluyen esta lección
Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje