Skip to content

Vectorisation

Express repeated numerical work as whole-array operations, with explicit attention to aligned observations, native loops, shapes, temporary arrays and data types.

Vectorisation expresses a repeated calculation as operations on whole arrays, moving element-by-element work out of an explicit Python loop and into an array library's implementation. In quant work, it can turn “calculate one return for each adjacent price pair” into a short expression whose alignment is visible. The arithmetic still happens for every pair; the loop has moved.
Here the term means array programming. It does not mean flattening a matrix into a vector, and it does not promise that the processor uses SIMD instructions or multiple cores. NumPy supplies the arrays and operations; vectorisation is the way a calculation is expressed using them.

The same returns, two places for the loop

Suppose one fictional asset has successive prices of 100, 110 and 99 USD, with no distributions or corporate actions. Its simple price return is

rt=pt−pt−1pt−1.r_t = \frac{p_t-p_{t-1}}{p_{t-1}}.

The two adjacent returns are 10/100=0.110/100=0.1 and −11/110=−0.1-11/110=-0.1: +10% followed by −10%. They use different denominators. Their arithmetic sum is zero, but the asset finishes 1% below its starting price; consecutive simple returns compound rather than add.

import numpy as np observations = [100.0, 110.0, 99.0] # successive prices, USD reference = [] for i in range(1, len(observations)): before, after = observations[i - 1], observations[i] reference.append((after - before) / before) prices = np.array(observations, dtype=np.float64) assert prices.ndim == 1 and prices.size >= 2 assert np.isfinite(prices).all() and (prices > 0).all() returns = (prices[1:] - prices[:-1]) / prices[:-1] print(returns.shape, returns) print(np.allclose(returns, reference, rtol=0, atol=1e-12)) # (2,) [ 0.1 -0.1] # True
The Python version selects and processes each pair in the for body. The array version pairs two slices: prices[1:] is [110, 99], and prices[:-1] is [100, 110]. Subtraction and division then operate element by element. For these ordinary float64 arrays, the arithmetic runs through NumPy's compiled numerical loops. NumPy calls such element-wise operations universal functions, or ufuncs. Its ufunc guide describes both the inner loops and their type selection.
Three fictional USD prices, 100, 110 and 99, form aligned previous-price and next-price slices of shape (2,). Subtraction gives 10 and minus 11 USD; division by 100 and 110 USD gives dimensionless returns plus 0.1 and minus 0.1. Two native array passes replace the explicit Python loop.
Three fictional USD prices, 100, 110 and 99, form aligned previous-price and next-price slices of shape (2,). Subtraction gives 10 and minus 11 USD; division by 100 and 110 USD gives dimensionless returns plus 0.1 and minus 0.1. Two native array passes replace the explicit Python loop.
The pairs stay the same when the loop moves. These ordinary NumPy expressions perform a subtraction pass and a division pass; the diagram makes no speed claim.

Shape is part of the calculation

Three prices produce two returns. Both slices have shape (2,), and the result preserves their order. A shape describes dimensions, but does not attach financial meaning to them. If a larger table has dates in rows and assets in columns, adjacent-price pairing must happen along the date axis, independently for each asset. Reversing axes can produce plausible numbers for the wrong question.
Broadcasting is a related mechanism: it specifies how compatible shapes of different sizes participate in an operation, such as one scalar strike against many terminal prices. The return example above already has equally shaped operands; it does not need an expanded axis. Vectorisation describes the overall calculation, while broadcasting describes shape compatibility within operations.

Observation order and data quality still belong to the author. The example assumes chronological, finite, strictly positive prices; its assertions check the numerical conditions, not the chronology. Missing dates, unadjusted splits or distributions can change what a computed price return means. A vectorised expression does not repair those inputs.

What the short expression costs

Basic slicing creates views of prices, so these slices share its data. The subtraction creates an intermediate difference array, and division creates the return array. This expression leaves prices unchanged. A compact line is therefore not necessarily a single pass or a calculation without extra storage. Large intermediate arrays can become the limiting cost, even when broadcasting avoids physically repeating a small input. NumPy's indexing and broadcasting guides explain these distinct memory behaviours.
The data type also matters. Explicit float64 makes this example's representation clear; it does not make decimal arithmetic exact. Fixed-width integer calculations can overflow, and object arrays do not inherit the same native numerical performance assumptions. Compare numerical results with a justified tolerance and check that a rewrite preserves the intended inputs, axis and units. See NumPy's data-type guide for precision and overflow limitations.

Vectorisation often reduces Python overhead on substantial numerical arrays, but it offers no blanket speed guarantee. Conversion, allocation, memory traffic, array size and the chosen operation all matter. Keep a clear reference loop, check agreement, then measure the actual workload if performance motivates the rewrite. A time loop can also remain useful while the independent paths within each time step are processed as arrays.

Finally, np.vectorize does not compile a Python function into a fast numerical kernel. It provides an array-shaped calling interface, but its implementation essentially loops over calls to that function. NumPy explicitly presents it as a convenience facility in the vectorize reference. Its name alone is not evidence of a performance improvement.

Continue with a quant example

The owned Vectorisation lesson extends this mechanism to Monte Carlo paths, portfolio products and axis checks. A public preview is available; the full lesson requires membership.

Sources

The official pages below were checked in the NumPy v2.5 manual. The example was executed with Python 3.14.4 and NumPy 2.5.1; no benchmark timings are reported.

Last updated
  • python
  • numpy
  • array programming
  • vectorisation
  • numerical computing
← Back to the glossaryBack to top ↑