Skip to content

Commit 9700a3a

Browse files
committed
Update ar1_turningpts.md
1 parent fc930ae commit 9700a3a

File tree

1 file changed

+94
-85
lines changed

1 file changed

+94
-85
lines changed

lectures/ar1_turningpts.md

Lines changed: 94 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,7 @@ from numpyro.infer import MCMC, NUTS
5959
6060
sns.set_style('white')
6161
colors = sns.color_palette()
62-
63-
import logging
64-
logging.basicConfig()
65-
logger = logging.getLogger('pymc')
66-
logger.setLevel(logging.CRITICAL)
62+
key = random.PRNGKey(0)
6763
```
6864

6965
## A Univariate First-Order Autoregressive Process
@@ -150,21 +146,21 @@ class AR1(NamedTuple):
150146
sigma:float
151147
152148
153-
def AR1_simulate(rho, sigma, y0, T):
149+
def AR1_simulate(ar1: AR1, y0, T, subkey):
154150
"""Generate a realization of the AR1 process given parameters and length"""
155151
# Allocate space and draw epsilons
156-
y = np.empty(T)
157-
eps = np.random.normal(0, sigma, T)
152+
y = jnp.empty(T)
153+
eps = ar1.sigma * random.normal(subkey, (T,))
158154
159155
# Initial condition and step forward
160-
y[0] = y0
156+
y.at[0].set(y0)
161157
for t in range(1, T):
162-
y[t] = rho * y[t-1] + eps[t]
158+
y.at[t].set(ar1.rho * y[t-1] + eps[t])
163159
164160
return jnp.asarray(y)
165161
166162
167-
def plot_initial_path(ar1, initial_path, predict_length):
163+
def plot_path(ar1, initial_path, predict_length):
168164
"""Plot the initial path and the preceding predictive densities"""
169165
rho, sigma = ar1.rho, ar1.sigma
170166
T0 = len(initial_path)
@@ -177,32 +173,33 @@ def plot_initial_path(ar1, initial_path, predict_length):
177173
[sigma**2 * (1 - rho**(2 * j)) / (1 - rho**2) for j in range(T1)]
178174
)
179175
180-
y_upper_c95 = center + 1.96 * np.sqrt(vars)
181-
y_lower_c95 = center - 1.96 * np.sqrt(vars)
176+
y_upper_c95 = center + 1.96 * jnp.sqrt(vars)
177+
y_lower_c95 = center - 1.96 * jnp.sqrt(vars)
182178
183-
y_upper_c90 = center + 1.65 * np.sqrt(vars)
184-
y_lower_c90 = center - 1.65 * np.sqrt(vars)
179+
y_upper_c90 = center + 1.65 * jnp.sqrt(vars)
180+
y_lower_c90 = center - 1.65 * jnp.sqrt(vars)
185181
186182
# Plot
187183
fig, ax = plt.subplots(1, 1)
188184
#ax.set_title("Initial Path and Predictive Densities", fontsize=15)
189-
ax.plot(np.arange(-T0 + 1, 1), initial_path)
185+
ax.plot(jnp.arange(-T0 + 1, 1), initial_path)
190186
ax.set_xlim([-T0, T1])
191187
ax.axvline(0, linestyle='--', alpha=.4, color='k', lw=1)
192188
193189
# Simulate 10 future paths
190+
subkeys = random.split(key, num=10)
194191
for i in range(10):
195-
y_future = AR1_simulate(rho, sigma, y_T0, T1)
196-
ax.plot(np.arange(T1), y_future, color='grey', alpha=.5)
192+
y_future = AR1_simulate(ar1, y_T0, T1, subkeys[i])
193+
ax.plot(jnp.arange(T1), y_future, color='grey', alpha=.5)
197194
198195
# Plot 90% CI
199196
ax.fill_between(
200-
np.arange(T1), y_upper_c95, y_lower_c95, alpha=.3, label='95% CI'
197+
jnp.arange(T1), y_upper_c95, y_lower_c95, alpha=.3, label='95% CI'
201198
)
202199
ax.fill_between(
203-
np.arange(T1), y_upper_c90, y_lower_c90, alpha=.35, label='90% CI'
200+
jnp.arange(T1), y_upper_c90, y_lower_c90, alpha=.35, label='90% CI'
204201
)
205-
ax.plot(np.arange(T1), center, color='red', alpha=.7, label='expectation')
202+
ax.plot(jnp.arange(T1), center, color='red', alpha=.7, label='expectation')
206203
ax.legend(fontsize=12)
207204
plt.show()
208205
```
@@ -216,17 +213,14 @@ mystnb:
216213
name: fig_path
217214
---
218215
ar1 = AR1(rho=0.9, sigma=1)
219-
rho = 0.9
220-
simga = 1
221216
T0, T1 = 100, 100
222217
y0 = 10
223218
224219
# Simulate
225-
np.random.seed(145)
226-
initial_path = AR1_simulate(rho, sigma, y0, T0)
220+
initial_path = AR1_simulate(ar1, y0, T0)
227221
228222
# Plot
229-
plot_initial_path(ar1, initial_path, T1)
223+
plot_path(ar1, initial_path, T1)
230224
```
231225
232226
As functions of forecast horizon, the coverage intervals have shapes like those described in
@@ -323,110 +317,122 @@ The next code cells use `pymc` to compute the time $t$ posterior distribution of
323317
Note that in defining the likelihood function, we choose to condition on the initial value $y_0$.
324318
325319
```{code-cell} ipython3
326-
def draw_from_posterior(sample):
327-
"""
328-
Draw a sample of size N from the posterior distribution.
329-
"""
330-
331-
AR1_model = pmc.Model()
332-
333-
with AR1_model:
334-
335-
# Start with priors
336-
rho = pmc.Uniform('rho',lower=-1.,upper=1.) # Assume stable rho
337-
sigma = pmc.HalfNormal('sigma', sigma = np.sqrt(10))
338-
339-
# Expected value of y at the next period (rho * y)
340-
yhat = rho * sample[:-1]
320+
---
321+
mystnb:
322+
figure:
323+
caption: |
324+
Posterior distributions (rho, sigma)
325+
name: fig_post
326+
---
327+
def draw_from_posterior(sample, size=1e5, bins=20, dis_plot=1):
328+
"""Draw a sample of size from the posterior distribution."""
329+
# Start with priors
330+
rho = numpyro.sample('rho', dist.Uniform(-1, 1)) # Assume stable rho
331+
sigma = numpyro.sample('sigma', dis.HalfNormal(sqrt(10)))
332+
333+
# Define likelihood recursively
334+
for t in range(1, len(sample))
335+
# Expectation of y_t
336+
mu = rho * sample[t-1]
341337
342338
# Likelihood of the actual realization.
343-
y_like = pmc.Normal('y_obs', mu=yhat, sigma=sigma, observed=sample[1:])
344-
345-
with AR1_model:
346-
trace = pmc.sample(10000, tune=5000)
339+
numpyro.sample(f'y_{t}', dist.Normal(mu, sigma), obs=sample[t])
347340
348-
# check condition
349-
with AR1_model:
350-
az.plot_trace(trace, figsize=(17, 6))
351-
352-
rhos = trace.posterior.rho.values.flatten()
353-
sigmas = trace.posterior.sigma.values.flatten()
341+
# Compute posterior distribution of parameters
342+
nuts_kernel = NUTS(model)
343+
mcmc = MCMC(
344+
nuts_kernek,
345+
num_warmup=5000,
346+
num_sample=size,
347+
progress_bar=False)
348+
mcmc.run(random.PRNGKey(0), sample=sample)
354349
355350
post_sample = {
356-
'rho': rhos,
357-
'sigma': sigmas
351+
'rho': mcmc.get_samples()['rho'],
352+
'sigma': mcmc.get_samples()['sigma']
358353
}
359-
354+
355+
if dis_plot==1:
356+
sns.displot(
357+
post_sample, kde=True, stat="density", bins=bins, height=5, aspect=1.5
358+
)
359+
360360
return post_sample
361361
362362
post_samples = draw_from_posterior(initial_path)
363363
```
364364
365-
The graphs on the left portray posterior marginal distributions.
365+
The graphs above portray posterior distributions.
366366
367367
## Calculating Sample Path Statistics
368368
369369
Our next step is to prepare Python code to compute our sample path statistics.
370370
371+
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.
372+
373+
Also, these two kinds of definitions are equivalent because $\omega$ determins path statistics only through $\{Y_t\}$
374+
371375
```{code-cell} ipython3
372-
# define statistics
373-
def next_recession(omega):
374-
n = omega.shape[0] - 3
375-
z = np.zeros(n, dtype=int)
376+
# Define statistics as functions of y, the realized path values.
377+
def next_recession(y):
378+
n = y.shape[0] - 3
379+
z = jnp.zeros(n, dtype=int)
376380
377381
for i in range(n):
378-
z[i] = int(omega[i] <= omega[i+1] and omega[i+1] > omega[i+2] and omega[i+2] > omega[i+3])
382+
z.at[i].set(int(y[i] <= y[i+1] and y[i+1] > y[i+2] and y[i+2] > y[i+3]))
379383
380-
if np.any(z) == False:
384+
if jnp.any(z) == False:
381385
return 500
382386
else:
383-
return np.where(z==1)[0][0] + 1
387+
return jnp.where(z == 1)[0][0] + 1
384388
385-
def minimum_value(omega):
386-
return min(omega[:8])
387389
388-
def severe_recession(omega):
389-
z = np.diff(omega)
390+
def next_severe_recession(y):
391+
z = jnp.diff(y)
390392
n = z.shape[0]
391393
392394
sr = (z < -.02).astype(int)
393-
indices = np.where(sr == 1)[0]
395+
indices = jnp.where(sr == 1)[0]
394396
395397
if len(indices) == 0:
396398
return T1
397399
else:
398400
return indices[0] + 1
399401
400-
def next_turning_point(omega):
402+
403+
def minimum_value_8q(y):
404+
return jnp.min(y[:8])
405+
406+
407+
def next_turning_point(y):
401408
"""
402-
Suppose that omega is of length 6
409+
Suppose that y is of length 6
403410
404411
y_{t-2}, y_{t-1}, y_{t}, y_{t+1}, y_{t+2}, y_{t+3}
405412
406413
that is sufficient for determining the value of P/N
407414
"""
408415
409-
n = np.asarray(omega).shape[0] - 4
410-
T = np.zeros(n, dtype=int)
416+
n = jnp.asarray(y).shape[0] - 4
417+
T = jnp.zeros(n, dtype=int)
411418
412419
for i in range(n):
413-
if ((omega[i] > omega[i+1]) and (omega[i+1] > omega[i+2]) and
414-
(omega[i+2] < omega[i+3]) and (omega[i+3] < omega[i+4])):
420+
if ((y[i] > y[i+1]) and (y[i+1] > y[i+2]) and
421+
(y[i+2] < y[i+3]) and (y[i+3] < y[i+4])):
415422
T[i] = 1
416-
elif ((omega[i] < omega[i+1]) and (omega[i+1] < omega[i+2]) and
417-
(omega[i+2] > omega[i+3]) and (omega[i+3] > omega[i+4])):
423+
elif ((y[i] < y[i+1]) and (y[i+1] < y[i+2]) and
424+
(y[i+2] > y[i+3]) and (y[i+3] > y[i+4])):
418425
T[i] = -1
419426
420-
up_turn = np.where(T == 1)[0][0] + 1 if (1 in T) == True else T1
421-
down_turn = np.where(T == -1)[0][0] + 1 if (-1 in T) == True else T1
427+
up_turn = jnp.where(T == 1)[0][0] + 1 if (1 in T) == True else T1
428+
down_turn = jnp.where(T == -1)[0][0] + 1 if (-1 in T) == True else T1
422429
423430
return up_turn, down_turn
424431
```
425432
426433
## Original Wecker Method
427434
428-
Now we apply Wecker's original method by simulating future paths and compute predictive distributions, conditioning
429-
on the true parameters associated with the data-generating model.
435+
Now we apply Wecker's original method by simulating future paths and compute predictive distributions, conditioning on the true parameters associated with the data-generating model.
430436
431437
```{code-cell} ipython3
432438
def plot_Wecker(initial_path, N, ax):
@@ -458,11 +464,12 @@ def plot_Wecker(initial_path, N, ax):
458464
ax[0, 0].plot(np.arange(T1), center, color='red', alpha=.7)
459465
460466
# Simulate future paths
467+
subkeys = random.split(key, num=N)
461468
for n in range(N):
462-
sim_path = AR1_simulate(rho, sigma, initial_path[-1], T1)
469+
sim_path = AR1_simulate(ar1, initial_path[-1], T1, subkeys[n])
463470
next_reces[n] = next_recession(np.hstack([initial_path[-3:-1], sim_path]))
464-
severe_rec[n] = severe_recession(sim_path)
465-
min_vals[n] = minimum_value(sim_path)
471+
severe_rec[n] = next_severe_recession(sim_path)
472+
min_vals[n] = minimum_value_8q(sim_path)
466473
next_up_turn[n], next_down_turn[n] = next_turning_point(sim_path)
467474
468475
if n%(N/10) == 0:
@@ -519,11 +526,13 @@ def plot_extended_Wecker(post_samples, initial_path, N, ax):
519526
ax[0, 0].axvline(0, linestyle='--', alpha=.4, color='k', lw=1)
520527
521528
# Simulate future paths
529+
subkeys = random.split(key, num=N)
522530
for n in range(N):
523-
sim_path = AR1_simulate(rho_sample[n], sigma_sample[n], initial_path[-1], T1)
531+
ar1_n = AR1(rho=rho_sample[n], sigma=sigma_sample[n])
532+
sim_path = AR1_simulate(ar1_n, initial_path[-1], T1, subkeys[n])
524533
next_reces[n] = next_recession(np.hstack([initial_path[-3:-1], sim_path]))
525-
severe_rec[n] = severe_recession(sim_path)
526-
min_vals[n] = minimum_value(sim_path)
534+
severe_rec[n] = next_severe_recession(sim_path)
535+
min_vals[n] = minimum_value_8q(sim_path)
527536
next_up_turn[n], next_down_turn[n] = next_turning_point(sim_path)
528537
529538
if n % (N / 10) == 0:

0 commit comments

Comments
 (0)