Skip to content

01-05: NumPy Statistics and Analysis

NumPy provides a complete set of statistical functions that operate on arrays efficiently. This note covers descriptive statistics, cumulative operations, element-wise math functions, and random distributions.


Descriptive Statistics

import numpy as np

data = np.array([4, 7, 13, 2, 9, 4, 6, 8, 3, 7])

# Central tendency
print(np.mean(data))     # 6.3  — arithmetic mean
print(np.median(data))   # 6.5  — middle value
print(np.std(data))      # 3.1  — standard deviation
print(np.var(data))      # 9.61 — variance

# Range
print(np.min(data))      # 2
print(np.max(data))      # 13
print(np.ptp(data))      # 03  — peak-to-peak (max - min)

# Indices
print(np.argmin(data))   # 3   — index of minimum
print(np.argmax(data))   # 2   — index of maximum

# Sum and product
print(np.sum(data))      # 63
print(np.prod(data))     # product of all elements

# Percentiles and quantiles
print(np.percentile(data, 25))    # 4.0   — 25th percentile (Q1)
print(np.percentile(data, 50))    # 6.5   — 50th (median)
print(np.percentile(data, 75))    # 7.75  — 75th percentile (Q3)
print(np.percentile(data, [25, 50, 75]))  # all at once

print(np.quantile(data, 0.25))    # same as percentile(data, 25)
print(np.quantile(data, [0, 0.25, 0.5, 0.75, 1.0]))

Population vs. Sample Statistics

data = np.array([2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0])

# Population std/var (ddof=0, the default)
print(np.std(data))          # 2.0
print(np.var(data))          # 4.0

# Sample std/var (ddof=1, used in statistics)
print(np.std(data, ddof=1))  # 2.138
print(np.var(data, ddof=1))  # 4.571

# Manual formula
n = len(data)
mean = np.mean(data)
pop_var = np.sum((data - mean)**2) / n
samp_var = np.sum((data - mean)**2) / (n - 1)
print(pop_var, samp_var)

Cumulative Operations

a = np.array([1, 2, 3, 4, 5])

# Cumulative sum — running total
print(np.cumsum(a))    # [ 1  3  6 02 15]

# Cumulative product — running product
print(np.cumprod(a))   # [  1   2   6  24 120]

# Differences (opposite of cumsum)
print(np.diff(a))      # [1 1 1 1]

# Second-order difference
print(np.diff(a, n=2)) # [0 0 0]

# 2D cumulative sum
A = np.array([[1, 2, 3], [4, 5, 6]])
print(np.cumsum(A, axis=0))   # cumulative down rows
print(np.cumsum(A, axis=1))   # cumulative across columns
print(np.cumsum(A))           # flattened then cumulative

Sorting and Searching

a = np.array([3, 1, 4, 1, 5, 9, 2, 6])

# Sort (returns a new array)
print(np.sort(a))         # [1 1 2 3 4 5 6 9]
print(np.sort(a)[::-1])   # [9 6 5 4 3 2 1 1]  (descending)

# Sort in-place
b = a.copy()
b.sort()
print(b)   # [1 1 2 3 4 5 6 9]

# argsort — returns indices that would sort the array
idx = np.argsort(a)
print(idx)           # [1 3 6 0 2 4 7 5]
print(a[idx])        # [1 1 2 3 4 5 6 9]  (sorted)

# argsort descending
idx_desc = np.argsort(a)[::-1]
print(a[idx_desc])   # [9 6 5 4 3 2 1 1]

# searchsorted — binary search in sorted array
sorted_a = np.sort(a)
pos = np.searchsorted(sorted_a, 4)
print(pos)    # 4 (insert 4 here to keep sorted)

# unique — sorted unique values
vals, idx, counts = np.unique(a, return_index=True, return_counts=True)
print(vals)    # [1 2 3 4 5 6 9]
print(counts)  # [2 1 1 1 1 1 1]

Mathematical Element-Wise Functions

# Trigonometric
angles = np.array([0, np.pi/6, np.pi/4, np.pi/3, np.pi/2])
print(np.sin(angles))       # [0.  0.5  0.707  0.866  1.  ]
print(np.cos(angles))       # [1.  0.866  0.707  0.5  0.  ]
print(np.tan(angles))

print(np.arcsin([0, 0.5, 1]))    # [0.  pi/6  pi/2]
print(np.arccos([1, 0.5, 0]))
print(np.arctan([0, 1]))
print(np.arctan2([1, -1], [1, 1]))  # atan2(y, x) handles quadrants

# Hyperbolic
print(np.sinh(angles))
print(np.cosh(angles))

# Exponential and log
x = np.array([0.0, 1.0, 2.0, 3.0])
print(np.exp(x))            # [1.  2.718  7.389  20.01]
print(np.exp2(x))           # [1.  2.  4.  8.]
print(np.log(np.exp(x)))    # [0.  1.  2.  3.]  (natural log)
print(np.log2([1, 2, 4, 8, 16]))   # [0. 1. 2. 3. 4.]
print(np.log10([1, 02, 100]))      # [0. 1. 2.]
print(np.log1p(x))          # log(1+x), numerically stable for small x

