--- title: Introduction to NumPy tags: - python - numpy - data-science - numerical-computing category: Lesson difficulty: Beginner to Intermediate created: 2026-05-26 --- # Introduction to NumPy > [!abstract] What is NumPy? > **NumPy** (Numerical Python) is the foundational library for scientific computing in Python. It provides a high-performance multidimensional array object, tools for working with these arrays, and linear algebra, Fourier transform, and random number capabilities. > > ### Why use NumPy instead of standard Python lists? > 1. **Speed:** NumPy arrays are written in C, making mathematical operations up to 100x faster than standard Python lists. > 2. **Memory Efficiency:** NumPy arrays use contiguous blocks of memory, whereas Python lists store pointers to objects scattered across memory. > 3. **Vectorization:** It allows performing mathematical operations on whole arrays without writing slow `for` loops. --- ## 📐 The N-Dimensional Array (`ndarray`) The core of NumPy is the **`ndarray`** (N-dimensional array). It is a grid of values, all of the **same type** (homogenous), indexed by a tuple of non-negative integers. ```mermaid graph TD A[ndarray] --> B["1D Array (Vector)
Shape: (n,)"] A --> C["2D Array (Matrix)
Shape: (m, n)"] A --> D["3D Array (Tensor)
Shape: (p, m, n)"] ``` ### Essential Array Attributes Every array has attributes that describe its structure: ```python import numpy as np arr = np.array([[1, 2, 3], [4, 5, 6]]) print(arr.ndim) # Number of dimensions (axes) -> 2 print(arr.shape) # Tuple representing sizes in each dimension -> (2, 3) print(arr.size) # Total number of elements -> 6 print(arr.dtype) # Data type of the elements -> int64 ``` --- ## 🛠️ Creating Arrays First, import the library using the standard alias: ```python import numpy as np ``` ### 1. From Python Lists ```python # 1D Vector v = np.array([1, 2, 3]) # 2D Matrix m = np.array([[1, 2], [3, 4]]) ``` ### 2. Built-in Placeholders NumPy provides functions to initialize arrays with placeholders, avoiding manual creation: ```python # Array of zeros zeros = np.zeros((3, 4)) # 3 rows, 4 columns # Array of ones ones = np.ones((2, 3), dtype=np.int32) # Range of numbers (similar to range()) range_arr = np.arange(0, 10, 2) # [0, 2, 4, 6, 8] # Linearly spaced numbers linspace_arr = np.linspace(0, 1, 5) # [0.0, 0.25, 0.5, 0.75, 1.0] # Identity Matrix eye_matrix = np.eye(3) # 3x3 identity matrix ``` ### 3. Random Number Generation ```python # Uniform random values between [0.0, 1.0) rand_arr = np.random.rand(2, 2) # Standard normal distribution (mean=0, std=1) randn_arr = np.random.randn(2, 2) # Random integers rand_ints = np.random.randint(1, 100, size=(5,)) ``` --- ## ⚡ Element-wise Operations & Vectorization In standard Python, to add two lists element-wise, you need a list comprehension or loop. In NumPy, you do it directly. ```python x = np.array([1, 2, 3]) y = np.array([4, 5, 6]) print(x + y) # [5, 7, 9] print(x * y) # [4, 10, 18] print(x ** 2) # [1, 4, 9] ``` ### 📡 Broadcasting Broadcasting is a powerful mechanism that allows NumPy to perform arithmetic operations on arrays of **different shapes**. The smaller array is "broadcast" across the larger array so that they have compatible shapes. ```python matrix = np.array([[1, 2, 3], [4, 5, 6]]) scalar = 10 # The scalar is added to every single element print(matrix + scalar) # [[11, 12, 13] # [14, 15, 16]] ``` --- ## 🔍 Indexing, Slicing & Masking ### Slicing 2D Arrays Slicing follows the format `array[row_start:row_end, col_start:col_end]`. ```python arr = np.array([ [10, 11, 12], [20, 21, 22], [30, 31, 32] ]) # Get row at index 1 print(arr[1, :]) # [20, 21, 22] # Get column at index 2 print(arr[:, 2]) # [12, 22, 32] # Slice a subgrid (top-left 2x2) print(arr[0:2, 0:2]) # [[10, 11] # [20, 21]] ``` ### 🎭 Boolean Masking (Conditional Filtering) You can filter arrays using conditions. NumPy returns elements where the condition resolves to `True`. ```python data = np.array([1, 5, 8, 12, 3, 15]) # Create a boolean mask mask = data > 5 # [False, False, True, True, False, True] # Filter using the mask filtered_data = data[mask] # [8, 12, 15] ``` --- ## 🧮 Common Aggregations & Axis Operations Aggregations allow you to compute statistics over entire arrays or along specific **axes**: * `axis=0`: Down the columns (collapses rows). * `axis=1`: Across the rows (collapses columns). ```python arr = np.array([[1, 2], [3, 4]]) # Sum of all elements print(np.sum(arr)) # 10 # Sum down the columns (vertical) print(np.sum(arr, axis=0)) # [4, 6] # Sum across the rows (horizontal) print(np.sum(arr, axis=1)) # [3, 7] ``` --- ## 🔄 Reshaping and Transposing You can change the shape of an array without changing its data using `.reshape()` or `.T` (Transpose). ```python flat = np.arange(1, 7) # [1, 2, 3, 4, 5, 6] # Reshape into a 2x3 matrix matrix = flat.reshape(2, 3) # [[1, 2, 3] # [4, 5, 6]] # Transpose matrix (swap rows and columns) transposed = matrix.T # [[1, 4] # [2, 5] # [3, 6]] ``` --- ## 💡 Best Practices > [!important] Avoid Standard Loops > Standard loops in Python are interpreted, which adds massive overhead. Vectorized operations execute in compiled C, taking advantage of CPU caches and SIMD instructions. > > **Example Comparison:** > ```python > # ❌ Extremely Slow > values = np.random.rand(1_000_000) > reciprocal = [1 / x for x in values] > > # ✅ Near Instantaneous > reciprocal = 1 / values > ``` > [!tip] Use In-place Operations to Save Memory > Instead of creating a new copy, perform calculations directly on the existing array if possible using syntax like `+=`, `-=`, or `*=`. > ```python > a = np.ones(1000000) > b = np.ones(1000000) > > # Allocates new memory > a = a + b > > # Modifies 'a' in-place (saves memory allocation time) > a += b > ```