NumPy Fundamentals

Data Science Fundamentals

Chapter 2 · NumPy Fundamentals

ds1-1's own stack roadmap named NumPy as the first library this course actually delivers in depth. Everything in the rest of this course — pandas' own DataFrame (ds1-3), the statistics chapter (ds1-6), even the plotting libraries (ds1-7, ds1-8) — is built directly on top of what this chapter introduces.

The ndarray vs. py1-6's Own Python List

py1-6 covered the Python list: flexible, resizable, and able to hold mixed types in the same container (a string, an integer, and another list, all in one list, is perfectly valid Python). NumPy's core data structure, the ndarray (n-dimensional array), makes the opposite trade-off deliberately: every element must be the same fixed type, and — while an array can be resized — its whole point is to represent a fixed-shape block of homogeneous numerical data efficiently.

import numpy as np

arr = np.array([1, 2, 3, 4, 5])
zeros = np.zeros(5)          # array([0., 0., 0., 0., 0.])
ones = np.ones((2, 3))       # a 2x3 array of 1.0
ranged = np.arange(0, 10, 2) # array([0, 2, 4, 6, 8])

Vectorization — Why This Trade-off Exists

Compare summing every element of a million-item collection, squared, using py1-6's own list-and-loop approach against NumPy's own vectorized equivalent:

total = 0
for x in my_list:            # plain Python loop
    total += x ** 2

total = (my_array ** 2).sum()  # NumPy — no explicit loop at all

The vectorized version isn't just shorter — it's genuinely, dramatically faster on large data, often by one to two orders of magnitude. The reason is structural, not a matter of Python's own loop syntax being slow in some vague sense: a Python list stores a collection of separate objects scattered in memory, and a plain Python for loop has to check each element's type and dispatch the right operation for it, one element at a time, through Python's own interpreter overhead. A NumPy array stores its elements as one single, contiguous, fixed-type block of memory, and a vectorized operation like ** 2 runs as one single, compiled, low-level loop over that block — no per-element type-checking, no per-element interpreter dispatch.

The same underlying kind of reasoning as build-tooling1-6
This is a genuine cousin of build-tooling1-6's own explanation for why Go/Rust-based build tools outrun JavaScript-based ones — compiled, low-level execution avoiding per-item interpreter overhead that an interpreted language pays repeatedly. The specific mechanisms differ (that chapter was about AOT-compiled tooling vs. JIT-compiled JS; this is about a single compiled C loop vs. Python's own per-element interpreter dispatch), but the underlying shape of the argument — avoid paying interpreter overhead once per element by pushing the work down into compiled code — is the same.

Broadcasting

Broadcasting lets NumPy apply an operation between two arrays of different, but compatible, shapes — without writing an explicit loop to line them up. The simplest case: adding a single number to an entire array applies that number to every element.

arr = np.array([1, 2, 3])
arr + 10        # array([11, 12, 13]) — the scalar is "broadcast" across every element

matrix = np.array([[1, 2, 3], [4, 5, 6]])
row = np.array([10, 20, 30])
matrix + row    # row is broadcast across each row of matrix

The compatibility rule, informally: comparing shapes from the trailing dimension backward, each pair of dimensions must either match exactly or one of them must be 1. A (2, 3) array and a (3,) array are compatible (the trailing dimensions both read 3); a (2, 3) array and a (2,) array are not, without reshaping first.

Indexing, Slicing & Boolean Masking

Basic indexing and slicing work much like py1-6's own list slicing (arr[1:4], arr[-1]). NumPy adds a capability plain lists don't have at all: boolean masking — indexing an array with a condition that evaluates to an array of True/False values, keeping only the elements where the condition holds.

arr = np.array([3, 7, 2, 9, 4])
arr > 5              # array([False, True, False, True, False])
arr[arr > 5]          # array([7, 9]) — only the matching elements
Worth remembering ahead of ds1-3
This exact pattern — filtering data down to only the rows that satisfy a condition — is precisely what pandas' own row-filtering syntax (ds1-3) is built on top of. Recognizing boolean masking here means ds1-3's own filtering syntax will look like a direct, familiar extension rather than new material.

Basic Linear Algebra

A quick preview, not a full course: arr.reshape(2, 3) changes an array's shape without changing its data; arr.T transposes a 2D array; a @ b (or np.dot(a, b)) performs matrix multiplication. These three operations alone are the mathematical backbone of how a neural network actually computes a prediction — a fact nn1 will build on directly once this course is complete.

Hands-On Exercises

Exercise 1

Explain, using this chapter's own contiguous-memory/compiled-loop explanation, why a NumPy vectorized operation is faster than py1-6's own list-and-loop approach — not just "NumPy is optimized," but the specific structural reason.

📄 View solution
Exercise 2

Using this chapter's own broadcasting compatibility rule, explain why a (2, 3) array and a (3,) array can be added directly, while a (2, 3) array and a (2,) array cannot without reshaping.

📄 View solution
Exercise 3

Explain what boolean masking is, using this chapter's own example, and explain why this chapter's own warn-box specifically calls this out as worth remembering ahead of ds1-3.

📄 View solution

Chapter 2 Quick Reference

  • ndarray — fixed-type, contiguous-memory array, vs. py1-6's own flexible, mixed-type list
  • Vectorization — one compiled loop over contiguous memory, avoiding per-element Python interpreter overhead
  • Broadcasting — operate across compatible shapes with no explicit loop; trailing dimensions must match or be 1
  • Boolean masking (arr[arr > 5]) — filtering by condition, the direct basis for ds1-3's own pandas filtering
  • reshape / T / @ — a first preview of the linear algebra nn1 will build on directly
  • Next chapter: Pandas Fundamentals — Series & DataFrame