{"record":{"id":"35c6275863dc5535","repo":"pola-rs/polars","slug":"map-with-returns-scalar-false-must-return-a-se","errorCode":null,"errorMessage":"`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.","messagePattern":"`map` with `returns_scalar=False` must return a Series; found (.+?)\\.\n\nIf `returns_scalar` is set to `True`, a returned value can be a scalar value\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/functions/lazy.py","lineNumber":1113,"sourceCode":"        try:\n            rv = self.function(slp, *args, **kwargs)\n        except TypeError as e:\n            if \"unexpected keyword argument 'return_dtype'\" in e.args[0]:\n                kwargs.pop(\"return_dtype\")\n                rv = self.function(slp, *args, **kwargs)\n            else:\n                raise\n\n        if _check_for_numpy(rv) and isinstance(rv, np.ndarray):\n            rv = pl.Series(rv, dtype=return_dtype)\n\n        if isinstance(rv, pl.Series):\n            return rv._s\n        elif self.returns_scalar:\n            return pl.Series([rv], dtype=return_dtype)._s\n        else:\n            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.\"\n            raise TypeError(msg)\n\n\ndef map_batches(\n    exprs: Sequence[str | Expr],\n    function: Callable[[Sequence[Series]], Series | Any],\n    return_dtype: PolarsDataType | pl.DataTypeExpr | None = None,\n    *,\n    is_elementwise: bool = False,\n    returns_scalar: bool = False,\n) -> Expr:\n    \"\"\"\n    Map a custom function over multiple columns/expressions.\n\n    Produces a single Series result.\n\n    .. warning::\n        This method is much slower than the native expressions API.\n        Only use it if you cannot implement your logic otherwise.","sourceCodeStart":1095,"sourceCodeEnd":1131,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/functions/lazy.py#L1095-L1131","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Return Series directly via vectorized ops: lambda ss: ss[0] * ss[1]","Wrap list results explicitly: return pl.Series(result)","If the function yields one value per call, pass returns_scalar=True","Fix None branches to return a typed empty Series, e.g. pl.Series(dtype=pl.Float64)"],"exampleFix":"# before\npl.map_batches(['a', 'b'], lambda ss: [v0 * v1 for v0, v1 in zip(ss[0], ss[1])])  # list -> TypeError\n\n# after\npl.map_batches(['a', 'b'], lambda ss: ss[0] * ss[1])\n# scalar result:\npl.map_batches(['a'], lambda ss: ss[0].mean(), returns_scalar=True)","handlingStrategy":"validation","validationCode":"def as_series(rv):\n    if isinstance(rv, pl.Series):\n        return rv\n    if _check_for_numpy(rv) and isinstance(rv, np.ndarray):\n        return pl.Series(rv)\n    if not isinstance(rv, pl.Series):\n        return pl.Series(rv)  # wrap list-like; scalars need returns_scalar=True\n    return rv\n\nexpr = pl.map_batches(['a', 'b'], lambda ss: as_series(my_fn(*ss)))","typeGuard":"def returns_series(fn, sample: list[pl.Series]) -> bool:\n    return isinstance(fn(*sample), pl.Series)","tryCatchPattern":null,"preventionTips":["Unit-test the mapping function against a small frame before wiring it into map_batches","Prefer vectorized Series operations inside the callback; return the result of Series arithmetic","Remember numpy arrays are auto-converted but plain lists and scalars are not; set returns_scalar=True for scalars"],"tags":["polars","map-batches","callback","return-type"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}