Skip to content

Commit 0389a1e

Browse files
authored
Merge pull request #251 from natashawatkins/patch-7
Fix typos and prettify code in ar1_turningpts.md
2 parents 3210e11 + f514620 commit 0389a1e

File tree

1 file changed

+49
-52
lines changed

1 file changed

+49
-52
lines changed

lectures/ar1_turningpts.md

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ We consider two sorts of statistics:
3131

3232
- prospective values $y_{t+j}$ of a random process $\{y_t\}$ that is governed by the AR(1) process
3333

34-
- sample path properties that are defined as non-linear functions of future values $\{y_{t+j}\}_{j \geq 1}$ at time $t$.
34+
- sample path properties that are defined as non-linear functions of future values $\{y_{t+j}\}_{j \geq 1}$ at time $t$
3535

36-
**Sample path properties** are things like "time to next turning point" or "time to next recession"
36+
**Sample path properties** are things like "time to next turning point" or "time to next recession".
3737

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

@@ -43,17 +43,14 @@ Let's start with some imports.
4343

4444
```{code-cell} ipython3
4545
import numpy as np
46-
4746
import arviz as az
4847
import pymc as pmc
49-
5048
import matplotlib.pyplot as plt
5149
import seaborn as sns
5250
5351
sns.set_style('white')
5452
colors = sns.color_palette()
5553
56-
5754
import logging
5855
logging.basicConfig()
5956
logger = logging.getLogger('pymc')
@@ -106,9 +103,9 @@ We also want to compute some predictive distributions of "sample path statistic
106103
- the time until the next "recession",
107104
- the minimum value of $Y$ over the next 8 periods,
108105
- "severe recession", and
109-
- the time until the next turning point (positive or negative)
106+
- the time until the next turning point (positive or negative).
110107
111-
To accomplish that for situations in which we are uncertain about parameter values, we shall extend the approach Wecker {cite}`wecker1979predicting` in the following way.
108+
To accomplish that for situations in which we are uncertain about parameter values, we shall extend Wecker's {cite}`wecker1979predicting` approach in the following way.
112109
113110
- first simulate an initial path of length $T_0$;
114111
- for a given prior, draw a sample of size $N$ from the posterior joint distribution of parameters $\left(\rho,\sigma\right)$ after observing the initial path;
@@ -130,12 +127,12 @@ def AR1_simulate(rho, sigma, y0, T):
130127
131128
# Allocate space and draw epsilons
132129
y = np.empty(T)
133-
eps = np.random.normal(0,sigma,T)
130+
eps = np.random.normal(0, sigma, T)
134131
135132
# Initial condition and step forward
136133
y[0] = y0
137134
for t in range(1, T):
138-
y[t] = rho*y[t-1] + eps[t]
135+
y[t] = rho * y[t-1] + eps[t]
139136
140137
return y
141138
@@ -144,26 +141,26 @@ def plot_initial_path(initial_path):
144141
"""
145142
Plot the initial path and the preceding predictive densities
146143
"""
147-
# compute .9 confidence interval]
144+
# Compute .9 confidence interval]
148145
y0 = initial_path[-1]
149-
center = np.array([rho**j*y0 for j in range(T1)])
150-
vars = np.array([sigma**2*(1-rho**(2*j))/(1-rho**2) for j in range(T1)])
146+
center = np.array([rho**j * y0 for j in range(T1)])
147+
vars = np.array([sigma**2 * (1 - rho**(2 * j)) / (1 - rho**2) for j in range(T1)])
151148
y_bounds1_c95, y_bounds2_c95 = center + 1.96 * np.sqrt(vars), center - 1.96 * np.sqrt(vars)
152149
y_bounds1_c90, y_bounds2_c90 = center + 1.65 * np.sqrt(vars), center - 1.65 * np.sqrt(vars)
153150
154-
# plot
155-
fig, ax = plt.subplots(1,1, figsize = (12, 6))
151+
# Plot
152+
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
156153
ax.set_title("Initial Path and Predictive Densities", fontsize=15)
157-
ax.plot(np.arange(-T0+1, 1), initial_path)
154+
ax.plot(np.arange(-T0 + 1, 1), initial_path)
158155
ax.set_xlim([-T0, T1])
159156
ax.axvline(0, linestyle='--', alpha=.4, color='k', lw=1)
160157
161-
# simulate future paths
158+
# Simulate future paths
162159
for i in range(10):
163160
y_future = AR1_simulate(rho, sigma, y0, T1)
164161
ax.plot(np.arange(T1), y_future, color='grey', alpha=.5)
165162
166-
# plot 90% CI
163+
# Plot 90% CI
167164
ax.fill_between(np.arange(T1), y_bounds1_c95, y_bounds2_c95, alpha=.3, label='95% CI')
168165
ax.fill_between(np.arange(T1), y_bounds1_c90, y_bounds2_c90, alpha=.35, label='90% CI')
169166
ax.plot(np.arange(T1), center, color='red', alpha=.7, label='expected mean')
@@ -176,11 +173,11 @@ rho = 0.9
176173
T0, T1 = 100, 100
177174
y0 = 10
178175
179-
# simulate
176+
# Simulate
180177
np.random.seed(145)
181178
initial_path = AR1_simulate(rho, sigma, y0, T0)
182179
183-
# plot
180+
# Plot
184181
plot_initial_path(initial_path)
185182
```
186183
@@ -196,7 +193,7 @@ He called these functions "path properties" to contrast them with properties of
196193
197194
He studied two special prospective path properties of a given series $\{y_t\}$.
198195
199-
The first was **time until the next turning point**
196+
The first was **time until the next turning point**.
200197
201198
* he defined a **"turning point"** to be the date of the second of two successive declines in $y$.
202199
@@ -227,7 +224,7 @@ W_t(\omega):= \inf \{ k\geq 1 \mid Z_{t+k}(\omega) = 1\}
227224
$$
228225
229226
Wecker {cite}`wecker1979predicting` also studied **the minimum value of $Y$ over the next 8 quarters**
230-
which can be defined as the random variable
227+
which can be defined as the random variable.
231228
232229
$$
233230
M_t(\omega) := \min \{ Y_{t+1}(\omega); Y_{t+2}(\omega); \dots; Y_{t+8}(\omega)\}
@@ -316,22 +313,22 @@ def draw_from_posterior(sample):
316313
317314
with AR1_model:
318315
319-
#start with priors
320-
rho = pmc.Uniform('rho',lower=-1.,upper=1.) #assume stable rho
316+
# Start with priors
317+
rho = pmc.Uniform('rho',lower=-1.,upper=1.) # Assume stable rho
321318
sigma = pmc.HalfNormal('sigma', sigma = np.sqrt(10))
322319
323320
# Expected value of y at the next period (rho * y)
324-
yhat = rho*sample[:-1]
321+
yhat = rho * sample[:-1]
325322
326323
# Likelihood of the actual realization.
327-
y_like = pmc.Normal('y_obs', mu = yhat, sigma=sigma, observed=sample[1:])
324+
y_like = pmc.Normal('y_obs', mu=yhat, sigma=sigma, observed=sample[1:])
328325
329326
with AR1_model:
330327
trace = pmc.sample(10000, tune=5000)
331328
332329
# check condition
333330
with AR1_model:
334-
az.plot_trace(trace, figsize=(17,6))
331+
az.plot_trace(trace, figsize=(17, 6))
335332
336333
rhos = trace.posterior.rho.values.flatten()
337334
sigmas = trace.posterior.sigma.values.flatten()
@@ -350,7 +347,7 @@ The graphs on the left portray posterior marginal distributions.
350347
351348
## Calculating Sample Path Statistics
352349
353-
Our next step is to prepare Python codeto compute our sample path statistics.
350+
Our next step is to prepare Python code to compute our sample path statistics.
354351
355352
```{code-cell} ipython3
356353
# define statistics
@@ -374,9 +371,9 @@ def severe_recession(omega):
374371
n = z.shape[0]
375372
376373
sr = (z < -.02).astype(int)
377-
indices = np.where(sr==1)[0]
374+
indices = np.where(sr == 1)[0]
378375
379-
if len(indices)==0:
376+
if len(indices) == 0:
380377
return T1
381378
else:
382379
return indices[0] + 1
@@ -401,8 +398,8 @@ def next_turning_point(omega):
401398
(omega[i+2] > omega[i+3]) and (omega[i+3] > omega[i+4])):
402399
T[i] = -1
403400
404-
up_turn = np.where(T==1)[0][0] + 1 if (1 in T) == True else T1
405-
down_turn = np.where(T==-1)[0][0] + 1 if (-1 in T) == True else T1
401+
up_turn = np.where(T == 1)[0][0] + 1 if (1 in T) == True else T1
402+
down_turn = np.where(T == -1)[0][0] + 1 if (-1 in T) == True else T1
406403
407404
return up_turn, down_turn
408405
```
@@ -417,31 +414,31 @@ def plot_Wecker(initial_path, N, ax):
417414
"""
418415
Plot the predictive distributions from "pure" Wecker's method.
419416
"""
420-
# store outcomes
417+
# Store outcomes
421418
next_reces = np.zeros(N)
422419
severe_rec = np.zeros(N)
423420
min_vals = np.zeros(N)
424421
next_up_turn, next_down_turn = np.zeros(N), np.zeros(N)
425422
426-
# compute .9 confidence interval]
423+
# Compute .9 confidence interval]
427424
y0 = initial_path[-1]
428-
center = np.array([rho**j*y0 for j in range(T1)])
429-
vars = np.array([sigma**2*(1-rho**(2*j))/(1-rho**2) for j in range(T1)])
425+
center = np.array([rho**j * y0 for j in range(T1)])
426+
vars = np.array([sigma**2 * (1 - rho**(2 * j)) / (1 - rho**2) for j in range(T1)])
430427
y_bounds1_c95, y_bounds2_c95 = center + 1.96 * np.sqrt(vars), center - 1.96 * np.sqrt(vars)
431428
y_bounds1_c90, y_bounds2_c90 = center + 1.65 * np.sqrt(vars), center - 1.65 * np.sqrt(vars)
432429
433-
# plot
430+
# Plot
434431
ax[0, 0].set_title("Initial path and predictive densities", fontsize=15)
435-
ax[0, 0].plot(np.arange(-T0+1, 1), initial_path)
432+
ax[0, 0].plot(np.arange(-T0 + 1, 1), initial_path)
436433
ax[0, 0].set_xlim([-T0, T1])
437434
ax[0, 0].axvline(0, linestyle='--', alpha=.4, color='k', lw=1)
438435
439-
# plot 90% CI
436+
# Plot 90% CI
440437
ax[0, 0].fill_between(np.arange(T1), y_bounds1_c95, y_bounds2_c95, alpha=.3)
441438
ax[0, 0].fill_between(np.arange(T1), y_bounds1_c90, y_bounds2_c90, alpha=.35)
442439
ax[0, 0].plot(np.arange(T1), center, color='red', alpha=.7)
443440
444-
# simulate future paths
441+
# Simulate future paths
445442
for n in range(N):
446443
sim_path = AR1_simulate(rho, sigma, initial_path[-1], T1)
447444
next_reces[n] = next_recession(np.hstack([initial_path[-3:-1], sim_path]))
@@ -452,7 +449,7 @@ def plot_Wecker(initial_path, N, ax):
452449
if n%(N/10) == 0:
453450
ax[0, 0].plot(np.arange(T1), sim_path, color='gray', alpha=.3, lw=1)
454451
455-
# return next_up_turn, next_down_turn
452+
# Return next_up_turn, next_down_turn
456453
sns.histplot(next_reces, kde=True, stat='density', ax=ax[0, 1], alpha=.8, label='True parameters')
457454
ax[0, 1].set_title("Predictive distribution of time until the next recession", fontsize=13)
458455
@@ -478,42 +475,42 @@ plt.show()
478475
Now we apply we apply our "extended" Wecker method based on predictive densities of $y$ defined by
479476
{eq}`ar1-tp-eq4` that acknowledge posterior uncertainty in the parameters $\rho, \sigma$.
480477
481-
To approximate the intergration on the right side of {eq}`ar1-tp-eq4`, we repeately draw parameters from the joint posterior distribution each time we simulate a sequence of future values from model {eq}`ar1-tp-eq1`.
478+
To approximate the intergration on the right side of {eq}`ar1-tp-eq4`, we repeatedly draw parameters from the joint posterior distribution each time we simulate a sequence of future values from model {eq}`ar1-tp-eq1`.
482479
483480
```{code-cell} ipython3
484481
def plot_extended_Wecker(post_samples, initial_path, N, ax):
485482
"""
486483
Plot the extended Wecker's predictive distribution
487484
"""
488-
# select a sample
489-
index = np.random.choice(np.arange(len(post_samples['rho'])), N+1, replace=False)
485+
# Select a sample
486+
index = np.random.choice(np.arange(len(post_samples['rho'])), N + 1, replace=False)
490487
rho_sample = post_samples['rho'][index]
491488
sigma_sample = post_samples['sigma'][index]
492489
493-
# store outcomes
490+
# Store outcomes
494491
next_reces = np.zeros(N)
495492
severe_rec = np.zeros(N)
496493
min_vals = np.zeros(N)
497494
next_up_turn, next_down_turn = np.zeros(N), np.zeros(N)
498495
499-
# plot
496+
# Plot
500497
ax[0, 0].set_title("Initial path and future paths simulated from posterior draws", fontsize=15)
501-
ax[0, 0].plot(np.arange(-T0+1, 1), initial_path)
498+
ax[0, 0].plot(np.arange(-T0 + 1, 1), initial_path)
502499
ax[0, 0].set_xlim([-T0, T1])
503500
ax[0, 0].axvline(0, linestyle='--', alpha=.4, color='k', lw=1)
504501
505-
# simulate future paths
502+
# Simulate future paths
506503
for n in range(N):
507504
sim_path = AR1_simulate(rho_sample[n], sigma_sample[n], initial_path[-1], T1)
508505
next_reces[n] = next_recession(np.hstack([initial_path[-3:-1], sim_path]))
509506
severe_rec[n] = severe_recession(sim_path)
510507
min_vals[n] = minimum_value(sim_path)
511508
next_up_turn[n], next_down_turn[n] = next_turning_point(sim_path)
512509
513-
if n%(N/10) == 0:
510+
if n % (N / 10) == 0:
514511
ax[0, 0].plot(np.arange(T1), sim_path, color='gray', alpha=.3, lw=1)
515512
516-
# return next_up_turn, next_down_turn
513+
# Return next_up_turn, next_down_turn
517514
sns.histplot(next_reces, kde=True, stat='density', ax=ax[0, 1], alpha=.6, color=colors[1], label='Sampling from posterior')
518515
ax[0, 1].set_title("Predictive distribution of time until the next recession", fontsize=13)
519516
@@ -529,19 +526,19 @@ def plot_extended_Wecker(post_samples, initial_path, N, ax):
529526
sns.histplot(next_down_turn, kde=True, stat='density', ax=ax[2, 1], alpha=.6, color=colors[1], label='Sampling from posterior')
530527
ax[2, 1].set_title("Predictive distribution of time until the next negative turn", fontsize=13)
531528
532-
fig, ax = plt.subplots(3, 2, figsize=(15,12))
529+
fig, ax = plt.subplots(3, 2, figsize=(15, 12))
533530
plot_extended_Wecker(post_samples, initial_path, 1000, ax)
534531
plt.show()
535532
```
536533
537534
## Comparison
538535
539-
Finally, we plot both the original Wecker method and the extended method with parameter values drawn from the posterior together to compare the differnces that emerge from pretending to know parameter values when they are actually uncertain.
536+
Finally, we plot both the original Wecker method and the extended method with parameter values drawn from the posterior together to compare the differences that emerge from pretending to know parameter values when they are actually uncertain.
540537
541538
```{code-cell} ipython3
542539
fig, ax = plt.subplots(3, 2, figsize=(15,12))
543540
plot_Wecker(initial_path, 1000, ax)
544-
ax[0,0].clear()
541+
ax[0, 0].clear()
545542
plot_extended_Wecker(post_samples, initial_path, 1000, ax)
546543
plt.legend()
547544
plt.show()

0 commit comments

Comments
 (0)