pola-rs/polars · error · NotImplementedError

only ufuncs that return one 1D array are supported

Error message

only ufuncs that return one 1D array are supported

What it means

Series.__array_ufunc__ supports numpy ufuncs applied to a Series only when the ufunc produces exactly one output array (nout == 1). Multi-output ufuncs like np.modf or np.frexp are not supported, so NotImplementedError is raised at py-polars/src/polars/series/series.py:1518.

Solutions

  1. Split the operation into single-output ufuncs: use `s % 1` and `s // 1` instead of np.modf
  2. Compute via polars expressions: `pl.Series([np.modf(x)[0] for x in s.to_numpy()])` as a fallback on the numpy conversion
  3. Wrap multi-output calls around `s.to_numpy()` so numpy sees a real ndarray, then reassemble Series from each output

Example fix

// before
frac, integral = np.modf(s)  # NotImplementedError
// after
frac = s % 1
integral = s // 1
Defensive patterns

Strategy: fallback

Validate before calling

if getattr(ufunc, "nout", 1) != 1:
    arr = series.to_numpy()
    outputs = ufunc(arr)  # run on ndarray instead
else:
    result = ufunc(series)

Type guard

def is_single_output_ufunc(ufunc) -> bool:
    return getattr(ufunc, "nout", 1) == 1

Try / catch

try:
    result = np.modf(series)
except NotImplementedError as e:
    if "one 1D array" in str(e):
        frac, integral = np.modf(series.to_numpy())
        result = (pl.Series(frac), pl.Series(integral))
    else:
        raise

Prevention

When it happens

Trigger: `np.modf(series)`, `np.frexp(series)`, or any other ufunc with nout > 1 called on a Series, entering the `method == "__call__"` branch with `ufunc.nout != 1`.

Common situations: Using np.modf to split fractional/integer parts of a float Series; np.frexp for mantissa/exponent; generic numpy code paths that call arbitrary ufuncs on array-likes.

Related errors


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

Appendix: source

Thrown at py-polars/src/polars/series/series.py:1518

                raise RuntimeError(msg)

            arr = arr.__array__(dtype)

        return arr

    def __array_ufunc__(
        self, ufunc: np.ufunc, method: str_, *inputs: Any, **kwargs: Any
    ) -> Series:
        """Numpy universal functions."""
        if self._s.n_chunks() > 1:
            self._s.rechunk(in_place=True)

        s = self._s

        if method == "__call__":
            if ufunc.nout != 1:
                msg = "only ufuncs that return one 1D array are supported"
                raise NotImplementedError(msg)

            args: list[int | float | np.ndarray[Any, Any]] = []
            for arg in inputs:
                if isinstance(arg, (int, float, np.ndarray)):
                    args.append(arg)
                elif isinstance(arg, Series):
                    phys_arg = arg.to_physical()
                    if phys_arg._s.n_chunks() > 1:
                        phys_arg._s.rechunk(in_place=True)
                    args.append(phys_arg._s.to_numpy_view())  # type: ignore[arg-type]
                else:
                    msg = f"unsupported type {qualified_type_name(arg)!r} for {arg!r}"
                    raise TypeError(msg)

            # Get minimum dtype needed to be able to cast all input arguments to the
            # same dtype.
            dtype_char_minimum: str = np.result_type(*args).char

View on GitHub (pinned to fe841f959e)