Aggregations, the axis Parameter, Cumulative and NaN-safe Functions
An aggregation turns many values into one: the total, the average, the biggest. The axis parameter decides which direction to aggregate in a 2D or 3D array: per column, per row, or everything. This lesson also covers cumulative functions (running totals) and NaN-safe functions that ignore missing values.
How to run the examples
Create a new file named aggregations_axis.py in your NumpyTutorials folder (see the setup in lesson 01). Any name works; this one just matches the topic. Start the file with these lines:
import numpy as np
Then copy the examples of this page into the same file, one after another, in the order shown, and run it with python aggregations_axis.py (with your virtual environment active). Some examples use variables created in earlier ones, so keep everything in the one file. Each Output box shows what that example prints.
1. Basic aggregations
marks = np.array([72, 85, 64, 91, 58, 77, 85])
print("sum :", marks.sum())
print("mean :", marks.mean().round(2))
print("median :", np.median(marks))
print("min/max:", marks.min(), marks.max())
print("range :", np.ptp(marks), "(max - min)")
print("std :", marks.std().round(2), "(how spread out)")
print("var :", marks.var().round(2))
print("np.sum(marks) is the same as marks.sum():", np.sum(marks) == marks.sum())
| Function | Gives |
|---|---|
sum(), mean() | total, average |
np.median() | the middle value when sorted (not disturbed by a few extreme values) |
min(), max(), np.ptp() | smallest, biggest, and the range between them |
std(), var() | standard deviation and variance: how spread out the values are |
Most of these exist both as a method (marks.sum()) and as a function (np.sum(marks)); they give the same result. median is only a function.
2. argmin and argmax
max() tells you what the biggest value is; argmax() tells you where it is. The position lets you look up related data, like the student's name.
names = np.array(["Asha", "Ravi", "Neha", "Kiran", "Meena", "Arjun", "Sneha"])
print("argmax:", marks.argmax(), "->", names[marks.argmax()], "scored", marks.max())
print("argmin:", marks.argmin(), "->", names[marks.argmin()], "scored", marks.min())
print("ties: argmax gives the FIRST one ->", np.argmax(np.array([5, 9, 9])))
When the biggest value appears more than once, argmax returns the first position.
3. axis=0 and axis=1
This is the part that confuses most beginners, so take it slowly. scores has 3 students (rows) x 4 subjects (columns).
# 3 students x 4 subjects
scores = np.array([[78, 85, 92, 66],
[88, 79, 95, 70],
[65, 90, 81, 99]])
print("scores:\n", scores)
print("sum() :", scores.sum(), "(everything)")
print("sum(axis=0) :", scores.sum(axis=0), "-> one per subject (column)")
print("sum(axis=1) :", scores.sum(axis=1), "-> one per student (row)")
print("max(axis=0) :", scores.max(axis=0), "-> best mark per subject")
print("argmax(axis=1):", scores.argmax(axis=1), "-> best subject of each student")
print("mean(axis=1, keepdims=True) shape:", scores.mean(axis=1, keepdims=True).shape)
| Call | Direction | Result |
|---|---|---|
sum() | everything | one number |
sum(axis=0) | down the rows; the rows disappear | one value per column (subject): 4 values |
sum(axis=1) | across the columns; the columns disappear | one value per row (student): 3 values |
Easy rule: the axis you name is the one that disappears. Shape (3, 4) with axis=0 loses the 3, so you get 4 results; with axis=1 it loses the 4, so you get 3 results.
keepdims=True keeps the collapsed axis with size 1: (3, 1) instead of (3,). That is exactly the column shape you need to broadcast back against the table (lesson 10).
4. axis on a 3D array
# 2 stores x 3 months x 4 products
data = np.arange(1, 25).reshape(2, 3, 4)
print("shape :", data.shape)
print("sum(axis=0) -> shape", data.sum(axis=0).shape, "(months x products, stores added)")
print("sum(axis=(1, 2)) -> per store:", data.sum(axis=(1, 2)))
print("sum(axis=2) -> per store and month:\n", data.sum(axis=2))
- Shape
(2, 3, 4)= stores x months x products.axis=0adds the stores together, leaving(3, 4). - A tuple of axes,
axis=(1, 2), collapses several axes at once: one total per store. axis=2adds up the products, leaving a store x month table.
5. cumsum, cumprod, diff
Cumulative functions keep every step of the calculation instead of just the final answer.
daily_sales = np.array([100, 150, 80, 200, 170])
print("daily :", daily_sales)
print("cumsum :", daily_sales.cumsum(), "(running total)")
print("diff :", np.diff(daily_sales), "(change from the day before)")
growth = np.array([1.10, 1.05, 0.95, 1.20]) # +10%, +5%, -5%, +20%
print("cumprod :", growth.cumprod().round(4), "(total growth so far)")
print("running max :", np.maximum.accumulate(np.array([3, 7, 5, 9, 4])))
print("cumsum(axis=1) per student:\n", scores.cumsum(axis=1))
cumsum: running total. The last value is the same assum().np.diff: the opposite idea, the change from each value to the next (one value fewer).cumprod: running product. Growth factors of +10%, +5%, -5%, +20% give a total of 1.3167 (+31.7%) after four periods.np.maximum.accumulate: the running maximum ("highest so far"). Every ufunc has an.accumulate.- Cumulative functions take
axistoo:cumsum(axis=1)keeps a running total along each row.
6. NaN-safe functions
np.nan marks a missing value (lesson 14). A single nan spoils normal aggregations: anything plus nan is nan. The nan... versions simply skip missing values.
temps = np.array([31.5, 33.0, np.nan, 29.5, np.nan, 35.0]) # nan = missing reading
print("sum :", temps.sum(), " <- one nan makes the result nan")
print("nansum :", np.nansum(temps))
print("nanmean :", np.nanmean(temps).round(2))
print("nanmin :", np.nanmin(temps), " nanmax:", np.nanmax(temps))
print("nanstd :", np.nanstd(temps).round(2))
print("nanargmax:", np.nanargmax(temps))
print("nancumsum:", np.nancumsum(temps))
print("readings that are not nan:", np.count_nonzero(~np.isnan(temps)))
| Normal | NaN-safe |
|---|---|
sum, mean, median | np.nansum, np.nanmean, np.nanmedian |
min, max, argmax | np.nanmin, np.nanmax, np.nanargmax |
std, var, cumsum | np.nanstd, np.nanvar, np.nancumsum |
nanmean divides by the number of real values (4), not by 6. To count them, use np.count_nonzero(~np.isnan(temps)).
7. More useful aggregations
print("any mark below 60? :", (marks < 60).any())
print("all marks above 50?:", (marks > 50).all())
print("how many above 80 :", np.count_nonzero(marks > 80))
print("25th / 50th / 75th percentile:", np.percentile(marks, [25, 50, 75]))
print("subjects where everyone passed 65:", (scores > 65).all(axis=0))
.any()and.all()on a condition answer yes/no questions about the whole array, or per column withaxis=0.np.count_nonzero(condition)counts how many elements match.np.percentile(marks, 75): 75% of the marks are at or below this value. The 50th percentile is the median.
8. Real example: monthly report of 3 shops
shops = np.array(["Pune", "Mumbai", "Nagpur"])
months = np.array(["Jan", "Feb", "Mar", "Apr"])
revenue = np.array([[120, 135, np.nan, 160],
[220, 210, 230, 240],
[90, np.nan, 100, 115]])
print("total per shop :", np.nansum(revenue, axis=1))
print("average / month :", np.nanmean(revenue, axis=0).round(1))
print("best shop :", shops[np.nansum(revenue, axis=1).argmax()])
print("best month :", months[np.nansum(revenue, axis=0).argmax()])
print("missing per shop:", np.isnan(revenue).sum(axis=1))
print("running total of Mumbai:", revenue[1].cumsum())
The revenue table has two missing months. NaN-safe functions with the right axis give totals per shop (axis=1) and averages per month (axis=0) without any cleaning. argmax plus the label arrays name the best shop and month, and np.isnan(...).sum(axis=1) counts the missing months per shop.
Exercises
Try these exercises on your own before moving on:
- For np.array([[3, 7, 2], [8, 1, 9]]) print the column sums, row means and the position of the biggest value in each row.
- Daily steps of a week: print the running total and the best day.
- Replace one value with np.nan and compare mean() with np.nanmean().
- For a (4, 7) array of 4 weeks x 7 days, find the average of each weekday.
Summary
sum,mean,median,min,max,stdreduce many values to one.argmin/argmaxgive the position of the smallest/biggest value.- The axis you name disappears:
axis=0gives one result per column,axis=1one per row. keepdims=Truekeeps the shape ready for broadcasting.cumsum,cumprodanddiffgive running totals and changes.- Use
nansum,nanmeanand friends when the data has missing values.