Command Palette

Search for a command to run...

Hectal
PHASE 14Intermediate ~31 min· topic 2 of 10

Topic 14.2

Indexing, Slicing and Reshaping Arrays

In one line

You pick elements out of an array with indices, slices, boolean masks and lists of positions. Slices are views that share memory with the original, and reshape changes the shape without changing the numbers.

Think of it like this

A spreadsheet. "Cell B3" picks one cell, "the whole of column B" picks a strip, "all rows where sales are above 100" picks by a condition. NumPy does the same on arrays, and you describe what you want rather than looping to find it.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Index
The position of an element, counted from 0. Negative numbers count from the end.
Slice
A range written start:stop:step. The stop is not included.
View
An array that looks at another array's memory. Changing one changes the other.
Copy
A new array with its own memory.
Boolean mask
An array of True and False used to choose elements.
Fancy indexing
Choosing elements with a list or array of positions.
Reshape
Giving the same numbers a different shape.
Axis
One dimension of the array. axis=0 is the first, axis=1 the second.

Step by step

01Index with one number per axis

np.arange(12).reshape(3, 4) builds the numbers 0 to 11 and lays them out in 3 rows of 4. grid[1, 2] means row 1, column 2, which is 6. Note it is grid[1, 2], not grid[1][2]. The second form works but builds an intermediate array and is slower.

main.pywhole filepython
import numpy as np

grid = np.arange(12).reshape(3, 4)
print(grid[1, 2])    # 6

02Slices pick rows, columns and blocks

grid[:, 0] is every row of column 0. grid[0:2, 1:3] is rows 0 and 1 and columns 1 and 2, a 2-by-2 block. Each axis has its own slice and the stop is excluded, exactly like list slices.

Slices pick rows, columns and blocksdiagram
Rendering diagram…

03A slice is a view, not a copy

view = grid[0, :] does not copy the row. view[0] = 99 changes grid[0, 0] as well. np.shares_memory(a, b) tells you whether two arrays overlap.

Recall from Topic 4.9: two names can point at one list, so changing it through one name shows through the other. This is the same idea, hidden behind slicing syntax (list slices copy, array slices do not). Use .copy() when you plan to change the piece and keep the original.

main.pywhole filepython
view = grid[0, :]
view[0] = 99
print(grid[0])          # [99  1  2  3]

copy = grid[1, :].copy()
copy[0] = -1
print(grid[1])          # unchanged

04Boolean masks choose by condition

A comparison on an array returns a mask. Using the mask as the index keeps the elements where it is True. The mask's .sum() counts the True values (True is 1). Combining two conditions needs brackets and &: (scores > 50) & (scores < 80).

Assigning through a mask edits the original: scores[scores < 50] = 50 raises every low score to 50. This replaces a loop with an if inside.

main.pywhole filepython
scores = np.array([72, 55, 90, 41, 68, 85])
mask = scores >= 60
print(scores[mask])        # [72 90 68 85]
print(mask.sum())          # 4

05Fancy indexing and reshape

A list of positions picks exactly those elements, in the order you gave and with repeats. x.reshape(2, 3) builds a 2-row, 3-column array of the same six numbers (a view when the memory layout allows it, otherwise a copy). A wrong total is an error: six numbers cannot become (4, 2).

reshape(-1, 2) says "2 columns, work out the rows". Only one -1 is allowed per call.

06Reducing along an axis

m.sum(axis=0) adds down the columns: for a (2, 3) array the result has shape (3,). m.sum(axis=1) adds across the rows and has shape (2,). A trick for remembering: the axis you name is the one that disappears.

Reducing along an axisdiagram
Rendering diagram…

Try it yourself

  1. 1

    Slice the last column

    With grid from the first example, write grid[:, -1] and grid[-1, :]. Which is a column and which is a row? Print each shape.

  2. 2

    Use and, then fix it

    Replace & with and in the between-50-and-80 line and read the error. Then put & back.

  3. 3

    Predict the axis

    For m = np.arange(6).reshape(2, 3) predict m.max(axis=0) and m.max(axis=1) before you run them.

Code & diagrams

Indexing, slices, and views New tab

Add print(grid) at the end: you will see that row 0 now starts with 99 because the slice was a view.

Sign in to run this example in your browser.

Expected output

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
row 1: [4 5 6 7]
cell [1, 2]: 6
column 0: [0 4 8]
block: [[1, 2], [5, 6]]
after changing the slice, original row 0: [99  1  2  3]
after changing a copy, original row 1: [4 5 6 7]
shares memory: True False
Masks, fancy indexing, reshape and axis New tab

Change 60 to 70 in the mask and predict which scores survive before you run it. This is the shape of a real task: keep the exam results that pass, or the log lines whose response time is too slow.

Sign in to run this example in your browser.

Expected output

mask: [ True False  True False  True  True]
passed: [72 90 68 85]
count passed: 4
between 50 and 80: [72 55 68]
fancy: [72 90 90 85]
floored: [72 55 90 50 68 85]
reshape 2x3: [[0, 1, 2], [3, 4, 5]]
reshape -1,2: [[0, 1], [2, 3], [4, 5]]
flatten: [0 1 2 3 4 5]
sum axis 0: [3 5 7] sum axis 1: [ 3 12]
ValueError: cannot reshape array of size 6 into shape (4,2)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Python's and on arrays

Combine two conditions with the word and.

main.pywhole filepython
import numpy as np

scores = np.array([72, 55, 90])
print(scores[(scores > 50) and (scores < 80)])
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 4, in <module>
print(scores[(scores > 50) and (scores < 80)])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Myth vs fact

