Skip to content

Commit cfbc632

Browse files
committed
fix typos and add explanations
1 parent d4612de commit cfbc632

File tree

1 file changed

+62
-42
lines changed

1 file changed

+62
-42
lines changed

lectures/ar1_turningpts.md

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ We consider two sorts of statistics:
4040

4141
To investigate sample path properties we'll use a simulation procedure recommended by Wecker {cite}`wecker1979predicting`.
4242

43-
To acknowledge uncertainty about parameters, we'll deploy `pymc` to construct a Bayesian joint posterior distribution for unknown parameters.
43+
To acknowledge uncertainty about parameters, we'll deploy `numpyro` to construct a Bayesian joint posterior distribution for unknown parameters.
4444

4545
Let's start with some imports.
4646

@@ -110,7 +110,7 @@ Predictive distribution {eq}`ar1-tp-eq3` assumes that parameters $(\rho,\sigma)$
110110
111111
Predictive distribution {eq}`ar1-tp-eq4` assumes that parameters $(\rho,\sigma)$ are uncertain, but have known probability distribution $\pi_t(\rho,\sigma | y^t )$. Notice the second equality follows that $\{y_t\}$ is a AR(1) process when $(\rho, \sigma)$ are given.
112112
113-
We also want to compute some predictive distributions of "sample path statistics" that might include, for example
113+
We also want to compute some predictive distributions of "sample path statistics" that might include, for example
114114
115115
- the time until the next "recession",
116116
- the minimum value of $Y$ over the next 8 periods,
@@ -152,8 +152,8 @@ class AR1(NamedTuple):
152152
T1 : int, optional
153153
Length of the future path to simulate (default is 100).
154154
"""
155-
rho:float
156-
sigma:float
155+
rho: float
156+
sigma: float
157157
y0: float
158158
T0: int=100
159159
T1: int=100
@@ -173,19 +173,19 @@ def AR1_simulate_past(ar1: AR1, key=key):
173173
Returns
174174
-------
175175
initial_path : jax.numpy.ndarray
176-
Simulated path of the AR(1) process of length T.
176+
Simulated path of the AR(1) process and the initial y0.
177177
"""
178178
rho, sigma, y0, T0 = ar1.rho, ar1.sigma, ar1.y0, ar1.T0
179179
# Draw epsilons
180-
eps = sigma * random.normal(key, (T0 - 1,))
180+
eps = sigma * random.normal(key, (T0,))
181181
# Set step function
182182
def ar1_step(y_prev, t_rho_eps):
183183
rho, eps_t = t_rho_eps
184184
y_t = rho * y_prev + eps_t
185185
return y_t, y_t
186186
187187
# Scan over time steps
188-
_, y_seq = lax.scan(ar1_step, y0, (jnp.full(T0-1, rho), eps))
188+
_, y_seq = lax.scan(ar1_step, y0, (jnp.full(T0, rho), eps))
189189
# Concatenate initial value
190190
initial_path = jnp.concatenate([jnp.array([y0]), y_seq])
191191
@@ -241,7 +241,7 @@ def plot_path(ar1, initial_path, future_path, ax, key=key):
241241
ar1 : AR1
242242
AR1 named tuple containing process parameters (rho, sigma, T0, T1).
243243
initial_path : array-like
244-
Simulated initial path of the AR(1) process of length T0.
244+
Simulated initial path of the AR(1) process, shape(T0+1,).
245245
future_path : array-like
246246
Simulated future paths of the AR(1) process, shape (N, T1).
247247
ax : matplotlib.axes.Axes
@@ -260,7 +260,7 @@ def plot_path(ar1, initial_path, future_path, ax, key=key):
260260
261261
# Compute moments and confidence intervals
262262
y_T0 = initial_path[-1]
263-
j = jnp.arange(T1)
263+
j = jnp.arange(1, T1+1)
264264
center = rho**j * y_T0
265265
vars = sigma**2 * (1 - rho**(2 * j)) / (1 - rho**2)
266266
# 95% CI
@@ -271,24 +271,24 @@ def plot_path(ar1, initial_path, future_path, ax, key=key):
271271
y_lower_c90 = center - 1.65 * jnp.sqrt(vars)
272272
273273
# Plot
274-
ax.plot(jnp.arange(-T0, 0), initial_path)
274+
ax.plot(jnp.arange(-T0, 1), initial_path)
275275
ax.axvline(0, linestyle='--', alpha=.4, color='k', lw=1)
276-
# Choose 10 future paths to plot
276+
# Chooise 10 future paths to plot
277277
index = random.choice(
278278
key, jnp.arange(future_path.shape[0]), (10,), replace=False
279279
)
280280
for i in index:
281-
ax.plot(jnp.arange(T1), future_path[i, :], color='grey', alpha=.5)
281+
ax.plot(jnp.arange(1, T1+1), future_path[i, :], color='grey', alpha=.5)
282282
283283
# Plot 90% and 95% CI
284284
ax.fill_between(
285-
jnp.arange(T1), y_upper_c95, y_lower_c95, alpha=.3, label='95% CI'
285+
jnp.arange(1, T1+1), y_upper_c95, y_lower_c95, alpha=.3, label='95% CI'
286286
)
287287
ax.fill_between(
288-
jnp.arange(T1), y_upper_c90, y_lower_c90, alpha=.35, label='90% CI'
288+
jnp.arange(1, T1+1), y_upper_c90, y_lower_c90, alpha=.35, label='90% CI'
289289
)
290290
ax.plot(
291-
jnp.arange(T1), center, color='red', alpha=.7, label='expectation'
291+
jnp.arange(1, T1+1), center, color='red', alpha=.7, label='expectation'
292292
)
293293
ax.set_xlim([-T0, T1])
294294
ax.set_xlabel("time", fontsize=13)
@@ -325,13 +325,15 @@ Wecker {cite}`wecker1979predicting` proposed using simulation techniques to char
325325
326326
He called these functions "path properties" to contrast them with properties of single data points.
327327
328-
He studied two special prospective path properties of a given series $\{y_t\}$.
328+
He studied two special prospective path properties of a given series $\{y_t\}$.
329329
330-
The first was **time until the next turning point**.
330+
The first was *time until the next turning point*.
331331
332-
* he defined a **"turning point"** to be the date of the second of two successive declines in $y$.
332+
* he defined a **"turning point"** to be the date of the second of two successive declines in $y$.
333333
334-
To examine this statistic, let $Z$ be an indicator process
334+
For example, if $y_t(\omega)< y_{t-1}(\omega)< y_{t-2}(\omega)$, then $t$, then period $t$ is a turning point.
335+
336+
To examine the *time until the next turning point*, let $Z$ be an indicator process
335337
336338
$$
337339
Z_t(\omega) :=
@@ -343,12 +345,18 @@ $$
343345
344346
Here $\omega \in Omega$ is a sequence of events, and $Y_t: \Omega \rightarrow R$ gives $y_t$ according to $\omega$ and the AR(1) process.
345347
346-
Then the random variable **time until the next turning point** is defined as the following *stopping time* with respect to $Z$:
348+
By Wecker's definition, period $t$ is a turning point, and $Y_{t-2}(\omega) \geq Y_{t-3}(\omega)$ excludes that period $t-1$ is a turning point.
349+
350+
Then the random variable **time until the next turning point** is defined as the following *stopping time* with respect to $Z$:
347351
348352
$$
349353
W_t(\omega):= \inf \{ k\geq 1 \mid Z_{t+k}(\omega) = 1\}
350354
$$
351355
356+
In the following code, we name this statistic *time until the next recession* to distinguish it from another concept of *turning point*.
357+
358+
Moreover, the statistic *time until the next severe recession* is defined in a similar way, except the decline between periods is greater than $0.02$.
359+
352360
Wecker {cite}`wecker1979predicting` also studied **the minimum value of $Y$ over the next 8 quarters**
353361
which can be defined as the random variable.
354362
@@ -383,8 +391,12 @@ This is designed to express the event
383391
384392
- "after one or two decrease(s), $Y$ will grow for two consecutive quarters"
385393
394+
The **negative turning point today or tomorrow** $N_t$ is defined in the same way.
395+
386396
Following {cite}`wecker1979predicting`, we can use simulations to calculate probabilities of $P_t$ and $N_t$ for each period $t$.
387397
398+
However, in the following code, we only use $T_{t+1}(\omega)=1$ to determine $P_t(\omega)$ and $N_t(\omega)$, because we only want to find out the first positive turning point.
399+
388400
## A Wecker-Like Algorithm
389401
390402
The procedure consists of the following steps:
@@ -404,7 +416,7 @@ $$
404416
405417
## Using Simulations to Approximate a Posterior Distribution
406418
407-
The next code cells use `pymc` to compute the time $t$ posterior distribution of $\rho, \sigma$.
419+
The next code cells use `numpyro` to compute the time $t$ posterior distribution of $\rho, \sigma$.
408420
409421
Note that in defining the likelihood function, we choose to condition on the initial value $y_0$.
410422
@@ -445,7 +457,7 @@ def draw_from_posterior(data, size=10000, bins=20, dis_plot=1, key=key):
445457
'sigma': mcmc.get_samples()['sigma']
446458
}
447459
# Plot posterior distributions
448-
if dis_plot==1:
460+
if dis_plot == 1:
449461
fig, ax = plt.subplots(1, 2, figsize=(12, 5))
450462
sns.histplot(
451463
post_sample['rho'], kde=True, stat="density", bins=bins, ax=ax[0]
@@ -469,28 +481,30 @@ Our next step is to prepare Python code to compute our sample path statistics.
469481
470482
These statistics were originally defined as random variables with respect to $\omega$, but here we use $\{Y_t\}$ as the argument because $\omega$ is implicit.
471483
472-
Also, these two kinds of definitions are equivalent because $\omega$ determins path statistics only through $\{Y_t\}$
484+
These two kinds of definitions are equivalent because $\omega$ determins path statistics only through $\{Y_t\}$.
485+
486+
Moreover, we ignore all equality in the definitions, as equality occurs with zero probablity for countinuous random variables.
473487
474488
```{code-cell} ipython3
475489
def compute_path_statistics(initial_path, future_path):
476490
"""Compute path statistics for the AR(1) process."""
477491
# Concatenate the last two elements of initial path to identify recession
478-
y = jnp.concatenate([initial_path[-2:], future_path])
492+
y = jnp.concatenate([initial_path[-3:], future_path])
479493
n = y.shape[0]
480494
def step(carry, i):
481495
# identify recession
482-
rec_cond = (y[i] < y[i-1]) & (y[i-1] < y[i-2])
496+
rec_cond = (y[i] < y[i-1]) & (y[i-1] < y[i-2]) & (y[i-2] > y[i-3])
483497
# identify severe recession
484-
sev_cond = (y[i] - y[i-1] < -0.02) & (y[i-1] - y[i-2] < -0.02)
485-
# identify positive turning point
498+
sev_cond = (
499+
(y[i] - y[i-1] < -0.02) & (y[i-1] - y[i-2] < -0.02) & (y[i-2] > y[i-3])
500+
)
501+
# identify positive turning point.
486502
up_cond = (
487-
((y[i-2] > y[i-1]) & (y[i-1] > y[i]) & (y[i] < y[i+1]) & (y[i+1] < y[i+2]))
488-
| ((y[i-1] > y[i]) & (y[i] > y[i+1]) & (y[i+1] < y[i+2]) & (y[i+2] < y[i+3]))
503+
(y[i-2] > y[i-1]) & (y[i-1] > y[i]) & (y[i] < y[i+1]) & (y[i+1] < y[i+2])
489504
)
490505
# identify negative turning point
491506
down_cond = (
492507
(y[i-2] < y[i-1]) & (y[i-1] < y[i]) & (y[i] > y[i+1]) & (y[i+1] > y[i+2])
493-
| ((y[i-1] < y[i]) & (y[i] < y[i+1]) & (y[i+1] > y[i+2]) & (y[i+2] > y[i+3]))
494508
)
495509
# Convert to int
496510
rec = jnp.where(rec_cond, 1, 0)
@@ -499,30 +513,37 @@ def compute_path_statistics(initial_path, future_path):
499513
down = jnp.where(down_cond, 1, 0)
500514
return carry, (rec, sev, up, down)
501515
502-
_, (rec_seq, sev_seq, up_seq, down_seq) = lax.scan(step, None, jnp.arange(2, n-3))
516+
_, (rec_seq, sev_seq, up_seq, down_seq) = lax.scan(step, None, jnp.arange(3, n-3))
503517
504-
# Get the index of the first recession
518+
# Get the time until the first recession
505519
next_recession = jnp.where(
506520
jnp.any(rec_seq == 1), jnp.argmax(rec_seq == 1) + 1, len(y)
507521
)
508522
next_severe_recession = jnp.where(
509523
jnp.any(sev_seq == 1), jnp.argmax(sev_seq == 1) + 1, len(y)
510524
)
511-
min_val_8q = jnp.min(future_path[:8]) # Minimum value in the next 8 periods
525+
# Minimum value in the next 8 periods
526+
min_val_8q = jnp.min(future_path[:8])
527+
# Get the time until the first turning point.
512528
next_up_turn = jnp.where(
513-
jnp.any(up_seq == 1), jnp.argmax(up_seq == 1) + 1, len(y)
529+
jnp.any(up_seq == 1),
530+
jnp.maximum(jnp.argmax(up_seq == 1), 1), # Exclude 0 return
531+
len(y)
514532
)
515533
next_down_turn = jnp.where(
516-
jnp.any(down_seq == 1), jnp.argmax(down_seq == 1) + 1, len(y)
534+
jnp.any(down_seq == 1),
535+
jnp.maximum(jnp.argmax(down_seq == 1), 1),
536+
len(y)
517537
)
518-
519-
path_stats = (next_recession, next_severe_recession, min_val_8q,
520-
next_up_turn, next_down_turn)
538+
path_stats = (
539+
next_recession, next_severe_recession, min_val_8q,
540+
next_up_turn, next_down_turn
541+
)
521542
return path_stats
522543
523544
524545
def plot_path_stats(next_recession, next_severe_recession, min_val_8q,
525-
next_up_turn, next_down_turn, ax):
546+
next_up_turn, next_down_turn, ax):
526547
"""Plot the path statistics in subplots(3,2)"""
527548
# ax[0, 0] is for paths of y
528549
sns.histplot(next_recession, kde=True, stat='density', ax=ax[0, 1], alpha=.8)
@@ -531,7 +552,7 @@ def plot_path_stats(next_recession, next_severe_recession, min_val_8q,
531552
sns.histplot(
532553
next_severe_recession, kde=True, stat='density', ax=ax[1, 0], alpha=.8
533554
)
534-
ax[1, 0].set_xlabel("time until the next severe recession",fontsize=13)
555+
ax[1, 0].set_xlabel("time until the next severe recession", fontsize=13)
535556
536557
sns.histplot(min_val_8q, kde=True, stat='density', ax=ax[1, 1], alpha=.8)
537558
ax[1, 1].set_xlabel("minimum value in next 8 periods", fontsize=13)
@@ -666,12 +687,11 @@ mystnb:
666687
figure:
667688
caption: |
668689
Comparison between two methods
669-
name: fig_wecker
690+
name: fig_compare_wecker
670691
---
671692
fig, ax = plt.subplots(3, 2, figsize=(12, 15))
672693
plot_Wecker(ar1, initial_path, ax)
673694
ax[0, 0].clear()
674695
plot_extended_Wecker(ar1, post_samples, initial_path, ax)
675-
plt.legend()
676696
plt.show()
677697
```

0 commit comments

Comments
 (0)