Topic 14.1
NumPy Arrays
In one line
A NumPy array (ndarray) is a grid of numbers that all share one type, stored side by side in memory. Because of that, maths on a whole array runs in fast compiled code instead of a slow Python loop.
Think of it like this
An egg tray. Every cup is the same size, the cups sit in neat rows, and you can say "the whole tray" or "the third cup in the second row". A Python list is more like a box of mixed things: an egg, a spoon, a note. It holds anything, but you must open each item to know what it is. A NumPy array is the egg tray: one kind of thing, in a fixed shape.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- NumPy
- The package that provides fast arrays and maths on them. Short name: np.
- ndarray
- NumPy's array type: a grid of same-typed numbers. The name means n-dimensional array.
- dtype
- The data type shared by all elements of an array, such as float64 or int64.
- shape
- A tuple with the size of each dimension. A 2-row, 3-column table has shape (2, 3).
- ndim
- The number of dimensions. A vector has 1, a table has 2.
- Dimension
- One direction you can step through the data. A row is 1 dimension, a table is 2.
- Vector
- A one-dimensional array of numbers: a single row, like a list of prices.
- Matrix
- A two-dimensional array of numbers: rows and columns, like a spreadsheet.
- Element-wise
- Applying an operation to each element, or to each matching pair of elements.
- Overflow
- A whole number too big for its type wraps around to a wrong value without an error.
Step by step
01Make an array and look at it
np.array takes a list and builds an ndarray. Print it and the brackets have no commas, one hint that this is not a list. The four facts to check first are dtype, shape, ndim and size.
Here the list holds 30.0, 12.5, 8.0, 99.0. Python floats become float64. Note the shape (4,): a tuple with one number, so the trailing comma is part of the syntax.
import numpy as np
prices = np.array([30.0, 12.5, 8.0, 99.0])
print(prices.dtype, prices.shape, prices.ndim, prices.size)02Two dimensions: a list of lists
A list of equal-length lists becomes a 2-D array. The outer list gives the rows. shape is (rows, columns), so (2, 3) is two rows of three. The lists must have the same length. If they do not, NumPy 2 raises an error instead of guessing.
The example passes dtype=np.int64 so the output is the same in the browser and on a desktop. The reason is in the key ideas above: the browser's default integer is 32-bit.
grid = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64)
print(grid.shape) # (2, 3): 2 rows, 3 columns03Ready-made arrays
You rarely type big arrays. np.zeros(3) and np.ones((2, 2)) make arrays filled with 0.0 and 1.0 (the shape goes in as a tuple). np.arange(0, 10, 2) is range that returns an array: start, stop (excluded), step. np.linspace(0, 1, 5) gives 5 evenly spaced numbers with both ends included.
Use arange when you know the step and linspace when you know how many points you want. With float steps, arange can surprise you with rounding, which is a good reason to prefer linspace for floats.
print(np.zeros(3))
print(np.arange(0, 10, 2))
print(np.linspace(0, 1, 5))04Maths without a loop
With a list, prices * 2 repeats the list. With an array it multiplies each number. The same goes for +, -, /, ** and comparisons: a > 1.5 gives an array of True and False (called a mask, Topic 14.2).
Two arrays of the same shape combine position by position. Arrays of different shapes can still combine under the broadcasting rules of Topic 14.3.
a = np.array([1.0, 2.0, 3.0])
b = np.array([10.0, 20.0, 30.0])
print(a + b) # [11. 22. 33.]
print(a * 2) # [2. 4. 6.]05One type for everything
Mixing ints and floats gives floats. Mixing numbers and text turns every element into text, which is almost never what you want, so a U dtype in numeric work is a warning sign.
You can ask for a type with dtype= or convert later with .astype(np.float32). Smaller types save memory (a model with a billion float32 weights needs 4 GB, with float64 8 GB) but hold less precision. np.float32(0.1) + np.float32(0.2) prints 0.3, while Python's float gives 0.30000000000000004.
06Overflow is silent
A Python int grows as large as memory allows. A NumPy integer has a fixed size. Adding two int8 values of 100 gives 200, which does not fit in 8 bits (-128 to 127), so it wraps to -56 and no error appears.
Sums of large counts are where this bites in practice. In the browser the default integer is 32 bits, so a sum above 2,147,483,647 wraps. Use dtype=np.int64 or floats when totals can be big.
small = np.array([100, 100], dtype=np.int8)
print(small.sum(dtype=np.int8)) # -5607Why arrays are faster than lists
A list holds *pointers* (addresses that say where each item lives) to Python objects scattered in memory. Each number is a full object with a header. An array holds the raw numbers in one block. A loop in Python must follow each pointer and run Python's internal instructions (bytecode, Topic 13.3) for every element. An array operation is one call into compiled C that streams through the block, often with SIMD instructions (the processor handles several numbers in one step). Topic 13.9 showed this underneath.
The speed-up is large for big arrays and small for tiny ones: calling NumPy has a fixed overhead, so for a three-element list a plain Python loop may actually be faster.
Try it yourself
- 1
Predict the shape
Before running, predict the shape of
np.array([[1, 2], [3, 4], [5, 6]]), then addprint(...shape)and check. Then make the lists different lengths and read the error. - 2
List versus array
In the second example change
* 2to+ 2for both the list and the array. The list line raises aTypeError; the array adds 2 to each number. Why can a list not do this? - 3
Cause an overflow
Change the
int8example to[100, 27]and[100, 28]. Where does it wrap? Then change the dtype tonp.int16and see it stop wrapping.
Code & diagrams
Change a number in the first list to an int-only list like [1, 2, 3] and print the dtype: it will say int32 in the browser and int64 on most desktops.
Expected output
array: [30. 12.5 8. 99. ]
type: ndarray
dtype: float64
shape: (4,)
ndim: 1
size: 4
[[1 2 3]
[4 5 6]]
grid shape: (2, 3) ndim: 2
zeros: [0. 0. 0.]
ones: [[1. 1.]
[1. 1.]]
arange: [0 2 4 6 8]
linspace: [0. 0.25 0.5 0.75 1. ]Compare the first two lines: the same * means repeat for a list and multiply for an array. For a realistic use, think of a shop's price list: one line, arr * 1.18, adds 18% tax to every price at once.
Expected output
list * 2 : [30.0, 12.5, 8.0, 99.0, 30.0, 12.5, 8.0, 99.0]
array * 2 : [ 60. 25. 16. 198.]
array + 1 : [ 31. 13.5 9. 100. ]
array ** 2 : [ 900. 156.25 64. 9801. ]
a + b: [11. 22. 33.]
a * b: [10. 40. 90.]
a > 1.5: [False True True]
mixed dtype: float64
text array: ['1' 'a'] U
int8 small: -56
float32 : 0.3
float64 : 0.30000000000000004Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A ragged list of lists
Build an array from rows of different lengths.
import numpy as np
np.array([[1, 2, 3], [4, 5]])Myth vs fact
Myth
A NumPy array is just a faster list.
Fact
It has a fixed type and fixed shape, and its operators mean element-wise maths. It is a different tool: great for numbers, poor for mixed or growing data.
Myth
Integer arrays work like Python ints.
Fact
Python ints are unlimited; NumPy ints have a fixed size and wrap silently on overflow. In the browser the default integer is 32 bits.
Myth
NumPy is always faster.
Fact
For a handful of elements the call overhead makes it slower than plain Python. It wins on large arrays.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
An
ndarrayis a small header (data pointer, dtype, shape, strides) over a block of bytes. Strides say how many bytes to step to reach the next element along each axis. Many operations (transpose, slicing, reshape) only change the header, which is why they are instant and share memory. - ▸
Memory is
size * dtype.itemsizebytes. A millionfloat64values take 8 MB; asfloat324 MB. Machine-learning code usesfloat32,float16andbfloat16to fit models in memory and speed up arithmetic. - ▸
np.float64is a subclass of Pythonfloat;np.int64is not a subclass ofint. Use.item()or.tolist()to get plain Python numbers when you need them, for example beforejson.dumps, which cannot serialise arrays or most NumPy scalars (np.int64andnp.float32fail;np.float64happens to work because it is afloat). - ▸
NumPy 2 treats a bare Python number as "weak": it adopts the array's dtype instead of upgrading it. So
float32_array * 3.0staysfloat32, andint8_array + 200raisesOverflowErrorbecause 200 does not fit inint8. The silent wrap-around happens when two values that already have the dtype are combined (such assumorarray + array). In-place maths also refuses to change type:int_array += 1.5raisesUFuncTypeError, because the float result cannot be stored back into integers without losing the decimals. - ▸
np.emptyreturns memory that has not been cleared, so it holds leftover garbage until you fill it; usenp.zerosunless you will overwrite every element.np.arangewith float steps can produce one element more or fewer than you expect because of rounding, so for floats ask for a count withlinspace.
Remember this
- 1
NumPy is a package: code written by other people that you install and then
import(Topics 6.1 and 6.5). It is not part of the standard library that ships with Python. You writeimport numpy as np(the short namenpis a universal habit) and create an array withnp.array([...]). The object you get is anndarray: an n-dimensional array. - 2
A dimension is one direction in which you can step through the data. Picture it in words. One dimension is a single row of egg cups: you can only walk left or right (a vector). Two dimensions is the whole tray: you can also walk forward and back, so a cup needs two numbers to find it, its row and its column (a matrix). Three dimensions is a stack of trays: you also choose which tray. More are possible (a batch of colour images has four).
- 3
Every array has a dtype: the one type shared by all its elements, such as
float64(a 64-bit decimal number) orint64. One type means NumPy knows each element takes the same number of bytes, so it can jump straight to any element and run maths in C without asking "what kind of object is this?" a million times. If you mix types, NumPy picks the one type that can hold them all: ints and floats become floats, numbers and text become text. - 4
Every array also has a shape: a tuple (Topic 4.3) giving the size of each dimension.
(4,)is a vector of four numbers.(2, 3)is a table of 2 rows and 3 columns.ndimis the number of dimensions, andsizeis the total count of elements. Beginners lose most of their NumPy time to shape mistakes, so get used to printing.shapeoften. - 5
Maths is element-wise:
arr * 2multiplies every element, anda + badds matching positions. Compare a list, where[1.0, 2.0] * 2gives a longer list[1.0, 2.0, 1.0, 2.0]. In NumPy the operators mean arithmetic. You write one expression for a million numbers and no loop. This is called vectorisation (Topic 14.3). - 6
Arrays have a fixed size and a fixed type. That is the price of speed. You cannot
appendto an array in place the way you do with a list (Topic 4.1). Adding an item creates a new array. When you need a growing collection, build a list and convert once at the end withnp.array(...). - 7
Types have limits you should know. Integer types wrap around when they overflow, like a car's odometer rolling from 99999 back to 00000 (an
int8holds -128 to 127).float32holds about 7 decimal digits,float64about 15 or 16. **In the browser, NumPy runs as a 32-bit WebAssembly build, so a default integer array isint32there andint64on a typical desktop.** Sums above about 2.1 billion can overflow silently in the browser. When integer size matters, say so:dtype=np.int64. - 8
Why does AI care? A tensor (PyTorch's name for an n-dimensional array), a dataset in scikit-learn and an embedding (a list of numbers a model uses to describe a piece of text or an image, Topic 14.5) are all arrays shaped like these. If you can read
(32, 128)as 32 examples with 128 numbers each, you can read most machine-learning code.
Explain it without notes
What is the difference between a Python list and a NumPy array?
What do shape, ndim and dtype tell you? Give the values for np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]).
Why is a NumPy array faster than a loop over a list for maths?
What happens when you put ints, floats and strings in one array? Why does that matter?
A learner's integer total comes out negative in the browser but correct on their laptop, with no error. What happened and how do you prevent it?
Practice
Create an array of the numbers 0, 5, 10, ..., 50 using np.arange, and print its shape and size.
Make a (3, 2) array of ones, multiply it by 7, and print it and its shape.
Given celsius = np.array([0.0, 37.0, 100.0]), compute the Fahrenheit values with one expression (c * 9 / 5 + 32).
Trade-offs
- ↔
Speed and compact memory versus flexibility. One fixed type and shape is what makes arrays fast, and also why you cannot mix types or append cheaply.
- ↔
Smaller dtypes versus precision.
float32halves memory and speeds up many operations, but holds fewer digits;int8overflows quickly. - ↔
Vectorised code versus readability for beginners. One-line array maths is short, but needs shapes in your head; a loop is longer but easier to step through.
Done when you can
Done when you can create arrays with
np.array,zeros,ones,arangeandlinspace.Done when you can read
shape,ndim,sizeanddtypeand say what each means.Done when you can explain why
arr * 2andlst * 2differ.Done when you can say why mixed inputs change the dtype, and spot an accidental text array.
Done when you can explain integer overflow and why the browser's default integer is 32-bit.