pola-rs/polars · error

cannot call `map_groups` when grouping by an expression

Error message

cannot call `map_groups` when grouping by an expression

What it means

`map_groups` passes group keys directly to the Rust `group_by_map_groups`; only column-name strings are supported. This TypeError fires when any group key is a non-string expression (e.g. `pl.col('a')`) rather than a literal column name.

Solutions

  1. Replace expressions with the column name string: `group_by('a').map_groups(fn)`.
  2. For computed keys, materialize them with `with_columns` first, then group by the new column name.
  3. Use `.over(key_expr)` expression APIs if a plain transformation (not arbitrary Python fn) is needed.

Example fix

// before
df.group_by(pl.col('region')).map_groups(fn)
// after
df.group_by('region').map_groups(fn)
Defensive patterns

Strategy: type-guard

Validate before calling

import polars as pl
keys = ['region']
assert all(isinstance(k, str) for k in keys), 'group keys for map_groups must be strings'

Type guard

def are_column_names(keys) -> bool:
    return all(isinstance(k, str) for k in keys)

Try / catch

try:
    out = gb.map_groups(fn)
except TypeError:
    names = [e.meta.output_name() for e in key_exprs]
    out = df.group_by(names).map_groups(fn)

Prevention

When it happens

Trigger: `df.group_by(pl.col('a')).map_groups(fn)` or grouping by a tuple/list containing expressions.

Common situations: Reusing an expression-based group_by written for aggregations; dynamic group keys built programmatically as expressions.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of pola-rs/polars@fe841f959e (2026-09-18). Data as JSON: /api/errors/e4c30c6e727f05d0. Report an issue: GitHub.

Appendix: source

Thrown at py-polars/src/polars/dataframe/group_by.py:448

        │ 3   ┆ red   ┆ triangle │
        └─────┴───────┴──────────┘

        It is better to implement this with an expression:

        >>> df.filter(
        ...     pl.int_range(pl.len()).shuffle().over("color") < 2
        ... )  # doctest: +IGNORE_RESULT
        """
        if self.predicates:
            msg = "cannot call `map_groups` when filtering groups with `having`"
            raise TypeError(msg)
        if self.named_by:
            msg = "cannot call `map_groups` when grouping by named expressions"
            raise TypeError(msg)
        by = list(_parse_inputs_as_iterable(self.by))
        if not all(isinstance(c, str) for c in by):
            msg = "cannot call `map_groups` when grouping by an expression"
            raise TypeError(msg)

        return self.df.__class__._from_pydf(
            self.df._df.group_by_map_groups(by, function, self.maintain_order)
        )

    def head(self, n: int = 5) -> DataFrame:
        """
        Get the first `n` rows of each group.

        Parameters
        ----------
        n
            Number of rows to return.

        Examples
        --------
        >>> df = pl.DataFrame(
        ...     {

View on GitHub (pinned to fe841f959e)