Myth

Slicing an array makes a copy, like slicing a list.

Fact

List slices copy. Array slices are views that share memory. Masks and fancy indexing do copy.

Myth

axis=0 means rows.

Fact

axis=0 is the first dimension, and a reduction along it combines the rows into one result per column.

Myth

reshape moves the data around.

Fact

It usually only changes the header (shape and strides) and returns a view of the same numbers.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Rule of thumb for views versus copies: basic slicing (a[1:3], a[:, 0], a.T, usually reshape) gives views; boolean and fancy indexing give copies. arr.base is None for an array that owns its data. Check np.shares_memory rather than guessing.

  • ▸

    reshape returns a view when the data layout allows it and silently copies when it does not (for example after a transpose). Code that relies on a view being shared should not depend on this.

  • ▸

    Writing into a mask result does not change the original (scores[mask][0] = 0 edits a temporary copy), but scores[mask] = 0 does. The two look alike and behave differently, a classic source of bugs.

  • ▸

    ravel() returns a view when it can and a copy when it must, while flatten() always copies. With fancy-index assignment, repeated positions do not accumulate: x[[0, 0, 1]] += 1 raises element 0 by only 1, because NumPy reads the old value once and writes it back twice. Use np.add.at(x, [0, 0, 1], 1) (or np.bincount) when repeats must count.

  • ▸

    Boolean masks scan the whole array and allocate a new one, which is fine for filtering but wasteful if you only want to count: mask.sum() or np.count_nonzero(mask) never builds the filtered array. For axes, sum(axis=(0, 1)) reduces several axes at once on higher-dimensional arrays, and keepdims=True keeps them as size 1 so the result broadcasts back.

Remember this

  1. 1

    One dimension works like a list: a[0], a[-1], a[1:4]. In two dimensions you give one index per axis, separated by a comma: grid[1, 2] is row 1, column 2. A bare : means "everything along this axis", so grid[:, 0] is column 0 and grid[1] (or grid[1, :]) is row 1.

  2. 2

    A slice (start:stop:step) does not copy. It returns a view: a new array header looking at the same memory. Change the view and the original changes. This is deliberate (copying a huge array is slow) and it surprises everyone once. Call .copy() when you want an independent array.

  3. 3

    A boolean mask is an array of True/False of the same shape. scores >= 60 produces one; scores[mask] keeps only the True positions. Combine conditions with & (and), | (or) and ~ (not). Put each condition in brackets. Without them, Python runs & before the comparisons and you get an error. Python's own and and or do not work on arrays.

  4. 4

    Fancy indexing means indexing with a list or array of positions: scores[[0, 2, 2, 5]] returns those elements in that order, repeats allowed. Unlike slices, fancy indexing and boolean masks return copies. But you can also assign through them: scores[scores < 50] = 50 replaces every low score in place.

  5. 5

    reshape gives the same numbers a new shape, as long as the total count matches: 6 numbers fit (2, 3), (3, 2), (6,) or (1, 6), but not (4, 2). Use -1 for one dimension to let NumPy work it out: x.reshape(-1, 2) means "2 columns, however many rows are needed". ravel() flattens to 1-D.

  6. 6

    Many functions take an axis: the direction to work along. Picture a mark sheet. To get each student's total you add across the row. To get each subject's total you add down the column. For a 2-D array, axis=0 goes down the rows (one result per column) and axis=1 goes across the columns (one result per row). The axis you name disappears from the result's shape. If you ever mix them up, check the result's shape.

  7. 7

    In AI work these moves are daily. A token is a small piece of text (often a word or part of a word) that a language model reads one at a time (Topic 16.4). You slice off the last token's vector, select rows by a mask of valid examples, or reshape (batch, tokens * features) into (batch, tokens, features). A batch is a group of examples handled together.

Explain it without notes

01

What is the difference between a view and a copy? Which indexing methods give each?

02

How do you select the elements of an array that satisfy two conditions? Why not use and?

03

What does reshape(-1, 2) do, and when does reshape fail?

04

For a (2, 3) array, what are the shapes of sum(axis=0) and sum(axis=1) and why?

05

A function receives an array, sets arr[0] = 0 and the caller's data changes unexpectedly. Why, and how do you design around it?

Practice

01

From x = np.arange(1, 11), print only the even numbers using a mask (x % 2 == 0).

02

From grid = np.arange(9).reshape(3, 3), print the middle row, the last column, and the 2-by-2 block in the top left corner.

03

Given a = np.array([5, 1, 4]), make b = a[:2].copy(), set b[0] = 0, and show that a did not change.

04

For a = np.arange(10), use np.shares_memory to show that a[6:] is a view but a[a > 5] is a copy. Then use a slice to set the last four elements of a to 0 and print a.

Trade-offs

  • ↔

    Views are fast and memory-friendly, copies are safe. A view avoids copying a huge array, at the risk of changing the original by accident.

  • ↔

    Masks versus loops. A mask is shorter and much faster; a loop is easier when each element needs complex steps.

  • ↔

    Fancy indexing copies. It is flexible (any order, repeats) but costs memory on very large selections.

Done when you can

  • Done when you can read grid[1, 2], grid[:, 0] and grid[0:2, 1:3] and say what each picks.

  • Done when you can explain why changing a slice changes the original, and how to avoid it.

  • Done when you can filter with a mask and combine conditions with &, | and brackets.

  • Done when you can reshape with -1 and explain why (4, 2) fails for six numbers.

  • Done when you can say which axis a sum(axis=...) removes.