Skip to main content
โšก Calmops

NumPy Practical Examples: Arrays, Math, and Linear Algebra

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.

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]

Resources

Comments