Arrays with NumPy
Lesson 2 of 9
2Arrays with NumPy
The big pictureThe numbers a model learns from do not live in Python lists but in arrays with a shape, a dtype and broadcasting rules. This lesson teaches you to read all three before you meet them in an error message.
What the picture shows: The (3, 4) table stays put while the (4,) vector passes over it row by row. That is broadcasting: the last dimension matches, so the vector is reused on every row with no copy and no loop.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
A Python list is like a row of different boxes, each opened on its own. A NumPy array is like an egg carton: every slot is the same size and side by side, so you can do the same thing to all the eggs at once. That is why it is hundreds of times faster than a loop going through numbers one by one.
An ndarray is a contiguous block of memory with one dtype and a shape. Operations are vectorised, running in C with no Python loop. broadcasting stretches length-1 dimensions automatically, so a (3, 4) array adds to a (4,) vector directly.
Before You Start
🐍Python basics
You can write a list, a for loop and a simple function, and you know how to run a script or a notebook.
🔢Rows and columns
You think of a table as rows and columns, so a shape like (3, 4) will look familiar at first sight.
The Core Idea
This lesson builds the array from three ideas: one block of one type, then shape and dtype, then broadcasting. You will meet each of them later in an error message.
1The array: one block, one type
A Python list holds scattered pointers, and each element can be any type. An array keeps its elements side by side in one block, all of one type.
Look at the picture: four prices in one block, all float64. The interpreter never has to ask what type each element is.
That is why NumPy sums the whole block in one low level pass, while a list needs an interpreted loop over every element.
2Shape and dtype
The shape is a tuple that counts the elements in each dimension. (3, 4) is three rows and four columns, and (4,) is a vector of four that is neither a row nor a column.
The dtype says what kind of number the block holds. Print both, then apply one operation to the whole vector at once.
import numpy as np prices = np.array([12.5, 9.0, 31.75, 4.25]) print(prices.shape) print(prices.dtype) print(float(prices.sum())) print(float(prices.mean())) with_vat = prices * 1.15 print([round(float(x), 2) for x in with_vat])
Look at the last line: VAT was applied to the whole vector in one expression, with no loop. And note 14.37 rather than 14.38, because 12.5 × 1.15 has no exact binary representation. That is floating point, not a NumPy flaw, and you will meet it everywhere.
3dtype decides memory and precision
Each dtype takes a fixed number of bytes per element. Look at the picture: float64 takes eight times as much as int8.
A small dtype saves memory but pays for it: int8 breaks at 128. In deep learning you will choose between float32 and float16 for exactly this reason.
4Broadcasting between two shapes
An operation between two different shapes works when the dimensions are compatible. They are compared right to left, and two match when they are equal or one of them is 1.
That saves you manual copies and loops. Try two cases: one you expect, and one that surprises people.
import numpy as np table = np.arange(12).reshape(3, 4) column_means = table.mean(axis=0) centred = table - column_means print(table.shape, column_means.shape, centred.shape) print([round(float(x), 1) for x in column_means]) print([round(float(x), 1) for x in centred[0]]) row = np.array([1, 2, 3]) column = np.array([[10], [20]]) print((row + column).shape)
Subtracting a (4,) vector from a (3, 4) table worked because the last dimension matched. Adding (3,) to (2, 1) produced (2, 3), which is the case that surprises people: broadcasting does not complain, it expands quietly.
How It Works, Step by Step: how NumPy decides whether broadcasting works
It lines the two shapes up from the right, so in (3, 4) and (4,) the 4 meets the 4.
It compares each pair of facing dimensions: they are compatible when they are equal or one of them is 1.
A missing dimension counts as 1, and a dimension of 1 is stretched to match the other without copying memory.
If two dimensions clash, such as 3 against 4, it rejects the operation with an error; otherwise it returns the result shape, such as (5, 3) from (5, 1) and (1, 3).
Going deeper
| Comparison | Python list | NumPy array |
|---|---|---|
| Types | Any type in any slot | One type for the whole array |
| Memory | Scattered pointers | One contiguous block |
| Summing everything | An interpreted loop | A single low level pass |
| Multiplying by a number | Repeats the list | Scales every element |
Worked example
(4,), (4, 1) and (1, 4) as the same thing. They hold identical numbers and behave three different ways under broadcasting, and most shape errors trace back to exactly this. Print shape before you assume.
Try it without code
Myth vs Reality
| Myth | Reality |
|---|---|
| An array is just a faster Python list. | A list takes any type in any slot, an array holds one type in one contiguous block. That is why * 2 repeats a list but doubles every element of an array. |
| (4,), (1, 4) and (4, 1) are the same thing. | They hold the same numbers and behave three different ways under broadcasting, and most shape errors trace back to this mix-up. |
| float64 is always the safe choice. | It costs eight times the memory of int8, and in deep learning you will choose between float32 and float16 for exactly that reason. The dtype is a memory and precision decision, not a default to forget. |
Try it yourself
You have table with shape (3, 4) and want to subtract each row's mean from that row. You wrote table - table.mean(axis=1). What happens?
mean(axis=1) gives shape (3,), and broadcasting compares from the right, so it lines up against four columns rather than three rows and fails. Keep the dimension instead: table.mean(axis=1, keepdims=True) gives (3, 1) and the subtraction means what you intended.
Quick review cards
shape actually tells you(3, 4) is three rows and four columns, while (4,) is a vector that is neither a row nor a column.1. Anything else is rejected.dtype mattersint8 breaks at 128, while float64 costs eight times the memory.Check Your Understanding
what shape comes out of adding a (5, 1) array to a (1, 3) array?
(5, 3). Compared from the right: 1 against 3 expands the first, then 5 against 1 expands the second. The result is a full five by three table, which is how you build every pair without writing a single loop.
What does np.array([1, 2, 3]) * 2 give compared with [1, 2, 3] * 2?
The array gives [2, 4, 6] because it doubles each element, while the list gives [1, 2, 3, 1, 2, 3] because it repeats itself.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Official docs2
Done with this lesson?
Sign in to save your progress.