pola-rs/polars · error · TypeError

`map` with `returns_scalar=False` must return a Series; foun

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

In F.map_batches (the multi-expression variant), the user-supplied function must return a pl.Series; a numpy ndarray return is auto-wrapped into one. Any other return value — list, tuple, None, a bare Python scalar, or a DataFrame — raises TypeError naming the actual type when returns_scalar=False (the default). Scalars are accepted only with returns_scalar=True.

Source

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

        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 df599052da)

Solutions

  1. Return Series directly via vectorized ops: lambda ss: ss[0] * ss[1]
  2. Wrap list results explicitly: return pl.Series(result)
  3. If the function yields one value per call, pass returns_scalar=True
  4. Fix None branches to return a typed empty Series, e.g. pl.Series(dtype=pl.Float64)

Example fix

# before
pl.map_batches(['a', 'b'], lambda ss: [v0 * v1 for v0, v1 in zip(ss[0], ss[1])])  # list -> TypeError

# after
pl.map_batches(['a', 'b'], lambda ss: ss[0] * ss[1])
# scalar result:
pl.map_batches(['a'], lambda ss: ss[0].mean(), returns_scalar=True)
Defensive patterns

Strategy: validation

Validate before calling

def as_series(rv):
    if isinstance(rv, pl.Series):
        return rv
    if _check_for_numpy(rv) and isinstance(rv, np.ndarray):
        return pl.Series(rv)
    if not isinstance(rv, pl.Series):
        return pl.Series(rv)  # wrap list-like; scalars need returns_scalar=True
    return rv

expr = pl.map_batches(['a', 'b'], lambda ss: as_series(my_fn(*ss)))

Type guard

def returns_series(fn, sample: list[pl.Series]) -> bool:
    return isinstance(fn(*sample), pl.Series)

Prevention

When it happens

Trigger: A callback returning [x.mean() for x in ...] (list comprehension); returning float/int with default returns_scalar=False; an early-exit branch returning None; returning a DataFrame column object instead of a Series.

Common situations: Porting pandas .apply code that returns lists; wrapping numpy-style functions (arrays are fine, scalars are not); forgetting returns_scalar=True for per-batch aggregates.

Related errors


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