Reshaping: pivot and unpivot

Polars · Practical · 10 min

Reports want wide tables while plotting and grouping want long ones, so you'll convert constantly.

Long (tidy) data has one row per observation: (store, month, sales). Wide data spreads one variable across columns: store, jan, feb, mar.

df.pivot(on="month", index="store", values="sales", aggregate_function="sum") makes it wide: each distinct value of on becomes a column. If several rows share the same index and on pair you must say how to combine them with aggregate_function ("sum", "mean", "first", "len", or an expression). Combinations with no rows become null, except with "sum" or "len", where they become 0.

Pivot is eager-only, since the output columns depend on the data. For a lazy query, compute the distinct values first, or use group_by with filtered aggregations like pl.col("sales").filter(pl.col("month") == "jan").sum().alias("jan").

df.unpivot(index="store", on=["jan", "feb"], variable_name="month", value_name="sales") goes back to long form. Leaving out on unpivots every non-index column, and selectors work: on=cs.numeric(). This used to be called melt, which was removed in Polars 2.

Rule of thumb: keep data long while computing (group_by, joins and plots are all easier) and pivot only at the end for presentation.

Example

import polars as pl

long = pl.DataFrame({
    "store": ["A", "A", "A", "B"],
    "month": ["jan", "feb", "feb", "jan"],
    "sales": [10, 4, 6, 7],
})

wide = long.pivot(on="month", index="store", values="sales",
                  aggregate_function="sum").sort("store")
print(wide.rows())      # [('A', 10, 10), ('B', 7, 0)]  (sum of nothing is 0)
print(wide.columns)     # ['store', 'jan', 'feb']

back = wide.unpivot(index="store", variable_name="month",
                    value_name="sales").sort("store", "month")
print(back.rows())
# [('A', 'feb', 10), ('A', 'jan', 10), ('B', 'feb', 0), ('B', 'jan', 7)]

Remember

  • pivot = long to wide; unpivot = wide to long (formerly melt)
  • Pass aggregate_function when index/on pairs repeat
  • Compute in long form, pivot at the end for display

Watch out for

  • Pivoting a column with thousands of distinct values and getting a frame with thousands of columns.

Check yourself

  1. Why can't pivot be part of a lazy query plan directly?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pola.rs/user-guide/transformations/pivot/

Builds on

Unlocks

Nothing builds on this yet.