NumPy Mathematical Functions — ufuncs, aggregations and statistics
- Ufuncs operate element-wise and support broadcasting — always prefer them over Python loops.
- Aggregation functions accept an axis parameter — axis=0 collapses rows, axis=1 collapses columns.
- np.std() uses ddof=0 (population) by default. Use ddof=1 for sample standard deviation.
- NumPy mathematical functions are vectorised: they run in compiled C, not Python loops.
- Ufuncs (universal functions) operate element-wise and support broadcasting.
- Aggregation functions reduce arrays: sum, mean, min, max with axis control.
- Axis=0 collapses rows (down), axis=1 collapses columns (across).
- Performance: vectorised operations are 10-100x faster than equivalent Python loops.
- Production trap: forgetting nan-safe aggregations (np.nanmean) silently propagates NaNs.
NaN in aggregation result
np.isnan(data).sum()np.nanmean(data) if using meanBroadcasting error
a.shape, b.shapenp.broadcast_shapes(a.shape, b.shape) (NumPy >= 1.20)Memory blow-up (OOM)
sys.getsizeof(a) / (1024**3) # GBa.nbytes / (1024**3) # more accurateWrong reduction dimension
result.shapenp.sum(a, axis=0, keepdims=True).shapeProduction Incident
Production Debug GuideCommon failure modes when using ufuncs and aggregations
np.isinf().Universal Functions (ufuncs)
Ufuncs apply a function to every element of an array. They support broadcasting, type casting, and output array specification.
import numpy as np a = np.array([1.0, 4.0, 9.0, 16.0]) print(np.sqrt(a)) # [1. 2. 3. 4.] print(np.log(a)) # [0. 1.386 2.197 2.773] print(np.exp([0, 1, 2])) # [1. 2.718 7.389] print(np.abs([-3, -1, 2])) # [3 1 2] # Two-array ufuncs b = np.array([2.0, 3.0, 4.0, 5.0]) print(np.add(a, b)) # same as a + b print(np.maximum(a, b)) # element-wise max print(np.power(b, 2)) # [4. 9. 16. 25.]
[3 1 2]
Aggregation Functions and the axis Parameter
axis=0 operates down rows (along columns). axis=1 operates across columns (along rows). axis=None reduces everything to a scalar.
import numpy as np m = np.array([[1, 2, 3], [4, 5, 6]]) print(m.sum()) # 21 — all elements print(m.sum(axis=0)) # [5 7 9] — column sums print(m.sum(axis=1)) # [6 15] — row sums print(m.mean(axis=0)) # [2.5 3.5 4.5] print(m.max(axis=1)) # [3 6] print(m.argmax(axis=1)) # [2 2] — index of max in each row
[5 7 9]
[6 15]
Statistical Functions
NumPy covers standard statistics. For variance and standard deviation, note the ddof parameter — ddof=0 is population (default), ddof=1 is sample.
import numpy as np data = np.array([2, 4, 4, 4, 5, 5, 7, 9]) print(np.mean(data)) # 5.0 print(np.median(data)) # 4.5 print(np.std(data)) # 2.0 (population) print(np.std(data, ddof=1)) # 2.138 (sample) print(np.var(data)) # 4.0 print(np.percentile(data, 75)) # 5.75 print(np.corrcoef([1,2,3], [1,2,3])) # correlation matrix
4.5
2.0
Broadcasting: How ufuncs Handle Different Shapes
Broadcasting allows ufuncs to operate on arrays of different shapes by automatically expanding dimensions. The arrays must be compatible: they align from the rightmost dimension, and each dimension must be equal or one of them must be 1.
import numpy as np a = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2,3) b = np.array([10, 20, 30]) # shape (3,) -> broadcasts to (1,3) then (2,3) print(a + b) # element-wise addition # [[11 22 33] # [14 25 36]] # Scalar broadcasting works too print(a * 2) # multiplies every element by 2
[14 25 36]]
[[2 4 6]
[8 10 12]]
Performance: Vectorisation vs Python Loops
Vectorised operations in NumPy run in compiled C and are orders of magnitude faster than Python loops. The performance gap grows with array size — for a 10-million-element array, vectorised sum takes ~5ms, while a Python loop takes over a minute.
import numpy as np import time arr = np.random.randn(10_000_000) # Vectorised start = time.time() result = np.sum(arr) print(f"Vectorised: {time.time() - start:.5f}s") # Python loop start = time.time() total = 0.0 for x in arr: total += x print(f"Loop: {time.time() - start:.5f}s")
Loop: 62.3s
Advanced ufunc Methods: reduce, accumulate, outer
Ufuncs provide methods beyond direct element-wise application. reduce applies the operation cumulatively along an axis, accumulate returns all intermediate results, and outer computes the outer product.
import numpy as np a = np.array([1, 2, 3, 4]) # reduce: cumulative sum produces single value print(np.add.reduce(a)) # 10 = 1+2+3+4 # accumulate: all intermediate sums print(np.add.accumulate(a)) # [1 3 6 10] # outer: outer product print(np.multiply.outer([1,2,3], [10,20,30])) # [[10 20 30] # [20 40 60] # [30 60 90]]
[1 3 6 10]
[[10 20 30]
[20 40 60]
[30 60 90]]
Handling NaN in Statistical Aggregations
By default, NumPy aggregation functions (mean, sum, std) return NaN if any element is NaN. Use nan-aware versions: np.nansum, np.nanmean, np.nanstd. They ignore NaN values and compute on the remaining elements.
import numpy as np data = np.array([1.0, 2.0, np.nan, 4.0]) print(np.mean(data)) # nan print(np.nanmean(data)) # 2.333... print(np.std(data)) # nan print(np.nanstd(data)) # ~1.247 # For percentage of missing values print(np.isnan(data).sum()) # 1
2.333333333333333
nan
1.247219128924647
1
np.isnan().any() in assertions before feeding aggregated results to downstream systems.🎯 Key Takeaways
- Ufuncs operate element-wise and support broadcasting — always prefer them over Python loops.
- Aggregation functions accept an axis parameter — axis=0 collapses rows, axis=1 collapses columns.
- np.std() uses ddof=0 (population) by default. Use ddof=1 for sample standard deviation.
- np.argmax() and
np.argmin()return indices, not values. - np.cumsum() and
np.cumprod()give running totals without reducing the array size. - Broadcasting avoids memory copies but can silently explode when shapes are non-optimal.
- Always use nan-aware functions (np.nanmean, np.nansum) when data may have missing values.
- Ufunc methods (reduce, accumulate, outer) are efficient for specialised operations but watch for overflow and memory.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat does the axis parameter mean in NumPy aggregation functions?JuniorReveal
- QWhat is the difference between
np.std()with ddof=0 and ddof=1?Mid-levelReveal - QHow does broadcasting work in NumPy?Mid-levelReveal
- QWhy are vectorised operations faster than Python loops in NumPy?SeniorReveal
Frequently Asked Questions
What is the difference between np.sum(a) and a.sum()?
Functionally identical. The method form a.sum() is slightly more common in practice. Both support the axis, keepdims, and dtype parameters.
How do I compute a weighted average in NumPy?
Use np.average(a, weights=w). Unlike np.mean(), np.average() accepts a weights array and computes the weighted mean. np.mean() treats all elements equally.
How do I compute the running sum or cumulative product?
Use np.cumsum(a) for running sum, np.cumprod(a) for running product. These return an array of the same shape with accumulated values along the given axis (default flattens).
What is the difference between np.add.reduce and np.sum?
np.add.reduce(a) is exactly equivalent to np.sum(a). The reduce method is the more general primitive; np.sum is a convenience wrapper. For integer types, both may overflow silently — use np.add.reduce with dtype=np.float64 to avoid.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.