What you'll learn
- Replace loops with array math
- Create arrays and read their shape, ndim and dtype
- Filter and count values with Boolean masks
- Combine arrays of different shapes with broadcasting
Replace slow Python loops with NumPy array math, and use broadcasting to combine arrays of different shapes.
Why this matters
Scientific and engineering data comes in bulk: 10,000 thermocouple readings, a 2,048-point spectrum, a strain gauge sampled at 1 kHz for an hour. Plain Python lists and for loops can handle that, but they are slow and wordy. NumPy gives you one object, the ndarray, that holds numbers of one type in a single block of memory and runs whole-array operations in compiled code. The result is code that is shorter, closer to the maths on paper, and often 10 to 100 times faster.
Almost every scientific Python library (SciPy, pandas, matplotlib, scikit-learn) is built on NumPy arrays, so this is the foundation for the rest of the course.
Core idea: an array is a grid of one type
import numpy as np
t = np.array([0.0, 0.5, 1.0, 1.5, 2.0]) # from a list
print(t.shape, t.dtype) # (5,) float64
x = np.linspace(0, 10, 101) # 101 evenly spaced points from 0 to 10 inclusive
k = np.arange(0, 1, 0.25) # [0. 0.25 0.5 0.75] (end excluded)
grid = np.zeros((3, 4)) # 3 rows, 4 columns of 0.0Key attributes:
shape: the size along each axis, such as(3, 4).ndim: the number of axes.dtype: the element type, usuallyfloat64for measurements orint64for counts.
Vectorisation: operate on the whole array
Arithmetic on arrays works element by element, with no loop in your code:
celsius = np.array([20.0, 25.5, 30.1, 18.9])
# Loop version
kelvin_loop = []
for c in celsius:
kelvin_loop.append(c + 273.15)
# Vectorised version
kelvin = celsius + 273.15NumPy's "universal functions" (ufuncsNumPy's universal functions, such as np.sqrt or np.exp, which act on every element of an array.) such as np.sqrt, np.exp, np.sin and np.log also work on every element. Reductions collapse an array to a summary: x.sum(), x.mean(), x.std(), x.min(), x.max(), np.argmax(x).
Worked example: RMS voltage
The root-mean-square of a signal is sqrt(mean(v^2)). For a sine wave of amplitude 10 V, it should be 10 / sqrt(2) ≈ 7.071 V.
t = np.linspace(0, 1, 10_001) # one second
v = 10 * np.sin(2 * np.pi * 50 * t) # 50 Hz, 10 V peak
v_rms = np.sqrt(np.mean(v ** 2))
print(round(v_rms, 3)) # 7.071Three lines, no loop, and each line reads like the formula.
Boolean masks: select by condition
Comparisons produce arrays of True/False. Use them to filter or count:
readings = np.array([4.98, 5.02, 7.40, 5.01, 4.99, -1.00])
valid = (readings > 0) & (readings < 6) # use & and |, not "and"/"or"
print(readings[valid]) # [4.98 5.02 5.01 4.99]
print(valid.sum()) # 4 valid readingsBrackets matter: (readings > 0) & (readings < 6). Without them Python's operator precedence evaluates 0 & readings first.
Broadcasting: combine different shapes
When two arrays have different shapes, NumPy tries to broadcastNumPy's rule for combining different shapes: line them up from the right and stretch any axis of size 1. them: it lines the shapes up from the right, and any axis of size 1 is stretched to match the other array. No data is copied.
data = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]]) # shape (2, 3)
col_means = data.mean(axis=0) # shape (3,)
centred = data - col_means # (2, 3) - (3,) -> (2, 3)axis=0 means "collapse the rows", giving one value per column. To subtract row means you need the shape (2, 1), which keepdims=True or [:, None] gives you:
row_means = data.mean(axis=1, keepdims=True) # shape (2, 1)
centred_rows = data - row_meansWorked example: all pairwise distances
Given n points in 2D (shape (n, 2)), the distance matrix D[i, j] can be built with no loops:
pts = np.array([[0.0, 0.0], [3.0, 4.0], [6.0, 8.0]])
diff = pts[:, None, :] - pts[None, :, :] # (3,1,2) - (1,3,2) -> (3,3,2)
D = np.sqrt((diff ** 2).sum(axis=-1)) # (3,3)
print(D[0, 1], D[0, 2]) # 5.0 10.0Inserting a new axis with None (also called np.newaxis) is the most common broadcasting trick.
Common mistakes
- Using lists where you meant arrays.
[1, 2] * 2is[1, 2, 1, 2];np.array([1, 2]) * 2is[2, 4]. - Integer arrays.
np.array([1, 2, 3])isint64. Assigning0.5into it silently truncates to0. Create float arrays (np.array([1.0, 2, 3])ordtype=float) for measurements. - Views, not copies. Slicing (
a[2:5]) returns a viewAn array that shares data with another, as a slice does; changing it changes the original unless you call .copy().; changing it changes the original. Call.copy()when you need an independent array. - Shape surprises. A
(3,)array and a(3, 1)array broadcast to(3, 3). Print.shapewhenever a result looks too big. - Writing a loop and wrapping it in
np.vectorize. It is convenient but runs a Python loop underneath; it is not real vectorisation.
Quick recap
- An
ndarrayholds one dtypeThe element type of an array, such as float64 for measurements or int64 for counts. in a fixed shape; arithmetic and ufuncs act on every element. - Reductions (
sum,mean,std,max) take anaxisargument. - Boolean masksArrays of True/False made by comparisons, used to select or count the elements that meet a condition. filter and count without loops.
- Broadcasting aligns shapes from the right and stretches size-1 axes;
[:, None]adds an axis.
Try it
Open the Do tab and replace three loop-style functions with vectorised NumPy versions: a unit conversion, an RMS calculation and a pairwise distance matrix.
Key terms (6)
- ndarray
- NumPy's array object: numbers of one type stored in one block of memory with a fixed shape.
- dtype
- The element type of an array, such as float64 for measurements or int64 for counts.
- ufuncs
- NumPy's universal functions, such as np.sqrt or np.exp, which act on every element of an array.
- Boolean masks
- Arrays of True/False made by comparisons, used to select or count the elements that meet a condition.
- broadcast
- NumPy's rule for combining different shapes: line them up from the right and stretch any axis of size 1.
- view
- An array that shares data with another, as a slice does; changing it changes the original unless you call .copy().