NumPy is the foundational library for numerical computing in Python, and it revolves around the ndarray object. Arrays can be created from existing Python lists using np.array([1, 2, 3]), or built from scratch with convenience constructors such as np.zeros(5) for arrays of zeros, np.eye(3) for an identity matrix, and np.arange(0, 10, 2) for evenly stepped integer ranges. When you need a specific number of evenly spaced points over an interval, np.linspace(0, 1, 5) is the right tool, since it returns exactly the requested number of samples between the endpoints.
NumPy's indexing and slicing follow Python conventions but extend naturally to multiple dimensions. The expression arr[1, 2] selects the element in the second row and third column using zero-based indexing, while arr[:, 0] pulls out the entire first column as a 1D array. Slicing such as arr[1:4] follows the half-open rule, so the stop index is excluded. A defining feature of NumPy is broadcasting, which lets you combine arrays of different shapes without writing explicit loops. For instance, arr * 3 multiplies every element by the scalar 3, while arr > 5 produces a boolean array from an element-wise comparison.
Reshaping operations like arr.reshape(3, 4) rearrange elements into a new view whose total size must match the original, and arr.ravel() flattens any array back into 1D. Universal functions, or ufuncs, are the workhorses of element-wise computation: np.sqrt(arr) and np.exp(arr) apply the corresponding mathematical function to every entry. Conditional transformations can be expressed compactly with np.where(arr > 0, arr, 0), which keeps positive values and replaces the rest. NumPy also supports linear-algebra operations such as np.dot(a, b) (equivalently a @ b), the transpose via arr.T, vertical stacking with np.vstack, and random sampling through np.random.randint.