NumPy Arrays, Shapes and Broadcasting Explained Visually (ML Lesson 31)

One million numbers, one slow Python loop, one fast NumPy line. Why is the gap so big, and how do you think in arrays?

Intro

Loop vs One Line

Here is a challenge. You have one million numbers, and you want to square every one of them and add them up. You can write a Python loop that visits each number one at a time. Or you can write a single short line of NumPy. Both give the same answer, but one of them is dramatically faster.

  • 1,000,000 numbers
  • Python loop
  • One NumPy line

Where We Are

Welcome to lesson thirty-one of Machine Learning from Zero to Advanced. Last time, in lesson thirty, we wrapped up the first part of the course with reinforcement learning and deployment. Now we go back and strengthen the foundations. Today is about NumPy, the library underneath almost every machine learning tool in Python.

  • Lesson 12: Python basics
  • Lesson 14: X and w
  • Today: think in arrays

What We Will Cover

By the end of this video you will read array shapes the way you read a sentence. You will know how indexing, masks, and reshaping work, and when NumPy copies your data and when it does not. The centerpiece is broadcasting, which explains how arrays of different shapes combine. We will not cover Pandas or plotting here, and we will not re-teach matrices from lesson four.

  • Arrays, shape, axis
  • Broadcasting rules
  • Products and reductions
  • Classic bugs

Why Vectorization Wins

Why Python Loops Are Slow

Why is the loop slow? A Python list holds pointers to separate objects, and every step of the loop asks the interpreter to look at an object, check its type, and decide what plus or times means for it. That bookkeeping is cheap once, but a million times it adds up. The arithmetic itself is tiny. The overhead is the cost.

  • Each item is an object
  • Type checked every step
  • Interpreter overhead

One Block of Memory

A NumPy array stores its numbers side by side in one block of memory, all of the same type. When you write an operation on the whole array, the loop runs inside fast compiled code, with no per-item type checking. This style is called vectorization. In practice it is often tens or even hundreds of times faster than a plain Python loop, and the code is shorter too.

  • Contiguous storage
  • One type for all
  • Loop runs in compiled code

The Vectorized Line

Here is the vectorized version. Write x times x, and every element is squared at once. Wrap it in np dot sum, and you get the total. That reads exactly like the sigma notation from lesson two. A good habit starts here: whenever you feel the urge to write a for loop over data, ask whether one array expression can do the job.

  • np.sum(x * x)
  • No explicit loop
  • Reads like the math

Arrays, dtype, Shape and Axis

The ndarray

The core object is the ndarray, short for n-dimensional array. It is a grid of values that all share one type. The number of axes is called ndim. A list of numbers has one axis, a table has two, and a stack of tables has three. In lesson three we called these vectors, matrices, and tensors. NumPy uses the same object for all of them.

  • Grid of same-typed values
  • ndim: number of axes
  • size: total elements

dtype: The Type of Every Cell

Every array has a dtype. A float64 takes eight bytes per number, so one million of them occupy about eight megabytes. A float32 takes four bytes, which is why deep learning often prefers it. Watch out for integer arrays. If you create an array of integers and assign 2.point 7 into one cell, NumPy quietly stores 2. The type is fixed when the array is created.

  • float64: 8 bytes
  • float32: 4 bytes
  • int64 truncates decimals

Shape Is a Tuple

The shape is a tuple that lists the length along each axis. Take this array: one, two; three, four; five, six. Its shape is three comma two, meaning three rows and two columns. In machine learning we follow a convention from lesson fourteen: rows are samples, columns are features. So a data matrix X has shape n by d, and we will keep using it.

  • X.shape = (3, 2)
  • 3 rows, 2 columns
  • Rows are samples

Axis Means Collapse

Axis is where beginners stumble, so here is a rule. The axis you name is the axis that disappears. Sum our array with axis equals zero, and the three rows collapse into one: nine and twelve, a result of shape two. Sum with axis equals one, and the columns collapse: three, seven, eleven, a result of shape three. Name the axis, and that dimension vanishes.

  • axis=0 collapses rows
  • axis=1 collapses columns
  • Result drops that axis

Indexing, Slicing and Masks

Indexing and Slicing

Take a equals arange of ten, the numbers zero through nine. The slice a, two colon five, gives two, three, four. The start is included, the stop is not. Adding a step, a colon colon two, picks every second element: zero, two, four, six, eight. For two dimensions, you give one slice per axis, separated by a comma.

  • a[2:5] → 2, 3, 4
  • Stop is excluded
  • Step: a[::2]

