NumPy cheat sheet
A scannable NumPy reference: 24 short snippets across 12 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| NumPy arrays | A Python list stores pointers to objects scattered in memory. A NumPy ndarray stores fixed-size numbers in one | lesson |
| Indexing, slicing and masks | A slice of an array is a view onto the same memory, not a copy. Changing the slice changes the original. .copy() breaks | lesson |
| Vectorised maths and broadcasting | These are ufuncs: functions that operate element by element at C speed and support an out= parameter so you can write | lesson |
| Reshaping, stacking and splitting | reshape returns a new view when the data is contiguous, so it is cheap. -1 means "work this dimension out for me" | lesson |
| Views, copies and memory layout | Two arrays can point at the same bytes. When they do, one is a view of the other: writing through the view changes the | lesson |
| Sorting, searching and set operations | np.sort returns a sorted copy; arr.sort() sorts in place. When you need the permutation rather than the values, use | lesson |
| Linear algebra with numpy.linalg | The single most expensive misunderstanding in NumPy: * multiplies element by element, while @ performs a matrix | lesson |
| Random number generation with the Generator API | np.random.default_rng() returns an explicit Generator object. It has better statistical properties than the legacy | lesson |
| File I/O: save, load, npz and memory-mapped arrays | NumPy's own formats store dtype, shape and order alongside the bytes, so a round trip is exact. CSV cannot do that: it | lesson |
| Structured arrays, datetimes and string dtypes | A structured array stores records with named fields in one contiguous buffer. It is the closest NumPy gets to a table | lesson |
| Performance: einsum, ufunc tricks and avoiding copies | In einsum you write the index letters of the inputs, then an arrow and the letters you want to keep. Repeated letters | lesson |
| Debugging array code: shape errors and NumPy 2 pitfalls | The message operands could not be broadcast together with shapes (3,4) (4,3) is a complete diagnosis if you read it the | lesson |
Quick snippets
NumPy arrays
Shape, ndim and dtype
import numpy as np
a = np.array([1, 2, 3]) # shape (3,), dtype int64
b = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
a.shape # (3,)
a.ndim # 1
a.dtype # dtype('int64')
a.size # 3 -> total elements
b.shape # (2, 3) rows, columns
Shape, ndim and dtype
np.zeros((2, 3)) # all zeros
np.ones((2, 3)) # all ones
np.full((2, 2), 7) # filled with 7
np.eye(3) # identity
np.arange(0, 10, 2) # 0 2 4 6 8
np.linspace(0, 1, 5) # 5 evenly spaced values
np.random.default_rng(0).normal(size=(2, 2))
dtypes and precision
np.array([1, 2, 3], dtype=np.float64)
np.array([1.7, 2.9]).astype(np.int64) # truncates toward zero: [1, 2]
a = np.arange(5)
a.mean() # 2.0 - float, because mean can be fractional
a.sum() # 10
a.max(), a.min(), a.std()
Indexing, slicing and masks
Slicing returns views
a = np.arange(10)
a[2:5] # array([2, 3, 4])
a[::-1] # reversed
a[::2] # every other element
v = a[0:3]
v[0] = 99
a[0] # 99 -> the slice was a view
c = a[0:3].copy()
c[0] = -1
a[0] # still 99
Slicing returns views
m = np.arange(12).reshape(3, 4)
m[1, 2] # row 1, column 2
m[1] # entire row 1
m[:, 1] # entire column 1
m[0:2, 1:3] # block: rows 0-1, columns 1-2
Boolean masks and fancy indexing
scores = np.array([88, 42, 95, 67, 71])
scores > 70 # array([ True, False, True, False, True])
scores[scores > 70] # array([88, 95, 71])
scores[(scores > 60) & (scores < 90)] # use & | ~ with parentheses
scores[scores < 70] = 0 # conditional assignment
np.where(scores > 70, "pass", "fail")Full lesson: Indexing, slicing and masks →
Vectorised maths and broadcasting
Element-wise operations
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
a + b # array([11, 22, 33])
a * 2 # array([2, 4, 6])
a ** 2 # array([1, 4, 9])
np.sqrt(a)
np.exp(a)
np.log(a)
a.sum(), a.prod(), a.mean(), a.std(), a.cumsum()
Element-wise operations
out = np.empty_like(a, dtype=float)
np.multiply(a, 2.5, out=out) # no new allocation
Broadcasting rules
m = np.ones((3, 4))
v = np.array([1, 2, 3, 4])
m + v # (3,4) + (4,) -> v broadcast across rows ✅
# m + np.array([1, 2, 3]) # (3,4) + (3,) -> ValueError ❌
col = np.array([[10], [20], [30]]) # shape (3, 1)
m + col # one value per row
# normalise each column to 0..1
norm = (m - m.min(axis=0)) / (m.max(axis=0) - m.min(axis=0))Full lesson: Vectorised maths and broadcasting →
Reshaping, stacking and splitting
Reshape and transpose
a = np.arange(12)
a.reshape(3, 4)
a.reshape(3, -1) # -1 -> 4
a.reshape(-1, 6) # -1 -> 2
a.ravel() # flatten (view when possible)
m = np.arange(6).reshape(2, 3)
m.T # transpose: (3, 2)
m.T.shape
m.flatten() # always a copy
Stacking and concatenating
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.concatenate([a, b]) # array([1, 2, 3, 4, 5, 6])
np.stack([a, b]) # shape (2, 3)
np.column_stack([a, b]) # shape (3, 2) - a tidy two-column table
Splitting and masking shapes
m = np.arange(12).reshape(3, 4)
np.split(m, 3) # three (1, 4) pieces
np.hsplit(m, 2) # split columns
top, bottom = np.vsplit(m, [2]) # rows 0-1, then 2
np.newaxis # add an axis
v = np.array([1, 2, 3])
v[:, None].shape # (3, 1)
v[None, :].shape # (1, 3)Full lesson: Reshaping, stacking and splitting →
Views, copies and memory layout
C order, Fortran order and strides
a = np.arange(6, dtype=np.int64).reshape(2, 3)
a.strides # (24, 8) - bytes per step along each axis
a.T.strides # (8, 24) - the transpose is just a view
a.T.flags["C_CONTIGUOUS"] # False
np.ascontiguousarray(a.T) # force a real copy into C order
np.asfortranarray(a) # column-major copy for BLAS-friendly code
a[:, 1].strides # (24,) - a column slice, not contiguous
Where the copies come from
m = np.ones((1000, 1000))
m.ravel() # view when the array is contiguous
m.flatten() # always a copy
m.T @ m # no copy: transposes feed BLAS directly
m.astype(np.float32) # always a copy: the dtype changes
m[m > 0.5] # copy: boolean mask output
np.asarray(m) is m # True - no copy when it is already an array
np.array(m, copy=False) # NumPy 2 raises if a copy would be requiredFull lesson: Views, copies and memory layout →
Sorting, searching and set operations
Searching in sorted data
grades = np.array([10, 20, 30, 40]) # must be sorted
np.searchsorted(grades, 25) # 2 - insert position
np.searchsorted(grades, [5, 35, 40], side="right") # array([0, 3, 4])
# bucket thousands of scores into grade bands in one call
scores = np.array([7, 22, 41, 15])
band = np.searchsorted(grades, scores, side="right") # array([0, 1, 3, 1])
np.nonzero(a > 1) # tuple of index arrays, one per dimension
np.where(a > 1) # same thing for a single conditionFull lesson: Sorting, searching and set operations →
Linear algebra with numpy.linalg
matmul, dot and the asterisk
A = np.array([[3.0, 1.0], [1.0, 2.0]])
B = np.array([[1.0, 0.0], [0.0, 1.0]])
A * B # element-wise, shape (2, 2)
A @ B # matrix product - use this
np.matmul(A, B)
A.dot(B) # same result for 2-D inputs
v = np.array([1.0, 2.0])
A @ v # matrix times vector -> shape (2,)
np.inner(v, v) # 5.0
np.outer(v, v) # shape (2, 2) outer product
Decompositions and norms
S = np.array([[2.0, 1.0], [1.0, 2.0]])
vals, vecs = np.linalg.eig(S) # general eigenvalues
vals, vecs = np.linalg.eigh(S) # symmetric/Hermitian: faster and real
U, s, Vt = np.linalg.svd(M, full_matrices=False) # singular values
L = np.linalg.cholesky(S) # S = L @ L.T for positive-definite S
np.linalg.norm(v) # Euclidean length
np.linalg.norm(M, axis=0) # per-column norms
np.linalg.matrix_rank(M)
np.linalg.cond(A) # large condition number means unstable
np.linalg.det(A)Full lesson: Linear algebra with numpy.linalg →
Random number generation with the Generator API
Choosing a distribution
rng.normal(0, 1, 1000) # heights, measurement error
rng.poisson(3.0, 1000) # counts of rare events per interval
rng.exponential(2.0, 1000) # waiting times between events
rng.beta(2, 5, 1000) # proportions and rates in [0, 1]
rng.lognormal(0, 0.5, 1000) # multiplicative effects, incomes
rng.multinomial(10, [0.2, 0.8], size=3) # dice-like draws
rng.random(3) < 0.3 # Bernoulli with p = 0.3
rng.integers(1, 7, size=10) # a fair die, values 1 to 6Full lesson: Random number generation with the Generator API →
File I/O: save, load, npz and memory-mapped arrays
Text files
np.savetxt("data.csv", a, delimiter=",", fmt="%.3f",
header="a,b,c", comments="")
np.loadtxt("data.csv", delimiter=",", skiprows=1) # numeric, fast, all-or-nothing
# tolerant version: handles missing values, headers and mixed types
t = np.genfromtxt("data.csv", delimiter=",", names=True,
dtype=None, encoding="utf-8")
t["a"]
d = np.loadtxt("data.csv", delimiter=",", skiprows=1,
usecols=(0, 2), max_rows=100)
Arrays bigger than memory
# create a file-backed array, then write through it in blocks
mm = np.memmap("big.dat", dtype=np.float32, mode="w+", shape=(10000, 10000))
for start in range(0, 10000, 500):
mm[start:start + 500] = 0.0
mm.flush()
# later, and on a machine without enough RAM to hold it all
mm = np.memmap("big.dat", dtype=np.float32, mode="r", shape=(10000, 10000))
mm[42, :10] # only the pages touched are read from disk
mm[:, 3].mean() # a column still walks the whole file: slow but possibleFull lesson: File I/O: save, load, npz and memory-mapped arrays →
Structured arrays, datetimes and string dtypes
Structured arrays
dt = np.dtype([("name", "U10"), ("age", "i4"), ("score", "f8")])
rows = np.array([("ada", 36, 91.5), ("bob", 41, 78.0)], dtype=dt)
rows["age"] # array([36, 41], dtype=int32)
rows[rows["age"] > 38] # boolean mask over a field
rows["score"].mean() # 84.75
np.sort(rows, order="score") # sort records by a field
rows[0] # a record, not a view
rows[0]["age"] = 37 # writes through to the buffer
rows["score"] = rows["score"] + 1 # update one field across all records
String dtypes and their traps
s = np.array(["abc", "de"], dtype="U3") # fixed width, space padded
s.astype("U10") # widening is safe
np.char.upper(s) # vectorised string operations
np.char.add(s, "_x")
np.char.str_len(s)
np.array(["long text"], dtype="U3") # silently truncated to 'lon'
s.astype(object) # unlimited length, much slowerFull lesson: Structured arrays, datetimes and string dtypes →
Performance: einsum, ufunc tricks and avoiding copies
einsum notation
A = rng.random((100, 200))
B = rng.random((200, 50))
M = rng.random((4, 4))
T = rng.random((3, 4, 5))
np.einsum("ij,jk->ik", A, B) # matrix product
np.einsum("ii->i", M) # diagonal
np.einsum("ij->ji", A) # transpose
np.einsum("ij,ij->", A, A) # sum of all squares
np.einsum("ij,ij->i", A, A) # one dot product per row
np.einsum("ij,kj->ik", A, B) # row-wise similarity matrix
np.einsum("...ij->...ji", T) # ellipsis: batch-aware transposeFull lesson: Performance: einsum, ufunc tricks and avoiding copies →
Debugging array code: shape errors and NumPy 2 pitfalls
What changed in NumPy 2
np.array(x, copy=False) # NumPy 2: raises if a copy is needed
np.asarray(x) # the portable way to say "no copy if possible"
np.array(x, copy=None) # explicit: copy only when required
np.float_ # removed -> np.float64
np.NaN, np.Inf # removed -> np.nan, np.inf
np.trapz(y, x) # renamed -> np.trapezoid(y, x)
a.ptp() # removed -> np.ptp(a)
print(np.float64(3.0)) # NumPy 2 repr: np.float64(3.0) rather than 3.0Full lesson: Debugging array code: shape errors and NumPy 2 pitfalls →
FAQ
Is this NumPy cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Python 3 pandas Matplotlib Jupyter Notebook Flask FastAPI
Last refreshed 2026-09-27.