NumPy Crash Course
NumPy is the bedrock of Python data science — every major ML library (Pandas, Scikit-learn, PyTorch) is built on top of NumPy arrays.
1. What You'll Learn
- ndarray creation and shape manipulation: reshape, transpose, and the broadcasting mechanism
- Array indexing and slicing: boolean indexing, fancy indexing, and multi-dimensional slicing
- Numerical computing: vectorized operations, matrix multiplication, and statistical functions
- Random number generation and linear algebra basics: np.random and np.linalg
- Working with Bob's SalesPredict sales data matrix using NumPy
2. A Real Story from a Data Engineer
(1) The Pain: Python Lists Are Too Slow for Millions of Rows
Bob needed to compute monthly sales statistics across 12 months and 5 product categories — 60 thousand records. Using native Python list loops to calculate the mean and standard deviation took over 30 seconds, and Alice's US dataset with 500 thousand records simply froze. Native Python loops are the bottleneck of ML data processing.
(2) NumPy's Solution
NumPy's ndarray uses contiguous memory blocks and C-level low-level operations, making vectorized computations 50–100x faster than Python loops.
import numpy as np
# Python list loop vs NumPy vectorization
sales_list = list(range(1, 60001)) # 60,000 records
sales_arr = np.array(sales_list)
# NumPy vectorized: 100x faster
mean_val = sales_arr.mean()
std_val = sales_arr.std()
print(f"Mean: {mean_val:.2f}, Std: {std_val:.2f}")
(3) The Payoff: Computation Time Drops from 30s to 0.3s
After Bob migrated his data processing from Python loops to NumPy vectorization, statistical computations on 60 thousand records dropped from 30 seconds to 0.3 seconds. Alice's 500 thousand records also complete in under 1 second.
3. ndarray Creation and Shape Manipulation
The ndarray is NumPy's core data structure — a multi-dimensional array of same-type elements stored in contiguous memory.
(1) Creating Arrays
import numpy as np
# From Python list
a = np.array([1, 2, 3, 4, 5])
# Built-in constructors
zeros = np.zeros((3, 4)) # 3x4 matrix of zeros
ones = np.ones((2, 3)) # 2x3 matrix of ones
range_arr = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
linspace = np.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1.0]
print(f"arange: {range_arr}")
print(f"linspace: {linspace}")
▶ Example: Creating the SalesPredict Sales Matrix
import numpy as np
# Monthly sales for 5 categories over 12 months (in thousand USD)
sales = np.array([
[120, 135, 150, 142, 160, 175, 180, 190, 195, 200, 220, 250], # Electronics
[80, 85, 90, 88, 95, 100, 105, 108, 110, 115, 130, 145], # Clothing
[50, 48, 55, 60, 58, 62, 65, 68, 70, 72, 80, 90], # Food
[30, 35, 40, 38, 42, 45, 48, 50, 52, 55, 60, 70], # Books
[200, 210, 225, 220, 240, 260, 270, 280, 290, 300, 350, 400], # Home
])
print(f"Shape: {sales.shape}")
print(f"Total annual revenue: {sales.sum() / 1000:.1f} million USD")
print(f"Q4 revenue: {sales[:, 9:].sum() / 1000:.1f} million USD")
Output:
Shape: (5, 12)
Total annual revenue: 6.3 million USD
Q4 revenue: 2.3 million USD
(2) Shape Operations
| Operation | Method | Description |
|---|---|---|
| Reshape | reshape() |
Changes the view without altering the data |
| Transpose | T or transpose() |
Swaps rows and columns |
| Flatten | flatten() / ravel() |
Multi-dimensional → 1D |
| Add dimension | np.newaxis / expand_dims |
Adds a new axis |
▶ Example: reshape and transpose
import numpy as np
# Reshape: 1D to 2D
data = np.arange(12)
matrix = data.reshape(3, 4)
print(f"Reshaped to 3x4:\n{matrix}")
# Transpose: swap rows and columns
transposed = matrix.T
print(f"Transposed to 4x3:\n{transposed}")
# -1 means auto-calculate dimension
auto_reshape = data.reshape(2, -1)
print(f"Auto reshape (2, -1): shape={auto_reshape.shape}")
Output:
# Executed successfully
(3) The Broadcasting Mechanism
Broadcasting allows arrays of different shapes to automatically expand during arithmetic operations, eliminating the need to manually copy data.
graph TB
A[Scalar + Array] --> B["Scalar broadcast to array shape"]
C["1D + 2D Array"] --> D["1D broadcast along axis 0"]
E["Shape (3,1) + (1,4)"] --> F["Both broadcast to (3,4)"]
▶ Example: Broadcasting in Practice
import numpy as np
# Scalar + array: scalar broadcasts
prices = np.array([100, 200, 300, 400])
discount = 0.9 # 10% off
print(f"Discounted prices: {prices * discount}")
# 1D + 2D: row broadcasts across rows
monthly_sales = np.array([[100, 200], [110, 210], [120, 220]]) # 3 months x 2 categories
growth_rate = np.array([1.05, 1.10]) # different growth per category
print(f"Projected sales:\n{monthly_sales * growth_rate}")
# Column + row broadcast
col = np.array([[1], [2], [3]]) # shape (3, 1)
row = np.array([10, 20, 30, 40]) # shape (4,)
print(f"Col + Row result shape: {(col + row).shape}")
Output:
# Executed successfully
4. Array Indexing and Slicing
(1) Basic Indexing and Slicing
import numpy as np
arr = np.arange(10)
print(f"arr[3]: {arr[3]}") # Single element
print(f"arr[2:7]: {arr[2:7]}") # Slice
print(f"arr[::2]: {arr[::2]}") # Step=2
# 2D indexing
matrix = np.arange(12).reshape(3, 4)
print(f"matrix[1, 2]: {matrix[1, 2]}") # Row 1, Col 2
print(f"matrix[0, :]: {matrix[0, :]}") # Entire row 0
print(f"matrix[:, 1]: {matrix[:, 1]}") # Entire col 1
(2) Boolean Indexing
▶ Example: Filtering High-Sales Categories
import numpy as np
# Category monthly sales (thousand USD)
sales = np.array([250, 145, 90, 70, 400])
categories = np.array(["Electronics", "Clothing", "Food", "Books", "Home"])
# Boolean indexing: find categories above 100k
high_sales = sales > 100
print(f"High sales mask: {high_sales}")
print(f"High sales categories: {categories[high_sales]}")
print(f"High sales values: {sales[high_sales]}")
# Compound conditions
mid_range = (sales >= 80) & (sales <= 200)
print(f"Mid-range categories: {categories[mid_range]}")
Output:
# Executed successfully
(3) Fancy Indexing
▶ Example: Selecting Specific Months and Categories
import numpy as np
sales = np.arange(60).reshape(5, 12) # 5 categories x 12 months
# Select specific months: Jan, Apr, Jul, Oct (indices 0,3,6,9)
quarterly = sales[:, [0, 3, 6, 9]]
print(f"Quarterly data shape: {quarterly.shape}")
# Select top 3 categories by total sales
total = sales.sum(axis=1)
top3_idx = np.argsort(total)[-3:]
print(f"Top 3 category indices: {top3_idx}")
print(f"Top 3 total sales: {total[top3_idx]}")
Output:
# Executed successfully
| Indexing Method | Syntax | Returned Dimensions | Use Case |
|---|---|---|---|
| Basic indexing | arr[2] |
Reduces dimension | Access a single element |
| Slicing | arr[1:5] |
Same dimension | Access a contiguous range |
| Boolean indexing | arr[mask] |
1D | Conditional filtering |
| Fancy indexing | arr[[1,3,5]] |
Same dimension | Access non-contiguous positions |
5. Numerical Computing and Statistics
(1) Vectorized Operations
NumPy's vectorized operations replace Python loops and are the core of its performance advantage.
▶ Example: Vectorization vs. Loop Performance Comparison
import numpy as np
import time
size = 1_000_000
a = np.random.rand(size)
b = np.random.rand(size)
# Vectorized computation
start = time.time()
c = a + b
vec_time = time.time() - start
# Python loop (slow!)
start = time.time()
c_list = [a[i] + b[i] for i in range(size)]
loop_time = time.time() - start
print(f"Vectorized: {vec_time:.4f}s")
print(f"Loop: {loop_time:.4f}s")
print(f"Speedup: {loop_time / vec_time:.0f}x")
Output:
# Executed successfully
(2) Statistical Functions
▶ Example: SalesPredict Sales Statistics
import numpy as np
# Daily sales data for 30 days (thousand USD)
daily_sales = np.array([
45, 52, 48, 61, 55, 72, 68, 50, 53, 49,
58, 63, 71, 66, 59, 54, 47, 70, 75, 62,
51, 57, 64, 69, 73, 60, 56, 67, 74, 78
])
print(f"Mean: {daily_sales.mean():.1f}k USD")
print(f"Median: {np.median(daily_sales):.1f}k USD")
print(f"Std: {daily_sales.std():.1f}k USD")
print(f"Min: {daily_sales.min()}k USD")
print(f"Max: {daily_sales.max()}k USD")
print(f"Total: {daily_sales.sum()}k USD")
# Cumulative sum for trend
cum_sales = daily_sales.cumsum()
print(f"Day 30 cumulative: {cum_sales[-1]}k USD")
Output:
# Executed successfully
(3) Matrix Operations
▶ Example: Ad Spend ROI Matrix Calculation
import numpy as np
# Ad spend per channel (3 channels) x 4 products
ad_matrix = np.array([
[10, 15, 8, 12], # Google Ads (thousand USD)
[5, 20, 10, 8], # Facebook Ads
[3, 7, 15, 10], # Email Marketing
])
# Conversion rate matrix (channel x product)
conv_rate = np.array([
[0.05, 0.03, 0.08, 0.04],
[0.03, 0.06, 0.04, 0.05],
[0.10, 0.08, 0.12, 0.09],
])
# Revenue per conversion (per product, thousand USD)
revenue_per_conv = np.array([50, 30, 80, 40])
# Matrix multiply: expected conversions
expected_conv = ad_matrix * conv_rate # element-wise
# Total revenue per product
total_revenue = expected_conv.sum(axis=0) * revenue_per_conv
print(f"Revenue by product: {total_revenue} thousand USD")
print(f"Total ROI revenue: {total_revenue.sum():.1f} thousand USD")
Output:
# Executed successfully
6. Random Numbers and Linear Algebra
(1) Random Number Generation
import numpy as np
rng = np.random.default_rng(seed=42) # Reproducible
# Common distributions
uniform = rng.uniform(0, 100, size=5) # Uniform [0, 100)
normal = rng.normal(50, 10, size=5) # Normal(mean=50, std=10)
integers = rng.integers(1, 100, size=5) # Random integers [1, 100)
choice = rng.choice(["A", "B", "C"], size=5) # Random choice
print(f"Uniform: {uniform}")
print(f"Normal: {normal}")
print(f"Integers: {integers}")
(2) Linear Algebra
▶ Example: Linear Regression Normal Equation
import numpy as np
# Simulate: sales = 50 + 0.8 * ad_spend + noise
rng = np.random.default_rng(42)
n = 100
ad_spend = rng.uniform(10, 100, n)
noise = rng.normal(0, 5, n)
sales = 50 + 0.8 * ad_spend + noise
# Solve with normal equation: w = (X^T X)^-1 X^T y
X = np.column_stack([np.ones(n), ad_spend]) # Add bias column
w = np.linalg.inv(X.T @ X) @ X.T @ y := sales
# Note: fixed syntax below
w = np.linalg.inv(X.T @ X) @ (X.T @ sales)
print(f"Intercept: {w[0]:.2f}")
print(f"Coefficient: {w[1]:.2f}")
print(f"True values: intercept=50, coeff=0.8")
Output:
# Executed successfully
| Function | Purpose | Syntax |
|---|---|---|
np.dot / @ |
Matrix multiplication | A @ B |
np.linalg.inv |
Matrix inverse | inv(A) |
np.linalg.det |
Determinant | det(A) |
np.linalg.norm |
Vector/matrix norm | norm(v) |
np.linalg.svd |
Singular value decomposition | U, S, Vt = svd(A) |
np.linalg.solve |
Solve linear systems | solve(A, b) |
❓ FAQ
.base attribute to check whether something is a view.np.shares_memory(a, b) to check. Alternatively, check whether a.base is b or b.base is a. When memory is shared, modifying one array will affect the other.📖 Summary
- The ndarray is a multi-dimensional array with uniform types and contiguous memory — NumPy's core data structure
- Broadcasting lets arrays of different shapes automatically expand for arithmetic, avoiding manual data copying
- Vectorized operations are 50–100x faster than Python loops — the core of NumPy's performance
- Boolean indexing and fancy indexing provide flexible ways to filter data
- np.linalg provides matrix inversion, SVD, and other linear algebra operations that underpin the math behind ML algorithms
- Use default_rng(seed) for random number generation to ensure reproducibility
📝 Exercises
- Basic (Difficulty ⭐): Create a 5x5 matrix with values from 1 to 25, then use slicing to extract the diagonal elements. Hint: diagonal elements have the same row and column index, or use
np.diag(). - Intermediate (Difficulty ⭐⭐): Generate 1000 random sales data points from a normal distribution N(100, 15), use boolean indexing to filter values above 130, and calculate their proportion. Hint: use
rng.normal()and boolean indexing. - Challenge (Difficulty ⭐⭐⭐): Implement multiple linear regression using NumPy's normal equation. Assume sales = 20 + 3ad_spend + 1.5traffic + noise. Generate simulated data, solve for the coefficients, and compare them with the true coefficients. Hint: add a bias column when building the X matrix, and use
np.linalg.invwith matrix multiplication.
← Previous: Introduction to Machine Learning | Next: Data Processing with Pandas →