ndarray
NumPy ndarray is the core data structure: shape, dtype, strides, and views. Once these are reflex, vectorisation falls out for free.
NumPy — ndarray essentials
EXAMPLE
import numpy as np # ===== Creation ===== a = np.array([1, 2, 3, 4]) # 1D, shape (4,) b = np.array([[1, 2, 3], [4, 5, 6]]) # 2D, shape (2, 3) z = np.zeros((3, 4)) # all zeros o = np.ones((2, 3)) r = np.arange(0, 10, 2) # [0, 2, 4, 6, 8] l = np.linspace(0, 1, 5) # [0., .25, .5, .75, 1.] e = np.empty((2, 2)) # uninitialised, fastest # ===== The four key attributes ===== print(b.shape, b.dtype, b.ndim, b.size) # (2, 3) int64 2 6 # ===== dtype matters ===== i = np.array([1, 2, 3], dtype=np.int32) f = np.array([1, 2, 3], dtype=np.float64) i.itemsize, f.itemsize # 4, 8 # Mismatched dtypes promote silently — costly in tight loops. # ===== Reshape (view, not copy) ===== m = np.arange(12) # shape (12,) m2 = m.reshape(3, 4) # shape (3, 4); same memory m3 = m.reshape(2, 2, 3) # 3D m.shape = (4, 3) # in-place reshape (no copy) # ===== Indexing ===== b[0, 0] # element b[:, 1] # column 1 -> [2, 5] b[1, :] # row 1 -> [4, 5, 6] b[:, 1:3] # all rows, cols 1..2 # Fancy (array) indexing returns a COPY: b[[0, 1], [0, 2]] # picks (0,0) and (1,2) -> [1, 6] # Boolean masks return a COPY: mask = b > 3 b[mask] # 1D array of all elements > 3 # Slicing returns a VIEW: v = b[:, 1:] v[0, 0] = 99 # mutates b too # ===== Broadcasting ===== x = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3) y = np.array([10, 20, 30]) # (3,) x + y # broadcasts y across rows c = np.array([[10], [20]]) # (2, 1) x + c # broadcasts c across cols # Broadcasting rule: align shapes from the right, dim must be equal or 1. # ===== Vectorised math ===== np.sin(b), np.exp(b), np.sqrt(np.abs(b)) b.sum(axis=0) # column sums -> shape (3,) b.sum(axis=1) # row sums -> shape (2,) b.mean(), b.std(), b.argmax(axis=1) # ===== Linear algebra ===== A = np.random.rand(3, 4) B = np.random.rand(4, 2) C = A @ B # matrix product, shape (3, 2) np.linalg.inv(A @ A.T) np.linalg.solve(A.T @ A, A.T @ np.ones(3)) # ===== Memory layout (strides) ===== m2.strides # bytes to step per axis # Knowing strides matters when interfacing with C / GPU / mmap. # ===== Copy vs view ===== copy = b.copy() # explicit copy v = b[:1] # view v.base is b # True — shares memory # ===== Patterns to internalise ===== # - Reach for vectorised ufuncs (np.sin, np.exp, broadcasting) BEFORE for loops # - Use .reshape with -1 to let NumPy infer one dim: x.reshape(-1, 3) # - Slice for views, fancy/boolean index for copies # - Watch dtype early; int64 + float32 promote silently # - axis= argument on reductions; otherwise it reduces everything # ===== Pitfalls ===== # - Modifying a slice view while iterating -> surprises in the parent # - Mixing arrays and Python lists in comparisons -> slow + sometimes wrong # - Forgetting that a.dot(b) requires shape compatibility (use @ for clarity) # - np.empty() in tests -> contains old memory; use np.zeros to be deterministic # - Looping over rows instead of broadcasting -> 10-100x slowdowns
Why it matters
ndarray is the language of NumPy. Shape, dtype, strides, views, broadcasting — five ideas that, once reflex, make the difference between Python-loop slow and vector-fast. Master broadcasting first; the rest follow.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import numpy as np a = np.array([1, 2, 3, 4]) print(a.shape, a.dtype, a.ndim)Try it Yourself »
Exercise
Build a NumPy array from a list.
a = np.
([1, 2, 3])
Five letters.
Discussion
Loading…