Chapter 6: NumPy Array Indexing
NumPy Array Indexing written as if a patient teacher is sitting next to you, showing examples on the screen, explaining the logic, warning about common traps, and showing realistic patterns you will actually use.
Let’s go slowly and thoroughly — indexing is one of the most important skills in NumPy.
|
0 1 2 3 4 5 6 |
import numpy as np |
1. Basic Indexing – Like lists, but more powerful
|
0 1 2 3 4 5 6 7 8 9 10 11 |
arr = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) print(arr[0]) # 10 print(arr[3]) # 40 print(arr[-1]) # 100 ← last element print(arr[-3]) # 80 ← third from the end |
2D array example
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
matrix = np.array([ [ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12], [13, 14, 15, 16] ]) print(matrix[0, 0]) # 1 print(matrix[2, 3]) # 12 print(matrix[3, -1]) # 16 print(matrix[-1, 0]) # 13 ← last row, first column |
Important rule to remember early:
|
0 1 2 3 4 5 6 7 |
In 2D: matrix[row, column] In 3D: array[depth, row, column] or [z, y, x] |
2. Slicing – The most frequently used operation
Slicing syntax: start:stop:step
All three parts are optional.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
a = np.arange(20) # [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19] print(a[3:8]) # [3 4 5 6 7] print(a[10:]) # [10 11 12 13 14 15 16 17 18 19] ← from index 10 to end print(a[:5]) # [0 1 2 3 4] ← from beginning to 4 print(a[::2]) # [ 0 2 4 6 8 10 12 14 16 18] ← every second element print(a[1::3]) # [ 1 4 7 10 13 16 19] print(a[::-1]) # [19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0] ← reverse |
2D slicing examples (very common)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
print(matrix) # [[ 1 2 3 4] # [ 5 6 7 8] # [ 9 10 11 12] # [13 14 15 16]] print(matrix[1:3, 2:]) # rows 1–2, columns 2 to end # [[ 7 8] # [11 12]] print(matrix[:, 1]) # all rows, second column # [ 2 6 10 14] print(matrix[2, :]) # third row, all columns # [ 9 10 11 12] print(matrix[::2, ::2]) # every other row and column # [[ 1 3] # [ 9 11]] |
3. Boolean Indexing – Extremely powerful (you will use this daily)
Instead of numbers, you give a boolean mask of the same shape.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
scores = np.array([78, 92, 65, 88, 71, 95, 82, 59, 67, 91]) passed = scores >= 70 print(passed) # [ True True False True True True True False False True] print(scores[passed]) # [78 92 88 71 95 82 91] # Even shorter & very common: high_scores = scores[scores >= 90] print(high_scores) # [92 95 91] |
Realistic example – cleaning data
|
0 1 2 3 4 5 6 7 8 9 10 |
ages = np.array([23, 45, 12, 67, 34, 8, 29, 91, 55, 41]) # Remove impossible ages valid_ages = ages[(ages >= 0) & (ages <= 120)] print(valid_ages) |
Replacing values conditionally (very frequent pattern)
|
0 1 2 3 4 5 6 7 8 9 10 |
data = np.random.randn(10) print(data) data[data < 0] = 0 # set all negative values to zero print(data) |
4. Fancy Indexing – Using arrays/lists of indices
You can pass an array or list of indices → picks elements in that order.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
values = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) idx = [1, 4, 7, 9] print(values[idx]) # [20 50 80 100] # Can also use numpy array of indices idx2 = np.array([0, 3, 6]) print(values[idx2]) # [10 40 70] |
2D fancy indexing – very common pattern
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
print(matrix) rows = [0, 2, 3] cols = [1, 3, 2] print(matrix[rows, cols]) # [ 2 12 15 ] ← matrix[0,1], matrix[2,3], matrix[3,2] |
5. Combining styles – Very powerful & common
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Boolean + slicing large = matrix[matrix > 8] # gets all values > 8 as 1D array # Fancy + slicing important_cols = [1, 3] sub = matrix[:, important_cols] # all rows, only columns 1 and 3 # Boolean mask on rows good_rows = np.any(matrix > 14, axis=1) # rows that have at least one value > 14 print(matrix[good_rows]) |
6. Views vs Copies – The trap almost everyone falls into
Most indexing creates a view (not a copy) → changes affect original array!
|
0 1 2 3 4 5 6 7 8 9 10 11 |
a = np.arange(12).reshape(3, 4) view = a[1:3, 1:3] # ← this is a VIEW view[0, 0] = 999 print(a) # original array changed! |
How to force a copy:
|
0 1 2 3 4 5 6 7 |
copy1 = a[1:3, 1:3].copy() copy2 = np.copy(a[1:3, 1:3]) |
Quick rule of thumb (very useful):
| Operation | Usually returns |
|---|---|
| Basic slicing a[2:5] | View |
| Boolean indexing a[a>0] | Copy |
| Fancy indexing a[[1,3,5]] | Copy |
| a[…] with lists/arrays | Copy |
| .copy() | Copy |
7. Realistic patterns you will write again and again
Pattern 1: Normalize only selected columns
|
0 1 2 3 4 5 6 7 8 |
X = np.random.randn(1000, 20) important = [0, 3, 7, 12, 19] X[:, important] = (X[:, important] - X[:, important].mean(axis=0)) / X[:, important].std(axis=0) |
Pattern 2: Remove rows with outliers
|
0 1 2 3 4 5 6 7 8 |
data = np.random.normal(100, 15, (500, 10)) outliers = np.any(np.abs(data - 100) > 45, axis=1) clean = data[~outliers] |
Pattern 3: Get top-k scores
|
0 1 2 3 4 5 6 7 8 |
scores = np.random.randint(40, 101, 200) top_indices = np.argsort(scores)[-10:] # indices of top 10 top_scores = scores[top_indices] |
Summary Table – Quick Reference
| You want… | Syntax example |
|---|---|
| Single element | arr[3], mat[2,1] |
| Row / column | mat[1], mat[:,3] |
| Slice | arr[2:7], mat[1:4, 2:] |
| Every nth element | arr[::3], arr[::-1] (reverse) |
| Boolean filter | arr[arr > 50], arr[arr % 2 == 0] |
| Replace conditionally | arr[arr < 0] = 0 |
| Fancy indexing (pick specific indices) | arr[[1,4,7]], mat[[0,2], [1,3]] |
| Get copy (not view) | arr[slice].copy() |
Where would you like to go next?
- Advanced indexing tricks (np.ix_, masking in multiple dimensions)
- Differences between view vs copy in depth (with memory examples)
- Indexing in 3D arrays / images
- Boolean indexing + assignment patterns
- Common bugs people make with indexing
- Mini-project using indexing (data filtering & cleaning)
Just tell me what feels most useful right now! 😊
