Working with NumPy arrays is foundational for numerical computing in Python — NumPy provides fast, memory-efficient multidimensional arrays (ndarrays) with vectorized operations, broadcasting, and a rich ecosystem for math, statistics, linear algebra, and more. In 2026, NumPy remains the backbone of scientific Python — powering pandas, Polars (via integration), scikit-learn, PyTorch, TensorFlow, Dask arrays, and large-scale data/ML workflows. Mastering NumPy array creation, indexing, reshaping, broadcasting, ufuncs, and aggregation lets you write concise, performant code that scales from small prototypes to massive datasets.
Here’s a complete, practical guide to working with NumPy arrays: creation methods, indexing/slicing, reshaping/transposing, element-wise & linear algebra ops, aggregation/statistics, real-world patterns, and modern best practices with type hints, memory optimization, and Polars/pandas integration.
Array creation — from lists, zeros/ones, arange/linspace, random, diag, eye.
import numpy as np
# From list
a = np.array([[1, 2, 3], [4, 5, 6]]) # 2D array
print(a.shape, a.dtype) # (2, 3) int64
# Zeros/ones/full
b = np.zeros((2, 3), dtype=np.float32) # 2×3 zeros
c = np.ones((2, 3), dtype=np.int32) # 2×3 ones
d = np.full((2, 3), fill_value=42) # 2×3 filled with 42
# Sequential: arange, linspace
e = np.arange(0, 10, 2) # [0 2 4 6 8]
f = np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1. ]
# Random
g = np.random.rand(2, 3) # uniform [0,1)
h = np.random.randn(2, 3) # standard normal
# Special: diagonal, identity
i = np.diag([1, 2, 3]) # 3×3 diagonal
j = np.eye(3) # 3×3 identity
Indexing & slicing — powerful views (no copy) for access/modification.
arr = np.arange(12).reshape(3, 4)
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# Single element
print(arr[1, 2]) # 6
# Row/column
print(arr[1]) # [4 5 6 7]
print(arr[:, 2]) # [ 2 6 10]
# Slice
print(arr[1:3, 1:3]) # [[ 5 6]
# [ 9 10]]
# Boolean indexing
mask = arr % 2 == 0
print(arr[mask]) # even numbers: [ 0 2 4 6 8 10]
Reshaping & transposing — change shape without copying (views when possible).
k = np.arange(12)
print(k.reshape(3, 4)) # 3×4 view
print(k.reshape(2, -1)) # 2×6 (infer -1)
print(k.T) # transpose (view for 2D+)
print(k.reshape(3, 2, 2).transpose(1, 0, 2)) # permute axes
Element-wise & linear algebra ops — vectorized, broadcasting.
m = np.array([1, 2, 3])
n = np.array([4, 5, 6])
print(m + n) # [5 7 9]
print(m * n) # [ 4 10 18]
print(m ** 2) # [1 4 9]
# Broadcasting
print(m + 10) # [11 12 13]
print(m[:, np.newaxis] + n) # outer sum
Best practices make NumPy array work safe, fast, and memory-efficient. Prefer vectorized ops — avoid loops; use broadcasting. Modern tip: use Polars for columnar data — NumPy shines for n-D numerical arrays. Choose dtype wisely — int32/float32 halves memory vs defaults. Use views — slicing/transpose/reshape — avoid copies (.copy() only when needed). Pre-allocate — np.zeros(n) faster than append. Use np.empty — fastest (uninitialized). Monitor memory — arr.nbytes for raw data, psutil for process usage. Add type hints — def func(arr: np.ndarray[np.float64]) -> np.ndarray[np.float64]. Use np.ascontiguousarray — ensure C-order for performance. Use np.ufunc.reduce — for custom aggregations. Use np.vectorize — for non-vectorized functions (slow fallback). Profile with line_profiler — find slow loops. Use numba — JIT for custom loops. Use dask.array — chunked NumPy for out-of-core. Use xarray — labeled arrays with metadata.
Working with NumPy arrays powers fast, vectorized numerical code — creation, indexing, reshaping, broadcasting, ufuncs, aggregation. In 2026, choose dtype, use views, prefer Polars for columnar, profile memory with psutil, and scale with Dask. Master NumPy arrays, and you’ll write efficient, scalable scientific Python code that handles large numerical data seamlessly.
Next time you need fast array operations — reach for NumPy. It’s Python’s cleanest way to say: “Work with numbers at machine speed.”