Aggregations — sum, mean, std, axis¶
Aggregations collapse an array down to summary numbers. The most important parameter: axis — which dimension to collapse along.
The basic stats¶
import numpy as np
a = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print("sum :", a.sum())
print("mean :", a.mean())
print("median :", np.median(a))
print("min :", a.min())
print("max :", a.max())
print("std :", a.std()) # standard deviation
print("var :", a.var()) # variance
print("ptp :", a.ptp()) # max - min ("peak to peak")
print("prod :", a.prod()) # product of all
print("argmin :", a.argmin()) # index of min
print("argmax :", a.argmax()) # index of max
The axis parameter¶
For a 2D array, axis=0 means collapse along rows (sum each column).
axis=1 means collapse along columns (sum each row).
import numpy as np
a = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
])
print("whole: ", a.sum()) # 78
print("per column: ", a.sum(axis=0)) # [15 18 21 24]
print("per row: ", a.sum(axis=1)) # [10 26 42]
print("mean per row :", a.mean(axis=1)) # [2.5 6.5 10.5]
Memorize this:
keepdims=True — preserve the dimensions¶
Reducing collapses an axis. keepdims=True keeps it as size 1 — useful for broadcasting:
import numpy as np
a = np.array([
[1, 2, 3],
[4, 5, 6],
])
# Reduces from (2, 3) to (3,)
print(a.sum(axis=0)) # shape (3,)
# Reduces but keeps a 1 — shape (1, 3)
print(a.sum(axis=0, keepdims=True))
# Now we can subtract it from the original (broadcasting works)
print(a - a.mean(axis=0, keepdims=True)) # zero-mean per column
np.cumsum and np.cumprod — running totals¶
import numpy as np
sales = np.array([100, 150, 200, 50, 300])
print("cumulative sum :", np.cumsum(sales)) # [100 250 450 500 800]
print("cumulative prod:", np.cumprod([1, 2, 3, 4])) # [1 2 6 24]
Counting¶
import numpy as np
a = np.array([1, 5, 3, 9, 2, 5, 8, 5, 4])
# Count occurrences
print("number of 5s :", (a == 5).sum())
# Count anything matching a condition
print("how many > 3:", (a > 3).sum())
# Unique values + counts
values, counts = np.unique(a, return_counts=True)
print("unique:", values)
print("counts:", counts)
np.unique is one of the most useful functions for quick exploratory analysis.
any() and all()¶
import numpy as np
a = np.array([3, 5, 7, 2, 9])
print("any > 8 ?", (a > 8).any()) # True
print("all > 0 ?", (a > 0).all()) # True
print("any < 0 ?", (a < 0).any()) # False
print("all < 5 ?", (a < 5).all()) # False
# With axis
b = np.array([
[True, True, True ],
[False, True, True ],
[True, True, False],
])
print("any per row :", b.any(axis=1)) # [True True True]
print("all per row :", b.all(axis=1)) # [True False False]
Percentiles / quantiles¶
import numpy as np
rng = np.random.default_rng(0)
scores = rng.integers(0, 100, size=50)
print(scores)
print("25th percentile:", np.percentile(scores, 25))
print("50th percentile:", np.percentile(scores, 50)) # = median
print("90th percentile:", np.percentile(scores, 90))
print("five-number summary:", np.percentile(scores, [0, 25, 50, 75, 100]))
Correlation and covariance¶
import numpy as np
# Two related quantities
hours_studied = np.array([1, 2, 3, 4, 5, 6, 7, 8])
scores = np.array([50, 55, 65, 70, 80, 85, 88, 95])
print("correlation:", np.corrcoef(hours_studied, scores)[0, 1].round(3))
print("covariance :", np.cov(hours_studied, scores)[0, 1].round(2))
corrcoef returns a 2×2 matrix; the off-diagonal is the correlation between the two vectors.
A real example — feature statistics from a dataset¶
import numpy as np
rng = np.random.default_rng(0)
# 100 students × 4 subjects (math, science, english, history)
scores = rng.integers(30, 100, size=(100, 4))
subjects = ["math", "science", "english", "history"]
# Mean per subject
print("subject means:")
for s, m in zip(subjects, scores.mean(axis=0)):
print(f" {s:8} {m:.1f}")
# Top student (highest total)
totals = scores.sum(axis=1)
top_idx = totals.argmax()
print(f"\nTop student is index {top_idx} with total {totals[top_idx]}")
print(f"Their scores: {scores[top_idx]}")
# How many students passed in EVERY subject (>= 50)?
all_pass = (scores >= 50).all(axis=1).sum()
print(f"\nStudents passing every subject: {all_pass}/100")
axis for higher-dimensional arrays¶
For 3D and up, axis is just the dimension index — same rule.
import numpy as np
# A batch of 4 grayscale 3x3 images
batch = np.arange(36).reshape(4, 3, 3)
print("batch shape:", batch.shape) # (4, 3, 3)
# Mean intensity of each image (collapses 3x3 → scalar per image)
print("per-image mean:", batch.mean(axis=(1, 2))) # shape (4,)
# Mean image (average pixel across the batch)
print("mean image shape:", batch.mean(axis=0).shape) # (3, 3)
axis can be a tuple — collapse multiple dims at once.
Cheatsheet¶
| Want | Use |
|---|---|
| Total sum | arr.sum() |
| Sum per column | arr.sum(axis=0) |
| Sum per row | arr.sum(axis=1) |
| Mean | arr.mean() / np.mean() |
| Std / variance | arr.std() / arr.var() |
| Median | np.median(arr) |
| Min / max | arr.min() / arr.max() |
| Index of min / max | arr.argmin() / arr.argmax() |
| Range | arr.ptp() |
| Cumulative sum | np.cumsum(arr) |
| Counts of unique values | np.unique(arr, return_counts=True) |
| Any / all match | (arr > 5).any() / (arr > 0).all() |
| Percentile | np.percentile(arr, [25, 50, 75]) |
| Correlation | np.corrcoef(x, y)[0, 1] |
Common pitfalls¶
- ❗ Mixing up
axis=0andaxis=1—axis=0collapses rows (gives column stats). The intuition: "axis you remove." - ❗
std()divides by N, not N-1 — for sample std usearr.std(ddof=1). For population std the default is fine. - ❗ Forgetting
keepdims=True— when you want to broadcast the result back. Always remember it for normalization. - ❗
maxvsargmax—maxgives the value,argmaxgives the index. Mixing them up is a common bug. - ❗
np.averagevsnp.mean—np.averagesupports weights,np.meandoesn't. They give the same result without weights.
Practice¶
What does this print?
Expected: [15 18 21 24]
Get per-row sum (one number per row), not the total
Expected: [ 6 15 24]
Quiz — Quick check¶
What you remember
Q1. For a 2D array, what does axis=0 mean?
- Collapse columns (gives per-row stats)
- Collapse rows (gives per-column stats)
- Collapse everything
- No effect
Why:
axis=Nmeans "the axis being removed".axis=0removes the first axis (rows), leaving column-wise summaries.axis=1removes the second axis (columns), leaving row-wise.
Q2. What does .argmin() return?
- The minimum value
- The index of the minimum value (a flat index by default)
- The number of minimum values
- An array of indices
Why:
.min()returns the value;.argmin()returns where it is. For 2D, you get a flat index by default — usenp.unravel_index(arr.argmin(), arr.shape)for(row, col).
Q3. Why use arr.sum(axis=1, keepdims=True)?
- Faster than without
- Keeps the reduced dimension as size 1, so the result broadcasts back to the original shape
- Returns more decimal places
- Required by NumPy
Why: Reducing
(M, N)withaxis=1gives(M,). Withkeepdims=Trueit stays(M, 1)— exactly the shape needed to broadcast back to(M, N)for normalization.
Common doubts¶
How do I remember which is axis=0 and which is axis=1?
axis=N is "the axis being removed". For shape (rows, cols):
- axis=0 removes rows → result has one value per column
- axis=1 removes cols → result has one value per row
Alternative mnemonic: axis=0 is down (collapse vertically); axis=1 is across (collapse horizontally).
Why does arr.std() give a different answer than what Excel/pandas computes?
NumPy's default is population std (divide by N). Excel and most statistics texts use sample std (divide by N-1). For sample std: arr.std(ddof=1). The same applies to var().
What's the difference between np.mean and np.average?
np.mean computes an unweighted average. np.average(arr, weights=w) lets you weight each element. Without weights, they give the same result.