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:

  1. Vectorisation. Loops in Python are slow because each iteration is interpreted. NumPy ships pre-compiled C code that does the loop internally.
  2. 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.

Toggle theme (T)