Skip to content

Broadcasting

How NumPy combines compatible array shapes, and why a valid broadcast can still scale the wrong financial axis.

Broadcasting is the set of rules that lets NumPy perform element-wise operations on arrays with compatible shapes. In a scenario valuation, it lets one position quantity per asset multiply every scenario price for that asset. The crucial choice is which axis represents assets.

Read shapes from the right

For ordinary NumPy element-wise arithmetic, align input shapes at their rightmost axes. Each pair of axis lengths must be equal, or one must be 1; a missing leading axis acts as length 1. Otherwise the operation raises ValueError. The official broadcasting guide gives the general rules.
For a price matrix shaped (2, 2) and quantities shaped (2,), the quantity axis matches the final price axis. If that final axis means assets, each quantity applies down its asset column. A one-dimensional array has no separate row or column orientation: its single axis aligns on the right.

Two assets across two scenarios

The following fictional portfolio holds 2 units of asset A and 3 units of asset B in both scenarios. All prices are USD per unit; all resulting values are USD. Asset order is A, B throughout.

import numpy as np # Rows: scenarios S1, S2. Columns: assets A, B. # Prices are USD per unit; quantities are units held. prices = np.array([[100, 200], [110, 180]], dtype=int) quantities = np.array([2, 3], dtype=int) # asset order: A, B values = prices * quantities print(prices.shape, quantities.shape, values.shape) # (2, 2) (2,) (2, 2) print(values.tolist()) # [[200, 600], [220, 540]] print(values.sum(axis=1).tolist()) # [800, 760]
Multiplication produces a value for each scenario–asset pair. Summing axis=1 then combines the asset columns, leaving one portfolio value per scenario: USD 800 in S1 and USD 760 in S2. That reduction is a separate operation from broadcasting. These are position values, not profits or losses.

The silent wrong-axis calculation

Adding None inside an index inserts an axis of length 1. Here, quantities[:, None] changes (2,) into (2, 1):
wrong_values = prices * quantities[:, None] print(quantities[:, None].shape) # (2, 1) print(wrong_values.tolist()) # [[200, 400], [330, 540]] print(wrong_values.sum(axis=1).tolist()) # [600, 870]

Now the multiplier 2 applies to both assets in S1, while 3 applies to both assets in S2. NumPy accepts the calculation because there happen to be two scenarios and two assets. The code has treated asset quantities as scenario multipliers. The output still has the expected shape, but USD 600 and USD 870 are wrong portfolio values for the stated holdings.

Fictional scenario-by-asset prices multiplied by asset quantities 2 and 3 give USD values 200, 600 and 220, 540, with totals 800 and 760. Rotating quantities into a column gives 200, 400 and 330, 540, with wrong totals 600 and 870. Both outputs have shape (2, 2).
Fictional scenario-by-asset prices multiplied by asset quantities 2 and 3 give USD values 200, 600 and 220, 540, with totals 800 and 760. Rotating quantities into a column gives 200, 400 and 330, 540, with wrong totals 600 and 870. Both outputs have shape (2, 2).

Original fictional example. Rows are scenarios S1 and S2; columns are assets A and B. Prices are USD per unit, quantities are units, and resulting values and totals are USD. Equal axis lengths allow the wrong orientation to run without an error.

A singleton axis is useful when its meaning is intentional: quantities[None, :] has shape (1, 2) and preserves the asset-wise multiplication above. A per-scenario multiplier would instead need the scenario axis retained and a singleton asset axis. Check axis meanings and asset ordering alongside shapes; a shape assertion cannot detect an A/B ordering swap.

Compact inputs do not guarantee a small result

Broadcasting supports vectorisation, but it is not a promise of speed or negligible memory use. The inputs need not be physically repeated into full matching tables; ordinary multiplication without an out argument still allocates its result. An expression that combines many scenarios, assets and parameter settings can create a large output or intermediate array. Estimate those shapes before expanding the calculation.

The examples use NumPy arrays. Ordinary Python list multiplication does not provide element-wise array multiplication. Code and outputs were checked with Python 3.14.4 and NumPy 2.5.1.

For a guided next step, the NumPy lesson introduces shapes and weighted arithmetic; Vectorisation develops larger scenario calculations. Both offer public previews; their full lessons require membership.

Sources

Last updated
  • numpy
  • array shapes
  • scientific python
← Back to the glossaryBack to top ↑