The whole point of NumPy is that you can do arithmetic on entire arrays without writing loops. NumPy applies the operation to every element, in fast C code under the hood.
Element-wise arithmetic
import numpy as np
a = np.array([1, 2, 3, 4, 5])
b = np.array([10, 20, 30, 40, 50])
print(a + b) # [11 22 33 44 55]
print(b - a) # [ 9 18 27 36 45]
print(a * b) # [ 10 40 90 160 250]
print(b / a) # [10. 10. 10. 10. 10.]
print(a ** 2) # [ 1 4 9 16 25]
No loop. NumPy works on the whole array at once.
Scalar operations
You can mix arrays and single numbers — NumPy applies the number to every element:
a = np.array([1, 2, 3])
print(a + 10) # [11 12 13]
print(a * 2) # [2 4 6]
print(a ** 0.5) # [1. 1.41 1.73]
This is how you’d normalise data: (a - a.mean()) / a.std(). One line, no loop.
Comparisons return arrays of booleans
scores = np.array([78, 92, 65, 85, 90])
print(scores > 80) # [False True False True True]
print(scores == 92) # [False True False False False]
print(scores != 65) # [ True True False True True]
You can use these arrays to select elements:
print(scores[scores > 80]) # [92 85 90]
The scores > 80 produces a boolean array. Indexing scores with it keeps only the positions where the mask is True. This is called boolean indexing.
Aggregations
scores = np.array([78, 92, 65, 85, 90])
print(scores.sum()) # 410
print(scores.mean()) # 82.0
print(scores.min()) # 65
print(scores.max()) # 92
print(scores.std()) # 9.77...
print(scores.argmax()) # 1 — position of the max
print(scores.argmin()) # 2 — position of the min
argmax and argmin are very useful — they tell you where the max or min is, which you’d use for “which class did the model predict?”.
Aggregating along axes
For multi-dimensional arrays, you can collapse one dimension at a time:
matrix = np.array([
[1, 2, 3],
[4, 5, 6],
])
print(matrix.sum()) # 21 — overall sum
print(matrix.sum(axis=0)) # [5 7 9] — sum down each column
print(matrix.sum(axis=1)) # [ 6 15] — sum across each row
axis=0 means “collapse the row dimension” (so you get one value per column). axis=1 means “collapse the column dimension”. This trips people up — the rule is that the axis you specify is the one that disappears.
Mathematical functions
NumPy ships fast versions of common maths functions, all element-wise:
a = np.array([0, 1, 2, 3])
print(np.exp(a)) # e^a
print(np.log(a + 1)) # ln (we use a+1 to avoid log(0))
print(np.sqrt(a)) # square roots
print(np.sin(a)) # sines
np.abs(...)
np.round(...)
np.floor(...) / np.ceil(...)
For each, the equivalent loop in plain Python would be 10–100× slower.
Why is NumPy so fast?
Two reasons:
- Vectorisation. Loops in Python are slow because each iteration is interpreted. NumPy ships pre-compiled C code that does the loop internally.
- Contiguous memory. A NumPy array stores its values back-to-back in one block of memory, the same way C arrays do. This lets the CPU read many at once with cache-friendly access.
This combination is why NumPy is everywhere in scientific Python. PyTorch, TensorFlow, scikit-learn — they all build on the same idea.
A tiny end-to-end example
Imagine you have temperatures in Fahrenheit and want Celsius averages:
import numpy as np
fahrenheit = np.array([72.5, 68.0, 80.1, 90.5, 65.3, 75.0, 78.2])
celsius = (fahrenheit - 32) * 5 / 9
print(celsius)
# [22.5 20. 26.7 32.5 18.5 23.9 25.7]
print(f"Mean Celsius: {celsius.mean():.1f}")
print(f"Max Celsius: {celsius.max():.1f}")
print(f"Days above 25°C: {(celsius > 25).sum()}")
Three transformations, no loops, no temporary lists.
What’s next
You can do maths on arrays of the same shape. Next — broadcasting, the rule that lets NumPy combine arrays of different shapes.