Vectorised maths and broadcasting
Element-wise operations, the ufunc model, aggregation, and the broadcasting rules that make array code concise.
Element-wise operations
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
a + b # array([11, 22, 33])
a * 2 # array([2, 4, 6])
a ** 2 # array([1, 4, 9])
np.sqrt(a)
np.exp(a)
np.log(a)
a.sum(), a.prod(), a.mean(), a.std(), a.cumsum()These are ufuncs: functions that operate element by element at C speed and support an out= parameter so you can write into preallocated memory instead of allocating a new array each call.
out = np.empty_like(a, dtype=float)
np.multiply(a, 2.5, out=out) # no new allocationBroadcasting rules
When shapes differ, NumPy compares them from the right: dimensions are compatible if they are equal or one of them is 1. The size-1 dimension is stretched without copying data.
m = np.ones((3, 4))
v = np.array([1, 2, 3, 4])
m + v # (3,4) + (4,) -> v broadcast across rows ✅
# m + np.array([1, 2, 3]) # (3,4) + (3,) -> ValueError ❌
col = np.array([[10], [20], [30]]) # shape (3, 1)
m + col # one value per row
# normalise each column to 0..1
norm = (m - m.min(axis=0)) / (m.max(axis=0) - m.min(axis=0))💡
Reshape with
None (or np.newaxis) to add a length-1 axis deliberately: v[:, None] turns a shape-(4,) vector into (4, 1). That one character is often the fix for a broadcasting error.Aggregation and where
x = np.array([3, np.nan, 7])
x.sum() # nan - NaN poisons everything
np.nansum(x) # 10 - ignores NaN
np.isnan(x) # array([False, True, False])
x[~np.isnan(x)] # drop NaN
np.percentile(np.arange(101), [25, 50, 75])
np.clip(x, 0, 5) # cap values into a range⚠️
A single
NaN silently destroys sum, mean and std. In real datasets, decide up front whether missing values should be dropped or filled — and use the nan* variants consciously rather than by accident.FAQ
Is a Python loop ever acceptable?
For genuinely sequential logic, yes — but measure first. If the loop body is pure maths on array elements, rewrite it as vectorised expressions or use
np.apply_along_axis as a middle ground.Why is my result a float when I expected an int?
Division,
mean, and mixing dtypes promote to float. Cast explicitly with .astype() when you need integers, and check for truncation.Related
NumPy arrays Indexing, slicing and masks
Last refreshed 2026-09-18.