pandas-dev/pandas · error · ValueError

Cannot modify read-only array

Error message

Cannot modify read-only array

What it means

Raised as ValueError by SparseArray.__setitem__ when the instance is marked read-only (self._readonly is True). Even on writable instances the next line raises TypeError ('does not support item assignment via setitem'), so SparseArray item assignment is unsupported in general; the read-only guard fires first for frozen arrays.

Solutions

  1. Replace, don't mutate: build a new SparseArray with the modified values.
  2. If you need mutability, materialize via np.asarray(arr) (which is writable) and rebuild a SparseArray after editing.
  3. Use pandas Series with a sparse dtype and assign via .loc for index-aligned updates.

Example fix

# before
arr[i] = new_value  # SparseArray, read-only
# after
vals = np.asarray(arr).copy()
vals[i] = new_value
new_arr = pd.arrays.SparseArray(vals, fill_value=arr.fill_value)
Defensive patterns

Strategy: fallback

Validate before calling

def sparse_array_is_writable(sparse_arr) -> bool:
    return not getattr(sparse_arr, '_readonly', False)

Type guard

def writable_sparse(sparse_arr) -> bool:
    return not bool(getattr(sparse_arr, '_readonly', False))

Try / catch

try:
    sparse_arr[i] = value
except (ValueError, TypeError):
    vals = np.asarray(sparse_arr).copy()
    vals[i] = value
    sparse_arr = pd.arrays.SparseArray(vals, fill_value=sparse_arr.fill_value)

Prevention

When it happens

Trigger: Calling arr[i] = value on a SparseArray exposed via .values/.to_numpy() with writeable=False; mutating a SparseArray returned from a zero-copy path that set the read-only flag.

Common situations: Treating SparseArray like a numpy array and trying in-place writes; reading from a memoryview-backed buffer that pandas marks read-only; libraries that freeze arrays for caching.

Related errors


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

Appendix: source

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

            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 3b7651241d)