Start hereQuick start · 6 you’ll reach for daily
Target versions · paceVersions
numpy ≥ 2.0
python ≥ 3.10
scipy ≥ 1.13
NumPy 2.0 cleaned house: np.float_ /
np.int0 / np.bool8 are gone, scalar promotion now follows NEP 50,
and most legacy aliases dropped. np.random.default_rng() is the modern RNG —
not the legacy np.random.seed / module-level API. np.matrix
has been deprecated for years — use 2-D arrays. This sheet pins to NumPy 2.0+; most idioms work on 1.26+.
Install · BLASSetup
# Install — pick the wheel that matches your accelerator
pip install "numpy>=2" # 2.x, modern default
pip install "numpy>=1.26,<2" # if a dependency pins to 1.x
# Conda — ships with MKL by default
conda install -c conda-forge numpy
# Confirm BLAS backend (huge perf difference)
python -c "import numpy; numpy.show_config()"
# Reproducible random numbers — the modern Generator API
python -c "
import numpy as np
rng = np.random.default_rng(seed=42)
print(rng.standard_normal(3))
"
Where things liveCommon imports
| import numpy as np | Canonical alias. |
| from numpy.linalg import inv, solve, eig, svd, norm, qr | Linear algebra. Prefer solve over inv. |
| from numpy.random import default_rng | Modern Generator API. Preferred. |
| from numpy.lib.stride_tricks import sliding_window_view | Rolling windows without copies. |
| from numpy.typing import NDArray, ArrayLike | Type hints. NDArray[np.float64] is parameterizable. |
| import numpy.ma as ma | Masked arrays — missing-data with sentinel masks. |
array · arange · zerosArray creation
| np.array([1, 2, 3], dtype=np.float32) | From a Python sequence. |
| np.asarray(x) | No-copy view if x is already an ndarray. |
| np.zeros((3, 4)) / np.ones((3, 4)) / np.full((3,4), 7.0) | Constant-filled. |
| np.zeros_like(a) / np.ones_like(a) / np.empty_like(a) | Shape + dtype from another array. |
| np.empty((3, 4)) | Uninitialized — faster, fill it yourself. |
| np.arange(0, 10, 0.5) | Range. Returns floats when step is float. |
| np.linspace(0, 1, 50) / np.logspace(-3, 3, 7) | Even spacing in linear / log scale. |
| np.eye(n) / np.identity(n) | Identity matrix. |
| np.diag([1, 2, 3]) / np.diag(M) | Build diag matrix / extract diagonal. |
| np.meshgrid(x, y, indexing="ij") | Cartesian grids. "ij" uses matrix indexing. |
| np.loadtxt("f.csv", delimiter=",", skiprows=1) | Quick CSV. For real I/O use pandas. |
| np.fromiter(it, dtype=np.float64, count=N) | From a Python iterator — specify count for speed. |
dtype · shape · reshapeDtype, shape, reshape
| a.dtype / a.shape / a.ndim / a.size / a.nbytes | Inspect. |
| a.astype(np.float32) | Cast — always returns a copy. |
| np.float32 / np.float64 / np.int32 / np.int64 / np.bool_ / np.complex128 | Concrete dtypes (no np.int alias in 2.0). |
| a.reshape(2, -1) | Reshape; -1 infers one axis. View if contiguous. |
| a.ravel() / a.flatten() | 1-D: ravel is a view when possible; flatten always copies. |
| a.T / np.transpose(a, axes=(1, 0)) | Transpose. View, not copy. |
| np.expand_dims(a, axis=0) / a[None, ...] | Insert a length-1 axis. |
| np.squeeze(a) | Drop length-1 axes. |
| np.moveaxis(a, 0, -1) / np.swapaxes(a, 0, 1) | Axis reorder. |
| np.concatenate([a, b], axis=0) | Glue along an existing axis. |
| np.stack([a, b], axis=0) / np.column_stack / row_stack | Glue along a NEW axis. |
| np.split(a, 3, axis=0) / np.array_split(a, 5) | Even / uneven split. |
Slice · mask · fancyIndexing & slicing
| a[0] / a[-1] / a[1:5] / a[::-1] | 1-D basics. Negative reverses; step -1 reverses too. |
| a[1, 2] / a[1:3, 2:] | N-D: comma-separated axes. Preferred over chained a[1][2]. |
| a[..., -1] | Ellipsis fills remaining axes. Great for variable rank. |
| a[a > 0] | Boolean mask. Returns 1-D copy. |
| np.where(cond, x, y) | Ternary — element-wise pick from x or y. |
| np.where(cond) | Indices where cond is true. Same as np.nonzero. |
| a[[0, 2, 1]] | Fancy: integer-array indexing. Returns a copy. |
| a[np.ix_([0, 2], [1, 3])] | Cross product of indices — submatrix. |
| np.take(a, idx, axis=0) / np.take_along_axis(...) | Index by integer array along an axis. |
| np.put(a, idx, values) | Inverse of take. In place. |
import numpy as np
a = np.arange(20).reshape(4, 5)
# array([[ 0, 1, 2, 3, 4],
# [ 5, 6, 7, 8, 9],
# [10, 11, 12, 13, 14],
# [15, 16, 17, 18, 19]])
# Slicing (returns a VIEW — writes propagate to the source)
a[1:3, 2:] # rows 1-2, cols 2-end
a[::2] # every other row
a[..., -1] # ellipsis: last col along any rank
# Boolean mask — broadcasts, returns a copy
mask = a > 10
a[mask] # 1-D array of matching values
a[mask] = 0 # in-place edit
# Fancy indexing with integer arrays — returns a copy
rows = np.array([0, 2])
cols = np.array([1, 4])
a[rows, cols] # picks (0,1) and (2,4)
# np.ix_ for cross-product of indices
a[np.ix_(rows, cols)] # 2x2 submatrix
# View vs copy — check with .base
b = a[1:3]
b.base is a # True → b is a view
# Force a copy when needed
c = a[1:3].copy()
Auto-aligned opsBroadcasting
| Rule: align trailing dims, expand size-1 | (3,4) + (4,) → fine. (3,4) + (3,) → fails; use (3,1). |
| a + b[:, None] | Add a column vector to every row. |
| np.newaxis is None | a[:, np.newaxis] = a[:, None]. |
| np.broadcast_to(a, (3, 4)) | Read-only broadcast view to a target shape. |
| np.broadcast_shapes(s1, s2, ...) | Compute the broadcast result shape without allocating. |
| np.outer(u, v) | Equivalent to u[:, None] * v[None, :]. |
| np.einsum("ij,jk->ik", A, B) | Tensor contraction. Explicit and fast. |
import numpy as np
# Broadcasting rule: align trailing dims, expand dims of size 1.
a = np.arange(12).reshape(3, 4) # (3, 4)
b = np.array([10, 20, 30, 40]) # (4,) → broadcasts along rows
c = np.array([[1], [2], [3]]) # (3, 1) → broadcasts along cols
a + b # (3, 4) — row-wise add
a + c # (3, 4) — column-wise add
# Add a new axis explicitly — clearer than reshape
x = np.array([1, 2, 3]) # (3,)
x[:, None] # (3, 1) same as x.reshape(-1, 1)
x[None, :] # (1, 3)
# Outer product via broadcasting — no Python loop
x[:, None] * np.array([10, 20])[None, :] # (3, 2)
# Pairwise distance — vectorized
pts = np.random.default_rng(0).standard_normal((100, 2))
d = np.linalg.norm(pts[:, None, :] - pts[None, :, :], axis=-1) # (100,100)
Vectorized element-wiseMath & ufuncs
| a + b - * / ** // % | Element-wise on broadcast shapes. |
| np.sin / cos / exp / log / sqrt / abs / sign | Element-wise ufuncs. |
| np.add(a, b, out=c) | Write into a pre-allocated buffer — avoid temp arrays. |
| np.add.reduce(a, axis=0) | Reduction. np.sum is the friendly wrapper. |
| np.add.accumulate(a) / np.cumsum(a) | Cumulative reduction. |
| np.add.reduceat(a, [0, 3, 7]) | Group-by-segment reduction. |
| np.clip(a, 0, 1) / np.maximum(a, b) / np.minimum(a, b) | Bound + pairwise max / min. |
| np.round(a, 3) / np.floor / ceil / trunc | Rounding. |
| np.isnan(a) / np.isinf(a) / np.isfinite(a) | Bad-value masks. |
| np.allclose(a, b, atol=1e-8) | Float-tolerance equality. Preferred over == on floats. |
sum · mean · argminReductions & stats
| a.sum(axis=0) / a.mean(axis=1) | Reduce along an axis. keepdims=True preserves rank. |
| np.nansum / nanmean / nanstd | Ignore NaN. Preferred for real-world data with gaps. |
| a.std(ddof=1) / a.var(ddof=1) | Sample std/var with ddof=1. |
| a.argmin(axis=0) / a.argmax(axis=0) | Index of min / max. |
| np.argsort(a) / np.sort(a, axis=0) | Indices that sort / sorted copy. |
| np.argpartition(a, kth=10) | Top-K without full sort. O(N) vs O(N log N). |
| np.quantile(a, [0.25, 0.5, 0.75]) | Quantiles. np.median = q=0.5. |
| np.unique(a, return_counts=True) | Distinct values + counts (sorted). |
| np.bincount(a) / np.histogram(a, bins=...) | Counts of small ints / general histograms. |
| np.corrcoef(X.T) / np.cov(X.T) | Correlation / covariance matrix. |
np.linalg · @ operatorLinear algebra
| A @ B | Matrix product. Preferred over np.dot for 2-D. |
| A.T @ A | Gram matrix. |
| np.linalg.inv(A) | Inverse — avoid in numerics. Use solve. |
| np.linalg.solve(A, b) | Solve A x = b stably. |
| np.linalg.lstsq(A, b, rcond=None) | Least squares — works when A is non-square / rank-deficient. |
| np.linalg.eig(A) / np.linalg.eigh(A) | Eigendecomposition. Use eigh for symmetric / Hermitian matrices. |
| U, s, Vt = np.linalg.svd(A, full_matrices=False) | Singular value decomposition. |
| Q, R = np.linalg.qr(A) | QR decomposition. |
| L = np.linalg.cholesky(A) | Cholesky — for SPD matrices. |
| np.linalg.norm(x, ord=2, axis=-1) | Vector / matrix norms. |
| np.linalg.matrix_rank(A) / cond(A) / det(A) | Numeric properties. |
Generator APIRandom
| rng = np.random.default_rng(seed=42) | Modern Generator. Preferred over np.random.* module fns. |
| rng.standard_normal((1000, 3)) | Standard Gaussian. |
| rng.normal(loc=0, scale=1, size=...) | Parametric Gaussian. |
| rng.uniform(0, 1, size=...) / rng.integers(low, high, size=...) | Uniform float / int. |
| rng.choice(arr, size=10, replace=False, p=weights) | Sampling. replace=False + weights → without replacement. |
| rng.permutation(N) / rng.shuffle(a) | Permute. shuffle is in-place. |
| np.random.seed(42) / np.random.rand(...) | Legacy module-level API. Avoid in new code. |
| rng.spawn(4) | Independent child generators — safe for multiprocessing. |
.npy · .npz · textSaving & loading
| np.save("x.npy", a) / np.load("x.npy") | Single-array binary. Lossless, fast. |
| np.savez("d.npz", x=a, y=b) / np.load("d.npz")["x"] | Multi-array zipped archive. |
| np.savez_compressed("d.npz", x=a) | Compressed variant. Smaller, slower. |
| np.memmap("f.dat", dtype, mode="r", shape=(N, D)) | Memory-mapped array — lazy disk-backed access. |
| np.savetxt("x.csv", a, delimiter=",", fmt="%.6f") | Text dump. Use pandas for real CSVs. |
| np.set_printoptions(precision=4, suppress=True) | Pretty-print floats. |
OLS regressionEnd-to-end · Normal-equation regression
Closed-form least squares, NumPy only. No inv, no loops — lstsq handles rank and stability.
# Linear regression — closed form (normal equation), pure NumPy.
import numpy as np
rng = np.random.default_rng(0)
# Synthetic data: y = 3x1 - 2x2 + 1 + noise
N = 500
X = rng.standard_normal((N, 2))
y = X @ np.array([3.0, -2.0]) + 1.0 + rng.standard_normal(N) * 0.1
# Add the bias column → design matrix
X_b = np.c_[np.ones(N), X] # (N, 3)
# Normal equation: theta = (X.T X)^-1 X.T y — solve, don't invert
theta, *_ = np.linalg.lstsq(X_b, y, rcond=None)
print("theta =", theta) # ≈ [1.0, 3.0, -2.0]
# Predictions + R^2
y_hat = X_b @ theta
ss_res = ((y - y_hat) ** 2).sum()
ss_tot = ((y - y.mean()) ** 2).sum()
r2 = 1 - ss_res / ss_tot
print(f"R^2 = {r2:.4f}")
Best practiceGood to know
numba / cython — not Python.
solve(A, b) over inv(A) @ b.
Inverse is numerically unstable and twice as slow. solve does an LU + back-substitution — smaller error, better conditioning.
default_rng(), not the module-level random functions.
The Generator is faster, statelessness-friendly, fork-safe via spawn(), and the only API that gets new distributions going forward.
Common trapsWatch out for
a[mask] don’t. Use a[mask] = ... (the indexed assignment form) to modify the original.
np.float32(1) + 1.0 now stays float32 instead of widening to float64. Code that relied on implicit upcasting can lose precision silently.
np.array([1, 2, 3]) == [1, 2, 3] returns an array, not a bool.
Using it in if raises "truth value of an array is ambiguous". Wrap with .all(), .any(), or np.array_equal.