pandas
dataframe
multiindex
select-columns
duplicate

pandas dataframe select columns in multiindex

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

MultiIndex columns are useful when a table has natural column groups, such as a stock symbol on one level and a metric on another. The tradeoff is that selection syntax becomes more explicit than ordinary df["col"] access. Once you learn the main patterns, selecting MultiIndex columns is predictable: use a top-level label for a whole group, a tuple for one exact column path, or helper tools such as xs and IndexSlice when the selection spans levels.

Build a small MultiIndex example

It helps to work from a concrete DataFrame. The example below has two column levels: ticker and metric.

python
1import numpy as np
2import pandas as pd
3
4columns = pd.MultiIndex.from_tuples(
5    [
6        ("AAPL", "price"),
7        ("AAPL", "volume"),
8        ("MSFT", "price"),
9        ("MSFT", "volume"),
10    ],
11    names=["ticker", "metric"],
12)
13
14df = pd.DataFrame(
15    np.array([
16        [190.3, 1200, 410.5, 900],
17        [191.1, 1500, 412.0, 950],
18        [189.8, 1300, 409.7, 1000],
19    ]),
20    index=pd.Index(["2026-03-01", "2026-03-02", "2026-03-03"], name="date"),
21    columns=columns,
22)
23
24print(df)

With this structure in place, each column is identified by a two-part key instead of a single string.

Select a whole top-level group or one exact column

If you want everything under one top-level label, use that label directly. If you want only one concrete column, use the full tuple.

python
1apple = df["AAPL"]
2apple_price = df[("AAPL", "price")]
3
4print(apple)
5print(apple_price)

df["AAPL"] returns a smaller DataFrame containing both price and volume for Apple. df[("AAPL", "price")] returns a single Series, because the full column path identifies one leaf column.

This distinction matters in downstream code. A DataFrame and a Series support different operations, so know which shape your selection will produce.

Use xs when you want one level across many groups

xs, short for cross section, is useful when you want all columns matching one label on a specific level. That is often cleaner than manually building several tuples.

python
1prices = df.xs("price", level="metric", axis=1)
2msft_all_metrics = df.xs("MSFT", level="ticker", axis=1)
3
4print(prices)
5print(msft_all_metrics)

prices keeps one column per ticker and drops the selected level. This is very convenient when you want all values for one metric, such as all price columns across every symbol.

The key detail is axis=1. Without it, pandas looks for the level on the row index instead of the column MultiIndex.

Use .loc and pd.IndexSlice for flexible selections

When you need more control, .loc plus pd.IndexSlice gives you readable slice syntax across levels.

python
1idx = pd.IndexSlice
2
3subset = df.loc[:, idx[:, "volume"]]
4apple_both = df.loc[:, idx["AAPL", :]]
5date_range = df.loc["2026-03-01":"2026-03-02", idx[:, "price"]]
6
7print(subset)
8print(apple_both)
9print(date_range)

This is especially helpful when a selection includes slices on rows and columns at the same time. If you use slice ranges on a MultiIndex, sorting the columns first is a good habit:

python
df = df.sort_index(axis=1)

That avoids errors when pandas requires a sorted index for slice-based lookups.

Filter by level values when the labels are dynamic

Sometimes the selection rule is not a fixed tuple but a condition on one level. In that case, get_level_values() lets you build a boolean mask.

python
1metric_mask = df.columns.get_level_values("metric") == "price"
2ticker_mask = df.columns.get_level_values("ticker").str.startswith("A")
3
4only_prices = df.loc[:, metric_mask]
5tickers_starting_with_a = df.loc[:, ticker_mask]
6
7print(only_prices)
8print(tickers_starting_with_a)

This approach is useful when the set of top-level labels changes over time and you do not want to hard-code every tuple.

Common Pitfalls

The most common mistake is forgetting that one exact MultiIndex column needs a tuple such as ("AAPL", "price"). A plain string selects only one level, not a full path.

Another frequent issue is omitting axis=1 in xs. When that happens, pandas searches the row index and raises a confusing KeyError.

Developers also get tripped up by shape changes. Selecting df["AAPL"] returns a DataFrame, but selecting df[("AAPL", "price")] returns a Series.

Finally, slice-based selections with IndexSlice can fail on unsorted MultiIndex columns. Sorting with sort_index(axis=1) before advanced slicing keeps the behavior predictable.

Summary

  • Use a top-level label such as df["AAPL"] to select one whole column group.
  • Use a tuple such as df[("AAPL", "price")] to select one exact MultiIndex column.
  • Use xs(..., axis=1) when you want one label across a column level.
  • Use .loc with pd.IndexSlice for more flexible row and column slicing.
  • Use get_level_values() when the selection depends on a dynamic rule instead of fixed labels.

Course illustration
Course illustration

All Rights Reserved.