GitHub

@@ -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".

37373838

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.

43434444

```{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

52505351

sns.set_style('white')

5452

colors = sns.color_palette()

555356-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).

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

176173

T0, T1 = 100, 100

177174

y0 = 10

178175179-

# simulate

176+

# Simulate

180177

np.random.seed(145)

181178

initial_path = AR1_simulate(rho, sigma, y0, T0)

182179183-

# 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

196193197194

He 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

$$

228225229226

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.

231228232229

$$

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):

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()

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$.

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

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]

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))

533530

plot_extended_Wecker(post_samples, initial_path, 1000, ax)

534531

plt.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

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()

Read the original on github.com ↗