cat 0019-introduction-to-numpy.md
Introduction to NumPy
A practical guide to understanding NumPy through mental models first, syntax second. Learn how arrays, shapes, indexing, broadcasting, aggregation, and linear algebra work as a foundation for Machine Learning and AI.
1. Introduction
NumPy is the core library for numerical computing in Python. It provides a high‑performance multidimensional array object called ndarray, along with tools for working with these arrays.
Why use NumPy?
- Speed: Operations on NumPy arrays are much faster than on Python lists.
- Convenience: Built‑in mathematical functions, broadcasting, and vectorization.
- Foundation: It's the base for many other libraries like Pandas, SciPy, Scikit‑learn, and ML libraries.
import numpy as np2. The NumPy Array
The central object in NumPy is the ndarray. Think of it as a container of numbers organized into dimensions.
- 1D array → vector
- 2D array → matrix
- 3D+ array → multidimensional array (tensor)
Let's build them gradually.
1D Array → Vector
a1 = np.array([1, 2, 3])Visualize:
[1, 2, 3]
There is only one dimension.
a1.ndim # 1
a1.shape # (3,)(3,) means: one dimension containing 3 values. It is not (row, column).
2D Array → Matrix
a2 = np.array([
[1, 2, 3],
[4, 5, 6]
])Visualize:
column
0 1 2
┌───────────
row 0 │ 1 2 3
row 1 │ 4 5 6
a2.shape # (2, 3)
a2.ndim # 2For a 2D array: shape = (rows, columns).
3D Array → Tensor
a3 = np.array([
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
],
[
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]
]
])Think of it as two matrices stacked:
Matrix 1 Matrix 2
1 2 3 10 11 12
4 5 6 13 14 15
7 8 9 16 17 18
a3.shape # (2, 3, 3)Read it as: 2 matrices, 3 rows each, 3 columns each.
Higher‑Dimensional Arrays
NumPy supports any number of dimensions. Example: shape (2, 3, 4, 5).
a4 = np.random.randint(0, 10, size=(2, 3, 4, 5))
a4.shape # (2, 3, 4, 5)3. Array Attributes
Every NumPy array provides useful information through attributes. Memorize these four:
| Attribute | Meaning |
|---|---|
.shape |
How the data is organized (tuple of dimension sizes) |
.ndim |
Number of dimensions |
.size |
Total number of elements |
.dtype |
Data type of the elements |
Example:
a = np.array([[1, 2, 3], [4, 5, 6]])
a.shape # (2, 3)
a.ndim # 2
a.size # 6
a.dtype # int64Mental model: Whenever you see an array, ask:
What is its shape?
↓
How many dimensions?
↓
How many values?
↓
What type are those values?
This habit will save you a lot of confusion later.
4. Creating Arrays
NumPy provides many functions for creating arrays without typing every value manually.
| Function | Description |
|---|---|
np.ones(shape) |
Array filled with ones |
np.zeros(shape) |
Array filled with zeros |
np.arange(start, stop, step) |
Sequence of numbers (stop excluded) |
np.random.randint(low, high, size) |
Random integers (high excluded) |
np.random.random(size) |
Random floats between 0 and 1 |
np.random.rand(d0, d1, ...) |
Random floats (alternative syntax) |
np.random.seed(n) |
Set seed for reproducible random numbers |
Examples
# Create arrays from Python lists/tuples.
a = np.array([1, 2, 3])
b = np.array([[1, 2], [3, 4]])
ones = np.ones((2, 3)) # 2×3 grid of 1s
zeros = np.zeros((2, 3)) # 2×3 grid of 0s
range_array = np.arange(0, 10, 2) # [0 2 4 6 8]
np.random.seed(42) # for reproducibility
# Random integers (low inclusive, high exclusive)
rand_ints = np.random.randint(0, 10, size=(3,5))
# Random floats between 0 and 1
rand_floats = np.random.random((5,3))
# Alternative syntax
rand_floats2 = np.random.rand(5, 3)Why np.random.seed()?
Computers generate pseudo‑random numbers using an algorithm. Setting a seed gives you the same sequence every time.
np.random.seed(42)
print(np.random.randint(10, size=5)) # same output each runThis is crucial in ML experiments for reproducibility.
5. Accessing Data: Indexing & Slicing
Indexing
NumPy uses zero‑based indexing.
a = np.array([10, 20, 30, 40])
a[0] # 10
a[2] # 30For 2D arrays, specify [row, column]:
a = np.array([[10, 20, 30],
[40, 50, 60]])
a[0, 1] # 20 (row 0, column 1)
a[1, 2] # 60For 3D arrays: [depth, row, column].
a3 = np.array([
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
],
[
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]
]
])
a3[0, 1, 2] # 6 (first matrix, second row, third column)Slicing
Slicing uses start:stop (stop is exclusive).
a = np.array([10, 20, 30, 40, 50])
a[:3] # [10 20 30] (everything before index 3)
a[1:4] # [20 30 40]Multidimensional Slicing
Use : to select all in a dimension, or specify ranges.
a3[:2, :2, :2] # first 2 in each dimension
a4[:, :, :, :4] # first 4 from innermost dimensionRead it dimension by dimension, not as a single pattern.
np.unique()
arr = np.array([1, 2, 2, 3, 3, 3])
np.unique(arr) # [1 2 3]6. Array Operations & Broadcasting
Arithmetic Operations
NumPy performs operations element‑wise.
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
a + b # [5 7 9]
a - b # [-3 -3 -3]
a * b # [4 10 18] ← element-wise, NOT matrix multiplication
a / b # [0.25 0.4 0.5]
a // b # [0 0 0] (floor division)
a % 2 # [1 0 1]
a ** 2 # [1 4 9]Universal Functions (ufuncs)
NumPy provides many mathematical functions:
np.add(a, b) # same as a + b
np.exp(a) # e^1, e^2, e^3
np.log(a) # natural logBroadcasting
Broadcasting lets NumPy operate on arrays of different shapes by stretching the smaller array.
a = np.array([1, 2, 3])
a + 5 # [6 7 8] (5 is stretched to [5,5,5])With matrices:
a = np.array([[1, 2, 3],
[4, 5, 6]]) # shape (2,3)
b = np.array([10, 20, 30]) # shape (3,)
a + b # [[11 22 33], [14 25 36]]The Broadcasting Golden Rule
Compare shapes from right to left. Dimensions are compatible if they are equal or one is 1.
(2,3)and(1,3)→ compatible ✅(2,3)and(2,4)→ incompatible ❌ (3 vs 4)
Memorize: right → left; equal? one is 1? otherwise error.
7. Aggregation
Aggregation means reducing many values to a single summary.
arr = np.array([[1, 2, 3],
[4, 5, 6]])
np.sum(arr) # 21
np.mean(arr) # 3.5
np.max(arr) # 6
np.min(arr) # 1
np.std(arr) # standard deviation
np.var(arr) # variancePython vs NumPy Aggregation
- Python lists → use built‑in
sum() - NumPy arrays → prefer
np.sum()(faster and handles multidimensional)
Mean, Variance, Standard Deviation
- Mean → where is the center?
- Variance → how spread out are the values? (average squared distance from mean)
- Standard deviation → square root of variance; roughly how far values are from the center.
high_var = np.array([1, 100, 200, 300, 4000, 5000])
low_var = np.array([2, 4, 6, 8, 10])
np.var(high_var) # much larger
np.var(low_var) # small
np.std(high_var) # large
np.mean(high_var) # around 933Visualizing Variance with Histogram
import matplotlib.pyplot as plt
plt.hist(high_var_array)
plt.show()
plt.hist(low_var_array)
plt.show()8. Reshaping & Transposing
Reshaping
reshape() changes the shape without changing the number of elements.
a = np.array([1, 2, 3, 4, 5, 6]) # shape (6,)
a.reshape(2, 3) # shape (2,3) (2×3=6)
a.reshape(2, 3, 1) # shape (2,3,1)Golden rule: Reshape can change organization, but not the number of elements.
Transposing
.T swaps axes. For a 2D matrix, it swaps rows and columns.
a = np.array([[1, 2, 3],
[4, 5, 6]]) # shape (2,3)
a.T # shape (3,2)
# result:
# [[1 4]
# [2 5]
# [3 6]]Transpose is essential for matrix multiplication.
9. Dot Product & Matrix Multiplication
Dot Product of Vectors
For two vectors:
[a, b, c] · [x, y, z] = a*x + b*y + c*z
A = np.array([1, 2, 3])
B = np.array([4, 5, 6])
np.dot(A, B) # 32* vs np.dot()
*performs element‑wise multiplication (Hadamard product).np.dot()performs multiply and add (dot product).
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
a * b # [4 10 18]
np.dot(a,b) # 32This is a very common beginner mistake.
Matrix Multiplication
Matrix multiplication requires the inner dimensions to match:
(m × n) @ (n × p) = (m × p)
mat1 = np.random.randint(10, size=(5,3))
mat2 = np.random.randint(10, size=(5,3))
mat1.shape # (5,3)
mat2.shape # (5,3) ← can't multiply directly
mat2.T.shape # (3,5)
result = np.dot(mat1, mat2.T) # (5,3) @ (3,5) → (5,5)Real‑World Example: Nut Butter Sales
Goal: Calculate daily revenue for a shop selling three types of nut butter.
Step 1 – The Problem
We have:
- Almond Butter → $10 per unit
- Peanut Butter → $8 per unit
- Cashew Butter → $12 per unit
We record the quantities sold each day for 5 days (Monday–Friday). We want the total revenue for each day.
Step 2 – Create the Data
np.random.seed(0)
sales_amount = np.random.randint(20, size=(5, 3))This produces a 2D array of shape (5, 3):
Almond Peanut Cashew
Day 1 (Mon) 12 15 0
Day 2 (Tue) 3 7 10
Day 3 (Wed) 9 5 1
Day 4 (Thu) 18 16 11
Day 5 (Fri) 17 14 19The prices are stored as a 1D array of shape (3,):
prices = np.array([10, 8, 12])This corresponds to [Almond price, Peanut price, Cashew price].
Step 3 – What the Dot Product Does
The dot product between sales_amount (shape (5,3)) and prices (shape (3,)) is a matrix–vector multiplication. For each row (each day), it computes:
(quantity of Almond × Almond price)
+ (quantity of Peanut × Peanut price)
+ (quantity of Cashew × Cashew price)and returns the sum for that day. The result is a 1D array of shape (5,) – one total per day.
Step 4 – Manual Calculation for Monday
Take the first row (Monday):
monday_sales = sales_amount[0] # array([12, 15, 0])Compute manually:
12 × 10 → 120
15 × 8 → 120
0 × 12 → 0
-----
Total = 240Monday’s revenue is $240.
Step 5 – NumPy Does It All at Once
Instead of writing a loop, we use:
total_sales = np.dot(sales_amount, prices)NumPy internally multiplies each row by the prices and sums them. This is exactly the dot product for each row.
Step 6 – Shape Compatibility
Why does this work?
sales_amount.shape=(5, 3)prices.shape=(3,)
The inner dimensions match: 3 == 3. The result shape is (5,) – one number per day. In matrix multiplication terms: (5,3) · (3,) → (5,). The same operation can be written with the @ operator:
total_sales = sales_amount @ pricesBoth produce the same result.
Step 7 – Seeing the Result
total_sales will be something like:
[240, 206, 167, 424, 486]These are the daily revenues.
Step 8 – Adding to a DataFrame
To make the output easier to read, put the data into a Pandas DataFrame and add a total column:
import pandas as pd
week_days = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri']
butter_types = ['Almond Butter', 'Peanut Butter', 'Cashew Butter']
weekly_sales = pd.DataFrame(
sales_amount,
index=week_days,
columns=butter_types
)
weekly_sales['Total ($)'] = total_salesNow weekly_sales looks like:
Almond Butter Peanut Butter Cashew Butter Total ($)
Mon 12 15 0 240
Tues 3 7 10 206
Wed 9 5 1 167
Thurs 18 16 11 424
Fri 17 14 19 486Step 9 – Why the Dot Product is Perfect Here
- We have multiple rows (days) and one common set of weights (prices).
- The dot product multiplies each row by the same weights and sums the results.
- This is a fundamental operation in many ML algorithms (linear regression, neural networks) where you compute a weighted sum of inputs.
10. Comparison Operators
NumPy comparisons are element‑wise and return Boolean arrays.
a = np.array([1, 5, 10])
a > 5 # [False False True]
a >= 2 # [False True True]
a == a # [True True True]
a != a # [False False False]These Boolean arrays are extremely useful for filtering data.
arr = np.array([1, 2, 3, 4, 5])
arr[arr > 2] # [3 4 5]11. Sorting & Searching
np.sort()– returns a sorted copy.np.argsort()– returns indices that would sort the array.np.argmin(),np.argmax()– return position of min/max.
a = np.array([5, 2, 8, 1, 3])
np.sort(a) # [1 2 3 5 8]
np.argsort(a) # [3 1 4 0 2] (indices of sorted order)
np.argmax(a) # 2 (value 8 is largest)
np.argmin(a) # 3For multi‑dimensional arrays, use axis (see next section).
12. Understanding axis
axis specifies the dimension along which an operation is performed.
For a 2D array:
column
0 1 2
┌───────────
row 0 │ 1 2 3
row 1 │ 4 5 6
axis=0→ operate down the rows (column‑wise).axis=1→ operate across each row (row‑wise).
a = np.array([[1, 2, 3],
[4, 5, 6]])
np.max(a, axis=0) # [4 5 6] (max of each column)
np.max(a, axis=1) # [3 6] (max of each row)
np.argmax(a, axis=0) # [1 1 1] (index of max in each column)
np.argmax(a, axis=1) # [2 2] (index of max in each row)Mental model: axis=0 = down, axis=1 = across.
13. Practical Example: Images as NumPy Arrays
Images are represented as NumPy arrays in computer vision. An RGB image has shape (height, width, 3), where the last dimension holds Red, Green, Blue intensities (0–255).
from matplotlib.image import imread
import matplotlib.pyplot as plt
# Load image (replace with your path)
image = imread('path/to/image.png')
print(type(image)) # numpy.ndarray
print(image.shape) # (height, width, channels)
print(image.ndim) # 3
plt.imshow(image)
plt.axis('off')
plt.show()Each pixel is an array of three numbers: Red, Green, Blue (0–255).
Connection:
Image → Pixels → Numbers → NumPy Array → Mathematics → Machine Learning
14. Performance: Python vs NumPy
NumPy operations are much faster than Python loops, especially on large arrays.
# Create a large array
massive_array = np.random.random(100000)
# Compare sum
%timeit sum(massive_array) # Python built-in
%timeit np.sum(massive_array) # NumPyTypical result: NumPy is 10–100x faster.
Why? NumPy uses optimized C code and vectorization.
15. NumPy to Pandas
NumPy arrays can be easily converted to Pandas DataFrames for tabular data work.
import pandas as pd
a2 = np.array([[1, 2.0, 3.3],
[4, 5, 6.5]])
df = pd.DataFrame(a2)
print(df)You can also set row/column names:
df = pd.DataFrame(a2, columns=['A', 'B', 'C'], index=['row1', 'row2'])16. Key Takeaways & Learning Path
What to Memorize (Prioritized)
- Attributes:
.shape,.ndim,.dtype,.size - Creation:
np.array,np.zeros,np.ones,np.arange, random functions,np.random.seed - Indexing/Slicing:
[row, col],:, multidimensional - Operations:
+,-,*(element‑wise),/,** - Broadcasting: right‑to‑left rule
- Aggregation:
np.sum,np.mean,np.max,np.min,np.std,np.var - Shape manipulation:
.reshape,.T - Linear algebra: dot product, matrix multiplication, shape compatibility
- Sorting:
np.sort,np.argsort,np.argmax,axis
Recommended Study Cycle
For each concept:
Learn idea → tiny example → predict output → run code → explain why
Final Advice
Practice reading shapes, predicting broadcasting compatibility, and understanding the difference between element‑wise multiplication and dot product. These skills will be used constantly in Machine Learning.
This tutorial now covers every concept from my original notebook.