NumPy: Core Array Operations - Arithmetic and Broadcasting
🎭 The Magic Factory Story
Imagine you have a magic factory that makes toys. In this factory, you have shelves with toy boxes. Each shelf can hold many boxes, and each box has a number on it.
NumPy arrays are like these shelves of numbered boxes. Today, we’ll learn how to do math with ALL the boxes at once — like magic!
1️⃣ Element-wise Arithmetic
What Does “Element-wise” Mean?
Think of two rows of toy boxes, side by side:
Row A: [1] [2] [3]
Row B: [4] [5] [6]
Element-wise means: Box 1 talks to Box 1. Box 2 talks to Box 2. Box 3 talks to Box 3.
They work in pairs, like dance partners!
The Four Basic Operations
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# Addition: Each box adds its partner
a + b # [5, 7, 9]
# Subtraction: Each box subtracts
a - b # [-3, -3, -3]
# Multiplication: Each box multiplies
a * b # [4, 10, 18]
# Division: Each box divides
a / b # [0.25, 0.4, 0.5]
🎨 Visual: Dance Partners
graph TD A1["1"] --> R1["1+4=5"] B1["4"] --> R1 A2["2"] --> R2["2+5=7"] B2["5"] --> R2 A3["3"] --> R3["3+6=9"] B3["6"] --> R3
Key Rule: Both arrays must have the same shape — same number of boxes!
2️⃣ Scalar Operations on Arrays
What’s a Scalar?
A scalar is just a single number. Not an array. Just ONE lonely number, like 5.
The Magic Multiplier
Imagine you have a magic wand that touches every box at once:
import numpy as np
toys = np.array([1, 2, 3, 4])
# Add 10 to EVERY box
toys + 10 # [11, 12, 13, 14]
# Multiply EVERY box by 2
toys * 2 # [2, 4, 6, 8]
# Subtract 1 from EVERY box
toys - 1 # [0, 1, 2, 3]
# Divide EVERY box by 2
toys / 2 # [0.5, 1.0, 1.5, 2.0]
🎨 Visual: Magic Wand Effect
graph TD S["Scalar: 10"] --> A1["1+10=11"] S --> A2["2+10=12"] S --> A3["3+10=13"] S --> A4["4+10=14"]
Why it works: NumPy automatically copies the scalar to match every element. One number becomes many!
3️⃣ Broadcasting Concept
The Stretchy Magic!
Broadcasting is NumPy’s superpower. It lets arrays of DIFFERENT sizes work together.
Think of it like this: If you have a small rubber stamp and a big piece of paper, the stamp can “stretch” to cover the whole paper!
Simple Example
import numpy as np
# A 2D grid (2 rows, 3 columns)
grid = np.array([
[1, 2, 3],
[4, 5, 6]
])
# A single row
row = np.array([10, 20, 30])
# Broadcasting magic!
grid + row
# Result:
# [[11, 22, 33],
# [14, 25, 36]]
🎨 What Happened?
The small row stretched to match the big grid:
graph TD R["[10, 20, 30]"] --> S["STRETCH!"] S --> E1["[10, 20, 30]"] S --> E2["[10, 20, 30]"] E1 --> G1["[1,2,3] + [10,20,30]"] E2 --> G2["[4,5,6] + [10,20,30]"]
No copying actually happens! NumPy is smart — it just pretends to copy. Super fast!
4️⃣ Broadcasting Rules
The Three Golden Rules
NumPy follows three simple rules to decide if broadcasting works:
Rule 1: Compare Dimensions Right-to-Left
Start from the rightmost dimension and move left.
Array A shape: (4, 3)
Array B shape: (3)
↑
Start comparing here!
Rule 2: Dimensions Must Match OR Be 1
At each position, the sizes must be:
- Equal, OR
- One of them is 1
✅ (4, 3) and (3,) → 3 == 3, OK!
✅ (4, 3) and (1, 3) → 1 stretches, OK!
✅ (4, 1) and (1, 3) → Both stretch!
❌ (4, 3) and (4,) → 3 ≠ 4, FAIL!
Rule 3: Size-1 Dimensions Stretch
Any dimension with size 1 will stretch to match the other array.
import numpy as np
a = np.array([[1], [2], [3]]) # Shape: (3, 1)
b = np.array([10, 20, 30]) # Shape: (3,)
# a stretches horizontally
# b stretches vertically
a + b
# [[11, 21, 31],
# [12, 22, 32],
# [13, 23, 33]]
🎨 Visual: The Rules
graph TD A["Compare shapes<br/>right to left"] --> B{"Sizes equal?"} B -->|Yes| C["✅ OK!"] B -->|No| D{"One size is 1?"} D -->|Yes| E["✅ Stretch it!"] D -->|No| F["❌ Error!"]
5️⃣ Broadcasting Different Shapes
Real Examples That Work
Example 1: Row + Column
import numpy as np
row = np.array([[1, 2, 3]]) # Shape: (1, 3)
col = np.array([[10], [20]]) # Shape: (2, 1)
row + col
# [[11, 12, 13],
# [21, 22, 23]]
What happens:
- Row stretches DOWN (1→2 rows)
- Column stretches RIGHT (1→3 columns)
- Result: 2×3 grid!
Example 2: 3D Broadcasting
import numpy as np
# Imagine: 2 pages, 3 rows, 4 columns
cube = np.ones((2, 3, 4))
# A single row of 4 numbers
addon = np.array([1, 2, 3, 4])
cube + addon # Shape: (2, 3, 4)
# addon stretches across all pages and rows!
Example 3: What Doesn’t Work
import numpy as np
a = np.array([1, 2, 3]) # Shape: (3,)
b = np.array([1, 2, 3, 4]) # Shape: (4,)
a + b # ❌ ERROR! 3 ≠ 4
🎨 Visual: Shapes Combining
graph TD A["#40;1, 3#41;"] --> M["Broadcasting<br/>Magic"] B["#40;2, 1#41;"] --> M M --> R["Result: #40;2, 3#41;"] A2["Row stretches<br/>1→2"] --> M B2["Col stretches<br/>1→3"] --> M
🎯 Quick Summary
| Operation | What It Does | Example |
|---|---|---|
| Element-wise | Pairs match up | [1,2] + [3,4] = [4,6] |
| Scalar | One number hits all | [1,2,3] * 5 = [5,10,15] |
| Broadcasting | Shapes stretch to match | (3,1) + (1,4) = (3,4) |
The Golden Rules Cheat Code:
- Right to left — compare from the end
- Equal or 1 — sizes must match or be 1
- 1 stretches — small becomes big
🚀 Why This Matters
Without broadcasting, you’d write slow loops:
# SLOW way (don't do this!)
result = []
for i in range(len(arr)):
result.append(arr[i] + 10)
# FAST way (do this!)
result = arr + 10
Broadcasting makes your code:
- Faster (100x or more!)
- Cleaner (fewer lines)
- Easier (no loops needed)
🎉 You Did It!
You now understand:
- ✅ Element-wise arithmetic (box partners dancing)
- ✅ Scalar operations (magic wand on all boxes)
- ✅ Broadcasting concept (stretchy rubber stamp)
- ✅ Broadcasting rules (the three golden rules)
- ✅ Broadcasting different shapes (stretching in action)
NumPy turns slow loops into lightning-fast operations. That’s the magic of arrays!