Rows and Columns of X

On our three by two matrix X, X at zero is the first row, a sample. X, colon comma one, means all rows, column one, so it is the second feature across every sample. The lone colon means take everything along that axis. This pattern, colon comma j, is how you pull out feature j, and you will use it constantly.

  • X[0] first row
  • X[:, 1] second column
  • X[:2, :] first two rows

Boolean Masks

A comparison on an array returns an array of true and false values, called a mask. For a equals zero through nine, a greater than six gives seven falses followed by three trues. Put that mask inside the brackets, and NumPy keeps only the positions marked true: seven, eight, nine. You can combine masks with an ampersand or a vertical bar, using parentheses around each condition.

  • a > 6 gives True/False
  • a[a > 6] → 7, 8, 9
  • Filter without a loop

Reshape, Views and Copies

Reshape Reorders the Labels

Reshape changes the shape without changing the data. Start with numbers zero to five, shape six. Reshape to two by three, and NumPy fills row by row: zero, one, two on top, three, four, five below. The total count must match, six equals two times three. If you write minus one for one dimension, NumPy works it out for you, so reshape with minus one comma three gives two rows.

  • arange(6) → shape (6,)
  • reshape(2, 3)
  • -1 means infer

Views Share Memory

Here is a subtle point. A basic slice does not copy data. It gives you a view, a new window onto the same memory. If b equals a, two colon five, and then you set b at zero to ninety-nine, the original array a changes too, at index two. Reshape also usually returns a view. This saves memory, but it can surprise you.

  • Slices are views
  • Edit the view, edit the original
  • Same buffer

Copies When You Need Them

If you want independence, call dot copy. Boolean masks and lists of indices always produce copies, while plain slices produce views. A good rule: if you plan to modify a piece of an array and you need the original intact, copy it first. If you are only reading, a view is perfect and free.

  • a[2:5].copy()
  • Mask indexing makes a copy
  • Fancy indexing copies too

Broadcasting

The Problem Broadcasting Solves

Now the centerpiece. Many ML steps need to combine arrays of different shapes. A classic one is centering: subtract each feature's mean from that feature's column. Our matrix X has shape three by two. The column means form a vector of shape two. These shapes differ, yet X minus the means works. That magic is called broadcasting.

  • Subtract mean from each column
  • Shapes differ: (3,2) and (2,)
  • No loop needed

Rule One: Align on the Right

Rule one: write the two shapes one above the other, aligned on the right. If one has fewer dimensions, pad it on the left with ones. So three comma two sits above two, and the shorter shape becomes one comma two. Think of the vector of means as a matrix with a single row.

  • Compare shapes from the right
  • Pad the shorter with ones
  • (2,) becomes (1, 2)

Rule Two: Equal or One

Rule two: go through the dimensions pair by pair. Each pair must either be equal, or one of them must be one. Here we compare three with one, which is fine, and two with two, which is fine. If instead we had a pair like three and two, neither equal nor one, NumPy raises an error. That error protects you.

  • Each pair must match
  • Or one of them is 1
  • Otherwise: error

Rule Three: Stretch the Ones

Rule three: any dimension of size one is stretched to match the other. Our single row of means is stretched into three identical rows, three, four, three, four, three, four. NumPy does this virtually, without actually copying the numbers, so it costs almost no memory. The output shape takes the larger size in each position.

  • Size-1 axis is repeated
  • No real copy is made
  • Result shape: (3, 2)

Worked Example: Centering

Let us do it with numbers. X is one, two; three, four; five, six. The means, from X dot mean with axis zero, are three and four. Subtracting gives minus two, minus two; zero, zero; two, two. Check: each column now sums to zero. This centered matrix is the starting point for covariance and PCA, which you met in lesson twenty-three.

  • Means: [3, 4]
  • X - mean
  • Result: [-2,-2],[0,0],[2,2]

Worked Example: Outer-Style Sum

Now both arrays stretch. Let a be a column with entries zero, ten, twenty, shape three by one. Let b be a row with entries one, two, three, shape one by three. Add them. The column stretches across, the row stretches down, and the result is three by three: one, two, three; eleven, twelve, thirteen; twenty-one, twenty-two, twenty-three. This is exactly how you compute all pairwise differences or distances without loops.

  • a shape (3,1)
  • b shape (1,3)
  • a + b shape (3,3)

