Python

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

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) or np.array? in IPython/Jupyter for local documentation.

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

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.

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 increases ndim by 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:

  1. Align dimensions from the right.
  2. Each aligned pair must be equal, or one must be 1.
  3. Missing leading dimensions behave as if they were 1.
  4. 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.

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)

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.

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.

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].

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 and keepdims=True when shape preservation helps.
  • Inspect .shape, .dtype, .strides, and .flags during 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 of np.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