2.6 Aggregation and Array Methods
: 15 minutes
An aggregation, or reduction, collapses many numbers into fewer. Where a ufunc preserves shape, a reduction deliberately destroys an axis—and the whole skill of this section is controlling which axis it destroys.
Some of the most commonly used statistical methods are listed below.
| Method | Description |
|---|---|
sum |
Sum of the elements, over the whole array or along an axis; an empty sum is 0 |
mean |
Arithmetic mean; returns nan (with a warning) on a zero-length axis |
std, var |
Standard deviation and variance |
min, max |
Minimum and maximum |
argmin, argmax |
Index of the minimum or maximum, not the value itself |
any, all |
Whether any / every element is truthy—most useful on boolean arrays |
cumsum |
Running totals: element i of the result is the sum of the first i+1 inputs |
cumprod |
Running products, formed the same way |
Note that cumsum and cumprod are the odd ones out: they are not reductions. They return an array of the same shape as the input, since each position keeps its own running total.
The axis argument
With no argument, a reduction flattens the entire array and returns a scalar:
Far more often we want a summary along an axis. If A is a matrix with observations in rows and features in columns, A.mean(axis=0) (equivalently numpy.mean(A, axis=0)) averages down the rows and gives one number per column:
This is the single sentence to memorise for this chapter, and it resolves the axis=0 versus axis=1 question every time without you having to remember anything about rows or columns.
A has shape (5, 4). Naming axis=0 deletes the 5 and leaves (4,). Naming axis=1 deletes the 4 and leaves (5,). Check by looking at the shape, not by squinting at the numbers.
Counter-intuitively, axis=0 gives you the column means. That is not a quirk: you are summing over the row axis, so the row axis is what vanishes.
For higher-order arrays the result has one axis fewer; its shape is the original shape with the named entry removed.
You may also collapse several axes at once by passing a tuple, which is occasionally exactly what you want:
keepdims
A reduction removes an axis, which is a problem the moment you want to combine the summary back with the array it came from—the shapes no longer line up. Passing keepdims=True retains the reduced axis with length 1:
A length-1 axis is precisely what broadcasting knows how to stretch, so the keepdims version slots straight back in:
Without keepdims, A / A.sum(axis=1) would right-align (3,) against (3, 4), compare 3 with 4, and raise a ValueError—which, as errors go, is the lucky outcome. On a square array it would have broadcast silently and given you the wrong answer.
Rule of thumb: if you reduce along an axis and then use the result together with the original array, you want keepdims=True.
argmin and argmax
These return a position, not a value—a distinction that matters because the position is usually what you actually want (“which patient had the highest reading?”). With no axis, the index refers to the flattened array:
To recover the value, index with the result: A.max() and A.ravel()[A.argmax()] agree. To turn a flat index back into a coordinate pair, use np.unravel_index(A.argmax(), A.shape).
any and all
On a boolean array—typically the output of a comparison—any and all reduce exactly like sum does, and take the same axis argument:
(A < 0).sum() counts how many, because True sums as 1; (A < 0).any() asks merely whether there are any. Both are more idiomatic and far faster than a loop.
Other Functions
numpy.where
The general form np.where(cond, arr1, arr2) takes a boolean array cond and, position by position, chooses the corresponding value from arr1 where cond is True and from arr2 where it is False. Either or both of arr1 and arr2 may be a scalar, and all three broadcast against each other. Think of it as a vectorized ternary expression: it selects, it does not skip (see the warning in 2.5).
Suppose you have drawn from the standard normal distribution and regard values beyond \pm 2 as too extreme to keep.
There are two such values here:
Now watch a bug that is easy to write and hard to see. The intent is “pull the extremes back to the boundary”:
Both offending values became +2—including the one that was -2.06, which has just had its sign flipped. The condition tested the magnitude but the replacement forgot it. What we wanted was to move each extreme to the nearer boundary:
This is the archetype of the bugs this module is about: it runs, it produces plausible numbers, and it is wrong. In practice you would not write either version—np.clip(A, -2, 2), below, says exactly what you mean and cannot get the sign wrong.
numpy.sort
A NumPy array arr can be sorted along an axis using numpy.sort(arr), which returns a new sorted array, or arr.sort(), which sorts in place and returns None.
np.sort(A) and A.sort() are not interchangeable
B = np.sort(A) leaves A untouched. B = A.sort() destroys the ordering of A and leaves B bound to None—after which the next line fails with a puzzling AttributeError: 'NoneType' object has no attribute ....
The same asymmetry holds throughout NumPy and pandas: a bare method name that returns None is a hint that it worked in place.
The following sorts along the last axis—that is, within each row. This is the default (axis=-1) when no axis is given.
A specific axis can also be supplied. Here axis=0 sorts within each column.
Note that sorting a 2-D array along an axis sorts each row (or column) independently; it does not reorder whole rows. To reorder rows by the values in one column—which is nearly always what you want with real data—sort the indices with argsort and use them to index:
numpy.clip
The numpy.clip() function limits the values in an array to a specified range. It takes an array and a minimum and maximum value as arguments. Any elements in the array that are less than the specified minimum value are replaced by the minimum value, and any elements greater than the specified maximum value are replaced by the maximum value.
numpy.stack
A list of NumPy arrays of the same shape can be stacked along an axis using the numpy.stack function. The function takes a list of arrays to stack and (optionally) an axis of choice.
Let us first consider stacking two 1D arrays or vectors vertically along axis=0, to form a (fat-short) 2D array.
Let us now stack them horizontally along axis=1, to form a (skinny-tall) 2D array.
Next, we stack two matrices along different axes.
stack creates an axis; concatenate extends one
This is the distinction to hold on to, and it is entirely predictable from the shapes.
np.stackrequires all inputs to have identical shapes and adds a new axis. Two (3, 5) arrays give (2, 3, 5), and the order rises from 2 to 3.np.concatenatejoins along an existing axis and adds no new one. Two (3, 5) arrays concatenated onaxis=0give (6, 5); the order stays at 2. The inputs must agree on every axis except the one being joined.
If the order of your result surprised you, you reached for the wrong one of the two.