numpy
NumPy is the base array library of scientific Python: a fast N-dimensional array object backed by C, broadcasting functions for elementwise math, plus linear algebra, Fourier transform, and random number modules. Nearly everything in the data stack (pandas, SciPy, scikit-learn, image and ML tooling) accepts or returns NumPy arrays, which makes it less a library you choose and more the substrate the ecosystem is built on.
If you compute on arrays in Python you are using NumPy, directly or through everything built on it. The only real decision left is managing 1.x versus 2.x compatibility across your dependency tree.
Use it if
- You do numeric work on homogeneous arrays: vectorized math, masking, aggregation, linear algebra
- You use any library that speaks ndarray (pandas, SciPy, scikit-learn, OpenCV, matplotlib), which is most of them
- You need to move numeric data between Python and C/C++/Fortran code without copying
- Your data is tabular with mixed types and named columns; pandas or polars is the right layer, not raw ndarrays
- You need GPU execution or automatic differentiation; NumPy is CPU-only by design, so reach for torch, jax, or cupy instead
- Your arrays do not fit in RAM; NumPy is strictly in-memory, which puts you in chunked-storage and out-of-core territory
- You maintain a stack pinned to NumPy 1.x; the 2.0 major removed long-deprecated aliases like np.float_ and np.NaN, changed scalar promotion rules, and broke the C ABI, so upgrading is a dependency-tree project, not a version bump
Setup reality
pip install numpy pulls a prebuilt wheel on every mainstream platform, so the install itself is painless. The pain is environmental: C extensions compiled against NumPy 1.x can refuse to import under 2.x until upstream ships rebuilt wheels, and the NEP 50 promotion changes can silently alter result dtypes in code that mixed Python scalars with narrow arrays. Building from source requires a compiler toolchain and Meson. Running the test suite needs pytest.
Patterns
Create arrays from data or shapescreate-array
import numpy as np
a = np.array([[1, 2], [3, 4]], dtype=np.float64)
z = np.zeros((3, 4))
r = np.arange(0, 10, 2) # [0 2 4 6 8]
l = np.linspace(0.0, 1.0, 5) # 5 evenly spaced pointsnp.array copies its input by default; np.asarray returns the same object when it is already a matching ndarray.
Replace a Python loop with array mathvectorize-math
import numpy as np
x = np.arange(1_000_000, dtype=np.float64)
y = np.sqrt(x) * 2.0 + 1.0 # one C loop, no Python loopA Python for loop over elements throws away the point of NumPy; look for a ufunc or array expression first.
Filter elements with a boolean maskboolean-mask
import numpy as np
a = np.array([-2, -1, 0, 1, 2])
pos = a[a > 0]
mid = a[(a > -2) & (a < 2)]Combine conditions with & and | plus parentheses; Python and/or raise ValueError on arrays.
Combine arrays of different shapesbroadcasting
import numpy as np
m = np.ones((3, 4))
row = np.array([0.0, 1.0, 2.0, 3.0])
out = m + row # row applied to each of the 3 rows
col = np.array([10.0, 20.0, 30.0])
out2 = m + col[:, None] # add an axis to broadcast down columnsShapes align from the trailing axis; use [:, None] (or np.newaxis) to insert axes instead of reshaping by hand.
Generate reproducible random numbersrandom-numbers
import numpy as np
rng = np.random.default_rng(42)
samples = rng.normal(loc=0.0, scale=1.0, size=(3, 3))
ints = rng.integers(low=0, high=10, size=5)
shuffled = rng.permutation(np.arange(10))default_rng is the current API; the legacy global np.random.seed state is discouraged for new code.
Matrix products and solving linear systemsmatrix-multiply
import numpy as np
A = np.array([[3.0, 1.0], [1.0, 2.0]])
b = np.array([9.0, 8.0])
C = A @ A # matrix product
x = np.linalg.solve(A, b) # solve Ax = bThe * operator is elementwise; @ is the matrix product. Prefer solve over inv(A) @ b for accuracy and speed.
Reduce along an axisaggregate-axis
import numpy as np
a = np.arange(12).reshape(3, 4)
col_sums = a.sum(axis=0) # shape (4,)
row_max = a.max(axis=1, keepdims=True) # shape (3, 1)
normalized = a / a.sum(axis=1, keepdims=True)axis is the dimension that disappears; keepdims=True preserves shape so the result broadcasts back cleanly.
Aggregate while ignoring NaNnan-safe-stats
import numpy as np
a = np.array([1.0, np.nan, 3.0])
m = np.nanmean(a) # 2.0
mask = np.isnan(a) # [False True False]
filled = np.where(mask, 0.0, a)Plain mean/sum return nan if any element is nan; the nan-prefixed variants skip them. np.NaN was removed in 2.0, use np.nan.
Persist arrays to disksave-load
import numpy as np
x = np.arange(10)
y = np.ones((2, 2))
np.savez_compressed('data.npz', x=x, y=y)
with np.load('data.npz') as d:
x2 = d['x']Loading object arrays requires allow_pickle=True, which executes pickle on load; treat it as a security decision, not a default.
Reshape and transpose without copyingreshape-transpose
import numpy as np
a = np.arange(6)
m = a.reshape(2, -1) # -1 infers the axis length
t = m.T # transposed view
flat = m.ravel() # view when memory layout allowsreshape and T return views when possible; writing through a view mutates the original array.