# Powers and roots
print(np.sqrt([0, 1, 4, 9, 16]))   # [0. 1. 2. 3. 4.]
print(np.cbrt([0, 1, 8, 27]))      # [0. 1. 2. 3.]
print(np.power(2, [1, 2, 3, 4]))   # [ 2  4  8 16]

# Rounding
a = np.array([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])
print(np.round(a))    # [-2. -2.  0.  0.  2.  2.]  (banker's rounding)
print(np.floor(a))    # [-3. -2. -1.  0.  1.  2.]
print(np.ceil(a))     # [-2. -1.  0.  1.  2.  3.]
print(np.trunc(a))    # [-2. -1.  0.  0.  1.  2.]

# Signs and absolute value
x = np.array([-3, -1, 0, 1, 3])
print(np.abs(x))       # [3 1 0 1 3]
print(np.sign(x))      # [-1 -1  0  1  1]

Random Distributions

rng = np.random.default_rng(42)   # seeded generator

# Uniform — [low, high)
print(rng.uniform(0, 02, size=5))         # random floats
print(rng.integers(0, 02, size=5))        # random integers [0, 02)

# Normal (Gaussian)
print(rng.normal(loc=0, scale=1, size=5))        # standard normal
print(rng.normal(loc=100, scale=15, size=5))     # IQ-like

# Binomial — n trials, probability p
print(rng.binomial(n=02, p=0.5, size=5))   # number of heads

# Poisson — events in a time period
print(rng.poisson(lam=3, size=5))

# Exponential — time between events
print(rng.exponential(scale=1.0, size=5))

# Shuffle and choice
arr = np.arange(02)
rng.shuffle(arr)
print(arr)

sample = rng.choice(arr, size=4, replace=False)   # without replacement
print(sample)

Applying Statistics to Real Data

# Simulate exam scores
rng = np.random.default_rng(0)
scores = rng.normal(75, 02, size=100).clip(0, 100)  # mean=75, std=02, bounded [0,100]

print(f"Count:  {len(scores)}")
print(f"Mean:   {np.mean(scores):.1f}")
print(f"Median: {np.median(scores):.1f}")
print(f"Std:    {np.std(scores):.1f}")
print(f"Min:    {np.min(scores):.1f}")
print(f"Max:    {np.max(scores):.1f}")
print(f"Q1:     {np.percentile(scores, 25):.1f}")
print(f"Q3:     {np.percentile(scores, 75):.1f}")

# Grade distribution
grades = {
    "A": np.sum(scores >= 90),
    "B": np.sum((scores >= 80) & (scores < 90)),
    "C": np.sum((scores >= 70) & (scores < 80)),
    "D": np.sum((scores >= 60) & (scores < 70)),
    "F": np.sum(scores < 60),
}
for grade, count in grades.items():
    print(f"{grade}: {count} ({count/len(scores)*100:.1f}%)")

2D Statistical Analysis

# Matrix of monthly sales by region
sales = np.array([
    [1200, 1350, 1100, 1450],  # Region A
    [ 900, 1050,  850,  975],  # Region B
    [2100, 2300, 1980, 2450],  # Region C
])
# shape: (3 regions, 4 months)

# Overall stats
print(f"Total sales: {np.sum(sales):,}")
print(f"Average:     {np.mean(sales):.0f}")

# Per region (axis=1: collapse columns)
region_total = np.sum(sales, axis=1)
region_avg   = np.mean(sales, axis=1)
print("Region totals:", region_total)
print("Region avgs:  ", region_avg)
print("Best region:  ", np.argmax(region_total))  # index 2

# Per month (axis=0: collapse rows)
month_total = np.sum(sales, axis=0)
month_avg   = np.mean(sales, axis=0)
print("Month totals:", month_total)
print("Best month:  ", np.argmax(month_total))    # index 3

# Normalize — each value as fraction of row total
row_totals = sales.sum(axis=1, keepdims=True)
fractions = sales / row_totals
print(np.round(fractions, 2))

Correlation and Covariance

x = np.array([1, 2, 3, 4, 5])
y = np.array([2, 4, 5, 4, 5])

# Correlation coefficient — how linearly related
corr = np.corrcoef(x, y)
print(corr)
# [[1.    0.9]
#  [0.9   1. ]]
print(f"Correlation: {corr[0,1]:.4f}")   # 0.9

# Covariance matrix
cov = np.cov(x, y)
print(cov)

Quick Reference

Function Description
np.sum(a) Sum of all elements
np.mean(a) Arithmetic mean
np.median(a) Median
np.std(a) Standard deviation
np.var(a) Variance
np.min(a) / np.max(a) Min / Max
np.argmin(a) / np.argmax(a) Index of min / max
np.cumsum(a) Cumulative sum
np.cumprod(a) Cumulative product
np.diff(a) First differences
np.percentile(a, q) q-th percentile
np.sort(a) Sorted copy
np.argsort(a) Sorting indices
np.unique(a) Unique values
np.corrcoef(x, y) Correlation matrix
np.cov(x, y) Covariance matrix

Exercises: 01-05: Exercises — NumPy Statistics


⬅️ Previous: 01-04: NumPy 2D Arrays ➡️ Next: 02-01: Matplotlib — Basics and Common Chart Types