What you'll learn
Quick Answer
A NumPy array holds one type and supports arithmetic on the whole array at once, so arr * 2 doubles every value. A Python list holds anything and list * 2 repeats the list instead. That difference is why numeric Python is built on NumPy.
The difference in one example
This is the whole idea, and it surprises most beginners the first time:
import numpy as np
py_list = [1, 2, 3, 4, 5]
arr = np.array(py_list)
print(py_list * 2) # [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
print(arr * 2) # [ 2 4 6 8 10]
Multiplying a list by 2 repeats it. Multiplying an array by 2 doubles every element. Neither is wrong — they are different data types with different meanings — but if you want maths, the list is not doing what you want, and it fails silently by producing a longer list instead of an error.
Doing this with a list requires a loop or a comprehension. With an array it is one expression, and that expression runs in optimised compiled code rather than the Python interpreter. This is called vectorisation, and it is the entire reason NumPy exists.
One array, one type
arr = np.array([1, 2, 3, 4, 5])
print(arr.dtype) # int64
A list can hold a number, a string and another list at once. An array holds exactly one type, recorded in dtype. That restriction is what buys the speed — every element is the same size, laid out contiguously in memory, so the processor can work through them without checking what each one is.
It also has consequences worth knowing. Putting a float into an integer array converts it. Putting a string in converts the whole array to strings. If an array's behaviour surprises you, printing dtype explains it more often than not.
The operations you will use constantly
arr = np.array([1, 2, 3, 4, 5])
print(arr.mean()) # 3.0
print(arr.max()) # 5
print(arr.sum()) # 15
Then boolean masking, which replaces a filtering loop:
print(arr[arr > 3]) # [4 5]
Read that inside out. arr > 3 produces an array of True/False values, one per element. Using it as an index keeps only the positions that were True. The same pattern powers filtering in pandas, so learning it here pays off twice — see pandas for beginners.
Two dimensions, and the axis argument
m = np.array([[1, 2, 3],
[4, 5, 6]])
print(m.shape) # (2, 3) -- 2 rows, 3 columns
print(m.mean(axis=0)) # [2.5 3.5 4.5]
axis is the argument beginners get wrong most often. axis=0 collapses down the rows, giving one result per column — here, the mean of each column. axis=1 collapses across the columns, giving one result per row.
The reliable way to remember it: the axis you name is the one that disappears. Starting from shape (2, 3), using axis=0 removes the 2 and leaves 3 numbers.
Almost every image, spreadsheet and dataset is a 2-D array underneath, which is why this generalises so far. An image is height by width by colour channels; a table is rows by columns.
How much faster, actually
Worth measuring rather than taking on faith. Doubling one million values, once with a generator over a list and once with NumPy:
import numpy as np, time
n = 1_000_000
big_list = list(range(n))
big_arr = np.arange(n)
t0 = time.perf_counter(); sum(x * 2 for x in big_list)
print(time.perf_counter() - t0) # about 0.060 s
t0 = time.perf_counter(); big_arr * 2
print(time.perf_counter() - t0) # about 0.003 s
Roughly twenty times faster on the machine this was run on. The exact ratio varies with hardware and with what you are computing, but the direction never changes, and it widens as the data grows.
The practical rule: if you are writing a loop over numbers in Python, there is usually an array operation that does it in one line and much faster. That single habit is most of what separates slow numeric Python from fast numeric Python.
When to use a list instead
NumPy is not a replacement for lists. Use a plain list when the items are of mixed types, when the collection grows one item at a time, or when you are not doing arithmetic. Appending to an array is expensive because it reallocates, whereas appending to a list is cheap.
A common and perfectly reasonable pattern is to build a list while reading data, then convert it once with np.array(...) before doing the maths.
NumPy is also the foundation under pandas, scikit-learn and most of the scientific Python stack, so the array thinking here transfers directly. If you are heading towards machine learning, our machine learning roadmap covers what comes next.
