Np NumPy Detailed Cheatsheet
A detailed, searchable NumPy 2.x reference covering array creation, indexing, dtypes, reshaping, broadcasting, mathematics, statistics, linear algebra, random sampling, I/O, performance, interoperability, and compact quick-reference tables.
132 reference blocks
1. Introduction to NumPy
What NumPy Is
NumPy is the foundational Python package for efficient numerical computing with homogeneous N-dimensional arrays.
NumPy stands for Numerical Python. Its central object is the ndarray, a fixed-size, homogeneous, N-dimensional array.
NumPy provides:
- compact typed storage for numbers and other fixed-size values;
- vectorized arithmetic and universal functions (
ufuncs); - broadcasting across compatible shapes;
- indexing, reshaping, aggregation, sorting, and searching;
- random sampling, linear algebra, Fourier transforms, and file I/O;
- a common array interface used throughout the scientific Python ecosystem.
import numpy as np
temperatures = np.array([29.5, 30.2, 28.9])
centered = temperatures - temperatures.mean()
Why Use NumPy
Use NumPy when data is naturally rectangular and the same operation must be applied to many values.
| Advantage | Why it matters |
|---|---|
| Speed | Operations run in optimized compiled code instead of Python element-by-element loops. |
| Memory efficiency | One shared dtype avoids storing a full Python object for each scalar. |
| Vectorization | Express array-wide calculations as concise operations. |
| Broadcasting | Combine compatible shapes without manually duplicating data. |
| Rich numerical API | Includes statistics, linear algebra, random sampling, and more. |
| Interoperability | Works naturally with pandas, Matplotlib, SciPy, scikit-learn, and many other libraries. |
NumPy is best for homogeneous numerical data. Python lists remain useful for heterogeneous, dynamically sized collections.
Installation and Import
Install NumPy with a package manager and conventionally import it as `np`.
# pip
python -m pip install numpy
# upgrade
python -m pip install --upgrade numpy
# conda
conda install numpy
# uv
uv add numpy
import numpy as np
Avoid from numpy import *; the np. prefix keeps function origins clear and prevents namespace collisions.
Version Check and Documentation
Inspect the installed version and use the stable user guide or reference for authoritative details.
import numpy as np
print(np.__version__)
np.show_config() # build, BLAS/LAPACK, and compiler information
Useful documentation entry points:
- User guide: concepts, tutorials, and explanations.
- API reference: signatures, parameters, return types, and edge cases.
- Release notes: additions, deprecations, removals, and compatibility changes.
help(np.array)ornp.array?in IPython/Jupyter for local documentation.
2. Creating Arrays
From Python Objects
Create arrays from lists, nested sequences, tuples, scalars, or existing array-like objects.
import numpy as np
a = np.array([1, 2, 3])
b = np.array((1.5, 2.5, 3.5), dtype=np.float32)
matrix = np.array([[1, 2, 3], [4, 5, 6]])
cube = np.array([
[[1, 2], [3, 4]],
[[5, 6], [7, 8]],
])
scalar = np.array(42) # 0-D array
Nested sequences must have a regular rectangular shape unless you deliberately request dtype=object.
ragged = np.array([[1, 2], [3]], dtype=object)
Copy Versus Reuse During Creation
Choose whether construction must copy data or may reuse an existing buffer.
source = np.arange(5)
copied = np.array(source) # copy=True by default
reused = np.asarray(source) # reuses source when compatible
explicit = np.array(source, copy=False)
In modern NumPy, copy=False means a copy is forbidden; a ValueError is raised if dtype, order, or input conversion makes a copy unavoidable. Use copy=None when a copy is acceptable only when necessary.
maybe_copy = np.asarray(source, dtype=np.float64, copy=None)
Test sharing when it matters:
np.shares_memory(source, reused)
np.may_share_memory(source, reused)
Filled and Identity Arrays
Create arrays with a known shape and initial contents.
np.zeros((2, 3)) # float64 zeros
np.ones((2, 3), dtype=np.int16)
np.full((2, 3), 7)
np.empty((2, 3)) # uninitialized memory
np.eye(3) # 3×3 identity-like array
np.eye(3, 4, k=1) # rectangular, offset diagonal
np.identity(3, dtype=int) # square identity only
np.diag([10, 20, 30]) # build diagonal matrix
np.diag(np.arange(9).reshape(3, 3)) # extract main diagonal
np.empty() is fast because it does not initialize values. Never assume its contents are zero.
Numeric Ranges
Generate evenly, logarithmically, or geometrically spaced values.
np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
np.arange(0.0, 1.0, 0.2) # floating steps can accumulate error
np.linspace(0, 1, num=5) # includes endpoint by default
np.linspace(0, 1, num=5, endpoint=False)
values, step = np.linspace(0, 1, 5, retstep=True)
np.logspace(0, 3, num=4, base=10) # 10**[0,1,2,3]
np.geomspace(1, 1000, num=4) # constant ratio
Prefer linspace() when the number of floating-point samples is known; prefer arange() for exact integer steps.
Legacy Random Convenience Functions
Older top-level random helpers remain common in existing code.
np.random.rand(2, 3) # uniform [0, 1), shape as arguments
np.random.randn(2, 3) # standard normal
np.random.randint(0, 10, size=(2, 3))
np.random.random((2, 3)) # uniform [0, 1), shape tuple
np.random.choice([10, 20, 30], size=5, replace=True)
These functions use the legacy global random state. They are useful for reading older code, but new code should prefer an explicit Generator.
Modern Random Generator
Create an independent `Generator` with `default_rng()` for modern random sampling.
rng = np.random.default_rng(2026)
uniform = rng.random((2, 3))
normal = rng.standard_normal((2, 3))
integers = rng.integers(0, 10, size=(2, 3))
sample = rng.choice([10, 20, 30], size=5, replace=False)
Passing a seed makes the sequence reproducible. Passing no seed uses fresh entropy.
rng1 = np.random.default_rng(123)
rng2 = np.random.default_rng(123)
assert np.array_equal(rng1.integers(10, size=5),
rng2.integers(10, size=5))
3. Array Attributes
Shape, Rank, Size, and Type
Inspect an array's dimensional structure and element representation.
a = np.arange(24, dtype=np.float32).reshape(2, 3, 4)
a.shape # (2, 3, 4)
a.ndim # 3
a.size # 24 elements
a.dtype # dtype('float32')
a.itemsize # 4 bytes per element
a.nbytes # 96 bytes for element storage
Relationships:
a.size == np.prod(a.shape)
a.nbytes == a.size * a.itemsize
Strides and Transpose
Use strides to understand memory traversal and `.T` for reversed axes.
a = np.arange(12, dtype=np.int32).reshape(3, 4)
a.strides # commonly (16, 4): byte steps by axis
a.T.shape # (4, 3)
a.T.strides # commonly (4, 16)
strides[i] is the number of bytes moved in memory when index i increases by one along that axis. A transpose usually changes metadata and returns a view instead of rearranging data immediately.
For a 1-D array, a.T has the same shape. Add an axis to create a row or column vector.
Array Flags
Inspect contiguity, ownership, alignment, and writeability through `flags`.
a = np.arange(12).reshape(3, 4)
a.flags
a.flags["C_CONTIGUOUS"]
a.flags["F_CONTIGUOUS"]
a.flags["OWNDATA"]
a.flags["WRITEABLE"]
a.flags["ALIGNED"]
Common meanings:
| Flag | Meaning |
|---|---|
C_CONTIGUOUS |
Data is contiguous in C-style row-major order. |
F_CONTIGUOUS |
Data is contiguous in Fortran-style column-major order. |
OWNDATA |
The array owns its data buffer. |
WRITEABLE |
Elements may be modified. |
ALIGNED |
Memory is suitably aligned for the dtype. |
4. Data Types (dtype)
Common NumPy Data Types
Choose a dtype that provides sufficient range and precision without wasting memory.
| Type | Typical use | Bytes |
|---|---|---|
np.int8 |
tiny signed integers | 1 |
np.int16 |
compact signed integers | 2 |
np.int32 |
general fixed-width integers | 4 |
np.int64 |
large fixed-width integers | 8 |
np.uint8 |
bytes, image channels, nonnegative small integers | 1 |
np.float16 |
compact low-precision floating point | 2 |
np.float32 |
scientific/ML data with moderate precision | 4 |
np.float64 |
default high-precision floating point | 8 |
np.complex64 |
two float32 components |
8 |
np.complex128 |
two float64 components |
16 |
np.bool_ |
boolean masks | commonly 1 |
np.object_ |
references to arbitrary Python objects | pointer-sized |
np.str_ / U |
fixed-width Unicode strings | 4 bytes per code point |
np.bytes_ / S |
fixed-width byte strings | 1 byte per slot |
Platform-sized aliases such as np.int_ can vary by platform. Use explicit widths for file formats, network protocols, and cross-platform binary compatibility.
Specifying and Inspecting dtype
Pass `dtype=` during creation or build a reusable `np.dtype` object.
a = np.array([1, 2, 3], dtype=np.float32)
b = np.zeros(5, dtype="uint8")
dt = np.dtype("<i4") # little-endian signed 32-bit integer
dt.kind # 'i'
dt.itemsize # 4
dt.byteorder
Useful type checks:
np.issubdtype(a.dtype, np.floating)
np.issubdtype(b.dtype, np.integer)
np.iinfo(np.int16) # integer limits
np.finfo(np.float32) # floating-point precision and range
Type Conversion and Safe Casting
Convert with `astype()` and validate risky conversions with casting rules.
a = np.array([1.2, 2.8, -3.5])
integers = a.astype(np.int32) # truncates toward zero
same = integers.astype(np.int32, copy=False)
floats = integers.astype(np.float64)
np.can_cast(np.int16, np.int32, casting="safe") # True
np.can_cast(np.float64, np.int32, casting="safe") # False
safe = integers.astype(np.int64, casting="safe")
common = np.result_type(np.int16, np.float32)
Casting modes include "no", "equiv", "safe", "same_kind", and "unsafe". Validate ranges before narrowing integers or reducing floating-point precision.
Object and String Arrays
Use object arrays sparingly and choose fixed- or variable-width text storage deliberately.
fixed_unicode = np.array(["cat", "tiger"], dtype="U5")
fixed_bytes = np.array([b"OK", b"NO"], dtype="S2")
objects = np.array([1, "two", {"three": 3}], dtype=object)
Fixed-width strings can silently truncate when assigning longer values:
fixed_unicode[0] = "elephant" # stored as "eleph"
NumPy 2.x also provides variable-width UTF-8 strings:
from numpy.dtypes import StringDType
text = np.array(["short", "a much longer value"],
dtype=StringDType())
Object arrays store Python references, lose many low-level performance advantages, and may require pickle-based serialization.
5. Array Indexing
1-D Indexing
Index from the front with nonnegative integers or from the end with negative integers.
a = np.array([10, 20, 30, 40])
a[0] # 10
a[2] # 30
a[-1] # 40
a[-2] # 30
a[1] = 99
2-D and 3-D Indexing
Provide one index per axis to select rows, columns, elements, or deeper slices.
a = np.arange(12).reshape(3, 4)
a[1] # row 1
a[:, 2] # column 2
a[1, 2] # single element
a[1][2] # works, but creates an intermediate lookup
cube = np.arange(24).reshape(2, 3, 4)
cube[1, 2, 3]
cube[0, :, 1]
Prefer comma-separated indexing such as a[1, 2]; it is clearer and usually more efficient than chained indexing.
Integer and Fancy Indexing
Select arbitrary positions with integer index arrays.
a = np.array([10, 20, 30, 40, 50])
a[[0, 3, 4]] # [10, 40, 50]
a[np.array([4, 0, 4])] # repeated selections allowed
m = np.arange(12).reshape(3, 4)
m[[0, 2]] # rows 0 and 2
m[[0, 2], [1, 3]] # elements (0,1) and (2,3)
m[np.ix_([0, 2], [1, 3])] # rectangular 2×2 selection
Advanced/fancy indexing returns a copy rather than a basic-slice view.
Boolean Indexing
Filter values using a boolean array whose shape matches the indexed dimensions.
a = np.array([3, 8, 1, 9, 5])
mask = a >= 5
a[mask] # [8, 9, 5]
a[a % 2 == 1] # odd values
a[(a >= 3) & (a <= 8)] # combine conditions
a[~mask] # invert condition
a[a < 5] = 0 # conditional assignment
Use &, |, and ~ for element-wise logical composition, and wrap each comparison in parentheses.
Ellipsis and New Axes
Use `...` to stand for omitted axes and `None`/`np.newaxis` to insert a length-one axis.
cube = np.zeros((2, 3, 4, 5))
cube[..., 0].shape # (2, 3, 4)
cube[1, ..., 2].shape # (3, 4)
v = np.array([1, 2, 3])
row = v[None, :] # (1, 3)
column = v[:, None] # (3, 1)
same_column = v[:, np.newaxis] # np.newaxis is None
6. Array Slicing
Basic and Step Slicing
Use `start:stop:step`; the stop position is excluded.
a = np.arange(10)
a[2:7] # 2, 3, 4, 5, 6
a[:4] # first four
a[6:] # from index 6
a[::2] # every second value
a[1::3] # start at 1, step by 3
a[::-1] # reverse
a[7:2:-2] # descending step
Multi-Dimensional Slicing
Apply an independent slice to each axis.
a = np.arange(30).reshape(5, 6)
a[1:4, 2:5] # rows 1..3, columns 2..4
a[:, ::2] # all rows, every second column
a[::2, ::-1] # alternating rows, reversed columns
a[1:4, 2] # 1-D result
a[1:4, 2:3] # keeps a length-one column axis
Slice Views and Copy Control
Basic slicing usually returns a view, so edits may affect the original array.
a = np.arange(6)
view = a[1:4]
view[0] = 99
# a is now [0, 99, 2, 3, 4, 5]
independent = a[1:4].copy()
independent[0] = -1
# a is unchanged by the second edit
np.shares_memory(a, view) # True
np.shares_memory(a, independent) # False
Advanced indexing and boolean indexing return copies. Basic indexing and slicing commonly return views.
7. Reshaping Arrays
reshape(), ravel(), and flatten()
Change shape without changing element count, or convert to one dimension.
a = np.arange(12)
m = a.reshape(3, 4)
inferred = a.reshape(2, -1) # infer one dimension
flat_view = m.ravel() # view when possible
flat_copy = m.flatten() # always a copy
reshape() returns a view when the layout permits and otherwise a copy. Modern NumPy supports copy=True, copy=None, or copy=False to control this explicitly.
reshaped = np.reshape(m, (4, 3), copy=None)
resize() Variants
Distinguish the in-place ndarray method from the function that returns a new repeated array.
a = np.arange(6)
a.resize((2, 3)) # modifies a in place; requires ownership
b = np.resize([1, 2, 3], (2, 5))
# repeats input values when the new array is larger
Prefer reshape() when element count stays constant. Use resizing only when changing storage size is genuinely intended.
Transpose and Axis Reordering
Reorder axes with transpose, swapaxes, or moveaxis.
a = np.zeros((2, 3, 4))
a.transpose(2, 0, 1).shape # (4, 2, 3)
np.transpose(a, (1, 2, 0)).shape # (3, 4, 2)
np.swapaxes(a, 0, 2).shape # (4, 3, 2)
np.moveaxis(a, 0, -1).shape # (3, 4, 2)
a.T.shape # reversed axes: (4, 3, 2)
Axis-reordering operations usually return views with different strides.
Removing and Adding Length-One Axes
Use squeeze to remove singleton dimensions and expand_dims to insert them.
a = np.zeros((1, 3, 1, 4))
np.squeeze(a).shape # (3, 4)
np.squeeze(a, axis=0).shape # (3, 1, 4)
b = np.arange(6).reshape(2, 3)
np.expand_dims(b, axis=0).shape # (1, 2, 3)
np.expand_dims(b, axis=-1).shape # (2, 3, 1)
squeeze(axis=...) raises an error when a specified axis does not have length 1.
8. Joining Arrays
concatenate() Versus stack()
Concatenate along an existing axis or stack along a newly created axis.
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.concatenate([a, b], axis=0).shape # (6,)
np.stack([a, b], axis=0).shape # (2, 3)
np.stack([a, b], axis=1).shape # (3, 2)
concatenate()requires matching shapes except on the joined axis.stack()requires identical input shapes and increasesndimby one.
Directional Stack Helpers
Use vertical, horizontal, depth, column, and row-oriented helpers when their conventions match your intent.
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.vstack([a, b]) # shape (2, 3)
np.hstack([a, b]) # shape (6,)
np.dstack([a, b]) # shape (1, 3, 2)
np.column_stack([a, b]) # shape (3, 2)
np.column_stack() turns 1-D inputs into columns before joining. hstack() treats 1-D inputs differently from 2-D inputs, so always inspect shapes.
row_stack Compatibility
Use `vstack()`; `row_stack` was an alias and has been removed in current NumPy.
rows = [np.array([1, 2]), np.array([3, 4])]
result = np.vstack(rows)
Older tutorials may show:
# Legacy only; removed in NumPy 2.5
# np.row_stack(rows)
Replace np.row_stack(...) with np.vstack(...).
block() for Nested Layouts
Assemble arrays from a nested block layout, similar to writing a matrix of submatrices.
A = np.eye(2)
B = np.ones((2, 1))
C = np.zeros((1, 2))
D = np.array([[9]])
joined = np.block([
[A, B],
[C, D],
])
# shape (3, 3)
Inner lists concatenate along the last axis; outer nesting proceeds toward earlier axes.
9. Splitting Arrays
split() and array_split()
Split into equal pieces with `split()` or allow uneven pieces with `array_split()`.
a = np.arange(12)
np.split(a, 3) # three equal arrays of length 4
np.split(a, [3, 8]) # boundaries: [:3], [3:8], [8:]
np.array_split(np.arange(10), 3) # lengths 4, 3, 3
split(a, n) raises ValueError if the chosen axis cannot be divided evenly. Split results are usually views.
Directional Splitting
Split along columns, rows, or depth with convenience functions.
a = np.arange(24).reshape(4, 6)
np.hsplit(a, 3) # split columns: axis=1
np.vsplit(a, 2) # split rows: axis=0
cube = np.arange(24).reshape(2, 3, 4)
np.dsplit(cube, 2) # split depth: axis=2
Equivalent explicit forms:
np.split(a, 3, axis=1)
np.split(a, 2, axis=0)
10. Array Manipulation
append(), insert(), and delete()
Add or remove elements by returning a new array; these functions do not resize in place.
a = np.array([10, 20, 30])
np.append(a, [40, 50])
np.insert(a, 1, [11, 12])
np.delete(a, [0, 2])
m = np.arange(6).reshape(2, 3)
np.append(m, [[6, 7, 8]], axis=0)
np.insert(m, 1, 99, axis=1)
np.delete(m, 0, axis=0)
Without axis=, inputs are flattened first. Repeated appending is inefficient; collect pieces and concatenate once.
repeat() and tile()
Repeat individual elements with `repeat()` or repeat an entire pattern with `tile()`.
a = np.array([1, 2, 3])
np.repeat(a, 2) # [1, 1, 2, 2, 3, 3]
np.repeat(a, [1, 2, 3]) # [1, 2, 2, 3, 3, 3]
np.tile(a, 2) # [1, 2, 3, 1, 2, 3]
np.tile(a, (2, 1)) # shape (2, 3)
For calculations, broadcasting is often more memory-efficient than physically tiling data.
flip(), roll(), and rot90()
Reverse, cyclically shift, or rotate array data along selected axes.
m = np.arange(9).reshape(3, 3)
np.flip(m) # reverse all axes
np.flip(m, axis=0) # reverse rows
np.fliplr(m) # reverse columns
np.flipud(m) # reverse rows
np.roll(m, shift=1, axis=1) # cyclic column shift
np.rot90(m) # 90° counter-clockwise
np.rot90(m, k=2) # 180°
flip commonly returns a view with negative strides; roll generally returns a new array.
11. Mathematical Operations
Arithmetic Operators
Apply element-wise arithmetic to arrays with compatible shapes.
a = np.array([5, 7, 9])
b = np.array([2, 2, 4])
a + b # addition
a - b # subtraction
a * b # element-wise multiplication
a / b # true division
a // b # floor division
a % b # remainder
a ** b # exponentiation
Operators broadcast operands when shapes are compatible. Integer overflow follows fixed-width dtype rules rather than automatically expanding like Python integers.
Named Arithmetic Functions
Use function forms for explicit calls, `out=`, and advanced ufunc options.
np.add(a, b)
np.subtract(a, b)
np.multiply(a, b)
np.divide(a, b)
np.power(a, b)
out = np.empty_like(a, dtype=float)
np.divide(a, b, out=out)
Most are ufuncs and support where=, out=, broadcasting, and dtype casting controls.
Common Numeric Functions
Compute roots, squares, magnitudes, and signs element-wise.
x = np.array([-4.0, -1.0, 0.0, 9.0])
np.sqrt(np.clip(x, 0, None))
np.square(x)
np.abs(x)
np.sign(x) # -1, 0, or 1 for real values
For complex numbers, np.abs() returns magnitude. np.sqrt() of negative real values produces nan unless the input uses a complex dtype.
np.sqrt(np.array([-1], dtype=np.complex128))
12. Universal Functions (ufuncs)
Trigonometric Functions
Apply trigonometric functions element-wise; angles are in radians.
angles = np.array([0, np.pi / 6, np.pi / 2])
np.sin(angles)
np.cos(angles)
np.tan(angles)
values = np.array([-1.0, 0.0, 1.0])
np.arcsin(values)
np.degrees(np.arcsin(values))
np.radians([0, 30, 90])
Exponential and Logarithmic Functions
Compute exponentials and logarithms with natural, base-10, or base-2 scales.
x = np.array([1.0, 2.0, 4.0])
np.exp(x)
np.log(x) # natural logarithm
np.log10(x)
np.log2(x)
np.expm1(1e-10) # accurate exp(x) - 1 for small x
np.log1p(1e-10) # accurate log(1 + x) for small x
Logarithms of zero produce -inf; logarithms of negative real values produce nan unless using a complex dtype.
Rounding Functions
Round toward positive infinity, negative infinity, nearest values, or zero.
x = np.array([-2.7, -1.5, 1.5, 2.7])
np.ceil(x) # toward +infinity
np.floor(x) # toward -infinity
np.round(x) # nearest; ties commonly round to even
np.round(x, 1)
np.trunc(x) # discard fractional part toward zero
Floating-point values are approximate, so decimal rounding may show representation effects.
Ufunc Features
Use ufunc methods and keyword arguments for reductions, selective execution, and in-place output.
a = np.arange(1, 6)
np.add.reduce(a) # sum
np.multiply.accumulate(a) # cumulative product
np.add.outer([1, 2], [10, 20, 30])
out = np.zeros_like(a)
np.square(a, out=out, where=a % 2 == 0)
Important ufunc capabilities include broadcasting, type resolution, out=, where=, reduce, accumulate, outer, and at.
13. Statistical Functions
Aggregations and Dispersion
Reduce an array to totals, central tendency, spread, or extrema.
a = np.array([2, 4, 4, 4, 5, 5, 7, 9])
np.sum(a)
np.mean(a)
np.median(a)
np.std(a) # population standard deviation, ddof=0
np.std(a, ddof=1) # sample standard deviation
np.var(a)
np.min(a)
np.max(a)
np.ptp(a) # max - min
Use dtype controls for precision-sensitive reductions:
np.mean(np.array([1, 2, 3], dtype=np.float32), dtype=np.float64)
Locations of Extrema
Return indices rather than values with argmin and argmax.
a = np.array([[8, 2, 5], [1, 9, 4]])
np.argmin(a) # flat index
np.argmax(a)
np.argmin(a, axis=0) # row index for each column
np.argmax(a, axis=1) # column index for each row
flat_index = np.argmax(a)
np.unravel_index(flat_index, a.shape)
Percentiles, Quantiles, and Weighted Average
Summarize distribution positions or compute a weighted mean.
a = np.array([10, 20, 30, 40, 50])
np.percentile(a, [25, 50, 75]) # percentages from 0 to 100
np.quantile(a, [0.25, 0.5, 0.75])# fractions from 0 to 1
values = np.array([70, 80, 90])
weights = np.array([1, 2, 3])
np.average(values, weights=weights)
avg, weight_sum = np.average(
values, weights=weights, returned=True
)
Use the method= parameter to select the quantile interpolation/estimation method in modern NumPy.
Axis Operations
Use `axis=0` to reduce rows and `axis=1` to reduce columns in a conventional 2-D array.
a = np.array([
[1, 2, 3],
[4, 5, 6],
])
a.sum(axis=0) # [5, 7, 9] column totals
a.sum(axis=1) # [6, 15] row totals
a.mean(axis=0, keepdims=True) # shape (1, 3)
a.std(axis=(0, 1)) # reduce multiple axes
keepdims=True retains reduced axes with length 1, which often makes later broadcasting easier.
14. Linear Algebra
Products and Matrix Multiplication
Choose the product operation that matches vector, matrix, or batched-matrix intent.
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
np.dot(a, b) # matrix product for 2-D inputs
np.matmul(a, b)
a @ b # preferred readable matrix product
x = np.array([1, 2, 3])
y = np.array([4, 5, 6])
np.inner(x, y) # scalar inner product
np.outer(x, y) # 3×3 outer product
np.cross(x, y) # 3-D cross product
* remains element-wise multiplication for ordinary ndarrays.
Trace, Determinant, and Inverse
Inspect square matrices and solve small algebraic tasks.
A = np.array([[4.0, 7.0], [2.0, 6.0]])
np.trace(A)
np.linalg.det(A)
np.linalg.inv(A)
Prefer solving A @ x = b with solve() instead of explicitly computing inv(A) @ b; it is generally clearer, faster, and numerically better.
Eigenvalues, SVD, and Solving Systems
Use decomposition routines for spectral analysis, compression, and linear systems.
A = np.array([[3.0, 1.0], [0.0, 2.0]])
eigenvalues, eigenvectors = np.linalg.eig(A)
U, singular_values, Vh = np.linalg.svd(A, full_matrices=False)
b = np.array([9.0, 8.0])
x = np.linalg.solve(A, b)
np.allclose(A @ x, b)
For symmetric or Hermitian matrices, prefer np.linalg.eigh(). For over- or under-determined systems, consider np.linalg.lstsq().
Vector and Matrix Norms
Measure vector length or matrix magnitude with `np.linalg.norm()`.
v = np.array([3.0, 4.0])
np.linalg.norm(v) # 5.0
np.linalg.norm(v, ord=1) # Manhattan/L1 norm
A = np.arange(1, 5).reshape(2, 2)
np.linalg.norm(A) # Frobenius norm by default
np.linalg.norm(A, ord=2) # largest singular value
np.linalg.norm(A, axis=1) # row-wise vector norms
15. Broadcasting
Broadcasting Rules
Compare shapes from the trailing dimension; dimensions are compatible when equal or when either is 1.
For two shapes:
- Align dimensions from the right.
- Each aligned pair must be equal, or one must be
1. - Missing leading dimensions behave as if they were
1. - The output uses the maximum size of each aligned pair.
(8, 1, 6, 1)
(7, 1, 5)
----------------
(8, 7, 6, 5)
Broadcasting usually changes how values are viewed during an operation; it does not physically duplicate the smaller operand.
Scalar and Vector Broadcasting
Scalars broadcast to every element; vectors align with trailing axes.
a = np.arange(6).reshape(2, 3)
a + 10 # scalar broadcasting
column_offsets = np.array([100, 200, 300])
a + column_offsets # shape (3,) aligns to columns
row_offsets = np.array([10, 20])[:, None]
a + row_offsets # shape (2, 1) aligns to rows
Matrix and Higher-Dimensional Broadcasting
Insert singleton axes to express pairwise and batched operations.
x = np.array([1, 2, 3])
y = np.array([10, 20])
pairwise_sum = x[:, None] + y[None, :]
# shape (3, 2)
images = np.zeros((32, 64, 64, 3))
channel_scale = np.array([0.9, 1.0, 1.1])
scaled = images * channel_scale
Use np.broadcast_shapes() to check a result shape and np.broadcast_to() to create a read-only broadcasted view.
np.broadcast_shapes((32, 64, 64, 3), (3,))
Common Broadcasting Errors
Fix incompatible shapes by checking axis meaning instead of blindly reshaping.
a = np.zeros((2, 3))
b = np.zeros((2,))
# a + b # ValueError: shapes (2,3) and (2,) are incompatible
fixed = a + b[:, None] # b becomes shape (2, 1)
Debugging checklist:
- print every operand's
.shape; - align shapes from the right;
- insert axes with
None/np.newaxis; - verify that the axis being broadcast represents the intended concept;
- avoid
tile()merely to silence an error, because it may waste memory.
16. Sorting
sort() and argsort()
Sort values directly or obtain indices that would sort them.
a = np.array([40, 10, 30, 20])
np.sort(a) # new sorted array
a.sort() # in-place method
order = np.argsort(a) # indices
sorted_a = a[order]
m = np.array([[3, 1], [2, 4]])
np.sort(m, axis=0) # sort down columns
np.sort(m, axis=1) # sort across rows
np.sort(m, axis=None) # flatten then sort
Use kind= when stability or a specific sorting algorithm matters.
lexsort() for Multiple Keys
Sort records by multiple keys; the last key is primary.
last_name = np.array(["Smith", "Jones", "Smith", "Jones"])
first_name = np.array(["Zoe", "Ana", "Ada", "Bob"])
idx = np.lexsort((first_name, last_name))
list(zip(last_name[idx], first_name[idx]))
Here last_name is the primary key because it is supplied last.
partition() and argpartition()
Partially order data when only the smallest, largest, or kth values are needed.
a = np.array([9, 1, 8, 2, 7, 3])
np.partition(a, 2)
# the element at index 2 is in final sorted position;
# smaller elements are before it, larger elements after it
smallest_three = np.partition(a, 3)[:3]
idx = np.argpartition(a, -2)[-2:]
largest_two = a[idx]
largest_two_sorted = largest_two[np.argsort(largest_two)]
Partial sorting is usually cheaper than fully sorting a large array.
17. Searching
where(), nonzero(), and argwhere()
Locate matching positions or choose values conditionally.
a = np.array([[0, 5, 0], [7, 0, 9]])
np.where(a > 0) # tuple of index arrays
np.nonzero(a) # nonzero positions
np.argwhere(a > 0) # rows of coordinates
np.where(a > 0, a, -1) # choose element-wise
argwhere() is convenient for displaying coordinates. For indexing, the tuple returned by where(condition) or nonzero() is usually more direct.
searchsorted()
Find insertion points in an already sorted 1-D array.
a = np.array([10, 20, 20, 30, 40])
np.searchsorted(a, 20, side="left") # 1
np.searchsorted(a, 20, side="right") # 3
np.searchsorted(a, [5, 25, 50]) # [0, 3, 5]
Use sorter= when the source is not physically sorted but you have indices that sort it.
extract()
Return flattened elements selected by a boolean condition.
a = np.arange(10)
condition = (a % 3 == 0)
np.extract(condition, a)
# equivalent for matching shapes:
a[condition]
Boolean indexing is often clearer, while extract() can be useful in functional pipelines.
18. Filtering
Boolean Masks
Build reusable boolean arrays and apply them to select or modify data.
scores = np.array([42, 78, 91, 65, 88])
passed = scores >= 70
scores[passed]
scores[~passed]
adjusted = scores.copy()
adjusted[adjusted < 50] = 50
Multiple Conditions
Combine element-wise comparisons with logical operators or named logical functions.
a = np.arange(20)
a[(a >= 5) & (a < 12)]
a[(a < 3) | (a > 16)]
a[~(a % 2 == 0)]
np.logical_and(a >= 5, a < 12)
np.logical_or(a < 3, a > 16)
np.logical_not(a % 2 == 0)
np.logical_xor(a < 5, a > 15)
Do not use Python's scalar and, or, or not with whole arrays.
Membership Filtering with isin()
Test each element against a collection of accepted or rejected values.
codes = np.array([10, 20, 30, 20, 40])
allowed = [10, 40]
mask = np.isin(codes, allowed)
codes[mask] # [10, 40]
codes[np.isin(codes, allowed, invert=True)]
Set assume_unique=True only when both inputs are known to contain unique values.
19. Unique Operations
unique() Basics
Return sorted unique values and optionally counts or reconstruction metadata.
a = np.array([4, 2, 4, 1, 2, 4])
np.unique(a)
values, counts = np.unique(a, return_counts=True)
values, first_idx = np.unique(a, return_index=True)
values, inverse = np.unique(a, return_inverse=True)
reconstructed = values[inverse]
np.array_equal(reconstructed, a)
Combine optional outputs in the documented order: values, index, inverse, counts.
unique_counts()
Use the Array API-compatible helper when only unique values and counts are needed.
result = np.unique_counts([4, 2, 4, 1, 2, 4])
result.values
result.counts
Equivalent traditional form:
values, counts = np.unique(
[4, 2, 4, 1, 2, 4],
return_counts=True,
equal_nan=False,
)
Unique Rows or Columns
Use `axis=` to treat subarrays as records.
rows = np.array([
[1, 2],
[3, 4],
[1, 2],
])
unique_rows = np.unique(rows, axis=0)
unique_columns = np.unique(rows, axis=1)
20. Set Operations
1-D Set Operations
Treat flattened 1-D values as mathematical sets and return sorted unique results.
a = np.array([1, 2, 2, 3])
b = np.array([3, 4, 4, 5])
np.union1d(a, b) # [1, 2, 3, 4, 5]
np.intersect1d(a, b) # [3]
np.setdiff1d(a, b) # [1, 2]
np.setxor1d(a, b) # [1, 2, 4, 5]
assume_unique=True can improve speed only when inputs truly contain unique values.
Membership with isin()
Preserve the original input shape while checking membership.
grid = np.array([[1, 2, 3], [4, 5, 6]])
selected = np.isin(grid, [2, 4, 6])
selected
grid[selected]
Unlike the 1-D set functions, isin() returns a boolean array shaped like its first input.
21. Conditional Operations
where()
Select from two broadcast-compatible alternatives based on a condition.
x = np.array([-3, -1, 0, 2, 5])
np.where(x >= 0, x, 0) # replace negatives with zero
labels = np.where(x % 2 == 0, "even", "odd")
select() and choose()
Handle multiple conditions or select from a list using integer choice indices.
x = np.array([-5, 0, 4, 12])
conditions = [x < 0, x == 0, x < 10]
choices = ["negative", "zero", "small positive"]
np.select(conditions, choices, default="large positive")
selector = np.array([0, 1, 2, 1])
options = [
np.array([10, 10, 10, 10]),
np.array([20, 20, 20, 20]),
np.array([30, 30, 30, 30]),
]
np.choose(selector, options)
In select(), the first true condition wins.
clip() and piecewise()
Clamp values to bounds or evaluate different functions over different regions.
x = np.array([-5, -1, 0, 4, 12])
np.clip(x, 0, 10)
y = np.piecewise(
x,
[x < 0, x >= 0],
[lambda v: -v, lambda v: v ** 2],
)
clip() is a compact equivalent to applying element-wise lower and upper bounds.
22. Missing and Special Values
NaN and Infinity
Represent undefined floating results with NaN and unbounded values with positive or negative infinity.
x = np.array([1.0, np.nan, np.inf, -np.inf])
np.nan
np.inf
-np.inf
Key behaviors:
np.nan == np.nan # False
np.isclose(np.inf, np.inf) # True
np.nan + 1 # nan
NaN and infinity require floating or complex dtypes; integer arrays cannot directly store them.
Testing Special Values
Detect NaNs, finite values, or infinities with element-wise predicates.
x = np.array([1.0, np.nan, np.inf, -np.inf])
np.isnan(x)
np.isfinite(x)
np.isinf(x)
valid = x[np.isfinite(x)]
NaN-Aware Reductions
Ignore NaNs during selected statistical reductions.
x = np.array([1.0, np.nan, 3.0, 5.0])
np.nanmean(x)
np.nansum(x)
np.nanstd(x)
np.nanmedian(x)
These functions ignore NaNs but not positive or negative infinity. An all-NaN slice may return NaN and emit a warning.
Floating-Point Warning Control
Temporarily control divide-by-zero, overflow, underflow, and invalid-operation handling.
x = np.array([0.0, 1.0])
with np.errstate(divide="ignore", invalid="ignore"):
result = np.log(x)
old = np.seterr(over="raise")
try:
np.exp(1000)
except FloatingPointError:
pass
finally:
np.seterr(**old)
23. Random Module
Generator API
Use a local `Generator` instead of global random state.
rng = np.random.default_rng(42)
rng.random(5)
rng.integers(0, 100, size=5)
rng.standard_normal(5)
Benefits include explicit state, easier reproducibility, independent streams, and access to the modern random API.
Common Distributions
Draw samples from standard probability distributions through `Generator` methods.
rng = np.random.default_rng(42)
uniform = rng.uniform(low=0.0, high=10.0, size=1000)
normal = rng.normal(loc=0.0, scale=1.0, size=1000)
binomial = rng.binomial(n=10, p=0.3, size=1000)
poisson = rng.poisson(lam=4.0, size=1000)
exponential = rng.exponential(scale=2.0, size=1000)
beta = rng.beta(a=2.0, b=5.0, size=1000)
gamma = rng.gamma(shape=2.0, scale=3.0, size=1000)
Remember that NumPy often uses scale, not rate. For an exponential distribution with rate λ, pass scale=1/λ.
choice(), shuffle(), and permutation()
Sample values, shuffle in place, or return a shuffled copy.
rng = np.random.default_rng(42)
a = np.arange(10)
rng.choice(a, size=4, replace=False)
rng.choice(a, size=20, replace=True)
rng.shuffle(a) # modifies a in place
shuffled = rng.permutation(a) # returns a permuted copy
Weighted sampling:
rng.choice(
["A", "B", "C"],
size=10,
p=[0.2, 0.3, 0.5],
)
Seeds and Reproducibility
Seed a Generator for repeatable tests while avoiding the legacy global seed in new code.
rng = np.random.default_rng(12345)
sample = rng.normal(size=5)
Legacy pattern:
np.random.seed(12345) # global RandomState; older code
A fixed seed reproduces the same stream for the same algorithm and call sequence. For parallel applications, use SeedSequence or spawned child generators rather than reusing one identical seed in every worker.
24. File Input and Output
Binary .npy and .npz Files
Preserve dtype and shape efficiently with NumPy's binary formats.
a = np.arange(12).reshape(3, 4)
np.save("array.npy", a)
restored = np.load("array.npy")
np.savez("arrays.npz", data=a, squared=a ** 2)
bundle = np.load("arrays.npz")
bundle["data"]
np.savez_compressed(
"arrays-compressed.npz",
data=a,
squared=a ** 2,
)
.npy stores one array. .npz stores multiple named arrays in a ZIP container. Compression saves disk space at a CPU cost.
Loading Safely and Memory Mapping
Control pickle loading and optionally map large files without reading everything at once.
a = np.load("array.npy", allow_pickle=False)
mapped = np.load(
"large-array.npy",
mmap_mode="r",
allow_pickle=False,
)
Keep allow_pickle=False for untrusted files. Object arrays require pickle, which can execute arbitrary code when loaded from a malicious source.
Text Files
Write delimited text with `savetxt()` and read regular or missing-value data with `loadtxt()` or `genfromtxt()`.
a = np.array([[1.25, 2.5], [3.75, 4.0]])
np.savetxt(
"data.csv",
a,
delimiter=",",
header="x,y",
comments="",
fmt="%.3f",
)
regular = np.loadtxt(
"data.csv",
delimiter=",",
skiprows=1,
)
tolerant = np.genfromtxt(
"data-with-missing.csv",
delimiter=",",
names=True,
missing_values="NA",
filling_values=np.nan,
)
loadtxt() is fast for clean, regular numeric text. genfromtxt() handles missing values and structured columns more flexibly.
25. Array Iteration
Direct Iteration and flat
Direct iteration walks the first axis; `.flat` visits scalar elements in C order.
a = np.arange(6).reshape(2, 3)
for row in a:
print(row)
for value in a.flat:
print(value)
a.flat[::2] = -1
.flat is a one-dimensional iterator object. flatten() is different: it creates a new 1-D array copy.
ndenumerate()
Iterate over `(index_tuple, value)` pairs.
a = np.array([[10, 20], [30, 40]])
for index, value in np.ndenumerate(a):
print(index, value)
# (0, 0) 10
# (0, 1) 20
# ...
nditer()
Use `nditer()` for controlled multi-array iteration, buffering, and write access.
a = np.arange(6).reshape(2, 3)
for value in np.nditer(a):
print(value)
with np.nditer(
a,
op_flags=["readwrite"],
) as it:
for value in it:
value[...] *= 2
Multi-operand iteration:
b = np.ones((1, 3))
for x, y in np.nditer([a, b]):
print(x, y)
Prefer vectorization for ordinary calculations; nditer() is mainly for specialized iteration control.
26. Memory Optimization
Views Versus Copies
Reuse data through views when safe, and copy only when independent storage is required.
a = np.arange(1_000_000)
view = a[100:200] # no element copy
independent = a[100:200].copy() # owns separate data
view.base is not None
np.shares_memory(a, view)
Operations that commonly create views: basic slicing, transpose, many reshapes. Operations that commonly create copies: fancy indexing, boolean indexing, flatten(), explicit .copy().
astype(copy=False)
Avoid a redundant allocation when the requested dtype and order already match.
a = np.arange(10, dtype=np.int32)
same = a.astype(np.int32, copy=False)
converted = a.astype(np.float32, copy=False)
same is a # often True
converted is a # False: conversion needs storage
copy=False is an optimization request, not a guarantee that conversion can avoid allocating memory.
Contiguity and Memory Order
Understand C order, Fortran order, and the cost of non-contiguous access.
a = np.arange(12).reshape(3, 4)
t = a.T
a.flags.c_contiguous # True
t.flags.c_contiguous # often False
t.flags.f_contiguous # often True
c = np.ascontiguousarray(t)
f = np.asfortranarray(a)
- C order: last axis changes fastest.
- Fortran order: first axis changes fastest.
order="K"tries to preserve the existing traversal order.- Contiguous arrays are often easier for compiled libraries and external interfaces to consume.
Choosing Compact dtypes
Reduce storage and memory bandwidth by using the smallest dtype that safely represents the data.
labels = np.array([0, 1, 2, 3], dtype=np.uint8)
measurements = np.array([1.5, 2.5], dtype=np.float32)
labels.nbytes
measurements.nbytes
Check limits before narrowing:
np.iinfo(np.uint8) # 0..255
np.finfo(np.float32)
Compact dtypes improve cache usage, but too little range or precision causes overflow, truncation, or numerical error.
27. Performance Tips
Vectorize Instead of Python Loops
Express element-wise work as array operations so compiled loops handle the data.
a = np.arange(1_000_000, dtype=np.float64)
# Vectorized
result = a * a + 2 * a + 1
# Avoid when a vectorized expression exists
# result = np.array([x*x + 2*x + 1 for x in a])
Vectorization reduces Python interpreter overhead and makes intent easier to inspect.
Use Broadcasting and Ufuncs
Combine arrays without constructing unnecessary repeated intermediates.
points = np.random.default_rng(0).normal(size=(100_000, 3))
offset = np.array([10.0, 20.0, 30.0])
shifted = points + offset # broadcast, no tiled offset needed
lengths = np.sqrt(np.square(points).sum(axis=1))
Use built-in reductions and ufuncs rather than repeatedly calling Python functions per element.
In-Place Operations and out=
Reuse existing buffers when aliasing and dtype rules make it safe.
a = np.arange(10.0)
a *= 2
np.add(a, 5, out=a)
result = np.empty_like(a)
np.sqrt(a, out=result)
In-place operations save allocations but can overwrite values needed later. They also reject casts that would not fit safely into the existing dtype.
Efficient Indexing and Preallocation
Prefer slices, grouped operations, and preallocated outputs over repeated growth.
n = 100_000
out = np.empty(n, dtype=np.float64)
# Fill a known-size result
out[:] = np.linspace(0, 1, n)
# Collect unknown-size pieces, then join once
pieces = [np.arange(1000) for _ in range(100)]
combined = np.concatenate(pieces)
Avoid repeatedly calling np.append() in a loop, because each call returns a new array and copies data.
Measure Real Workloads
Benchmark representative inputs and inspect memory behavior before optimizing.
# IPython/Jupyter examples
%timeit a * a
%timeit np.square(a)
# Memory/layout inspection
a.nbytes
a.strides
a.flags
Choose dtype, layout, algorithm, and chunk size based on measured bottlenecks rather than assumptions.
28. Structured Arrays
Creating Structured Arrays
Store records with named fields and potentially different dtypes in one ndarray.
person_dtype = np.dtype([
("name", "U20"),
("age", "u1"),
("score", "f4"),
])
people = np.array([
("Ada", 36, 98.5),
("Linus", 28, 91.0),
], dtype=person_dtype)
A structured dtype describes a fixed binary record layout rather than a normal homogeneous scalar.
Accessing and Updating Fields
Select fields by name or select one record by position.
people["name"]
people["score"]
people[0]
people[0]["age"]
people["score"] += 1
subset = people[["name", "score"]]
Field access usually returns a view into the underlying record storage.
Custom Field Shapes and Offsets
Define subarray fields or explicit binary layouts when matching external data.
particle_dtype = np.dtype([
("id", np.int32),
("position", np.float64, (3,)),
("active", np.bool_),
])
particles = np.zeros(100, dtype=particle_dtype)
particles["position"].shape # (100, 3)
Inspect layout:
particle_dtype.names
particle_dtype.fields
particle_dtype.itemsize
Use align=True only when C-like field alignment is required.
29. Masked Arrays
Creating Masks
Combine normal ndarray data with a boolean mask where `True` marks invalid entries.
import numpy.ma as ma
data = np.array([10.0, -999.0, 30.0, 40.0])
masked = ma.masked_array(
data,
mask=[False, True, False, False],
)
by_value = ma.masked_equal(data, -999.0)
by_rule = ma.masked_where(data < 0, data)
The mask is separate from the underlying values; masked data may still be present in .data.
Masked Operations
Most masked-array operations ignore invalid entries and propagate masks.
masked.mean()
masked.sum()
masked.std()
masked + 5
np.sqrt(masked)
masked.mask
masked.data
masked.count() # unmasked value count
Masked arrays are useful when a separate validity mask is preferable to NaN, including integer data that cannot store NaN.
Filling and Recovering Values
Convert a masked array to an ordinary ndarray by replacing invalid entries.
masked.filled(np.nan)
ma.filled(masked, fill_value=-1)
masked.fill_value = 999.0
masked.filled()
Choose a fill value compatible with the destination dtype. Filling with np.nan requires a floating or complex output dtype.
30. Date and Time Arrays
datetime64 Arrays
Store date or timestamp values at an explicit unit such as days, seconds, or nanoseconds.
dates = np.array(
["2026-07-21", "2026-07-22", "2026-07-25"],
dtype="datetime64[D]",
)
timestamp = np.datetime64("2026-07-21T14:30", "m")
today = np.datetime64("today", "D")
Common units: Y, M, W, D, h, m, s, ms, us, ns.
timedelta64 and Date Arithmetic
Subtract datetimes to obtain timedeltas and add timedeltas to shift dates.
start = np.datetime64("2026-07-21", "D")
end = np.datetime64("2026-08-01", "D")
duration = end - start # timedelta64[D]
next_week = start + np.timedelta64(7, "D")
schedule = start + np.arange(5) * np.timedelta64(2, "D")
Use explicit units for timedeltas. In NumPy 2.5, generic timedelta64 units are deprecated.
Units, Casting, and NaT
Change precision carefully and represent missing datetime values with `NaT` at an explicit unit.
times = np.array(
["2026-07-21T12:00:00", "2026-07-21T12:00:01"],
dtype="datetime64[s]",
)
days = times.astype("datetime64[D]")
unit, step = np.datetime_data(times.dtype)
missing = np.datetime64("NaT", "D")
np.isnat(missing)
Month (M) and year (Y) durations are calendar-based and cannot always be safely converted to a fixed number of days.
31. Matrix Operations (Legacy)
matrix Versus ndarray
The legacy `matrix` class is always 2-D and changes the meaning of multiplication operators.
A_matrix = np.matrix([[1, 2], [3, 4]])
A_array = np.array([[1, 2], [3, 4]])
A_matrix * A_matrix # matrix multiplication
A_array * A_array # element-wise multiplication
A_array @ A_array # matrix multiplication
Other differences include persistent 2-D shape and matrix-specific attributes such as .A, .A1, .H, and .I.
Why matrix Is Discouraged
Use ordinary ndarrays because they integrate consistently with modern NumPy and support N dimensions.
NumPy documentation no longer recommends np.matrix, even for linear algebra, and warns that it may be removed in the future.
Preferred pattern:
A = np.asarray(A_matrix)
product = A @ A
inverse = np.linalg.inv(A)
Use @ for matrix multiplication and explicit functions such as np.linalg.solve() for linear algebra.
32. Common NumPy Errors
Shape Mismatch and Broadcasting Errors
Inspect operand shapes and align intended axes.
a = np.zeros((2, 3))
b = np.zeros((2,))
# a + b
# ValueError: operands could not be broadcast together
fixed = a + b[:, None]
For matrix multiplication, verify inner dimensions:
A = np.zeros((2, 3))
B = np.zeros((4, 2))
# A @ B # inner dimensions 3 and 4 do not match
IndexError and AxisError
Keep indices and axis numbers within array bounds.
a = np.zeros((2, 3))
# a[2, 0] # IndexError: axis 0 has size 2
# a.sum(axis=2) # AxisError: a has only axes 0 and 1
valid_last_axis = a.sum(axis=-1)
Valid positive axes are 0 through a.ndim - 1; valid negative axes are -a.ndim through -1.
ValueError from Ragged or Invalid Reshapes
Ensure nested sequences are rectangular and reshape sizes match.
# np.array([[1, 2], [3]]) # ragged nested sequence error
ragged = np.array([[1, 2], [3]], dtype=object)
a = np.arange(10)
# a.reshape(3, 4) # 12 slots requested for 10 values
a.reshape(2, 5)
TypeError and Casting Errors
Check dtype compatibility and avoid unsupported scalar operations.
text = np.array(["1", "2", "three"])
# text.astype(int) # invalid literal for integer conversion
numeric = np.array([1.2, 2.8])
# numeric.astype(np.int8, casting="safe")
# TypeError: safe cast is not permitted
Validate input, use np.can_cast(), and handle parsing before constructing a numeric array.
33. NumPy vs Python Lists
Comparison Table
Choose lists for flexible Python objects and ndarrays for homogeneous numerical computation.
| Aspect | Python list | NumPy ndarray |
|---|---|---|
| Element types | Can freely mix types | Normally one shared dtype |
| Memory | Stores Python object references | Compact typed buffer |
| Arithmetic | list * 2 repeats the list |
array * 2 multiplies values |
| Vectorization | Usually requires loops/comprehensions | Built-in element-wise operations |
| Broadcasting | Not built in | Core feature |
| Dimensions | Nested lists by convention | Explicit N-dimensional shape |
| Numerical functions | Standard library or loops | Large optimized numerical API |
| Dynamic resizing | Efficient append to list | Fixed-size storage; joining creates arrays |
| Best use | Heterogeneous records, control flow, dynamic collections | Numeric tables, signals, images, matrices, simulations |
Operation Differences
The same operator can mean different things for lists and arrays.
values_list = [1, 2, 3]
values_array = np.array([1, 2, 3])
values_list * 2
# [1, 2, 3, 1, 2, 3]
values_array * 2
# array([2, 4, 6])
# values_list + 10 # TypeError
values_array + 10 # broadcasts scalar
Conversion
Convert between Python containers and NumPy arrays deliberately.
values_list = [1, 2, 3]
values_array = np.asarray(values_list)
back_to_list = values_array.tolist()
scalar = np.array(42).item()
tolist() converts NumPy scalars to nearby built-in Python types and recursively converts dimensions to nested lists.
34. NumPy with Pandas
Pandas to NumPy
Prefer `.to_numpy()` when an explicit ndarray is required.
import pandas as pd
df = pd.DataFrame({
"x": [1, 2, 3],
"y": [10.0, 20.0, 30.0],
})
arr = df.to_numpy()
arr_float = df.to_numpy(dtype=np.float64, copy=True)
.values still exposes underlying values but offers less control and is not the recommended DataFrame conversion API.
legacy = df.values
Creating DataFrames from Arrays
Provide column labels and optional index labels when wrapping a 2-D array.
arr = np.array([
[1, 10.5],
[2, 20.5],
[3, 30.5],
])
df = pd.DataFrame(arr, columns=["id", "score"])
series = pd.Series(np.array([10, 20, 30]), name="value")
Dtype and Copy Caveats
Mixed pandas columns may be coerced to a common NumPy dtype or an object array.
mixed = pd.DataFrame({
"id": [1, 2],
"label": ["A", "B"],
})
mixed.to_numpy().dtype # commonly object
copy=False does not guarantee zero-copy conversion. Extension dtypes, missing values, and heterogeneous columns may require materialization or conversion.
Converting Back
Wrap a computed ndarray in a DataFrame or assign results as columns.
standardized = (
arr - arr.mean(axis=0)
) / arr.std(axis=0)
result_df = pd.DataFrame(
standardized,
columns=["id_z", "score_z"],
)
df["sum"] = df.to_numpy().sum(axis=1)
35. NumPy with Matplotlib
Plotting Arrays
Pass 1-D arrays as x and y data to Matplotlib.
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set(
xlabel="x",
ylabel="sin(x)",
title="Sine wave",
)
plt.show()
Generating Curves and Grids
Use NumPy to create coordinates and evaluate functions before plotting.
x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2))
fig, ax = plt.subplots()
contour = ax.contourf(X, Y, Z)
fig.colorbar(contour, ax=ax)
plt.show()
Image Arrays
Display 2-D scalar data or 3-D RGB/RGBA arrays with `imshow()`.
image = np.random.default_rng(0).random((100, 150))
fig, ax = plt.subplots()
displayed = ax.imshow(image, origin="lower")
fig.colorbar(displayed, ax=ax)
plt.show()
Typical image shapes:
(height, width)for scalar/grayscale data;(height, width, 3)for RGB;(height, width, 4)for RGBA.
Floating RGB/RGBA data is conventionally in [0, 1]; uint8 image channels are conventionally in [0, 255].
36. Best Practices
Core Practices
Write array code that is explicit about shape, dtype, axes, and ownership.
- Prefer vectorized operations and built-in ufuncs over Python loops.
- Use broadcasting instead of materializing repeated arrays with
tile(). - Choose the smallest dtype that safely preserves range and precision.
- Avoid unnecessary copies, but copy when independent mutation is required.
- Use meaningful
axis=arguments andkeepdims=Truewhen shape preservation helps. - Inspect
.shape,.dtype,.strides, and.flagsduring debugging. - Preallocate known-size outputs and concatenate unknown-size pieces once.
Numerical Reliability
Treat floating-point calculations as approximate and prefer stable algorithms.
np.isclose(0.1 + 0.2, 0.3)
np.allclose(computed, expected, rtol=1e-7, atol=1e-9)
- Do not compare general floating results with exact
==. - Use
np.linalg.solve(A, b)instead ofnp.linalg.inv(A) @ b. - Accumulate low-precision data in a wider dtype when needed.
- Check for NaN and infinity at data boundaries.
- Validate integer ranges before narrowing casts.
Modern API Choices
Prefer current APIs and explicit state over compatibility aliases.
rng = np.random.default_rng(42) # modern random Generator
rows = np.vstack([a, b]) # not removed np.row_stack
values = df.to_numpy() # preferred pandas conversion
product = A @ B # clear matrix multiplication
Read release notes when upgrading across major or recent minor NumPy versions, especially when relying on deprecated aliases or edge-case dtype behavior.
Shape-First Debugging
Make shape reasoning visible in code and tests.
assert features.ndim == 2
assert features.shape[1] == expected_columns
assert weights.shape == (expected_columns,)
predictions = features @ weights
assert predictions.shape == (features.shape[0],)
Small shape assertions catch silent axis mistakes before they propagate into later calculations.
37. Quick Reference Tables
Array Creation and Data Types
Compact lookup for constructors and common dtype choices.
Array creation
| Function | Purpose |
|---|---|
np.array(obj, dtype=...) |
Create from an array-like object |
np.asarray(obj) |
Convert while avoiding copies when possible |
np.zeros(shape) |
Initialize with zeros |
np.ones(shape) |
Initialize with ones |
np.empty(shape) |
Allocate without initialization |
np.full(shape, value) |
Fill with one value |
np.eye(n, m=None, k=0) |
Identity-like 2-D array |
np.diag(v, k=0) |
Build or extract a diagonal |
np.arange(start, stop, step) |
Values with a fixed step |
np.linspace(start, stop, num) |
Fixed number of linear samples |
np.logspace(start, stop, num) |
Logarithmically spaced samples |
np.geomspace(start, stop, num) |
Geometrically spaced samples |
np.fromiter(iterable, dtype) |
Create from an iterator |
np.frombuffer(buffer, dtype) |
View compatible buffer data |
Common dtypes
| dtype | Bytes | Notes |
|---|---|---|
int8, uint8 |
1 | Tiny integers / byte data |
int16 |
2 | Compact integers |
int32 |
4 | Fixed-width general integers |
int64 |
8 | Large fixed-width integers |
float16 |
2 | Low precision |
float32 |
4 | Moderate precision |
float64 |
8 | Default high precision |
complex64 |
8 | Two float32 components |
complex128 |
16 | Two float64 components |
bool_ |
commonly 1 | Boolean masks |
object_ |
pointer-sized | Python object references |
U, S |
fixed width | Unicode / bytes |
StringDType() |
variable | Variable-width UTF-8 strings |
Attributes and Reshaping
Compact lookup for array metadata and shape manipulation.
Common array attributes
| Attribute | Meaning |
|---|---|
.shape |
Length of each axis |
.ndim |
Number of axes |
.size |
Total element count |
.dtype |
Element data type |
.itemsize |
Bytes per element |
.nbytes |
Bytes used by element storage |
.strides |
Byte step for each axis |
.T |
View with reversed axes |
.flags |
Contiguity, ownership, alignment, writeability |
.base |
Object providing shared data, when applicable |
Reshaping
| Function | Purpose |
|---|---|
reshape() |
Change dimensions without changing element count |
ravel() |
Flatten, returning a view when possible |
flatten() |
Flatten to a copy |
resize() |
Change storage size or return resized repeated data |
transpose() |
Permute axes |
swapaxes() |
Exchange two axes |
moveaxis() |
Move axes to new positions |
squeeze() |
Remove axes of length 1 |
expand_dims() |
Add an axis of length 1 |
Joining and Splitting
Compact lookup for combining and partitioning arrays.
Joining
| Function | Purpose |
|---|---|
concatenate() |
Join along an existing axis |
stack() |
Join along a new axis |
vstack() |
Stack row-wise |
hstack() |
Stack horizontally |
dstack() |
Stack along a third axis |
column_stack() |
Turn 1-D inputs into columns |
block() |
Assemble nested blocks |
row_stack() |
Removed in NumPy 2.5; use vstack() |
Splitting
| Function | Purpose |
|---|---|
split() |
Equal splits or explicit boundaries |
array_split() |
Uneven splits allowed |
hsplit() |
Split columns |
vsplit() |
Split rows |
dsplit() |
Split depth/axis 2 |
Math and Statistics
Compact lookup for element-wise functions and reductions.
Mathematical functions
| Function | Purpose |
|---|---|
add, subtract |
Element-wise addition/subtraction |
multiply, divide |
Element-wise multiplication/division |
power, square, sqrt |
Powers, squares, square roots |
abs, sign |
Magnitude and sign |
sin, cos, tan |
Trigonometric functions |
exp, log, log10, log2 |
Exponentials and logarithms |
ceil, floor, round, trunc |
Rounding modes |
clip |
Clamp to bounds |
Statistical functions
| Function | Purpose |
|---|---|
sum, mean, median |
Total and central tendency |
std, var |
Dispersion |
min, max, ptp |
Extrema and range |
argmin, argmax |
Positions of extrema |
percentile, quantile |
Distribution cut points |
average |
Weighted or unweighted average |
nanmean, nansum |
NaN-aware reductions |
Linear Algebra and Random Sampling
Compact lookup for matrix routines and Generator methods.
Linear algebra
| Function | Purpose |
|---|---|
dot() |
Dot/generalized product |
matmul() / @ |
Matrix and batched matrix product |
inner(), outer() |
Inner and outer products |
cross() |
3-D vector cross product |
trace() |
Sum a matrix diagonal |
linalg.det() |
Determinant |
linalg.inv() |
Matrix inverse |
linalg.eig() |
General eigen decomposition |
linalg.svd() |
Singular value decomposition |
linalg.solve() |
Solve square linear systems |
linalg.norm() |
Vector or matrix norm |
Random sampling with rng = np.random.default_rng()
| Method | Purpose |
|---|---|
rng.random() |
Uniform floats in [0, 1) |
rng.uniform() |
Uniform interval |
rng.integers() |
Discrete uniform integers |
rng.normal() |
Normal distribution |
rng.binomial() |
Binomial counts |
rng.poisson() |
Poisson counts |
rng.exponential() |
Exponential waiting times |
rng.beta() |
Beta distribution |
rng.gamma() |
Gamma distribution |
rng.choice() |
Sample values |
rng.shuffle() |
Shuffle in place |
rng.permutation() |
Return shuffled copy |
Searching, Sorting, and File I/O
Compact lookup for ordering, lookup, filtering, and persistence.
Searching and sorting
| Function | Purpose |
|---|---|
sort() |
Sorted values |
argsort() |
Sorting indices |
lexsort() |
Indirect multi-key sort |
partition() |
Partial value ordering |
argpartition() |
Partial index ordering |
where() |
Positions or conditional selection |
nonzero() |
Nonzero positions |
argwhere() |
Coordinate rows |
searchsorted() |
Sorted insertion locations |
extract() |
Flattened conditional extraction |
isin() |
Membership mask |
unique() |
Unique values and metadata |
File I/O
| Function | Purpose |
|---|---|
save() |
One binary .npy array |
load() |
Read .npy or .npz |
savez() |
Multiple arrays in .npz |
savez_compressed() |
Compressed .npz |
savetxt() |
Write text/CSV-like data |
loadtxt() |
Read clean numeric text |
genfromtxt() |
Read text with missing/structured data |
