2025, Nov 16 03:00

Fixing NumPy tuple indexing with np.ndindex and Ellipsis: unpack indices correctly (Python 3.11+)

Learn why np.ndindex tuples with Ellipsis misindex in NumPy and how to unpack tuple indices correctly: use scores[(*pos, ...)] or Python 3.11 tuple unpacking.

Indexing NumPy arrays with tuples is convenient until it suddenly isn’t. A frequent pitfall appears when you iterate with np.ndindex and then try to use the produced tuple together with Ellipsis. The result looks like multi-axis indexing, but the array reads it differently, leading to unexpected assignments or lookups.

The setup

for pos in np.ndindex(cfg_dims):
    ...
    scores[pos, ...] = ...

If pos is (0, 1), the intention is to write into scores[0, 1, ...]. Instead, this behaves as if you weren’t expanding the tuple at all, which doesn’t target the intended slice.

What’s really happening

Inside the square brackets, Python builds a single indexing tuple. When you pass pos as the first element and add Ellipsis as the second, the tuple of indices stays nested instead of being split across axes. In other words, the elements of pos are not applied as separate dimensions. That’s why the indexing doesn’t land where you expect. You want a flat set of indices, not a tuple sitting inside the index tuple.

The fix

Unpack the tuple so that each coordinate becomes a top-level index. To make that work, place the unpacked values inside another tuple together with Ellipsis.

for pos in np.ndindex(cfg_dims):
    ...
    scores[(*pos, ...)] = ...

For this specific pattern, when the last slice is Ellipsis, it’s not needed. If your use case matches that shape, you can index directly with the tuple:

for pos in np.ndindex(cfg_dims):
    ...
    scores[pos] = ...

There is also a shorter subscript form that unpacks the tuple directly in the index. It works in Python 3.11 and above:

for pos in np.ndindex(cfg_dims):
    ...
    scores[*pos, ...] = ...

Why this matters

Iterating over grids with np.ndindex is a common pattern in numerical code, parameter sweeps, and log-likelihood surfaces. If indexing does not land on the intended axes, you may silently read from or write to the wrong region of the array. That’s a hard class of bugs to spot by eye, especially when the code looks perfectly reasonable. Knowing how to expand tuple indices correctly helps keep array operations predictable.

Takeaways

When a tuple of coordinates should address multiple axes, unpack it so each element becomes a standalone index. Use scores[(*pos, ...)] if you also need Ellipsis in that position. If Ellipsis is the last slice, simplify to scores[pos]. And where available, the concise scores[*pos, ...] is a clean option in Python 3.11+.