NumPy Arrays
NumPy ndarray objects store homogeneous numeric data and expose vectorized operations that replace slow Python loops.
Recipe
Quick-reference recipe card - copy-paste ready.
import numpy as np
# Create, cast dtype, vectorize
a = np.array([1, 2, 3], dtype=np.float64)
b = np.arange(3) # [0, 1, 2]
result = a * 2 + b # broadcasting + ufunc
print(result.shape, result.dtype)When to reach for this:
- Numeric transforms on millions of values (math, masks, aggregations)
- Linear algebra, statistics, and image/tensor preprocessing
- Interop with pandas, SciPy, and ML libraries that expect ndarray input
- Any hot loop that profiling shows spending time in Python bytecode
Working Example
import numpy as np
# Monthly revenue matrix: rows = regions, cols = months
revenue = np.array(
[[120_000, 135_000, 128_000],
[98_000, 102_000, 110_000],
[150_000, 148_000, 155_000]],
dtype=np.int64,
)
# Per-region growth rate vs prior month (column 0 has no prior)
growth = np.diff(revenue, axis=1) / revenue[:, :-1]
# Normalize each region to its own mean (broadcasting)
normalized = (revenue - revenue.mean(axis=1, keepdims=True)) / revenue.std(axis=1, keepdims=True)
# Mask underperforming cells
threshold = revenue.mean()
under = np.where(revenue < threshold, revenue, 0)
print("growth shape:", growth.shape)
print("normalized mean per row:", normalized.mean(axis=1))
print("underperforming total:", under.sum())What this demonstrates:
- 2-D creation with explicit
dtypefor stable integer math np.diffalongaxis=1for column-wise month-over-month change- Broadcasting via
keepdims=Trueso row stats align with matrix shape np.whereas a vectorized conditional without Pythonifper cell
Deep Dive
How It Works
- An
ndarrayis a contiguous buffer plus shape/strides metadata - all elements share one dtype. - Universal functions (ufuncs) apply element-wise ops in compiled loops, often releasing the GIL.
- Broadcasting compares shapes from the right: dimensions match when equal or one side is 1.
- Views share memory with the parent array; copies are explicit (
arr.copy()) or from incompatible assignments.
Creation Patterns
| Function | Typical use |
|---|---|
np.array | From Python sequences |
np.arange / np.linspace | Evenly spaced ranges |
np.zeros / np.ones / np.full | Preallocated buffers |
np.random.default_rng | Reproducible random data |
dtype Quick Reference
| dtype | When |
|---|---|
int32 / int64 | Counts and IDs |
float32 | Large ML features where precision trade-off is OK |
float64 | Default analytics precision |
bool | Masks and predicates |
Python Notes
import numpy as np
# Prefer explicit dtype on ingest
ids = np.array(["1", "2", "3"], dtype=np.int64) # ValueError - cast strings first
# Use default_rng, not legacy np.random.seed
rng = np.random.default_rng(42)
samples = rng.normal(0, 1, size=10_000)
# np.ndarray is not a drop-in for list - .append does not existGotchas
- Mixed types in
np.array-np.array([1, 2.5, 3])becomes float64 and may surprise downstream integer code. Fix: cast explicitly at creation. - Integer division -
/always promotes to float;//truncates toward negative infinity. Fix: usenp.floor_dividewhen you need consistent flooring. - Silent copies vs views - fancy indexing (
arr[[0, 2]]) returns a copy; slices often return views. Fix: call.copy()before mutating filtered results. - NaN propagation -
np.mean([1, np.nan, 3])is NaN unless you passnanmean. Fix: usenp.nanmeanor mask first. - Broadcasting mistakes -
(3, 4) + (4,)works;(3, 4) + (3,)raises unless you reshape. Fix: align witharr[:, np.newaxis]orreshape.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Python lists | Tiny sequences, heterogeneous objects | Million-element numeric transforms |
| pandas Series | Labeled 1-D data with missing values | Raw linear algebra on unlabeled buffers |
| Polars Series | Lazy columnar pipelines at scale | You only need a quick NumPy ufunc |
array module (stdlib) | Typed numeric arrays without NumPy dep | You need broadcasting or linear algebra |
FAQs
Why must NumPy arrays be homogeneous?
- A single dtype lets NumPy store data contiguously and vectorize in C.
- Mixed Python objects live in an object-dtype array - slower and no ufunc speedup.
When does broadcasting copy data?
- Broadcasting creates a virtual expanded shape via strides - no copy until you materialize with
np.broadcast_to(...).copy(). - Writing through a broadcast view can corrupt the underlying buffer.
How do I pick float32 vs float64?
- Use
float32for large tensors where ~7 decimal digits suffice. - Use
float64for financial aggregates unless you have a measured memory win.
What replaced np.random.seed?
import numpy as np
rng = np.random.default_rng(42)
rng.integers(0, 100, size=5)default_rngis the modern, statistically safer API.
How do I sum without overflow on int32 columns?
- Cast to
int64beforesum:arr.astype(np.int64).sum(). - Or use
np.sum(arr, dtype=np.int64).
Can I use NumPy strings for text analytics?
- Fixed-width
np.str_dtypes exist but pandas/Polars handle text better. - Use NumPy for numeric kernels; delegate strings to DataFrame libraries.
Why is my boolean mask the wrong shape?
- Masks must broadcast to the array shape:
(n,)mask on(n, m)needs(n, 1). - Use
mask[:, np.newaxis]ornp.broadcast_to.
How do I avoid modifying a view?
safe = arr[mask].copy()
safe *= 2 # does not touch arrIs np.dot the same as @?
- For 2-D arrays,
@andnp.dotare matrix multiply. - For higher dimensions,
@follows PEP 465 stacking rules - prefer@for clarity.
How do I time NumPy vs pure Python?
- Use
timeiton array sizes you actually process. - Small
ncrosses over where Python lists win due to creation overhead.
Related
- NumPy Indexing & Reshaping - slices, fancy indexing, axes
- pandas Series & DataFrames - labeled tables built on NumPy
- Performance & Memory - dtype downsizing and chunking
- Data Analysis Basics - first analysis tour
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.