When Broadcasting Fails

Suppose you want to subtract each row's mean instead. The row means have shape three. Align it with three by two, and you compare three against two, which fails. The fix is to make the vector a column, shape three by one, either with reshape or by passing keepdims equals True to the mean. Then it stretches sideways across the columns.

  • (3,2) with (3,) fails
  • 3 vs 2 mismatch
  • Fix: reshape to (3,1)

Products and Reductions

Elementwise vs Matrix Product

The star operator multiplies elementwise, matching positions. It is not a matrix product. For the matrix multiplication from lesson four, use the at symbol, or np dot. Mixing these up is a common source of wrong results that still run without errors, so check which one you meant every single time.

  • * multiplies elementwise
  • @ is matrix product
  • Do not mix them up

X @ w: Predictions

Back to lesson fourteen. Linear regression predicts with X times w, where X is n by d and w has shape d. Take our X and let w be zero point five and two. Row one gives one times zero point five plus two times two, which is four point five. Row two gives nine point five, and row three gives fifteen. The inner dimension d must match, and the output has shape n.

  • X shape (n, d)
  • w shape (d,)
  • y_hat shape (n,)

Einsum Basics

Einsum is a compact notation for these operations. Each input gets index letters, and any letter missing from the output is summed away. The string i j, comma, j, arrow i is exactly X times w. The string i j arrow j sums over rows. And i, comma, j, arrow i j builds an outer product. You do not need einsum yet, but recognizing it will help later with attention.

  • 'ij,j->i' is X @ w
  • 'ij->j' sums over rows
  • 'i,j->ij' is outer product

Reductions and keepdims

The same axis logic applies to mean, max, min, standard deviation, and argmax, which returns the position of the largest value. By default the collapsed axis disappears. Passing keepdims equals True keeps it as a size-one axis. That is the trick that makes the result broadcast cleanly back against the original. Standardizing features, subtracting the mean and dividing by the standard deviation, is a one-line broadcast.

  • sum, mean, max, argmax
  • axis picks what collapses
  • keepdims keeps the axis

Common Bugs

Bug 1: (n,) vs (n,1)

Bug number one. An array of shape n and an array of shape n by one hold the same numbers, but they are different objects. A flat vector has no notion of row or column. A column vector is a two-dimensional array. Many functions accept both, then quietly return different shapes. Print dot shape often, especially on labels and predictions.

  • Flat vector vs column
  • Different shapes, different behavior
  • Check .shape often

Bug 2: Silent Broadcasting

Bug number two is the dangerous one. Say your labels y have shape three, but your predictions y hat have shape three by one. Subtract them, and broadcasting happily produces a three by three grid of differences, nine numbers instead of three. Take the mean of squares, and you get a loss that is wrong but still looks like a number. Always make shapes explicit before computing a loss.

  • y: (3,) minus y_hat: (3,1)
  • Result is (3,3)
  • Loss is wrong, no error

Bug 3: In-Place Mutation

Bug number three is in-place mutation. The operators plus-equals and minus-equals change the array itself, and if that array is a view, the source changes too. You might standardize a slice and corrupt your raw data without noticing. Also, in-place adding a float to an integer array raises an error, since the result cannot fit the type. When in doubt, write x equals x minus m, which makes a new array.

  • x -= m edits original
  • Views propagate changes
  • Integer array rejects float

A Debugging Checklist

Here is a short checklist. Before an operation, write down the shape you expect. After it, print the shape and dtype. Use assert statements such as assert y dot shape equals y hat dot shape, so a mismatch stops your program at once instead of corrupting a loss silently. A few seconds of shape checking save hours of confused debugging.

  • Print shape and dtype
  • Write expected shapes first
  • Use assert on shapes

Recap and Next Steps

Recap

Let us recap. NumPy stores same-typed numbers in one block, so whole-array operations beat Python loops. Shape tells you the size along each axis, and the axis you name in a reduction is the one that disappears. Slices are views, masks make copies. Broadcasting aligns shapes on the right, requires each pair to be equal or one, and stretches the ones.

  • Vectorize, avoid loops
  • Axis named = axis removed
  • Three broadcasting rules

Next: Pandas

You now have the array fluency that every later lesson in this course relies on. Next time, in lesson thirty-two, we build on top of NumPy with Pandas, the tool for labelled tables of real-world data.

  • Lesson 32: Pandas Essentials
  • Labelled tables on top of NumPy
  • Subscribe for the series