import numpy as np

# vectorized elementwise operations (avoid Python-level loops)
a = np.array([1, 2, 3])
b = a ** 2 + 1                     # array([2, 5, 10])

# broadcasting: operations between differently-shaped arrays
X = np.random.randn(100, 3)        # 100 examples, 3 features
mean = X.mean(axis=0)              # shape (3,)
X_centered = X - mean              # broadcasts mean across all 100 rows

# reproducible randomness (used in every worked example with
# synthetic data throughout this book)
rng = np.random.default_rng(seed=0)
sample = rng.normal(0, 1, size=(10,))
