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

5.8 KiB

title, tags, category, difficulty, created
title tags category difficulty created
Introduction to NumPy
python
numpy
data-science
numerical-computing
Lesson Beginner to Intermediate 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.

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:

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:

import numpy as np

1. From Python Lists

# 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:

# 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

# 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.

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.

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].

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.

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).
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).

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:

# ❌ 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 *=.

a = np.ones(1000000)
b = np.ones(1000000)

# Allocates new memory
a = a + b 

# Modifies 'a' in-place (saves memory allocation time)
a += b