2026-04-21
\[ \bar x = \left(\begin{matrix}\bar x_1\\\bar x_2\\ \vdots\\ \bar x _n\end{matrix}\right) \]
\[ p = \left(\begin{matrix}p_1\\p_2\\ \vdots\\ p _n\end{matrix}\right) \]
\[ \sum_i p_i = 1 \]
\[ \bar w = \left(\begin{matrix}-1\\1\end{matrix}\right) \quad\text{and}\quad p = \left(\begin{matrix}0.5\\0.5\end{matrix}\right) \]
\[ p = \left(\begin{matrix}0.8\\0.2\end{matrix}\right) \]
Categorical distribution represents a discrete random variableDistributions.Categorical{Float64, Vector{Float64}}(support=Base.OneTo(2), p=[0.5, 0.5])
wbar to get the vector of winningsDistributions.Categorical{Float64, Vector{Float64}}(support=Base.OneTo(2), p=[0.8, 0.2])
10-element Vector{Float64}:
-1.0
-1.0
1.0
-1.0
1.0
-1.0
-1.0
-1.0
-1.0
-1.0
Notice
With the loaded coin, you see many more \(-1\)s (heads/losses) than \(1\)s (tails/wins).
\[ P = \left(\begin{matrix}P_{1,1}&P_{1,2}&\cdots&P_{1,n}\\ P_{2,1}&P_{2,2}&\cdots&P_{2,n}\\ \vdots&\vdots&\ddots&\vdots\\ P_{n,1}&P_{n,2}&\cdots&P_{n,n}\end{matrix}\right) \]
Key Property
Row \(i\) of \(P\) gives the full conditional distribution of \(x_t\) given \(x_{t-1} = \bar x_i\).
drawDiscrete Function"""
drawDiscrete(p, X̄)
Draws a discrete random variable with probability vector p
over values X̄. Returns (state, value).
"""
function drawDiscrete(p, X̄) # Declare the function
dist = Categorical(p) # Create the distribution from p
s = rand(dist) # Draw a random state index
X = X̄[s] # Look up the value for that state
return s, X # Return both the index and value
endMain.Notebook.drawDiscrete
drawDiscretesimulateMarkov FunctionsimulateMarkov Functionfunction simulateMarkov(P, X̄, T; s₁=1)
X = zeros(T) # Set up storage for values
s = zeros(Int, T) # Set up storage for states
s[1] = s₁ # Initialize first period
X[1] = X̄[s₁] # Initialize first period
for t in 2:T # For each period t = 2, ..., T
s[t], X[t] = drawDiscrete(P[s[t-1], :], X̄) # Draw next state from row s[t-1] of P
end
return s, X # Return states and values
endsimulateMarkov (generic function with 1 method)
[ Info: Saved animation to /var/folders/ms/mjkz2xp96zgblq028jcm5y0r0000gr/T/jl_nWRurOlu1k.gif
\[ \mathbb{E}[X] = p_1 \bar X_1 + p_2 \bar X_2 + \ldots + p_n \bar X_n = \sum_{j=1}^n p_j \bar X_j \]
-0.59866
Monte Carlo Method
As \(N \to \infty\), the sample average converges to the true expected value.
\[ \mathbb{E}[X_{t+1} | X_t = \bar X_i] \]
\[ \mathbb{E}[X_{t+1} | X_t = \bar X_i] = \sum_j P_{i,j} \bar X_j \]
\[ \mathbb{E}[X_{t+1}|X_t = \bar X_i] = P_{i,\cdot} \cdot \bar X \]
\[ \left(\begin{matrix}\mathbb{E}[X_{t+1}|X_t = \bar X_1]\\\mathbb{E}[X_{t+1}|X_t = \bar X_2]\\\vdots\\\mathbb{E}[X_{t+1}|X_t = \bar X_n]\end{matrix}\right) = \left(\begin{matrix} P_{1,\cdot}\cdot \bar X \\ P_{2,\cdot}\cdot \bar X \\ \vdots \\ P_{n,\cdot}\cdot \bar X\end{matrix}\right) = P\bar X \]
Key Result
Each row of \(P\) dotted with \(\bar X\) gives one conditional expectation. Stacking them all is just \(P\bar X\)!
gboom = [] # Will hold growth after booms
grec = [] # Will hold growth after recessions
for t in 1:T-1 # Loop over all periods
if s[t] == 1 # Was period t a boom?
push!(gboom, g[t+1]) # → save next period's growth
else # Was period t a recession?
push!(grec, g[t+1]) # → save next period's growth
end
end2-element Vector{Float64}:
0.026337805840568232
0.001619365609348914
Julia Tip
List comprehensions with if conditions are a powerful and concise way to filter and collect values!
Maximum Likelihood
Pick the parameters that make the observed data most probable.
\[ L(P) = \prod_{t=2}^{T} P_{s_{t-1}, s_t} \]
\[ \log L(P) = \sum_{t=2}^{T} \log P_{s_{t-1}, s_t} \]
\[ n_{ij} = \sum_{t=2}^{T} \mathbf{1}(s_{t-1} = i,\; s_t = j) \]
\[ \log L(P) = \sum_{i=1}^{n} \sum_{j=1}^{n} n_{ij} \log P_{ij} \]
\[ \hat{P}_{ij} = \frac{n_{ij}}{\sum_{k=1}^{n} n_{ik}} = \frac{n_{ij}}{n_{i \cdot}} \]
MLE of Transition Probabilities
The MLE estimate of \(P_{ij}\) is simply the fraction of transitions from state \(i\) that go to state \(j\).
countTransitions: PseudocodecountTransitions: Full Function"""
countTransitions(s, n_states)
Count transitions in state sequence `s` and return
an n_states × n_states matrix of counts.
"""
function countTransitions(s, n_states)
N = zeros(Int, n_states, n_states) # Initialize count matrix
for t in 2:length(s)
N[s[t-1], s[t]] += 1 # Increment the (i,j) count
end
return N
endMain.Notebook.countTransitions
N[i, j] counts how many times the chain moved from state \(i\) to state \(j\)estimateTransitionMatrix: PseudocodeestimateTransitionMatrix: Full Function"""
estimateTransitionMatrix(s, n_states)
Estimate the transition matrix from a state sequence
using maximum likelihood (row-normalized transition counts).
"""
function estimateTransitionMatrix(s, n_states)
N = countTransitions(s, n_states)
P̂ = N ./ sum(N, dims=2) # Divide each row by its sum
return P̂
endMain.Notebook.estimateTransitionMatrix
True P: [0.9 0.1; 0.3 0.7]
Estimated P̂: [0.81 0.19; 0.244 0.756]
Difference (P̂ - P): [-0.09 0.09; -0.056 0.056]
Consistency
As \(T \to \infty\), the MLE \(\hat{P}\) converges to the true \(P\). With \(T = 5{,}000\) we already get very close!
sample_sizes = [50, 200, 1000, 5000, 20000]
errors = map(sample_sizes) do T_sim #Fancy way to loop over sample sizes :)
s_sim, _ = simulateMarkov(P, ḡ, T_sim) # Simulate a Markov chain
P̂_sim = estimateTransitionMatrix(s_sim, 2)
return maximum(abs.(P̂_sim .- P)) # Max absolute error
end
hcat(sample_sizes, round.(errors, digits=4))sample_sizes = [50, 200, 1000, 5000, 20000]
errors = map(sample_sizes) do T_sim #Fancy way to loop over sample sizes :)
s_sim, _ = simulateMarkov(P, ḡ, T_sim) # Simulate a Markov chain
P̂_sim = estimateTransitionMatrix(s_sim, 2)
return maximum(abs.(P̂_sim .- P)) # Max absolute error
end
hcat(sample_sizes, round.(errors, digits=4))5×2 Matrix{Float64}:
50.0 0.05
200.0 0.0889
1000.0 0.0911
5000.0 0.0077
20000.0 0.0035
Log-likelihood at P̂ (MLE): -50.95
Log-likelihood at true P: -53.38
Log-likelihood at P = 0.5 I: -68.62
Key Insight
The MLE has the highest log-likelihood — it fits the observed data best. The true \(P\) is close behind, while a naive equal-probability matrix does much worse.
p11_grid = 0.01:0.01:0.99
p22_grid = 0.01:0.01:0.99
#Compute the log-likelihood for each combination of p11 and p22
ll_surface = [loglikelihood_mc([p11 1-p11; 1-p22 p22], s)
for p11 in p11_grid, p22 in p22_grid]
#Plot the log-likelihood surface
contourf(p11_grid, p22_grid, ll_surface',
xlabel="P₁₁ (boom persistence)",
ylabel="P₂₂ (recession persistence)",
title="Log-Likelihood Surface",
color=:viridis, size=(600, 450), levels=20)
#Add the MLE and true P to the plot
scatter!([P̂[1,1]], [P̂[2,2]], marker=:star5, markersize=12,
color=:red, label="MLE")
#Add the true P to the plot
scatter!([P[1,1]], [P[2,2]], marker=:diamond, markersize=8,
color=:white, label="True P")Optim.jlOptim.jl to minimize the negative log-likelihood * Status: success
* Candidate solution
Final objective value: 5.094891e+01
* Found with
Algorithm: Fminbox with L-BFGS
* Convergence measures
|x - x'| = 1.74e-10 ≰ 0.0e+00
|x - x'|/|x'| = 2.15e-10 ≰ 0.0e+00
|f(x) - f(x')| = 5.16e-11 ≰ 0.0e+00
|f(x) - f(x')|/|f(x')| = 1.01e-12 ≰ 0.0e+00
|g(x)| = 5.87e-09 ≤ 1.0e-08
* Work counters
Seconds run: 0 (vs limit Inf)
Iterations: 4
f(x) calls: 33
∇f(x) calls: 33
Optim MLE: [0.81 0.19; 0.244 0.756]
Closed-form MLE: [0.81 0.19; 0.244 0.756]
True P: [0.9 0.1; 0.3 0.7]
Verify
The numerical optimizer finds the same answer as our closed-form solution — a good sanity check! In more complex models where no closed form exists, Optim.jl is essential.
# NBER recession dates: (peak, trough)
nber_recessions = [
(Date(1948,11), Date(1949,10)),
(Date(1953,7), Date(1954,5)),
(Date(1957,8), Date(1958,4)),
(Date(1960,4), Date(1961,2)),
(Date(1969,12), Date(1970,11)),
(Date(1973,11), Date(1975,3)),
(Date(1980,1), Date(1980,7)),
(Date(1981,7), Date(1982,11)),
(Date(1990,7), Date(1991,3)),
(Date(2001,3), Date(2001,11)),
(Date(2007,12), Date(2009,6)),
(Date(2020,2), Date(2020,4))
]12-element Vector{Tuple{Date, Date}}:
(Date("1948-11-01"), Date("1949-10-01"))
(Date("1953-07-01"), Date("1954-05-01"))
(Date("1957-08-01"), Date("1958-04-01"))
(Date("1960-04-01"), Date("1961-02-01"))
(Date("1969-12-01"), Date("1970-11-01"))
(Date("1973-11-01"), Date("1975-03-01"))
(Date("1980-01-01"), Date("1980-07-01"))
(Date("1981-07-01"), Date("1982-11-01"))
(Date("1990-07-01"), Date("1991-03-01"))
(Date("2001-03-01"), Date("2001-11-01"))
(Date("2007-12-01"), Date("2009-06-01"))
(Date("2020-02-01"), Date("2020-04-01"))
start_date = Date(1948, 1)
end_date = Date(2024, 12)
months = collect(start_date:Month(1):end_date)
# Default to expansion (state 1)
states = ones(Int, length(months))
for (peak, trough) in nber_recessions
for (t, m) in enumerate(months)
if peak <= m <= trough
states[t] = 2 # recession
end
end
end
println("Sample: $(start_date) to $(end_date) — $(length(months)) months")
println("Months in expansion: $(sum(states .== 1))")
println("Months in recession: $(sum(states .== 2))")Sample: 1948-01-01 to 2024-12-01 — 924 months
Months in expansion: 788
Months in recession: 136
estimateTransitionMatrix — let’s use it!2×2 Matrix{Float64}:
0.984752 0.0152478
0.0882353 0.911765
println("Estimated monthly transition matrix:")
println(" Expansion → Expansion: $(round(P̂_nber[1,1], digits=4))")
println(" Expansion → Recession: $(round(P̂_nber[1,2], digits=4))")
println(" Recession → Expansion: $(round(P̂_nber[2,1], digits=4))")
println(" Recession → Recession: $(round(P̂_nber[2,2], digits=4))")Estimated monthly transition matrix:
Expansion → Expansion: 0.9848
Expansion → Recession: 0.0152
Recession → Expansion: 0.0882
Recession → Recession: 0.9118
Expected Duration
If the probability of staying in state \(i\) is \(P_{ii}\), the expected duration is \(\frac{1}{1 - P_{ii}}\) periods.
Expected duration of an expansion: 65.6 months
Expected duration of a recession: 11.3 months
Transition counts:
Expansion → Expansion: 775
Expansion → Recession: 12
Recession → Expansion: 12
Recession → Recession: 124
Rare Events
Transitions into recession are rare: only about 12 in 77 years of monthly data. This is why recessions are hard to predict!
Concepts
Julia Tools
Distributions: Categorical, randLinearAlgebra: dotOptim: numerical MLERemember
The transition matrix \(P\) encodes everything about a Markov chain’s dynamics. Row \(i\) gives the distribution of the next state given current state \(i\). When \(P\) is unknown, the MLE estimator — just counting and normalizing transitions — is simple, intuitive, and consistent.
EC 410 | University of Oregon