@@ -31,9 +31,9 @@ We consider two sorts of statistics:
31313232- prospective values $y_{t+j}$ of a random process $\{y_t\}$ that is governed by the AR(1) process
333334-- 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$
353536-**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".
37373838To 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.
43434444```{code-cell} ipython3
4545import numpy as np
46-4746import arviz as az
4847import pymc as pmc
49-5048import matplotlib.pyplot as plt
5149import seaborn as sns
52505351sns.set_style('white')
5452colors = sns.color_palette()
555356-5754import logging
5855logging.basicConfig()
5956logger = 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).
110107111-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.
112109113110- 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):
130127131128 # 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)
134131135132 # 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]
139136140137 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)
153150154- # 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)
160157161- # 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)
165162166- # 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
176173T0, T1 = 100, 100
177174y0 = 10
178175179-# simulate
176+# Simulate
180177np.random.seed(145)
181178initial_path = AR1_simulate(rho, sigma, y0, T0)
182179183-# plot
180+# Plot
184181plot_initial_path(initial_path)
185182```
186183@@ -196,7 +193,7 @@ He called these functions "path properties" to contrast them with properties of
196193197194He studied two special prospective path properties of a given series $\{y_t\}$.
198195199-The first was **time until the next turning point**
196+The first was **time until the next turning point**.
200197201198* 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$$
228225229226Wecker {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.
231228232229$$
233230M_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):
316313317314 with AR1_model:
318315319- #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))
322319323320 # Expected value of y at the next period (rho * y)
324- yhat = rho*sample[:-1]
321+ yhat = rho * sample[:-1]
325322326323 # 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:])
328325329326 with AR1_model:
330327 trace = pmc.sample(10000, tune=5000)
331328332329 # check condition
333330 with AR1_model:
334- az.plot_trace(trace, figsize=(17,6))
331+ az.plot_trace(trace, figsize=(17, 6))
335332336333 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.
350347351348## Calculating Sample Path Statistics
352349353-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.
354351355352```{code-cell} ipython3
356353# define statistics
@@ -374,9 +371,9 @@ def severe_recession(omega):
374371 n = z.shape[0]
375372376373 sr = (z < -.02).astype(int)
377- indices = np.where(sr==1)[0]
374+ indices = np.where(sr == 1)[0]
378375379- 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
403400404- 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
406403407404 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)
425422426- # 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)
432429433- # 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)
438435439- # 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)
443440444- # 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)
454451455- # 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()
478475Now 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$.
480477481-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`.
482479483480```{code-cell} ipython3
484481def 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]
492489493- # 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)
498495499- # 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)
504501505- # 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)
512509513- 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)
515512516- # 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)
531528532-fig, ax = plt.subplots(3, 2, figsize=(15,12))
529+fig, ax = plt.subplots(3, 2, figsize=(15, 12))
533530plot_extended_Wecker(post_samples, initial_path, 1000, ax)
534531plt.show()
535532```
536533537534## Comparison
538535539-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.
540537541538```{code-cell} ipython3
542539fig, ax = plt.subplots(3, 2, figsize=(15,12))
543540plot_Wecker(initial_path, 1000, ax)
544-ax[0,0].clear()
541+ax[0, 0].clear()
545542plot_extended_Wecker(post_samples, initial_path, 1000, ax)
546543plt.legend()
547544plt.show()