pola-rs/polars · error · InvalidOperationError

`pivot` needs either `index or `values` needs to be specifie

Error message

`pivot` needs either `index or `values` needs to be specified

What it means

LazyFrame.pivot() requires at least one of index= or values= to be given (on= is mandatory separately). If neither is provided, polars cannot decide which columns become the row identifiers versus the pivoted values, and raises InvalidOperationError. When only one is given, the other is inferred as the complement of the on= columns.

Source

Thrown at py-polars/src/polars/lazyframe/frame.py:8651

        ╞══════╪══════════╪══════════╡
        │ a    ┆ 0.998347 ┆ null     │
        │ b    ┆ 0.964028 ┆ 0.999954 │
        └──────┴──────────┴──────────┘
        """  # noqa: W505
        on_selector = parse_list_into_selector(on)

        if index is not None and values is not None:
            index_selector = parse_list_into_selector(index)
            values_selector = parse_list_into_selector(values)
        elif index is not None:
            index_selector = parse_list_into_selector(index)
            values_selector = cs.all() - on_selector - index_selector
        elif values is not None:
            values_selector = parse_list_into_selector(values)
            index_selector = cs.all() - on_selector - values_selector
        else:
            msg = "`pivot` needs either `index or `values` needs to be specified"
            raise InvalidOperationError(msg)

        agg = F.element()
        if isinstance(aggregate_function, str):
            if aggregate_function == "first":
                agg = agg.first()
            elif aggregate_function == "item":
                agg = agg.item()
            elif aggregate_function == "sum":
                agg = agg.sum()
            elif aggregate_function == "max":
                agg = agg.max()
            elif aggregate_function == "min":
                agg = agg.min()
            elif aggregate_function == "mean":
                agg = agg.mean()
            elif aggregate_function == "median":
                agg = agg.median()
            elif aggregate_function == "last":

View on GitHub (pinned to df599052da)

Solutions

  1. Pass index=['id'] to identify output rows, letting values default to all remaining non-on columns
  2. Or pass values=['val'] to pivot explicitly, letting index default to the complement
  3. Validate config: require at least one of index/values before calling pivot
  4. Check for typos — the argument names are exactly 'index' and 'values'

Example fix

# before
wide = lf.pivot(on='month')

# after
wide = lf.pivot(on='month', index='id', aggregate_function='sum')
Defensive patterns

Strategy: validation

Validate before calling

if index is None and values is None:
    index = ['id']  # sensible default row identifier
wide = lf.pivot(on=on, index=index, values=values, aggregate_function=aggregate_function)

Type guard

def has_pivot_axes(index, values) -> bool:
    return index is not None or values is not None

Try / catch

try:
    wide = lf.pivot(on='month')
except Exception:
    wide = lf.pivot(on='month', index='id', aggregate_function='sum')

Prevention

When it happens

Trigger: lf.pivot(on='metric', values=... missing and index missing); dynamic pipelines where index/values come from config that is empty; porting DataFrame.pivot calls that had different parameter requirements.

Common situations: Config-driven reshaping jobs; wide/long transformations where the caller assumed column inference from on= alone.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/f798018947f82f83. Report an issue: GitHub.