MultiIndex: índices jerárquicos en Pandas

Avanzado
Pandas
Pandas
Actualizado: 05/05/2026

¿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 - Autor del tutorial

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 con pd.MultiIndex.from_tuples() y from_product().
  • Seleccionar filas y columnas en DataFrames con MultiIndex usando loc con tuplas y pd.IndexSlice.
  • Operar con xs() para extraer secciones transversales de un MultiIndex.
  • Reordenar y renombrar niveles del índice con swaplevel(), reorder_levels() y rename_axis().
  • Agregar datos agrupados con groupby() generando resultados con MultiIndex.
  • Transformar MultiIndex con stack(), unstack() y reset_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