pola-rs/polars · error · RuntimeError

copy not allowed: cannot create structured array without cop

Error message

copy not allowed: cannot create structured array without copying data

What it means

Raised by DataFrame.to_numpy(structured=True, allow_copy=False) on any non-empty frame. A structured (record) numpy array interleaves per-column data into one array of tuples, which fundamentally requires materializing and copying data — there is no zero-copy path from polars' columnar layout. When the caller has forbidden copies (the allow_copy=False zero-copy contract used by the interchange protocol), polars raises RuntimeError instead of silently violating the contract. Empty frames are the one exception and pass through.

Source

Thrown at py-polars/src/polars/dataframe/frame.py:2059

        Set `structured=True` to convert to a structured array, which can better
        preserve individual column data such as name and data type.

        >>> df.to_numpy(structured=True)
        array([(1, 6.5, 'a'), (2, 7. , 'b'), (3, 8.5, 'c')],
              dtype=[('foo', 'u1'), ('bar', '<f4'), ('ham', '<U1')])
        """  # noqa: W505
        if use_pyarrow is not None:
            issue_deprecation_warning(
                "the `use_pyarrow` parameter for `DataFrame.to_numpy` is deprecated."
                " Polars now uses its native engine by default for conversion to NumPy.",
                version="0.20.28",
            )

        if structured:
            if not allow_copy and not self.is_empty():
                msg = "copy not allowed: cannot create structured array without copying data"
                raise RuntimeError(msg)

            arrays = []
            struct_dtype = []
            for s in self.iter_columns():
                if s.dtype == Struct:
                    arr = s.struct.unnest().to_numpy(
                        structured=True,
                        allow_copy=True,
                        use_pyarrow=use_pyarrow,
                    )
                else:
                    arr = s.to_numpy(use_pyarrow=use_pyarrow)

                if s.dtype == String and not s.has_nulls():
                    arr = arr.astype(str, copy=False)
                arrays.append(arr)
                struct_dtype.append((s.name, arr.dtype, arr.shape[1:]))

View on GitHub (pinned to df599052da)

Solutions

  1. Allow the copy: `df.to_numpy(structured=True, allow_copy=True)`
  2. If copies are unacceptable, take unstructured 2D output `df.to_numpy()` (which can be zero-copy for a single uniform numeric block)
  3. Skip conversion for empty frames explicitly (`if df.is_empty(): ...`) if your code path can hit that
  4. Restructure downstream code to consume columns (arrow/series) instead of record arrays

Example fix

# before
arr = df.to_numpy(structured=True, allow_copy=False)  # RuntimeError if df non-empty

# after
arr = df.to_numpy(structured=True, allow_copy=True)
Defensive patterns

Strategy: validation

Validate before calling

structured = True
allow_copy = False
if structured and not allow_copy and not df.is_empty():
    raise RuntimeError('structured conversion requires a copy for non-empty frames')
arr = df.to_numpy(structured=structured, allow_copy=allow_copy)

Try / catch

try:
    arr = df.to_numpy(structured=True, allow_copy=False)
except RuntimeError as e:
    if 'copy not allowed' in str(e):
        arr = df.to_numpy(structured=True, allow_copy=True)
    else:
        raise

Prevention

When it happens

Trigger: `df.to_numpy(structured=True, allow_copy=False)` with `df.height > 0`; dataframe-interchange / `__dataframe__` consumers that request zero-copy and then hit a structured conversion; any pipeline that sets allow_copy=False globally and later requests a record array.

Common situations: Building zero-copy interchange adapters (e.g. feeding tools that honor the df interchange protocol); copy-avoidance audits in memory-constrained ETL; converting structured output for libraries that only accept record arrays while enforcing strict no-copy policies.

Related errors


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