pandas-dev/pandas · error · ValueError

'indices' must be an array, not a scalar '{indices}'.

Error message

'indices' must be an array, not a scalar '{indices}'.

What it means

Raised by SparseArray.take when the `indices` argument is a Python/numpy scalar. The take protocol (NEP 29 / ExtensionArray.take) requires a 1-d array of positions because it must build a new SparseArray of the same length as indices; a single scalar has no length to drive that. Pandas raises explicitly rather than letting np.asarray produce a 0-d array that later fails confusingly.

Source

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

        if loc < 0:
            loc += n

        if loc >= n or loc < 0:
            raise IndexError(
                f"index is out of bounds: must be an integer between -{n} and {n - 1}"
            )

        sp_loc = self.sp_index.lookup(loc)
        if sp_loc == -1:
            return self.fill_value
        else:
            val = self.sp_values[sp_loc]
            val = maybe_box_datetimelike(val, self.sp_values.dtype)
            return val

    def take(self, indices, *, allow_fill: bool = False, fill_value=None) -> Self:
        if is_scalar(indices):
            raise ValueError(f"'indices' must be an array, not a scalar '{indices}'.")
        indices = np.asarray(indices, dtype=np.int32)

        dtype = None
        if indices.size == 0:
            result = np.array([], dtype="object")
            dtype = self.dtype
        elif allow_fill:
            result = self._take_with_fill(indices, fill_value=fill_value)
        else:
            return self._take_without_fill(indices)

        return type(self)(
            result, fill_value=self.fill_value, kind=self.kind, dtype=dtype
        )

    def _take_with_fill(self, indices, fill_value=None) -> np.ndarray:
        if fill_value is None:
            fill_value = self.dtype.na_value

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Pass a 1-d array: sparse_arr.take([2]) or sparse_arr.take(np.array([2], dtype=np.int32)).
  2. For single-position access use sparse_arr._get_val_at(int(idx)) or wrap the array in a Series and use .iloc[int(idx)].
  3. Normalize at the boundary: idx = np.atleast_1d(np.asarray(idx, dtype=np.int32)) before calling take.

Example fix

// before
val = sparse_arr.take(3)  # raises 'indices must be an array'

// after
val = sparse_arr.take([3])
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np

def take_safe(arr, indices):
    idx = np.atleast_1d(np.asarray(indices, dtype=np.int32))
    return arr.take(idx)

Type guard

import numpy as np

def is_array_indices(indices) -> bool:
    return hasattr(indices, '__len__') or np.asarray(indices).ndim >= 1

Try / catch

try:
    out = arr.take(indices)
except ValueError as e:
    if 'must be an array' in str(e):
        out = arr.take([int(indices)])
    else:
        raise

Prevention

When it happens

Trigger: Calling sparse_arr.take(2), pd.api.extensions.take(arr, 5), or sparse_arr[[2]] where the list was collapsed to a scalar by upstream code. Also from .reindex internals that hand a scalar indexer to take.

Common situations: Mixing scalar .iloc[pos] expectations with the .take API, or writing helper functions that accept 'index or indices' and forward the value unchanged to take.

Related errors


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