σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademySkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
Skills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python skill›Module 2 · NumPy & pandas for market data›Topic 2.4·~40 minBeginner

Broadcasting

Skill positionModule 2 of 11 · Topic 4 of 20
2.4 Broadcasting
—Lab score— / 100Total XP—Streak—

Arrays of different shapes, working together

So far we combined arrays of the same shape. Broadcasting is the set of rules that lets NumPy combine arrays of different shapes without copying data. The simplest case is a scalar: a + 10 adds 10 to every element, as if 10 had been stretched to the shape of a. The same stretching works between arrays: a vector of three numbers can be added to every row of a matrix with three columns, one value per column. It is how you subtract each column's mean, scale each asset by its volatility or build a table of all pairwise differences without a single loop.

import numpy as np

a = np.array([[1.0, 2.0, 3.0],
              [4.0, 5.0, 6.0]])       # shape (2, 3)

print(a + 10)                          # a scalar is stretched to every element
# [[11. 12. 13.]
#  [14. 15. 16.]]

per_column = np.array([1, 10, 100])    # shape (3,): one value per column
print(a * per_column)
# [[  1.  20. 300.]
#  [  4.  50. 600.]]

print(a - a.mean(axis=0))              # subtract each column's mean
# [[-1.5 -1.5 -1.5]
#  [ 1.5  1.5  1.5]]

The rule: compare shapes from the right

NumPy lines the two shapes up from the right. In each position the two sizes must be equal, or one of them must be 1 (that one is stretched), or one array simply has no dimension there (treated as 1). Shapes (2, 3) and (3,) work: 3 meets 3, and the missing dimension is stretched over the 2 rows. Shapes (2, 3) and (2,) fail, because 3 meets 2: NumPy raises ValueError: operands could not be broadcast together. The result shape takes the larger size in each position.

import numpy as np

a = np.ones((2, 3))
print((a + np.ones(3)).shape)        # (2, 3)   3 meets 3

try:
    a + np.ones(2)                   # 3 meets 2: no match
except ValueError as error:
    print(error)                     # operands could not be broadcast together with shapes (2,3) (2,)

# Size 1 stretches: (3, 1) with (1, 4) gives (3, 4)
print((np.ones((3, 1)) + np.ones((1, 4))).shape)   # (3, 4)

Column vectors and tables of pairs

A 1-D array of shape (n,) always lines up with the last axis, so it acts like a row. To apply one value per row you need a column of shape (n, 1): write x[:, np.newaxis] or x.reshape(-1, 1). Combining a column with a row builds a whole table: x[:, None] - x[None, :] is the n×n matrix of all differences x[i] - x[j], and corr * vols[:, None] * vols[None, :] turns a correlation matrix into a covariance matrix. Be careful with the opposite mistake: adding shapes (3,) and (3, 1) silently gives a 3×3 table, not a vector of three numbers.

import numpy as np

x = np.array([1.0, 2.0, 4.0])
print(x[:, np.newaxis].shape, x.reshape(-1, 1).shape, x[None, :].shape)   # (3, 1) (3, 1) (1, 3)

# All pairwise differences x[i] - x[j]
print(x[:, None] - x[None, :])
# [[ 0. -1. -3.]
#  [ 1.  0. -2.]
#  [ 3.  2.  0.]]

# One factor per row needs a column
a = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
print(a * np.array([1.0, 2.0])[:, None])
# [[ 1.  2.  3.]
#  [ 8. 10. 12.]]

# Correlation matrix to covariance matrix
corr = np.array([[1.0, 0.5], [0.5, 1.0]])
vols = np.array([0.2, 0.3])
print(corr * vols[:, None] * vols[None, :])
# [[0.04 0.03]
#  [0.03 0.09]]

print((np.ones(3) + np.ones((3, 1))).shape)   # (3, 3)  a silent table, not a vector
import numpy as np

# Standardise every column at once: (x - mean) / std, column by column
a = np.array([[1.0, 10.0],
              [2.0, 20.0],
              [3.0, 30.0]])
z = (a - a.mean(axis=0)) / a.std(axis=0)   # (3, 2) - (2,) -> (3, 2), then divided by (2,)
print(z.round(3).tolist())                 # [[-1.225, -1.225], [0.0, 0.0], [1.225, 1.225]]
print(z.mean(axis=0).round(9).tolist())    # [0.0, 0.0]

# keepdims keeps the reduced axis, so the result can be broadcast back by row too
row_share = a / a.sum(axis=1, keepdims=True)
print(row_share.sum(axis=1).tolist())      # [1.0, 1.0, 1.0]
Print the shapes before you combineBroadcasting never complains when the shapes happen to fit, even if the result is not what you meant. Before combining arrays, write down the shapes and the shape you expect; afterwards check result.shape. A (n, n) result where you expected (n,), or a table of billions of entries from two long vectors, is the classic sign of a missing [:, None] or an extra one. The result is built in memory, so two vectors of 100,000 values make a 10-billion-entry table.
  • Shapes are lined up from the right: sizes must be equal or 1 (or missing); the result takes the larger size.
  • (n,) acts like a row; make a column with x[:, None] or x.reshape(-1, 1).
  • Column with row = a table: x[:, None] - x[None, :]; check result.shape, mismatches fail with ValueError.

Next up: pandas Series, one-dimensional arrays that carry a label with every value.

Lab · what's graded

  • Centre every column with demean_columns25 pts
  • Give every row its own factor with scale_rows25 pts
  • Build the table of all differences with pairwise_differences25 pts
  • Turn correlations into covariances with covariance_from_correlation25 pts
Explorer · lab-37-1
▾ lab-37-1
…
bash · in-browser
learner@quantcraft:~/lab-37-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-37-1 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes

Log in to run your code.

Every lecture is free to read. Create an account or log in to run code, run tests and submit this lab.

  • In-browser IDE with a bash terminal — nothing to install
  • Instant grading, XP and your place on the skill
Create an accountLog in
OR
Continue with GitHub