pythonforbio.
[WASM idle]
NumPy06/18

Indexing and Slicing

Starting sandbox…
Beginnerlesson2 tests

Indexing and Slicing

In one line: basic indexing with integers and slices returns a view; anything fancier returns a copy.

That distinction is the whole note. Everything else is syntax.

Basic indexing

a = np.arange(24).reshape(2, 3, 4)

a[0]              # first slab      → shape (3, 4)
a[0, 1]           # → shape (4,)
a[0, 1, 2]        # a scalar
a[0][1][2]        # same value, but three temporary arrays — don't
a[:, 1, :]        # → shape (2, 4)
a[..., 0]         # Ellipsis: "all remaining axes"  → shape (2, 3)
a[::2]            # every other
a[::-1]           # reversed
a[-1]             # last

Use the comma form a[0, 1, 2], not the chained form a[0][1][2]. The chained form creates intermediate views at every step and is measurably slower.

... (Ellipsis) is the idiom for "index the last axis regardless of how many there are" — a[..., 0] works for 2-D, 3-D or 5-D input.

Slices are half-open and 0-based

seq[0:3]     # elements 0, 1, 2 — three of them

stop is excluded. This matches BED coordinates and not GFF/VCF/HGVS coordinates — see Coordinate Systems, which is where the off-by-one bugs in your genomics code will come from.

Slices are views

a = np.arange(10)
b = a[2:5]
b[0] = 999
a               # array([0, 1, 999, 3, 4, ...])  ← a changed
b.base is a     # True

No copy was made. b is a window onto a's memory. This is a feature — slicing a 10 GB array is instant — but it means functions that slice and modify silently mutate their caller's data.

b = a[2:5].copy()    # explicit, safe

Full discussion in Views vs Copies.

Assignment through a slice

a[2:5] = 0                # broadcast a scalar into the window
a[2:5] = [1, 2, 3]        # shape must match
a[a > 100] = 100          # clip via boolean mask
a[:, 0] = new_column

This is in-place mutation, which is the fast path — no new allocation.

The three indexing modes

Mode Syntax Returns
Basic integers, slices, ..., None view
Boolean a[mask] copy — see Boolean Masking
Integer array ("fancy") a0, 3, 5 copy — see Fancy Indexing

Mixing modes gets subtle. a[0, [1, 2]] mixes basic and fancy; a[[0, 1], [1, 2]] pairs the indices elementwise rather than taking a submatrix. When you want a submatrix, use np.ix_.

Adding and dropping axes

v = np.arange(3)          # shape (3,)
v[None, :]                # (1, 3)
v[:, None]                # (3, 1)
v[..., None]              # (3, 1)
a[:, 0]                   # drops the axis → shape (n,)
a[:, 0:1]                 # keeps it       → shape (n, 1)
np.squeeze(a)             # drop all length-1 axes

An integer index removes an axis; a length-1 slice keeps it. This trips people up constantly when a downstream function demands 2-D input.

Bioinformatics examples

# expression matrix: genes × samples
expr[0]                  # first gene, all samples
expr[:, 3]               # fourth sample, all genes → shape (n_genes,)
expr[:, 3:4]             # same data, shape (n_genes, 1) — keeps the axis

# extract a genomic window (converting from 1-based closed GFF coords)
region = coverage[start1 - 1 : end1]

# reverse complement of a one-hot encoded sequence: reverse position, flip base order
rc = onehot[::-1, ::-1]

# every third base — codon position 1
first_positions = seq_array[0::3]

Common mistakes

  • Modifying a slice and being surprised the original changed. Or the reverse: expecting a slice to be a view when the operation actually produced a copy.
  • a[0][1] = x on a copy-producing first step — the write goes to a temporary and vanishes. This is the NumPy analogue of pandas chained assignment. See Copy-on-Write.
  • Assuming a[[0,1], [2,3]] is a submatrix. It is [a[0,2], a[1,3]]. Use a[np.ix_([0,1],[2,3])].
  • Off-by-one at format boundaries. Python slicing is half-open; VCF/GFF/HGVS are closed. See Coordinate Systems.
  • Negative index confusion with slices. a[-1] is the last element; a[:-1] is everything except the last; a[::-1] is reversed.
  • Out-of-bounds slicing is silent. np.arange(5)[2:100] returns 3 elements, no error. Out-of-bounds integer indexing does raise. This asymmetry hides bugs at sequence ends.

See also

ndarray · Views vs Copies · Boolean Masking · Fancy Indexing · Coordinate Systems · loc vs iloc

Test cases · 2

#viainputexpected stdout
1stdin10 12 15 9 4 30 22 2 5[15, 9, 4]
2stdin1 2 3 4 5 0 3[1, 2, 3]

Hints · 2

01Hint

Slices are start:stop, with stop excluded.

02Hint

A negative index counts back from the end: arr[-1] is the last element.

starter

No output yet — run the code to populate this drawer.