beginner · ~12 min

About NumPy

The array library underneath almost every ML tool in Python — ndarrays, reshaping, slicing, and broadcasting.

Before TensorFlow, before Keras, before scikit-learn — there's NumPy. Almost every Python machine-learning library, TensorFlow and Keras included, either builds directly on NumPy arrays or borrows their conventions wholesale — the shape, reshape, and broadcasting you'll see below are the exact same ideas the tensors elsewhere in this course use, because TensorFlow's tensor design deliberately mirrors NumPy's.

At its core, NumPy adds one thing to Python: the ndarray. A Python list can hold anything and grows one slow, individually-boxed object at a time; an ndarray is a fixed-type, contiguous block of memory, and every operation on it runs as a tight loop in compiled C — not a Python for loop. That's why NumPy code is written to avoid loops entirely wherever possible, using vectorized operations instead.

import numpy as np

arr = np.arange(12).reshape(3, 4)   # a 3x4 ndarray, values 0..11
arr.shape   # (3, 4)
arr.ndim    # 2
arr.dtype   # dtype('int64')

Beginner tip

If the words shape, rank (NumPy calls it ndim), and broadcasting sound familiar, that's because you've already met them in the "What is a Tensor?" module — a NumPy ndarray and a TensorFlow tensor are the same underlying idea, just from two different libraries.

Three operations account for most of the NumPy code you'll ever write:

  • Reshape — reinterpret the same underlying data as a different shape, as long as the total element count matches. np.arange(12).reshape(3, 4) and .reshape(4, 3) hold the same 12 numbers, arranged differently.
  • Slice — pull out a sub-array with arr[row, colStart:colEnd] syntax, without copying data.
  • Broadcast — combine arrays (or an array and a scalar) of different shapes by "stretching" the smaller one, with no explicit loop.

Play with all three below: reshape an array, slice a row out of it, then broadcast a scalar across the whole thing and watch the values update live.

🔍 Deep dive: Why vectorization is the whole point

A pure-Python loop over a million elements pays Python's interpreter overhead — type checks, reference counting, dynamic dispatch — a million separate times. arr * 2 on a NumPy array pays that overhead exactly once, then runs a single compiled C loop over contiguous memory. In practice this is routinely 10-100x faster, and it's the entire reason data science in Python is viable at all — plain Python alone is far too slow for numeric work at this scale. Every .mul(), .add(), and .sum() you've used on a TensorFlow tensor elsewhere in this course is the same idea again, just running on TensorFlow's own compiled backend instead of NumPy's.

Production note

In real training code, X is almost always a 2D array shaped (num_examples, num_features) — one row per example — and NumPy arrays are usually the last stop before that data becomes a TensorFlow or PyTorch tensor: tf.convert_to_tensor(X) and torch.from_numpy(X) both wrap a NumPy array directly, often without copying its underlying memory at all.

🔍 Deep dive: Broadcasting rules, precisely

Two shapes are compatible for broadcasting if, comparing dimensions from the right, every pair is either equal or one of them is 1. A (3, 4) array and a scalar (shape ()) always broadcast, because a missing dimension is treated as 1 everywhere. A (3, 4) array and a (4,) array also broadcast — the 1D array is treated as (1, 4) and stretched down all 3 rows — but a (3, 4) array and a (3,) array do not, because comparing from the right, 4 vs 3 matches neither the "equal" nor the "one of them is 1" rule. This is the single most common source of ValueError: operands could not be broadcast together in real code.

Beginner tip

arr.reshape(...) never changes how many numbers you have — only how they're grouped. If your reshape raises ValueError: cannot reshape array of size 12 into shape (5,3), it's because 5 × 3 = 15 ≠ 12. The dropdown above only ever offers shapes whose rows × cols equals the total element count, for exactly this reason.

Playground

1. Create & reshape a ndarray

0
1
2
3
4
5
6
7
8
9
10
11

arr = np.arange(12).reshape(3, 4)

2. Slice a row

0
1
2
3

arr[0, 0:4]

3. Broadcast a scalar across the whole array

2
2
3
4
5
6
7
8
9
10
11
12
13

arr + 2 — no loop written, NumPy broadcasts the scalar to every element.