pola-rs/polars · error · TypeError

`map` with `returns_scalar=False` must return a Series…

Error message

`map` with `returns_scalar=False` must return a Series; found {qualified_type_name(rv)!r}.

If `returns_scalar` is set to `True`, a returned value can be a scalar value.

What it means

The Python function passed to `Expr.map` is applied per group/window and, with `returns_scalar=False` (the default), must return a `pl.Series`. If it returns any other object (list, scalar, numpy array), the map callback raises TypeError. Scalars require explicitly setting `returns_scalar=True`.

Solutions

  1. Return a `pl.Series` from the mapped function
  2. Set `returns_scalar=True` if the function returns a single scalar value
  3. Use `map_batches` if you want batch semantics with flexible return handling
  4. Wrap non-Series results: `pl.Series(rv)`

Example fix

// before
pl.col("x").map(lambda s: s.sum())
// after
pl.col("x").map(lambda s: s.sum(), returns_scalar=True)
# or return a Series:
pl.col("x").map(lambda s: pl.Series([s.sum()]))
Defensive patterns

Strategy: type-guard

Validate before calling

def mapped(fn, returns_scalar=False):
    def wrapped(s):
        rv = fn(s)
        if not returns_scalar and not isinstance(rv, pl.Series):
            raise TypeError("map function must return pl.Series or set returns_scalar=True")
        return rv
    return wrapped

Type guard

def map_result_ok(rv, returns_scalar): return isinstance(rv, pl.Series) or returns_scalar

Try / catch

try:
    out = df.select(pl.col("x").map(fn))
except TypeError:
    out = df.select(pl.col("x").map(fn, returns_scalar=True))

Prevention

When it happens

Trigger: `df.select(pl.col("x").map(lambda s: s.sum()))` or a lambda returning `s.to_list()` / a plain value while `returns_scalar` is left False.

Common situations: Confusing `map` with `map_elements`; migrating from older `apply`; forgetting `returns_scalar=True` for aggregate-style callbacks; returning numpy arrays instead of Series.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at py-polars/src/polars/functions/lazy.py:1088

        try:
            rv = self.function(slp, *args, **kwargs)
        except TypeError as e:
            if "unexpected keyword argument 'return_dtype'" in e.args[0]:
                kwargs.pop("return_dtype")
                rv = self.function(slp, *args, **kwargs)
            else:
                raise

        if _check_for_numpy(rv) and isinstance(rv, np.ndarray):
            rv = pl.Series(rv, dtype=return_dtype)

        if isinstance(rv, pl.Series):
            return rv._s
        elif self.returns_scalar:
            return pl.Series([rv], dtype=return_dtype)._s
        else:
            msg = f"`map` with `returns_scalar=False` must return a Series; found {qualified_type_name(rv)!r}.\n\nIf `returns_scalar` is set to `True`, a returned value can be a scalar value."
            raise TypeError(msg)


def map_batches(
    exprs: Sequence[str | Expr],
    function: Callable[[Sequence[Series]], Series | Any],
    return_dtype: PolarsDataType | pl.DataTypeExpr | None = None,
    *,
    is_elementwise: bool = False,
    returns_scalar: bool = False,
) -> Expr:
    """
    Map a custom function over multiple columns/expressions.

    Produces a single Series result.

    .. warning::
        This method is much slower than the native expressions API.
        Only use it if you cannot implement your logic otherwise.

View on GitHub (pinned to fe841f959e)