NumPy's real superpower is vectorization: doing operations on entire arrays at once, without writing slow Python loops. It's like upgrading from walking to flying! 🚀
We'll start with simple math on arrays and build up to powerful, real-world data analysis. No prior NumPy knowledge needed — just follow along! 🐍
What Are Vectorized Operations?
Think of regular Python lists: to add two lists element-wise, you need a loop.
NumPy arrays let you skip the loop entirely — operations apply to every element automatically. This is fast because it's written in optimized C code under the hood.
Basic Arithmetic - No Loops Needed
First, import NumPy:
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# Add 10 to every element
print(arr + 10)
# Multiply by 2
print(arr * 2)
# Element-wise operations between arrays
arr2 = np.array([10, 20, 30, 40, 50])
print(arr + arr2)
print(arr * arr2)
[11 12 13 14 15]
[ 2 4 6 8 10]
[11 22 33 44 55]
[ 10 40 90 160 250]
No loops! NumPy handles everything at once. ✨
Comparison and Boolean Operations
# Find elements > 3
print(arr > 3)
# Combine conditions
print((arr > 2) & (arr < 5)) # AND
print((arr < 2) | (arr > 4)) # OR
[False False False True True]
[False False True True False]
[ True False False False True]
These return boolean arrays — perfect for filtering later!
Universal Functions (ufuncs) - Built-in Speed
NumPy has dozens of fast functions that work element-wise.
print(np.sqrt(arr)) # Square root
print(np.exp(arr)) # e raised to power
print(np.sin(arr)) # Sine
print(np.log(arr)) # Natural log
[1. 1.41421356 1.73205081 2. 2.23606798]
[ 2.71828183 7.3890561 20.08553692 54.59815003 148.4131591 ]
[ 0.84147098 0.90929743 0.14112001 -0.7568025 -0.95892427]
[0. 0.69314718 1.09861229 1.38629436 1.60943791]
Broadcasting - Magic for Different Shapes
Broadcasting lets you operate on arrays of different sizes — NumPy automatically "stretches" the smaller one.
# 1D + 2D
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
vector = np.array([10, 20, 30])
print(matrix + vector) # Vector added to each row
[[11 22 33]
[14 25 36]]
# Scalar + array (scalar broadcasts to all)
print(matrix * 5)
[[ 5 10 15]
[20 25 30]]
array.shape!
Real-World Example: Student Grade Analysis
Let's analyze marks for 5 students across 3 subjects — all vectorized!
marks = np.array([
[85, 88, 92], # Alice
[90, 76, 85], # Bob
[78, 92, 88], # Charlie
[92, 85, 79], # David
[88, 90, 94] # Eva
])
print("Marks:\n", marks)
Marks:
[[85 88 92]
[90 76 85]
[78 92 88]
[92 85 79]
[88 90 94]]
Vectorized Calculations
# Total marks per student
total = marks.sum(axis=1)
# Average per student
average = marks.mean(axis=1)
# Bonus: Add 5 grace marks to everyone
marks_with_grace = marks + 5
# New averages
new_average = marks_with_grace.mean(axis=1)
print("Total:", total)
print("Average:", average)
print("New Average:", new_average)
Total: [265 251 258 256 272]
Average: [88.33333333 83.66666667 86. 85.33333333 90.66666667]
New Average: [90.33333333 85.66666667 88. 87.33333333 92.66666667]
Advanced: Conditional Bonuses
# Give 10 extra marks only to students who scored < 80 in any subject
low_scores = marks < 80
bonus = np.where(low_scores, 10, 0)
final_marks = marks + bonus
print("Students needing bonus:\n", low_scores)
print("Final marks:\n", final_marks)
Students needing bonus:
[[False False False]
[False True False]
[ True False False]
[False False True]
[False False False]]
Final marks:
[[85 88 92]
[90 86 85]
[88 92 88]
[92 85 89]
[88 90 94]]
Subject-wise Stats
# Highest, lowest, average per subject
highest = marks.max(axis=0)
lowest = marks.min(axis=0)
subject_avg = marks.mean(axis=0)
print("Highest per subject:", highest)
print("Lowest per subject:", lowest)
print("Subject averages:", subject_avg)
Highest per subject: [92 92 94]
Lowest per subject: [78 76 79]
Subject averages: [86.6 86.2 87.6]
Performance: Vectorized vs Loops
import time
large_arr = np.arange(1000000)
# Vectorized
start = time.time()
result_vec = large_arr * 2 + 10
print("Vectorized time:", time.time() - start)
# Loop version
result_loop = np.zeros(1000000)
start = time.time()
for i in range(len(large_arr)):
result_loop[i] = large_arr[i] * 2 + 10
print("Loop time:", time.time() - start)
Vectorized time: 0.002 seconds (typical)
Loop time: 0.4 seconds (typical)
Vectorized wins by a huge margin! 🏆
Beginner Mistakes - Common Errors and How to Avoid Them
np.array().
axis in reductions (sum, mean) — wrong axis gives wrong results!
Advanced Usage & Next Steps
Optimization Tips
(arr + 10) * 2 — NumPy optimizes internally, no temporary arrays wasted.
in1d, where, select for complex conditionals.
Real-World Use Cases
- Finance: Calculate returns on thousands of stocks instantly
- Physics simulations: Update positions/velocities of millions of particles
- Image processing: Adjust brightness/contrast on entire images
- Machine learning: Feature scaling and normalization
Quick Summary 📝
- Vectorized ops: No loops, element-wise automatically
- Arithmetic, ufuncs, comparisons all vectorized
- Broadcasting handles shape mismatches
- Aggregations with
axisfor row/column stats - Massive speed advantage over loops
Vectorization is the heart of NumPy's speed. Practice it daily — you'll think in arrays before you know it! Happy coding! 🐼✨
Comments
Post a Comment