2.2 Indexing, Slicing, and Boolean Masks
The previous section gave arrays shapes and axis meanings. Selection now becomes a coordinate problem: use integer positions when you know where values are, slices when positions form a regular interval, and Boolean masks when inclusion depends on the values themselves.
We will reuse a five-day by eight-hour order matrix. Rows are weekdays; columns are hours from 14:00 through 21:00.
orders = np.array([
[12, 15, 18, 24, 31, 36, 28, 17],
[11, 14, 20, 27, 35, 39, 30, 19],
[13, 16, 21, 29, 38, 42, 32, 20],
[10, 13, 19, 26, 34, 37, 29, 18],
[14, 18, 23, 32, 41, 46, 35, 22],
])Integer indexing locates coordinates
A comma separates coordinates along successive axes:
tuesday_at_17 = orders[1, 3]
friday = orders[4]
last_hour = orders[:, -1]orders[1, 3] selects one scalar. orders[4] supplies only the axis-0 coordinate, so the entire row remains. A colon means “all positions on this axis.” Negative index -1 counts from the end.
These are positional coordinates, not labels. NumPy does not know that row 1 means Tuesday or column 3 means 17:00. Keep labels separately and verify that they remain aligned whenever rows or columns are reordered.
Slices select regular intervals
A slice has the form start:stop:step. It includes start, excludes stop, and advances by step:
peak = orders[1:4, 3:7]
every_other_hour = orders[1:4, 3:7:2]The first expression selects rows 1, 2, and 3, plus columns 3, 4, 5, and 6. Its shape is (3, 4). The second keeps the same rows but selects columns 3 and 5, producing shape (3, 2).
Before running a slice, predict its coordinates and output shape. That habit catches swapped axes and off-by-one errors earlier than inspecting a plausible-looking table.
A slice can share memory
Basic NumPy slices often return a view, an array that refers to the same underlying data rather than copying it. That behavior is efficient, but mutation can surprise you:
window = orders[1:3, 2:5]
window[0, 0] = -1
print(orders[1, 2]) # -1: the original changed tooIf an independent result is required, request one:
window_copy = orders[1:3, 2:5].copy()Chapter 3 will examine views, copies, and memory layout in depth. For now, treat mutation of any sliced array as an operation that needs an explicit decision.
Boolean masks select by condition
An expression such as minutes <= 40 produces a Boolean array with one True or False for every element:
minutes = np.array([31.0, np.nan, 52.0, 68.0, 44.0])
within_target = minutes <= 40
print(within_target)
# [ True False False False False]Using the Boolean array inside brackets keeps values where the mask is True:
selected = minutes[within_target]A mask must align with the axis or array it filters. A length-5 mask can filter a length-5 one-dimensional array. For a two-dimensional array, a full Boolean mask of the same shape selects matching cells and normally returns a one-dimensional result; a one-dimensional row mask can select whole rows.
Combine conditions element by element
Real filters usually need several conditions:
zones = np.array(["North", "South", "South", "South", "South"])
cancelled = np.array([False, False, False, False, True])
mask = (
~np.isnan(minutes)
& (minutes <= 60)
& (zones == "South")
& ~cancelled
)
selected_minutes = minutes[mask]Use & for element-wise “and,” | for element-wise “or,” and ~ for element-wise negation. Put each comparison in parentheses because these operators have different precedence from comparison operators. Python’s and, or, and not expect one truth value; an array contains many, so NumPy refuses the ambiguous instruction.
Keep masks inspectable
A long one-line filter can be correct and still be difficult to audit. Name important sub-conditions:
has_time = ~np.isnan(minutes)
is_south = zones == "South"
is_active = ~cancelled
within_limit = minutes <= 60
analysis_mask = has_time & is_south & is_active & within_limitNow you can count records rejected by each rule:
print(np.count_nonzero(~has_time))
print(np.count_nonzero(analysis_mask))The mask is part of the evidence boundary. Filtering cancelled or missing records changes the population and denominator, so the final report must disclose those exclusions.
Selections prepare inputs for computation. The next section removes unnecessary Python loops and introduces broadcasting, NumPy’s shape-based rule for combining arrays without manually repeating data.