The VikiD Truth

Knowledge base / Computing

computingFreedom | Build | Picolisp | Picolisp Machine | pil sources | Linux | BASH | C Programmming | Operating Systems | Javascipt | Emacs | vim | Pharo Smalltalk | Computer Security | Python |

NumPy

Here is the truth that makes Python fast at maths: the maths isn’t actually done in Python. NumPy hands whole arrays to compiled C (and Fortran-descended) code in one go, and that single idea – think in arrays, not loops – is what makes the whole scientific Python world possible.

What it is, in plain words

NumPy (Numerical Python) is the library that gives Python a fast, fixed-type, multi-dimensional array: the ndarray. On top of that array it provides:

NumPy 2.x (first released in 2024) is the current major line. It cleaned up the API, changed some type-promotion rules (NEP 50) and has an updated C ABI, so very old compiled extensions may need rebuilding – a one-time pain most of the ecosystem has long since got through.

Why it matters

A plain Python loop over a million numbers spends almost all its time on interpreter overhead: checking types, boxing integers, looking up methods. A NumPy array stores raw numbers contiguously in memory, like a C array, and runs a tight compiled loop over them – often 50–100× faster, and able to use SIMD instructions. That’s why data science, machine learning and scientific computing happen in Python at all.

An everyday analogy: the egg carton

A Python list of numbers is a bag of eggs, each wrapped individually in its own box with a label – flexible, but you have to unwrap each one. A NumPy array is an egg carton: same-sized slots, all in a row, all the same type. You can do something to the whole carton at once (“stamp every egg”), and a machine can move along the row at full speed.

flowchart LR
  L["Python list<br/>pointers to separate int objects"] -->|slow loop in Python| R1[result]
  A["ndarray<br/>contiguous float64 buffer"] -->|one call, compiled loop| R2[result]

A hands-on example

python -m venv ~/venvs/sci && . ~/venvs/sci/bin/activate
pip install numpy
import numpy as np

# Creating arrays
a = np.arange(10)                 # [0 1 2 ... 9]
m = np.arange(12).reshape(3, 4)   # 3 rows, 4 columns
z = np.zeros((2, 3)); o = np.ones(5); r = np.linspace(0, 1, 5)

# Vectorised maths – no loops
print(a * 2 + 1)                  # [ 1  3  5 ... 19]
print(np.sqrt(a).round(2))

# Shape, type, axis-wise reductions
print(m.shape, m.dtype)           # (3, 4) int64
print(m.sum(axis=0))              # column sums  [12 15 18 21]
print(m.mean(axis=1))             # row means    [1.5 5.5 9.5]

# Slicing and boolean masks
print(m[1, :])                    # second row
print(m[:, -1])                   # last column
print(a[a % 2 == 0])              # [0 2 4 6 8]

# Broadcasting: (3,4) + (4,) -> each row gets the vector added
print(m + np.array([100, 200, 300, 400]))

# Linear algebra
A = np.array([[3., 1.], [1., 2.]])
b = np.array([9., 8.])
x = np.linalg.solve(A, b)         # solves A x = b
print(x)                          # [2. 3.]

# Modern random API
rng = np.random.default_rng(seed=42)
samples = rng.normal(loc=0, scale=1, size=100_000)
print(samples.mean().round(3), samples.std().round(3))

Feel the speed difference:

import time
xs = list(range(10_000_000))
t = time.perf_counter(); s = sum(x * x for x in xs); print("python", time.perf_counter() - t)
arr = np.arange(10_000_000)
t = time.perf_counter(); s = (arr * arr).sum(); print("numpy ", time.perf_counter() - t)

Broadcasting in one rule

Compare shapes from the right. Two dimensions are compatible if they are equal or one of them is 1. Missing dimensions count as 1. So shapes (3, 4) and (4,) work, (3, 1) and (1, 4) broadcast to (3, 4), but (3, 4) and (3,) fail – reshape to (3, 1) first.

Common misconceptions

Connections

numpy tutorial

Libraries, Tools

Last edited · 4 revisions

Tags: #Computing