DS DevShelfHub Projects · AI tools
Cheatsheets / NumPy
Cheatsheet · AI frameworks

NumPy: Arrays, Dtypes, Broadcasting, Indexing and Linear Algebra Reference Guide

By DevShelfHub

Arrays, dtypes, shapes, indexing, boolean masks, broadcasting, ufuncs, linear algebra, random, and the Python buffer protocol — NumPy 2.x reference for scientific computing, machine learning data prep, and PyTorch/TensorFlow interop.

118 items ◷ 8 min Arrays Broadcasting ufuncs

Start hereQuick start · 6 you’ll reach for daily

Make an arraynp.array([1,2,3])
Rangenp.arange(10).reshape(2,5)
Mask + indexa[a > 0]
Reduce along axisa.sum(axis=0)
Matrix productA @ B
Modern RNGnp.random.default_rng(42)

Target versions · paceVersions

Targets: 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

bash
# 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 npCanonical alias.
from numpy.linalg import inv, solve, eig, svd, norm, qrLinear algebra. Prefer solve over inv.
from numpy.random import default_rngModern Generator API. Preferred.
from numpy.lib.stride_tricks import sliding_window_viewRolling windows without copies.
from numpy.typing import NDArray, ArrayLikeType hints. NDArray[np.float64] is parameterizable.
import numpy.ma as maMasked 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.nbytesInspect.
a.astype(np.float32)Cast — always returns a copy.
np.float32 / np.float64 / np.int32 / np.int64 / np.bool_ / np.complex128Concrete 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_stackGlue 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.
python
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 Nonea[:, 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.
python
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 / signElement-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 / truncRounding.
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 / nanstdIgnore 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 @ BMatrix product. Preferred over np.dot for 2-D.
A.T @ AGram 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.

python
# 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

Vectorize — never write a Python loop over array elements. A 1000× speedup is typical. If you can’t see a vectorization, write the loop in numba / cython — not Python.
Prefer 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.
Use 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

Slices are views; fancy / boolean indexing returns copies. Writes to a slice mutate the source; writes to a[mask] don’t. Use a[mask] = ... (the indexed assignment form) to modify the original.
NumPy 2.0 promotion is stricter (NEP 50). 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.

Go deeperSee also

NumPy FAQ

What is NumPy and why is it used in Python data science?

NumPy is the foundational numerical computing library for Python. It provides n-dimensional arrays (ndarray) stored in contiguous memory with vectorised C-level operations, making it 10-100x faster than pure Python loops. Pandas, scikit-learn, TensorFlow, and PyTorch all build on NumPy arrays.

What is broadcasting in NumPy?

Broadcasting lets NumPy perform element-wise operations on arrays with different shapes without copying data. NumPy aligns dimensions from the right: if a dimension is 1, it is stretched to match the other. For example, adding a (3, 1) array to a (1, 4) array produces a (3, 4) result.

How do I select elements from a NumPy array?

Use slicing (arr[1:3, :]), integer indexing (arr[[0, 2]]), and boolean masks (arr[arr > 5]). Boolean masks are the most flexible — combine conditions with & and | (not and/or). For structured arrays or complex selections, np.where(condition, x, y) is often cleaner than nested masks.

What is the difference between np.copy() and a NumPy view?

Slicing a NumPy array returns a view — a different object pointing to the same memory. Modifying a view changes the original. Use arr.copy() to get an independent copy. Integer indexing and boolean masking always return copies. Check arr.base is None to tell if an array owns its data.

How do I do linear algebra in NumPy?

The numpy.linalg module covers the essentials: np.linalg.dot (or the @ operator for matrix multiply), np.linalg.inv for matrix inverse, np.linalg.eig for eigenvalues and eigenvectors, and np.linalg.solve to solve Ax=b without computing the inverse explicitly. For performance-critical work, use scipy.linalg instead.

How do I generate random numbers with NumPy?

Use the modern Generator API: rng = np.random.default_rng(seed=42) then rng.integers(0, 10, size=(3, 3)) or rng.standard_normal((100,)). The Generator API is reproducible, thread-safe, and statistically superior to the legacy np.random.* functions. Always pass a seed for reproducible experiments.