Skip to content

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:

axis=0 → reduce DOWN (column statistics)
axis=1 → reduce ACROSS (row statistics)

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=0 and axis=1axis=0 collapses rows (gives column stats). The intuition: "axis you remove."
  • std() divides by N, not N-1 — for sample std use arr.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.
  • max vs argmaxmax gives the value, argmax gives the index. Mixing them up is a common bug.
  • np.average vs np.meannp.average supports weights, np.mean doesn't. They give the same result without weights.

Practice

What does this print?

Expected: [15 18 21 24]

import numpy as np
a = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(a.sum(axis=0))

Get per-row sum (one number per row), not the total

Expected: [ 6 15 24]

import numpy as np
a = np.array([[1,2,3],[4,5,6],[7,8,9]])
print(a.sum())                # bug: collapses everything — need axis=1 for per-row

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=N means "the axis being removed". axis=0 removes the first axis (rows), leaving column-wise summaries. axis=1 removes 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 — use np.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) with axis=1 gives (M,). With keepdims=True it 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.

What's next

Sorting & Searching