Introduction
NumPy is the foundation of Python’s scientific computing stack. This guide covers practical NumPy patterns you’ll use regularly — from array creation and manipulation to linear algebra and vectorized operations. See Python Guide for more context. See Python Guide for more context. See Python Guide for more context.
Array Creation
import numpy as np
# From Python lists
a = np.array([1, 2, 3, 4, 5])
m = np.array([[1, 2, 3], [4, 5, 6]])
# Built-in constructors
zeros = np.zeros((3, 4)) # 3x4 array of 0.0
ones = np.ones((2, 3)) # 2x3 array of 1.0
eye = np.eye(4) # 4x4 identity matrix
full = np.full((2, 3), 7) # 2x3 array of 7
empty = np.empty((3, 3)) # uninitialized (fast)
# Ranges
arange = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
linspace = np.linspace(0, 1, 5) # [0.0, 0.25, 0.5, 0.75, 1.0]
logspace = np.logspace(0, 3, 4) # [1, 10, 100, 1000]
# Random
rand = np.random.rand(3, 3) # uniform [0, 1)
randn = np.random.randn(3, 3) # standard normal
randint = np.random.randint(0, 10, (3, 3)) # integers
seed = np.random.seed(42) # reproducibility
Array Properties and Reshaping
a = np.arange(12)
print(a.shape) # => (12,)
print(a.ndim) # => 1
print(a.size) # => 12
print(a.dtype) # => int64
# Reshape
m = a.reshape(3, 4) # 3x4 matrix
m = a.reshape(2, -1) # 2 rows, infer columns (2x6)
m = a.reshape(-1, 3) # infer rows, 3 columns (4x3)
# Flatten
flat = m.flatten() # always returns a copy
flat = m.ravel() # returns a view when possible
# Transpose
print(m.T) # transpose
print(m.T.shape) # => (4, 3) if m is (3, 4)
# Add/remove dimensions
a = np.array([1, 2, 3])
print(a[np.newaxis, :].shape) # => (1, 3)
print(a[:, np.newaxis].shape) # => (3, 1)
print(np.expand_dims(a, 0).shape) # => (1, 3)
Indexing and Slicing
m = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Basic indexing
print(m[1, 2]) # => 6 (row 1, col 2)
print(m[0]) # => [1 2 3] (first row)
print(m[:, 1]) # => [2 5 8] (second column)
# Slicing
print(m[0:2, 1:3]) # => [[2 3], [5 6]]
print(m[::2, ::2]) # => [[1 3], [7 9]] (every other)
# Boolean indexing
mask = m > 5
print(m[mask]) # => [6 7 8 9]
m[m < 3] = 0 # in-place modification
# Fancy indexing
rows = [0, 2]
cols = [1, 2]
print(m[rows, cols]) # => [2 9] (m[0,1] and m[2,2])
Math Operations
a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
# Element-wise arithmetic
print(a + b) # => [ 6 8 10 12]
print(a * b) # => [ 5 12 21 32]
print(a ** 2) # => [ 1 4 9 16]
print(a / b) # => [0.2 0.33 0.43 0.5]
# Universal functions (ufuncs)
print(np.sqrt(a)) # => [1. 1.41 1.73 2. ]
print(np.exp(a)) # => [ 2.72 7.39 20.09 54.6 ]
print(np.log(a)) # => [0. 0.693 1.099 1.386]
print(np.abs(np.array([-1, -2, 3]))) # => [1 2 3]
# Aggregations
print(a.sum()) # => 10
print(a.mean()) # => 2.5
print(a.std()) # => 1.118
print(a.min(), a.max()) # => 1 4
print(a.argmin()) # => 0 (index of min)
print(a.argmax()) # => 3 (index of max)
print(a.cumsum()) # => [ 1 3 6 10]
print(a.cumprod()) # => [ 1 2 6 24]
# Axis-wise aggregations
m = np.array([[1, 2, 3], [4, 5, 6]])
print(m.sum(axis=0)) # => [5 7 9] (column sums)
print(m.sum(axis=1)) # => [ 6 15] (row sums)
print(m.mean(axis=0)) # => [2.5 3.5 4.5]
Broadcasting
Broadcasting allows operations between arrays of different shapes:
# Scalar broadcast
a = np.array([1, 2, 3])
print(a + 10) # => [11 12 13]
print(a * 2) # => [2 4 6]
# 1D + 2D broadcast
m = np.array([[1, 2, 3],
[4, 5, 6]])
row = np.array([10, 20, 30])
print(m + row)
# => [[11 22 33]
# [14 25 36]]
# Column broadcast
col = np.array([[100], [200]])
print(m + col)
# => [[101 102 103]
# [204 205 206]]
# Outer product via broadcasting
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print(a[:, np.newaxis] * b)
# => [[10 20 30]
# [20 40 60]
# [30 60 90]]
Euclidean Norm (np.linalg.norm)
A = np.array([[1, 2], [1, 3]])
# Row-wise norms (L2 norm of each row)
norms = np.linalg.norm(A, axis=1)
print(norms)
# => [2.23606798 3.16227766]
# Row 0: sqrt(1² + 2²) = sqrt(5) ≈ 2.236
# Row 1: sqrt(1² + 3²) = sqrt(10) ≈ 3.162
# Column-wise norms
col_norms = np.linalg.norm(A, axis=0)
print(col_norms)
# => [1.41421356 3.60555128]
# Frobenius norm (entire matrix)
frob = np.linalg.norm(A)
print(frob) # => 3.872983...
# Normalize rows to unit vectors
normalized = A / norms[:, np.newaxis]
print(np.linalg.norm(normalized, axis=1)) # => [1. 1.]
Vectorize Functions
np.vectorize applies a Python function element-wise to arrays:
def classify(x):
if x < 0:
return "negative"
elif x == 0:
return "zero"
else:
return "positive"
vec_classify = np.vectorize(classify)
arr = np.array([-2, -1, 0, 1, 2])
print(vec_classify(arr))
# => ['negative' 'negative' 'zero' 'positive' 'positive']
Note: np.vectorize is convenient but not faster than a loop — it’s syntactic sugar. For performance, use NumPy’s built-in ufuncs or np.where:
# Faster equivalent using np.where
result = np.where(arr < 0, "negative", np.where(arr == 0, "zero", "positive"))
Linear Algebra
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# Matrix multiplication
print(A @ B) # => [[19 22], [43 50]]
print(np.dot(A, B)) # same
# Determinant
print(np.linalg.det(A)) # => -2.0
# Inverse
print(np.linalg.inv(A))
# => [[-2. 1. ]
# [ 1.5 -0.5]]
# Eigenvalues and eigenvectors
eigenvalues, eigenvectors = np.linalg.eig(A)
print(eigenvalues) # => [-0.372 5.372]
# Solve linear system Ax = b
b = np.array([1, 2])
x = np.linalg.solve(A, b)
print(x) # => [0. 0.5]
print(A @ x) # => [1. 2.] (verify)
# SVD decomposition
U, S, Vt = np.linalg.svd(A)
print(S) # singular values
# Rank
print(np.linalg.matrix_rank(A)) # => 2
Combining Arrays
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# Concatenate
print(np.concatenate([a, b])) # => [1 2 3 4 5 6]
m1 = np.array([[1, 2], [3, 4]])
m2 = np.array([[5, 6], [7, 8]])
print(np.vstack([m1, m2])) # vertical stack (add rows)
# => [[1 2], [3 4], [5 6], [7 8]]
print(np.hstack([m1, m2])) # horizontal stack (add columns)
# => [[1 2 5 6], [3 4 7 8]]
# Stack along new axis
print(np.stack([a, b], axis=0)) # => [[1 2 3], [4 5 6]]
print(np.stack([a, b], axis=1)) # => [[1 4], [2 5], [3 6]]
Useful Patterns
# Clip values to a range
a = np.array([-2, -1, 0, 1, 2, 3])
print(np.clip(a, 0, 2)) # => [0 0 0 1 2 2]
# Unique values and counts
a = np.array([1, 2, 2, 3, 3, 3])
values, counts = np.unique(a, return_counts=True)
print(values, counts) # => [1 2 3] [1 2 3]
# Sort
a = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print(np.sort(a)) # => [1 1 2 3 4 5 6 9]
print(np.argsort(a)) # => [1 3 6 0 2 4 7 5] (indices)
# Where (conditional selection)
a = np.array([1, -2, 3, -4, 5])
print(np.where(a > 0, a, 0)) # => [1 0 3 0 5]
# Tile and repeat
print(np.tile([1, 2], 3)) # => [1 2 1 2 1 2]
print(np.repeat([1, 2], 3)) # => [1 1 1 2 2 2]
Comments