Files
framework_note/note/LLM/Introduction to NumPy.md
T
2026-05-30 16:02:33 -04:00

238 lines
5.8 KiB
Markdown

---
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) <br> Shape: (n,)"]
A --> C["2D Array (Matrix) <br> Shape: (m, n)"]
A --> D["3D Array (Tensor) <br> 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
> ```