Array shapes, axes, broadcasting and safe copies
On this page
The NumPy lessons introduce vectors, matrices, selection and universal functions. This lesson makes their structure explicit so that later tables, feature matrices and image batches have a clear meaning. You should already be able to create an array, inspect its shape and select a slice.
Name the axes before calculating
Consider three cultural institutions measured in two years. Rows identify institutions and columns identify years. Reducing over axis 0 removes the institution axis, producing one result for each year; reducing over axis 1 removes the year axis, producing one result for each institution. The number alone does not tell a reader which interpretation was intended.
import numpy as np
visits = np.array([[10, 14], [20, 22], [30, 36]])
print(visits.shape)
print(visits.sum(axis=0))
print(visits.sum(axis=1))
assert visits.sum(axis=0).tolist() == [60, 72]
assert visits.sum(axis=1).tolist() == [24, 42, 66](3, 2)
[60 72]
[24 42 66]
The totals across years combine different time periods. They may describe recorded visits, but they do not count distinct visitors unless the underlying records support that interpretation. Naming axes is therefore part of explaining the result, not just part of debugging code.
Distinguish a vector, a row and a column
Shapes (3,), (1, 3) and (3, 1) all contain three elements, but they do not express the same arrangement. Adding a dimension can be useful when you intend to apply one adjustment per row. Reshaping changes the arrangement of existing elements; it does not discover which observation belongs to which group. A reshape is justified only when you know the original order.
offsets = np.array([1, 2, 3])
print(offsets.shape, offsets[None, :].shape, offsets[:, None].shape)
adjusted = visits + offsets[:, None]
print(adjusted)
assert adjusted.tolist() == [[11, 15], [22, 24], [33, 39]](3,) (1, 3) (3, 1)
[[11 15]
[22 24]
[33 39]]
NumPy compares broadcasting dimensions from the right; dimensions must match or one must be 1. The inserted length-one dimension makes the row adjustment explicit. Consult the NumPy broadcasting guide for the formal rule. Never solve a shape error merely by trying reshapes until the code stops complaining: an operation can be syntactically valid and scientifically wrong.
Understand a view before changing it
A basic NumPy slice commonly shares memory with its source; advanced indexing creates a copy. An explicit .copy() is useful when a selected part must be changed independently. These distinctions are documented in NumPy’s copies and views guide.
original = np.array([5, 10, 15, 20])
view = original[1:3]
view[0] = 99
print(original)
separate = original[1:3].copy()
separate[0] = -1
print(original)
assert original.tolist() == [5, 99, 15, 20]
print(np.shares_memory(original, view), np.shares_memory(original, separate))[ 5 99 15 20]
[ 5 99 15 20]
True False
The first change reaches the original array; the second does not. This is useful when intentional and dangerous when you thought you had preserved a baseline. Keep the raw source separate from exploratory work and check a small example before applying an unfamiliar operation to a large array.
Check values, types and storage
A shape check cannot detect an infinite value. A numeric type cannot prove that an identifier should be treated as a quantity. Postal codes with leading zeros, for example, are labels even if they contain only digits. For a numerical calculation, inspect the type and check whether all required values are finite.
values = np.array([2.0, np.nan, np.inf, 8.0])
print(np.isfinite(values))
print(values[np.isfinite(values)].mean())
print(visits.dtype, visits.size, visits.nbytes)[ True False False True]
5.0
int64 6 48
The mean of retained finite values is 5.0, but filtering is a decision, not a universal cleaning policy. Report how many observations were excluded and why. If missingness is related to the phenomenon you study, the retained values may describe a different population. Memory size also matters: creating a temporary broadcasted result can require substantial storage even when the input arrays are small.
Practice and independent check
Create a 4-by-3 array whose rows represent institutions and columns represent months. Calculate one total per month and one mean per institution. State the units of both outputs. Apply a different adjustment to each institution without using a Python loop. Verify the result for one row by hand.
Next, demonstrate the difference between a view and a copy using your own values. An adequate explanation names the shared storage and predicts which object will change. “NumPy is faster” does not explain this behaviour. Finally, show a shape-compatible operation that would mix up the meaning of rows and columns, and explain why you reject it.
Transition
Arrays supply the structure for numerical reasoning. The mathematical supplement that follows develops checks on summaries, systems of equations and random experiments, so that correct shapes lead to defensible calculations.