Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/statsmodels/tsa/vector_ar/irf.py : 12%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3Impulse reponse-related code
4"""
6import numpy as np
7import numpy.linalg as la
8import scipy.linalg as L
11from statsmodels.tools.decorators import cache_readonly
12import statsmodels.tsa.tsatools as tsa
13import statsmodels.tsa.vector_ar.plotting as plotting
14import statsmodels.tsa.vector_ar.util as util
16mat = np.array
19class BaseIRAnalysis(object):
20 """
21 Base class for plotting and computing IRF-related statistics, want to be
22 able to handle known and estimated processes
23 """
25 def __init__(self, model, P=None, periods=10, order=None, svar=False,
26 vecm=False):
27 self.model = model
28 self.periods = periods
29 self.neqs, self.lags, self.T = model.neqs, model.k_ar, model.nobs
31 self.order = order
33 if P is None:
34 sigma = model.sigma_u
36 # TODO, may be difficult at the moment
37 # if order is not None:
38 # indexer = [model.get_eq_index(name) for name in order]
39 # sigma = sigma[:, indexer][indexer, :]
41 # if sigma.shape != model.sigma_u.shape:
42 # raise ValueError('variable order is wrong length')
44 P = la.cholesky(sigma)
46 self.P = P
48 self.svar = svar
50 self.irfs = model.ma_rep(periods)
51 if svar:
52 self.svar_irfs = model.svar_ma_rep(periods, P=P)
53 else:
54 self.orth_irfs = model.orth_ma_rep(periods, P=P)
56 self.cum_effects = self.irfs.cumsum(axis=0)
57 if svar:
58 self.svar_cum_effects = self.svar_irfs.cumsum(axis=0)
59 else:
60 self.orth_cum_effects = self.orth_irfs.cumsum(axis=0)
62 # long-run effects may be infinite for VECMs.
63 if not vecm:
64 self.lr_effects = model.long_run_effects()
65 if svar:
66 self.svar_lr_effects = np.dot(model.long_run_effects(), P)
67 else:
68 self.orth_lr_effects = np.dot(model.long_run_effects(), P)
70 # auxiliary stuff
71 if vecm:
72 self._A = util.comp_matrix(model.var_rep)
73 else:
74 self._A = util.comp_matrix(model.coefs)
76 def _choose_irfs(self, orth=False, svar=False):
77 if orth:
78 return self.orth_irfs
79 elif svar:
80 return self.svar_irfs
81 else:
82 return self.irfs
84 def cov(self, *args, **kwargs):
85 raise NotImplementedError
87 def cum_effect_cov(self, *args, **kwargs):
88 raise NotImplementedError
90 def plot(self, orth=False, impulse=None, response=None,
91 signif=0.05, plot_params=None, subplot_params=None,
92 plot_stderr=True, stderr_type='asym', repl=1000,
93 seed=None, component=None):
94 """
95 Plot impulse responses
97 Parameters
98 ----------
99 orth : bool, default False
100 Compute orthogonalized impulse responses
101 impulse : {str, int}
102 variable providing the impulse
103 response : {str, int}
104 variable affected by the impulse
105 signif : float (0 < signif < 1)
106 Significance level for error bars, defaults to 95% CI
107 subplot_params : dict
108 To pass to subplot plotting funcions. Example: if fonts are too big,
109 pass {'fontsize' : 8} or some number to your taste.
110 plot_params : dict
112 plot_stderr: bool, default True
113 Plot standard impulse response error bands
114 stderr_type: str
115 'asym': default, computes asymptotic standard errors
116 'mc': monte carlo standard errors (use rpl)
117 repl: int, default 1000
118 Number of replications for Monte Carlo and Sims-Zha standard errors
119 seed: int
120 np.random.seed for Monte Carlo replications
121 component: array or vector of principal component indices
122 """
123 periods = self.periods
124 model = self.model
125 svar = self.svar
127 if orth and svar:
128 raise ValueError("For SVAR system, set orth=False")
130 irfs = self._choose_irfs(orth, svar)
131 if orth:
132 title = 'Impulse responses (orthogonalized)'
133 elif svar:
134 title = 'Impulse responses (structural)'
135 else:
136 title = 'Impulse responses'
138 if plot_stderr is False:
139 stderr = None
141 elif stderr_type not in ['asym', 'mc', 'sz1', 'sz2','sz3']:
142 raise ValueError("Error type must be either 'asym', 'mc','sz1','sz2', or 'sz3'")
143 else:
144 if stderr_type == 'asym':
145 stderr = self.cov(orth=orth)
146 if stderr_type == 'mc':
147 stderr = self.errband_mc(orth=orth, svar=svar,
148 repl=repl, signif=signif,
149 seed=seed)
150 if stderr_type == 'sz1':
151 stderr = self.err_band_sz1(orth=orth, svar=svar,
152 repl=repl, signif=signif,
153 seed=seed,
154 component=component)
155 if stderr_type == 'sz2':
156 stderr = self.err_band_sz2(orth=orth, svar=svar,
157 repl=repl, signif=signif,
158 seed=seed,
159 component=component)
160 if stderr_type == 'sz3':
161 stderr = self.err_band_sz3(orth=orth, svar=svar,
162 repl=repl, signif=signif,
163 seed=seed,
164 component=component)
166 fig = plotting.irf_grid_plot(irfs, stderr, impulse, response,
167 self.model.names, title, signif=signif,
168 subplot_params=subplot_params,
169 plot_params=plot_params,
170 stderr_type=stderr_type)
171 return fig
173 def plot_cum_effects(self, orth=False, impulse=None, response=None,
174 signif=0.05, plot_params=None,
175 subplot_params=None, plot_stderr=True,
176 stderr_type='asym', repl=1000, seed=None):
177 """
178 Plot cumulative impulse response functions
180 Parameters
181 ----------
182 orth : bool, default False
183 Compute orthogonalized impulse responses
184 impulse : {str, int}
185 variable providing the impulse
186 response : {str, int}
187 variable affected by the impulse
188 signif : float (0 < signif < 1)
189 Significance level for error bars, defaults to 95% CI
190 subplot_params : dict
191 To pass to subplot plotting funcions. Example: if fonts are too big,
192 pass {'fontsize' : 8} or some number to your taste.
193 plot_params : dict
195 plot_stderr: bool, default True
196 Plot standard impulse response error bands
197 stderr_type: str
198 'asym': default, computes asymptotic standard errors
199 'mc': monte carlo standard errors (use rpl)
200 repl: int, default 1000
201 Number of replications for monte carlo standard errors
202 seed: int
203 np.random.seed for Monte Carlo replications
204 """
206 if orth:
207 title = 'Cumulative responses responses (orthogonalized)'
208 cum_effects = self.orth_cum_effects
209 lr_effects = self.orth_lr_effects
210 else:
211 title = 'Cumulative responses'
212 cum_effects = self.cum_effects
213 lr_effects = self.lr_effects
215 if stderr_type not in ['asym', 'mc']:
216 raise ValueError("`stderr_type` must be one of 'asym', 'mc'")
217 else:
218 if stderr_type == 'asym':
219 stderr = self.cum_effect_cov(orth=orth)
220 if stderr_type == 'mc':
221 stderr = self.cum_errband_mc(orth=orth, repl=repl,
222 signif=signif, seed=seed)
223 if not plot_stderr:
224 stderr = None
226 fig = plotting.irf_grid_plot(cum_effects, stderr, impulse, response,
227 self.model.names, title, signif=signif,
228 hlines=lr_effects,
229 subplot_params=subplot_params,
230 plot_params=plot_params,
231 stderr_type=stderr_type)
232 return fig
235class IRAnalysis(BaseIRAnalysis):
236 """
237 Impulse response analysis class. Computes impulse responses, asymptotic
238 standard errors, and produces relevant plots
240 Parameters
241 ----------
242 model : VAR instance
244 Notes
245 -----
246 Using Lütkepohl (2005) notation
247 """
248 def __init__(self, model, P=None, periods=10, order=None, svar=False,
249 vecm=False):
250 BaseIRAnalysis.__init__(self, model, P=P, periods=periods,
251 order=order, svar=svar, vecm=vecm)
253 if vecm:
254 self.cov_a = model.cov_var_repr
255 else:
256 self.cov_a = model._cov_alpha
257 self.cov_sig = model._cov_sigma
259 # memoize dict for G matrix function
260 self._g_memo = {}
262 def cov(self, orth=False):
263 """
264 Compute asymptotic standard errors for impulse response coefficients
266 Notes
267 -----
268 Lütkepohl eq 3.7.5
270 Returns
271 -------
272 """
273 if orth:
274 return self._orth_cov()
276 covs = self._empty_covm(self.periods + 1)
277 covs[0] = np.zeros((self.neqs ** 2, self.neqs ** 2))
278 for i in range(1, self.periods + 1):
279 Gi = self.G[i - 1]
280 covs[i] = Gi @ self.cov_a @ Gi.T
282 return covs
284 def errband_mc(self, orth=False, svar=False, repl=1000,
285 signif=0.05, seed=None, burn=100):
286 """
287 IRF Monte Carlo integrated error bands
288 """
289 model = self.model
290 periods = self.periods
291 if svar:
292 return model.sirf_errband_mc(orth=orth, repl=repl, steps=periods,
293 signif=signif, seed=seed,
294 burn=burn, cum=False)
295 else:
296 return model.irf_errband_mc(orth=orth, repl=repl, steps=periods,
297 signif=signif, seed=seed,
298 burn=burn, cum=False)
300 def err_band_sz1(self, orth=False, svar=False, repl=1000,
301 signif=0.05, seed=None, burn=100, component=None):
302 """
303 IRF Sims-Zha error band method 1. Assumes symmetric error bands around
304 mean.
306 Parameters
307 ----------
308 orth : bool, default False
309 Compute orthogonalized impulse responses
310 repl : int, default 1000
311 Number of MC replications
312 signif : float (0 < signif < 1)
313 Significance level for error bars, defaults to 95% CI
314 seed : int, default None
315 np.random seed
316 burn : int, default 100
317 Number of initial simulated obs to discard
318 component : neqs x neqs array, default to largest for each
319 Index of column of eigenvector/value to use for each error band
320 Note: period of impulse (t=0) is not included when computing
321 principle component
323 References
324 ----------
325 Sims, Christopher A., and Tao Zha. 1999. "Error Bands for Impulse
326 Response". Econometrica 67: 1113-1155.
327 """
329 model = self.model
330 periods = self.periods
331 irfs = self._choose_irfs(orth, svar)
332 neqs = self.neqs
333 irf_resim = model.irf_resim(orth=orth, repl=repl, steps=periods,
334 seed=seed, burn=burn)
335 q = util.norm_signif_level(signif)
337 W, eigva, k =self._eigval_decomp_SZ(irf_resim)
339 if component is not None:
340 if np.shape(component) != (neqs,neqs):
341 raise ValueError("Component array must be " + str(neqs) + " x " + str(neqs))
342 if np.argmax(component) >= neqs*periods:
343 raise ValueError("Atleast one of the components does not exist")
344 else:
345 k = component
347 # here take the kth column of W, which we determine by finding the largest eigenvalue of the covaraince matrix
348 lower = np.copy(irfs)
349 upper = np.copy(irfs)
350 for i in range(neqs):
351 for j in range(neqs):
352 lower[1:,i,j] = irfs[1:,i,j] + W[i,j,:,k[i,j]]*q*np.sqrt(eigva[i,j,k[i,j]])
353 upper[1:,i,j] = irfs[1:,i,j] - W[i,j,:,k[i,j]]*q*np.sqrt(eigva[i,j,k[i,j]])
355 return lower, upper
357 def err_band_sz2(self, orth=False, svar=False, repl=1000, signif=0.05,
358 seed=None, burn=100, component=None):
359 """
360 IRF Sims-Zha error band method 2.
362 This method Does not assume symmetric error bands around mean.
364 Parameters
365 ----------
366 orth : bool, default False
367 Compute orthogonalized impulse responses
368 repl : int, default 1000
369 Number of MC replications
370 signif : float (0 < signif < 1)
371 Significance level for error bars, defaults to 95% CI
372 seed : int, default None
373 np.random seed
374 burn : int, default 100
375 Number of initial simulated obs to discard
376 component : neqs x neqs array, default to largest for each
377 Index of column of eigenvector/value to use for each error band
378 Note: period of impulse (t=0) is not included when computing
379 principle component
381 References
382 ----------
383 Sims, Christopher A., and Tao Zha. 1999. "Error Bands for Impulse
384 Response". Econometrica 67: 1113-1155.
385 """
386 model = self.model
387 periods = self.periods
388 irfs = self._choose_irfs(orth, svar)
389 neqs = self.neqs
390 irf_resim = model.irf_resim(orth=orth, repl=repl, T=periods, seed=seed,
391 burn=100)
393 W, eigva, k = self._eigval_decomp_SZ(irf_resim)
395 if component is not None:
396 if np.shape(component) != (neqs,neqs):
397 raise ValueError("Component array must be " + str(neqs) + " x " + str(neqs))
398 if np.argmax(component) >= neqs*periods:
399 raise ValueError("Atleast one of the components does not exist")
400 else:
401 k = component
403 gamma = np.zeros((repl, periods+1, neqs, neqs))
404 for p in range(repl):
405 for i in range(neqs):
406 for j in range(neqs):
407 gamma[p,1:,i,j] = W[i,j,k[i,j],:] * irf_resim[p,1:,i,j]
409 gamma_sort = np.sort(gamma, axis=0) #sort to get quantiles
410 indx = round(signif/2*repl)-1,round((1-signif/2)*repl)-1
412 lower = np.copy(irfs)
413 upper = np.copy(irfs)
414 for i in range(neqs):
415 for j in range(neqs):
416 lower[:,i,j] = irfs[:,i,j] + gamma_sort[indx[0],:,i,j]
417 upper[:,i,j] = irfs[:,i,j] + gamma_sort[indx[1],:,i,j]
419 return lower, upper
421 def err_band_sz3(self, orth=False, svar=False, repl=1000, signif=0.05,
422 seed=None, burn=100, component=None):
423 """
424 IRF Sims-Zha error band method 3. Does not assume symmetric error bands around mean.
426 Parameters
427 ----------
428 orth : bool, default False
429 Compute orthogonalized impulse responses
430 repl : int, default 1000
431 Number of MC replications
432 signif : float (0 < signif < 1)
433 Significance level for error bars, defaults to 95% CI
434 seed : int, default None
435 np.random seed
436 burn : int, default 100
437 Number of initial simulated obs to discard
438 component : vector length neqs, default to largest for each
439 Index of column of eigenvector/value to use for each error band
440 Note: period of impulse (t=0) is not included when computing
441 principle component
443 References
444 ----------
445 Sims, Christopher A., and Tao Zha. 1999. "Error Bands for Impulse
446 Response". Econometrica 67: 1113-1155.
447 """
449 model = self.model
450 periods = self.periods
451 irfs = self._choose_irfs(orth, svar)
452 neqs = self.neqs
453 irf_resim = model.irf_resim(orth=orth, repl=repl, T=periods, seed=seed,
454 burn=100)
455 stack = np.zeros((neqs, repl, periods*neqs))
457 #stack left to right, up and down
459 for p in range(repl):
460 for i in range(neqs):
461 stack[i, p,:] = np.ravel(irf_resim[p,1:,:,i].T)
463 stack_cov=np.zeros((neqs, periods*neqs, periods*neqs))
464 W = np.zeros((neqs, periods*neqs, periods*neqs))
465 eigva = np.zeros((neqs, periods*neqs))
466 k = np.zeros((neqs))
468 if component is not None:
469 if np.size(component) != (neqs):
470 raise ValueError("Component array must be of length " + str(neqs))
471 if np.argmax(component) >= neqs*periods:
472 raise ValueError("Atleast one of the components does not exist")
473 else:
474 k = component
476 #compute for eigen decomp for each stack
477 for i in range(neqs):
478 stack_cov[i] = np.cov(stack[i],rowvar=0)
479 W[i], eigva[i], k[i] = util.eigval_decomp(stack_cov[i])
481 gamma = np.zeros((repl, periods+1, neqs, neqs))
482 for p in range(repl):
483 c = 0
484 for j in range(neqs):
485 for i in range(neqs):
486 gamma[p,1:,i,j] = W[j,k[j],i*periods:(i+1)*periods] * irf_resim[p,1:,i,j]
487 if i == neqs-1:
488 gamma[p,1:,i,j] = W[j,k[j],i*periods:] * irf_resim[p,1:,i,j]
490 gamma_sort = np.sort(gamma, axis=0) #sort to get quantiles
491 indx = round(signif/2*repl)-1,round((1-signif/2)*repl)-1
493 lower = np.copy(irfs)
494 upper = np.copy(irfs)
495 for i in range(neqs):
496 for j in range(neqs):
497 lower[:,i,j] = irfs[:,i,j] + gamma_sort[indx[0],:,i,j]
498 upper[:,i,j] = irfs[:,i,j] + gamma_sort[indx[1],:,i,j]
500 return lower, upper
502 def _eigval_decomp_SZ(self, irf_resim):
503 """
504 Returns
505 -------
506 W: array of eigenvectors
507 eigva: list of eigenvalues
508 k: matrix indicating column # of largest eigenvalue for each c_i,j
509 """
510 neqs = self.neqs
511 periods = self.periods
513 cov_hold = np.zeros((neqs, neqs, periods, periods))
514 for i in range(neqs):
515 for j in range(neqs):
516 cov_hold[i,j,:,:] = np.cov(irf_resim[:,1:,i,j],rowvar=0)
518 W = np.zeros((neqs, neqs, periods, periods))
519 eigva = np.zeros((neqs, neqs, periods, 1))
520 k = np.zeros((neqs, neqs))
522 for i in range(neqs):
523 for j in range(neqs):
524 W[i,j,:,:], eigva[i,j,:,0], k[i,j] = util.eigval_decomp(cov_hold[i,j,:,:])
525 return W, eigva, k
527 @cache_readonly
528 def G(self):
529 # Gi matrices as defined on p. 111
531 K = self.neqs
533 # nlags = self.model.p
534 # J = np.hstack((np.eye(K),) + (np.zeros((K, K)),) * (nlags - 1))
536 def _make_g(i):
537 # p. 111 Lutkepohl
538 G = 0.
539 for m in range(i):
540 # be a bit cute to go faster
541 idx = i - 1 - m
542 if idx in self._g_memo:
543 apow = self._g_memo[idx]
544 else:
545 apow = la.matrix_power(self._A.T, idx)
546 # apow = np.dot(J, apow)
547 apow = apow[:K]
548 self._g_memo[idx] = apow
550 # take first K rows
551 piece = np.kron(apow, self.irfs[m])
552 G = G + piece
554 return G
556 return [_make_g(i) for i in range(1, self.periods + 1)]
558 def _orth_cov(self):
559 # Lutkepohl 3.7.8
561 Ik = np.eye(self.neqs)
562 PIk = np.kron(self.P.T, Ik)
563 H = self.H
565 covs = self._empty_covm(self.periods + 1)
566 for i in range(self.periods + 1):
567 if i == 0:
568 apiece = 0
569 else:
570 Ci = np.dot(PIk, self.G[i-1])
571 apiece = Ci @ self.cov_a @ Ci.T
573 Cibar = np.dot(np.kron(Ik, self.irfs[i]), H)
574 bpiece = (Cibar @ self.cov_sig @ Cibar.T) / self.T
576 # Lutkepohl typo, cov_sig correct
577 covs[i] = apiece + bpiece
579 return covs
581 def cum_effect_cov(self, orth=False):
582 """
583 Compute asymptotic standard errors for cumulative impulse response
584 coefficients
586 Parameters
587 ----------
588 orth : bool
590 Notes
591 -----
592 eq. 3.7.7 (non-orth), 3.7.10 (orth)
594 Returns
595 -------
596 """
597 Ik = np.eye(self.neqs)
598 PIk = np.kron(self.P.T, Ik)
600 F = 0.
601 covs = self._empty_covm(self.periods + 1)
602 for i in range(self.periods + 1):
603 if i > 0:
604 F = F + self.G[i - 1]
606 if orth:
607 if i == 0:
608 apiece = 0
609 else:
610 Bn = np.dot(PIk, F)
611 apiece = Bn @ self.cov_a @ Bn.T
613 Bnbar = np.dot(np.kron(Ik, self.cum_effects[i]), self.H)
614 bpiece = (Bnbar @ self.cov_sig @ Bnbar.T) / self.T
616 covs[i] = apiece + bpiece
617 else:
618 if i == 0:
619 covs[i] = np.zeros((self.neqs**2, self.neqs**2))
620 continue
622 covs[i] = F @ self.cov_a @ F.T
624 return covs
626 def cum_errband_mc(self, orth=False, repl=1000,
627 signif=0.05, seed=None, burn=100):
628 """
629 IRF Monte Carlo integrated error bands of cumulative effect
630 """
631 model = self.model
632 periods = self.periods
633 return model.irf_errband_mc(orth=orth, repl=repl,
634 T=periods, signif=signif, seed=seed, burn=burn, cum=True)
636 def lr_effect_cov(self, orth=False):
637 """
638 Returns
639 -------
640 """
641 lre = self.lr_effects
642 Finfty = np.kron(np.tile(lre.T, self.lags), lre)
643 Ik = np.eye(self.neqs)
645 if orth:
646 Binf = np.dot(np.kron(self.P.T, np.eye(self.neqs)), Finfty)
647 Binfbar = np.dot(np.kron(Ik, lre), self.H)
649 return (Binf @ self.cov_a @ Binf.T +
650 Binfbar @ self.cov_sig @ Binfbar.T)
651 else:
652 return Finfty @ self.cov_a @ Finfty.T
654 def stderr(self, orth=False):
655 return np.array([tsa.unvec(np.sqrt(np.diag(c)))
656 for c in self.cov(orth=orth)])
658 def cum_effect_stderr(self, orth=False):
659 return np.array([tsa.unvec(np.sqrt(np.diag(c)))
660 for c in self.cum_effect_cov(orth=orth)])
662 def lr_effect_stderr(self, orth=False):
663 cov = self.lr_effect_cov(orth=orth)
664 return tsa.unvec(np.sqrt(np.diag(cov)))
666 def _empty_covm(self, periods):
667 return np.zeros((periods, self.neqs ** 2, self.neqs ** 2),
668 dtype=float)
670 @cache_readonly
671 def H(self):
672 k = self.neqs
673 Lk = tsa.elimination_matrix(k)
674 Kkk = tsa.commutation_matrix(k, k)
675 Ik = np.eye(k)
677 # B = Lk @ (np.eye(k**2) + commutation_matrix(k, k)) @ \
678 # np.kron(self.P, np.eye(k)) @ Lk.T
679 # return Lk.T @ L.inv(B)
681 B = Lk @ (np.kron(Ik, self.P) @ Kkk + np.kron(self.P, Ik)) @ Lk.T
683 return np.dot(Lk.T, L.inv(B))
685 def fevd_table(self):
686 raise NotImplementedError