Skip to content
  • Overview
  • Curriculum
    • FLUMental maths and numerical fluency
    • MKTMarkets and products
    • CSData structures and algorithms
    • PYPython and data for quants
      • 1Python fundamentals

        • Idiomatic Python: generators, itertools and the gotchas
      • 2NumPy

        • NumPy: broadcasting, axes and vectorisation
      • 3pandas

        • pandas: groupby, joins, resampling and time zones
      • 4Market data handling

        • Market data in pandas, and where look-ahead hides
      • 5Vectorised backtesting

        • Vectorised backtesting: signal to position to P&L
      • 6Performance

        • Performance: profiling, vectorising and when to leave Python
    • NUMNumerical methods
    • SYSSystems and low latency

Practise

  • Question bank
  • Mental arithmetic
  • Market simulator
  • Arbitrage trees
  • Horse racing
  • Bid book
  • Screening tests
  • Mock papers

Reference

  • Formula reference
  • Search

Your record

  • Review queue
  • Progress
  • Leaderboard
  • Profile
  • Invite friends
AccountSend feedback
  1. Curriculum
  2. /Quantitative development
  3. /Python and data for quants
  4. /NumPy

NumPy: broadcasting, axes and vectorisation

PY · Chapter 2·12 min read·Asked at Two Sigma, Citadel Securities, Jump, QuantCo

Assumes Idiomatic Python: generators, itertools and the gotchas.

After this lesson you should be able to

  • Predict the result shape of a broadcast operation.
  • Use axis arguments correctly and without guessing.
  • Say why vectorised code is faster and when a view is a trap.

NumPy is fast because the loop runs in compiled code over contiguous memory instead of in the interpreter over boxed objects. Writing good NumPy means expressing the computation so that loop can happen, and the two things you must be able to do in your head are broadcasting and axes.

Proposition 2.1

The broadcasting rules

Align the shapes from the *trailing* dimension. Two dimensions are compatible if they are equal or one of them is 1; a missing leading dimension is treated as 1. The result takes the larger of each pair, and no data is copied — the size-1 axis is simply reused.

Holds when

  • (100, 5) with (5,) gives (100, 5) — the row is applied to every row.
  • (100, 5) with (100, 1) gives (100, 5) — the column is applied to every column.
  • (100, 5) with (100,) is an error: the trailing dimensions 5 and 100 do not match.

The [:, None] idiom. The third example above is the one that bites, and the fix is to make the intent explicit: a / b[:, None] turns the shape-(100,) vector into (100, 1), which then broadcasts down the columns. Getting into the habit of writing the None rather than relying on a lucky alignment means the code says which axis you meant, and it fails loudly rather than silently doing the wrong thing when a shape changes.

Call on shape (rows, cols)Result shapeMeaning
a.sum()()Everything
a.sum(axis=0)(cols,)Down the rows — one number per column
a.sum(axis=1)(rows,)Across the columns — one number per row
a.sum(axis=0, keepdims=True)(1, cols)Keeps the axis, so it broadcasts back
Table 2.2 · Axes, without guessing. The reliable way to remember it: axis=k is the axis that *disappears*. Summing a (dates, assets) array over axis=0 collapses the dates and leaves one number per asset.

Example 2.3

Standardise a (250, 500) array of returns cross-sectionally — within each date. What do you write?

Show the worked solutionHide the worked solution

Worked solution

  1. Formula
    z=x−μrowσrowz = \frac{x - \mu_{\text{row}}}{\sigma_{\text{row}}}z=σrow​x−μrow​​
  2. Substitute
    dates are axis 0, assets are axis 1\text{dates are axis } 0, \text{ assets are axis } 1dates are axis 0, assets are axis 1
  3. Solve
    mu = a.mean(axis=1, keepdims=True)⇒(250,1)\texttt{mu = a.mean(axis=1, keepdims=True)} \Rightarrow (250, 1)mu = a.mean(axis=1, keepdims=True)⇒(250,1)
  4. sd = a.std(axis=1, keepdims=True)⇒(250,1)\texttt{sd = a.std(axis=1, keepdims=True)} \Rightarrow (250, 1)sd = a.std(axis=1, keepdims=True)⇒(250,1)
  5. (a - mu) / sd⇒(250,500)\texttt{(a - mu) / sd} \Rightarrow (250, 500)(a - mu) / sd⇒(250,500)
  6. Answer
    keepdims=True is what makes it broadcast\texttt{keepdims=True} \text{ is what makes it broadcast}keepdims=True is what makes it broadcast

Sanity check. Without keepdims the mean has shape (250,), which broadcasts against the *columns* and standardises by date across the wrong axis — silently, with no error. That is the single most common NumPy bug in research code.

The rest of this lesson is in Premium

You have read the opening. 11 more sections follow, including 3 worked examples and 3 quick checks.

Start the free 7-day trialSign in

Nothing is charged for 7 days, and you can cancel before then. Or read Complexity: reading it off, and deriving it in full, free.

← Idiomatic Python: generators, itertools and the gotchaspandas: groupby, joins, resampling and time zones →
On this page
  • The broadcasting rules
  • Axes, without guessing
  • Worked example

QuantMax · 141 lessons · 1342 questions · c5c0caa

  • Premium
  • Arbitrage trees
  • Horse racing
  • Invite friends
  • Account
  • About QuantMax
  • Terms
  • Privacy

Firm names identify publicly reported question patterns and nothing more. QuantMax is not affiliated with, endorsed by, or recruiting for any firm named in the curriculum. Everything you do in lessons and the question bank is kept to your account.