pandas-dev/pandas · error · ValueError

Cannot modify read-only array

Error message

Cannot modify read-only array

What it means

Raised in SparseArray.__setitem__ when the instance's _readonly flag is set. pandas marks a SparseArray read-only when it was built from a view of an immutable/read-only buffer (e.g. the no-gap fast path in __array__ propagates the flag), so mutation would corrupt shared memory. It is the first guard in __setitem__.

Source

Thrown at pandas/core/arrays/sparse/array.py:611

            if self.sp_values.dtype.kind == "M":
                # However, we *do* special-case the common case of
                # a datetime64 with pandas NaT.
                if fill_value is NaT:
                    # Can't put pd.NaT in a datetime64[ns]
                    unit = np.datetime_data(self.sp_values.dtype)[0]
                    fill_value = np.datetime64("NaT", unit)  # type: ignore[call-overload]
            try:
                dtype = np.result_type(self.sp_values.dtype, type(fill_value))
            except TypeError:
                dtype = object

        out = np.full(self.shape, fill_value, dtype=dtype)
        out[self.sp_index.indices] = self.sp_values
        return out

    def __setitem__(self, key, value) -> None:
        if self._readonly:
            raise ValueError("Cannot modify read-only array")
        # I suppose we could allow setting of non-fill_value elements.
        # TODO(SparseArray.__setitem__): remove special cases in
        # ExtensionBlock.where
        msg = "SparseArray does not support item assignment via setitem"
        raise TypeError(msg)

    def sort(
        self,
        *,
        ascending: bool = True,
        kind: SortKind = "quicksort",
        na_position: str = "last",
    ) -> None:
        raise NotImplementedError("SparseArray does not support in-place sort")

    @classmethod
    def _from_sequence(
        cls, scalars, *, dtype: Dtype | None = None, copy: bool = False

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Copy before mutating: arr = arr.copy(); arr[0] = 5 (note: SparseArray forbids setitem entirely, so prefer rebuilding).
  2. Rebuild via _from_sequence with modified data instead of setitem.
  3. Avoid __setitem__ on SparseArray altogether; construct a new SparseArray from the modified dense values.

Example fix

// before
arr[0] = 5
// after
dense = arr.to_dense(); dense[0] = 5; arr = pd.arrays.SparseArray(dense)
Defensive patterns

Strategy: try-catch

Validate before calling

import pandas as pd

def setitem_sparse_safe(arr, idx, value):
    if getattr(arr, '_readonly', False):
        arr = arr.copy()
    dense = arr.to_dense()
    dense[idx] = value
    return pd.arrays.SparseArray(dense, dtype=arr.dtype)

Type guard

def is_writable_sparse(arr) -> bool:
    return not getattr(arr, '_readonly', False)

Try / catch

try:
    arr[0] = value
except (ValueError, TypeError) as e:
    if 'read-only' in str(e) or 'setitem' in str(e):
        dense = arr.to_dense(); dense[0] = value
        arr = pd.arrays.SparseArray(dense, dtype=arr.dtype)
    else:
        raise

Prevention

When it happens

Trigger: arr[0] = 5 on a SparseArray produced by slicing/viewing a read-only source; setting items on an array exposed via .values from a read-only-backed Series; in-place writes after np.asarray(arr) where numpy returned a read-only view.

Common situations: Operating on arrays handed back from numpy interop that mark them non-writeable; multiprocessing/ shared-memory pipelines; defensive read-only flags set by upstream code.

Related errors


AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07). Data as JSON: /api/errors/c31948fc2f491591. Report an issue: GitHub.