Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/statsmodels/tsa/statespace/tools.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"""
2Statespace Tools
4Author: Chad Fulton
5License: Simplified-BSD
6"""
7import numpy as np
8from scipy.linalg import solve_sylvester
9import pandas as pd
11from statsmodels.compat.pandas import Appender
12from statsmodels.tools.data import _is_using_pandas
13from scipy.linalg.blas import find_best_blas_type
14from . import (_initialization, _representation, _kalman_filter,
15 _kalman_smoother, _simulation_smoother, _tools)
18compatibility_mode = False
19has_trmm = True
20prefix_dtype_map = {
21 's': np.float32, 'd': np.float64, 'c': np.complex64, 'z': np.complex128
22}
23prefix_initialization_map = {
24 's': _initialization.sInitialization,
25 'd': _initialization.dInitialization,
26 'c': _initialization.cInitialization,
27 'z': _initialization.zInitialization
28}
29prefix_statespace_map = {
30 's': _representation.sStatespace, 'd': _representation.dStatespace,
31 'c': _representation.cStatespace, 'z': _representation.zStatespace
32}
33prefix_kalman_filter_map = {
34 's': _kalman_filter.sKalmanFilter,
35 'd': _kalman_filter.dKalmanFilter,
36 'c': _kalman_filter.cKalmanFilter,
37 'z': _kalman_filter.zKalmanFilter
38}
39prefix_kalman_smoother_map = {
40 's': _kalman_smoother.sKalmanSmoother,
41 'd': _kalman_smoother.dKalmanSmoother,
42 'c': _kalman_smoother.cKalmanSmoother,
43 'z': _kalman_smoother.zKalmanSmoother
44}
45prefix_simulation_smoother_map = {
46 's': _simulation_smoother.sSimulationSmoother,
47 'd': _simulation_smoother.dSimulationSmoother,
48 'c': _simulation_smoother.cSimulationSmoother,
49 'z': _simulation_smoother.zSimulationSmoother
50}
51prefix_pacf_map = {
52 's': _tools._scompute_coefficients_from_multivariate_pacf,
53 'd': _tools._dcompute_coefficients_from_multivariate_pacf,
54 'c': _tools._ccompute_coefficients_from_multivariate_pacf,
55 'z': _tools._zcompute_coefficients_from_multivariate_pacf
56}
57prefix_sv_map = {
58 's': _tools._sconstrain_sv_less_than_one,
59 'd': _tools._dconstrain_sv_less_than_one,
60 'c': _tools._cconstrain_sv_less_than_one,
61 'z': _tools._zconstrain_sv_less_than_one
62}
63prefix_reorder_missing_matrix_map = {
64 's': _tools.sreorder_missing_matrix,
65 'd': _tools.dreorder_missing_matrix,
66 'c': _tools.creorder_missing_matrix,
67 'z': _tools.zreorder_missing_matrix
68}
69prefix_reorder_missing_vector_map = {
70 's': _tools.sreorder_missing_vector,
71 'd': _tools.dreorder_missing_vector,
72 'c': _tools.creorder_missing_vector,
73 'z': _tools.zreorder_missing_vector
74}
75prefix_copy_missing_matrix_map = {
76 's': _tools.scopy_missing_matrix,
77 'd': _tools.dcopy_missing_matrix,
78 'c': _tools.ccopy_missing_matrix,
79 'z': _tools.zcopy_missing_matrix
80}
81prefix_copy_missing_vector_map = {
82 's': _tools.scopy_missing_vector,
83 'd': _tools.dcopy_missing_vector,
84 'c': _tools.ccopy_missing_vector,
85 'z': _tools.zcopy_missing_vector
86}
87prefix_copy_index_matrix_map = {
88 's': _tools.scopy_index_matrix,
89 'd': _tools.dcopy_index_matrix,
90 'c': _tools.ccopy_index_matrix,
91 'z': _tools.zcopy_index_matrix
92}
93prefix_copy_index_vector_map = {
94 's': _tools.scopy_index_vector,
95 'd': _tools.dcopy_index_vector,
96 'c': _tools.ccopy_index_vector,
97 'z': _tools.zcopy_index_vector
98}
101def set_mode(compatibility=None):
102 if compatibility:
103 raise NotImplementedError('Compatibility mode is only available in'
104 ' statsmodels <= 0.9')
107def companion_matrix(polynomial):
108 r"""
109 Create a companion matrix
111 Parameters
112 ----------
113 polynomial : array_like or list
114 If an iterable, interpreted as the coefficients of the polynomial from
115 which to form the companion matrix. Polynomial coefficients are in
116 order of increasing degree, and may be either scalars (as in an AR(p)
117 model) or coefficient matrices (as in a VAR(p) model). If an integer,
118 it is interpreted as the size of a companion matrix of a scalar
119 polynomial, where the polynomial coefficients are initialized to zeros.
120 If a matrix polynomial is passed, :math:`C_0` may be set to the scalar
121 value 1 to indicate an identity matrix (doing so will improve the speed
122 of the companion matrix creation).
124 Returns
125 -------
126 companion_matrix : ndarray
128 Notes
129 -----
130 Given coefficients of a lag polynomial of the form:
132 .. math::
134 c(L) = c_0 + c_1 L + \dots + c_p L^p
136 returns a matrix of the form
138 .. math::
139 \begin{bmatrix}
140 \phi_1 & 1 & 0 & \cdots & 0 \\
141 \phi_2 & 0 & 1 & & 0 \\
142 \vdots & & & \ddots & 0 \\
143 & & & & 1 \\
144 \phi_n & 0 & 0 & \cdots & 0 \\
145 \end{bmatrix}
147 where some or all of the :math:`\phi_i` may be non-zero (if `polynomial` is
148 None, then all are equal to zero).
150 If the coefficients provided are scalars :math:`(c_0, c_1, \dots, c_p)`,
151 then the companion matrix is an :math:`n \times n` matrix formed with the
152 elements in the first column defined as
153 :math:`\phi_i = -\frac{c_i}{c_0}, i \in 1, \dots, p`.
155 If the coefficients provided are matrices :math:`(C_0, C_1, \dots, C_p)`,
156 each of shape :math:`(m, m)`, then the companion matrix is an
157 :math:`nm \times nm` matrix formed with the elements in the first column
158 defined as :math:`\phi_i = -C_0^{-1} C_i', i \in 1, \dots, p`.
160 It is important to understand the expected signs of the coefficients. A
161 typical AR(p) model is written as:
163 .. math::
164 y_t = a_1 y_{t-1} + \dots + a_p y_{t-p} + \varepsilon_t
166 This can be rewritten as:
168 .. math::
169 (1 - a_1 L - \dots - a_p L^p )y_t = \varepsilon_t \\
170 (1 + c_1 L + \dots + c_p L^p )y_t = \varepsilon_t \\
171 c(L) y_t = \varepsilon_t
173 The coefficients from this form are defined to be :math:`c_i = - a_i`, and
174 it is the :math:`c_i` coefficients that this function expects to be
175 provided.
176 """
177 identity_matrix = False
178 if isinstance(polynomial, (int, np.integer)):
179 # GH 5570, allow numpy integer types, but coerce to python int
180 n = int(polynomial)
181 m = 1
182 polynomial = None
183 else:
184 n = len(polynomial) - 1
186 if n < 1:
187 raise ValueError("Companion matrix polynomials must include at"
188 " least two terms.")
190 if isinstance(polynomial, list) or isinstance(polynomial, tuple):
191 try:
192 # Note: cannot use polynomial[0] because of the special
193 # behavior associated with matrix polynomials and the constant
194 # 1, see below.
195 m = len(polynomial[1])
196 except TypeError:
197 m = 1
199 # Check if we just have a scalar polynomial
200 if m == 1:
201 polynomial = np.asanyarray(polynomial)
202 # Check if 1 was passed as the first argument (indicating an
203 # identity matrix)
204 elif polynomial[0] == 1:
205 polynomial[0] = np.eye(m)
206 identity_matrix = True
207 else:
208 m = 1
209 polynomial = np.asanyarray(polynomial)
211 matrix = np.zeros((n * m, n * m), dtype=np.asanyarray(polynomial).dtype)
212 idx = np.diag_indices((n - 1) * m)
213 idx = (idx[0], idx[1] + m)
214 matrix[idx] = 1
215 if polynomial is not None and n > 0:
216 if m == 1:
217 matrix[:, 0] = -polynomial[1:] / polynomial[0]
218 elif identity_matrix:
219 for i in range(n):
220 matrix[i * m:(i + 1) * m, :m] = -polynomial[i+1].T
221 else:
222 inv = np.linalg.inv(polynomial[0])
223 for i in range(n):
224 matrix[i * m:(i + 1) * m, :m] = -np.dot(inv, polynomial[i+1]).T
225 return matrix
228def diff(series, k_diff=1, k_seasonal_diff=None, seasonal_periods=1):
229 r"""
230 Difference a series simply and/or seasonally along the zero-th axis.
232 Given a series (denoted :math:`y_t`), performs the differencing operation
234 .. math::
236 \Delta^d \Delta_s^D y_t
238 where :math:`d =` `diff`, :math:`s =` `seasonal_periods`,
239 :math:`D =` `seasonal\_diff`, and :math:`\Delta` is the difference
240 operator.
242 Parameters
243 ----------
244 series : array_like
245 The series to be differenced.
246 diff : int, optional
247 The number of simple differences to perform. Default is 1.
248 seasonal_diff : int or None, optional
249 The number of seasonal differences to perform. Default is no seasonal
250 differencing.
251 seasonal_periods : int, optional
252 The seasonal lag. Default is 1. Unused if there is no seasonal
253 differencing.
255 Returns
256 -------
257 differenced : ndarray
258 The differenced array.
259 """
260 pandas = _is_using_pandas(series, None)
261 differenced = np.asanyarray(series) if not pandas else series
263 # Seasonal differencing
264 if k_seasonal_diff is not None:
265 while k_seasonal_diff > 0:
266 if not pandas:
267 differenced = (differenced[seasonal_periods:] -
268 differenced[:-seasonal_periods])
269 else:
270 sdiffed = differenced.diff(seasonal_periods)
271 differenced = sdiffed[seasonal_periods:]
272 k_seasonal_diff -= 1
274 # Simple differencing
275 if not pandas:
276 differenced = np.diff(differenced, k_diff, axis=0)
277 else:
278 while k_diff > 0:
279 differenced = differenced.diff()[1:]
280 k_diff -= 1
281 return differenced
284def concat(series, axis=0, allow_mix=False):
285 """
286 Concatenate a set of series.
288 Parameters
289 ----------
290 series : iterable
291 An iterable of series to be concatenated
292 axis : int, optional
293 The axis along which to concatenate. Default is 1 (columns).
294 allow_mix : bool
295 Whether or not to allow a mix of pandas and non-pandas objects. Default
296 is False. If true, the returned object is an ndarray, and additional
297 pandas metadata (e.g. column names, indices, etc) is lost.
299 Returns
300 -------
301 concatenated : array or pd.DataFrame
302 The concatenated array. Will be a DataFrame if series are pandas
303 objects.
304 """
305 is_pandas = np.r_[[_is_using_pandas(s, None) for s in series]]
307 if np.all(is_pandas):
308 if isinstance(series[0], pd.DataFrame):
309 base_columns = series[0].columns
310 else:
311 base_columns = pd.Index([series[0].name])
312 for s in series[1:]:
313 if isinstance(s, pd.DataFrame):
314 s_columns = s.columns
315 else:
316 s_columns = pd.Index([s.name])
318 if axis == 0 and not base_columns.equals(s_columns):
319 raise ValueError('Columns must match to concatenate along'
320 ' rows.')
321 elif axis == 1 and not series[0].index.equals(s.index):
322 raise ValueError('Index must match to concatenate along'
323 ' columns.')
324 concatenated = pd.concat(series, axis=axis)
325 elif np.all(~is_pandas) or allow_mix:
326 concatenated = np.concatenate(series, axis=axis)
327 else:
328 raise ValueError('Attempted to concatenate Pandas objects with'
329 ' non-Pandas objects with `allow_mix=False`.')
331 return concatenated
334def is_invertible(polynomial, threshold=1 - 1e-10):
335 r"""
336 Determine if a polynomial is invertible.
338 Requires all roots of the polynomial lie inside the unit circle.
340 Parameters
341 ----------
342 polynomial : array_like or tuple, list
343 Coefficients of a polynomial, in order of increasing degree.
344 For example, `polynomial=[1, -0.5]` corresponds to the polynomial
345 :math:`1 - 0.5x` which has root :math:`2`. If it is a matrix
346 polynomial (in which case the coefficients are coefficient matrices),
347 a tuple or list of matrices should be passed.
348 threshold : number
349 Allowed threshold for `is_invertible` to return True. Default is 1.
351 Notes
352 -----
354 If the coefficients provided are scalars :math:`(c_0, c_1, \dots, c_n)`,
355 then the corresponding polynomial is :math:`c_0 + c_1 L + \dots + c_n L^n`.
358 If the coefficients provided are matrices :math:`(C_0, C_1, \dots, C_n)`,
359 then the corresponding polynomial is :math:`C_0 + C_1 L + \dots + C_n L^n`.
361 There are three equivalent methods of determining if the polynomial
362 represented by the coefficients is invertible:
364 The first method factorizes the polynomial into:
366 .. math::
368 C(L) & = c_0 + c_1 L + \dots + c_n L^n \\
369 & = constant (1 - \lambda_1 L)
370 (1 - \lambda_2 L) \dots (1 - \lambda_n L)
372 In order for :math:`C(L)` to be invertible, it must be that each factor
373 :math:`(1 - \lambda_i L)` is invertible; the condition is then that
374 :math:`|\lambda_i| < 1`, where :math:`\lambda_i` is a root of the
375 polynomial.
377 The second method factorizes the polynomial into:
379 .. math::
381 C(L) & = c_0 + c_1 L + \dots + c_n L^n \\
382 & = constant (L - \zeta_1) (L - \zeta_2) \dots (L - \zeta_3)
384 The condition is now :math:`|\zeta_i| > 1`, where :math:`\zeta_i` is a root
385 of the polynomial with reversed coefficients and
386 :math:`\lambda_i = \frac{1}{\zeta_i}`.
388 Finally, a companion matrix can be formed using the coefficients of the
389 polynomial. Then the eigenvalues of that matrix give the roots of the
390 polynomial. This last method is the one actually used.
392 See Also
393 --------
394 companion_matrix
395 """
396 # First method:
397 # np.all(np.abs(np.roots(np.r_[1, params])) < 1)
398 # Second method:
399 # np.all(np.abs(np.roots(np.r_[1, params][::-1])) > 1)
400 # Final method:
401 eigvals = np.linalg.eigvals(companion_matrix(polynomial))
402 return np.all(np.abs(eigvals) < threshold)
405def solve_discrete_lyapunov(a, q, complex_step=False):
406 r"""
407 Solves the discrete Lyapunov equation using a bilinear transformation.
409 Notes
410 -----
411 This is a modification of the version in Scipy (see
412 https://github.com/scipy/scipy/blob/master/scipy/linalg/_solvers.py)
413 which allows passing through the complex numbers in the matrix a
414 (usually the transition matrix) in order to allow complex step
415 differentiation.
416 """
417 eye = np.eye(a.shape[0], dtype=a.dtype)
418 if not complex_step:
419 aH = a.conj().transpose()
420 aHI_inv = np.linalg.inv(aH + eye)
421 b = np.dot(aH - eye, aHI_inv)
422 c = 2*np.dot(np.dot(np.linalg.inv(a + eye), q), aHI_inv)
423 return solve_sylvester(b.conj().transpose(), b, -c)
424 else:
425 aH = a.transpose()
426 aHI_inv = np.linalg.inv(aH + eye)
427 b = np.dot(aH - eye, aHI_inv)
428 c = 2*np.dot(np.dot(np.linalg.inv(a + eye), q), aHI_inv)
429 return solve_sylvester(b.transpose(), b, -c)
432def constrain_stationary_univariate(unconstrained):
433 """
434 Transform unconstrained parameters used by the optimizer to constrained
435 parameters used in likelihood evaluation
437 Parameters
438 ----------
439 unconstrained : ndarray
440 Unconstrained parameters used by the optimizer, to be transformed to
441 stationary coefficients of, e.g., an autoregressive or moving average
442 component.
444 Returns
445 -------
446 constrained : ndarray
447 Constrained parameters of, e.g., an autoregressive or moving average
448 component, to be transformed to arbitrary parameters used by the
449 optimizer.
451 References
452 ----------
453 .. [*] Monahan, John F. 1984.
454 "A Note on Enforcing Stationarity in
455 Autoregressive-moving Average Models."
456 Biometrika 71 (2) (August 1): 403-404.
457 """
459 n = unconstrained.shape[0]
460 y = np.zeros((n, n), dtype=unconstrained.dtype)
461 r = unconstrained/((1 + unconstrained**2)**0.5)
462 for k in range(n):
463 for i in range(k):
464 y[k, i] = y[k - 1, i] + r[k] * y[k - 1, k - i - 1]
465 y[k, k] = r[k]
466 return -y[n - 1, :]
469def unconstrain_stationary_univariate(constrained):
470 """
471 Transform constrained parameters used in likelihood evaluation
472 to unconstrained parameters used by the optimizer
474 Parameters
475 ----------
476 constrained : ndarray
477 Constrained parameters of, e.g., an autoregressive or moving average
478 component, to be transformed to arbitrary parameters used by the
479 optimizer.
481 Returns
482 -------
483 unconstrained : ndarray
484 Unconstrained parameters used by the optimizer, to be transformed to
485 stationary coefficients of, e.g., an autoregressive or moving average
486 component.
488 References
489 ----------
490 .. [*] Monahan, John F. 1984.
491 "A Note on Enforcing Stationarity in
492 Autoregressive-moving Average Models."
493 Biometrika 71 (2) (August 1): 403-404.
494 """
495 n = constrained.shape[0]
496 y = np.zeros((n, n), dtype=constrained.dtype)
497 y[n-1:] = -constrained
498 for k in range(n-1, 0, -1):
499 for i in range(k):
500 y[k-1, i] = (y[k, i] - y[k, k]*y[k, k-i-1]) / (1 - y[k, k]**2)
501 r = y.diagonal()
502 x = r / ((1 - r**2)**0.5)
503 return x
506def _constrain_sv_less_than_one_python(unconstrained, order=None,
507 k_endog=None):
508 """
509 Transform arbitrary matrices to matrices with singular values less than
510 one.
512 Parameters
513 ----------
514 unconstrained : list
515 Arbitrary matrices. Should be a list of length `order`, where each
516 element is an array sized `k_endog` x `k_endog`.
517 order : int, optional
518 The order of the autoregression.
519 k_endog : int, optional
520 The dimension of the data vector.
522 Returns
523 -------
524 constrained : list
525 Partial autocorrelation matrices. Should be a list of length
526 `order`, where each element is an array sized `k_endog` x `k_endog`.
528 Notes
529 -----
530 Corresponds to Lemma 2.2 in Ansley and Kohn (1986). See
531 `constrain_stationary_multivariate` for more details.
533 There is a Cython implementation of this function that can be much faster,
534 but which requires SciPy 0.14.0 or greater. See
535 `constrain_stationary_multivariate` for details.
536 """
538 from scipy import linalg
540 constrained = [] # P_s, s = 1, ..., p
541 if order is None:
542 order = len(unconstrained)
543 if k_endog is None:
544 k_endog = unconstrained[0].shape[0]
546 eye = np.eye(k_endog)
547 for i in range(order):
548 A = unconstrained[i]
549 B, lower = linalg.cho_factor(eye + np.dot(A, A.T), lower=True)
550 constrained.append(linalg.solve_triangular(B, A, lower=lower))
551 return constrained
554def _compute_coefficients_from_multivariate_pacf_python(
555 partial_autocorrelations, error_variance, transform_variance=False,
556 order=None, k_endog=None):
557 """
558 Transform matrices with singular values less than one to matrices
559 corresponding to a stationary (or invertible) process.
561 Parameters
562 ----------
563 partial_autocorrelations : list
564 Partial autocorrelation matrices. Should be a list of length `order`,
565 where each element is an array sized `k_endog` x `k_endog`.
566 error_variance : ndarray
567 The variance / covariance matrix of the error term. Should be sized
568 `k_endog` x `k_endog`. This is used as input in the algorithm even if
569 is not transformed by it (when `transform_variance` is False). The
570 error term variance is required input when transformation is used
571 either to force an autoregressive component to be stationary or to
572 force a moving average component to be invertible.
573 transform_variance : bool, optional
574 Whether or not to transform the error variance term. This option is
575 not typically used, and the default is False.
576 order : int, optional
577 The order of the autoregression.
578 k_endog : int, optional
579 The dimension of the data vector.
581 Returns
582 -------
583 coefficient_matrices : list
584 Transformed coefficient matrices leading to a stationary VAR
585 representation.
587 Notes
588 -----
589 Corresponds to Lemma 2.1 in Ansley and Kohn (1986). See
590 `constrain_stationary_multivariate` for more details.
592 There is a Cython implementation of this function that can be much faster,
593 but which requires SciPy 0.14.0 or greater. See
594 `constrain_stationary_multivariate` for details.
595 """
596 from scipy import linalg
598 if order is None:
599 order = len(partial_autocorrelations)
600 if k_endog is None:
601 k_endog = partial_autocorrelations[0].shape[0]
603 # If we want to keep the provided variance but with the constrained
604 # coefficient matrices, we need to make a copy here, and then after the
605 # main loop we will transform the coefficients to match the passed variance
606 if not transform_variance:
607 initial_variance = error_variance
608 # Need to make the input variance large enough that the recursions
609 # do not lead to zero-matrices due to roundoff error, which would case
610 # exceptions from the Cholesky decompositions.
611 # Note that this will still not always ensure positive definiteness,
612 # and for k_endog, order large enough an exception may still be raised
613 error_variance = np.eye(k_endog) * (order + k_endog)**10
615 forward_variances = [error_variance] # \Sigma_s
616 backward_variances = [error_variance] # \Sigma_s^*, s = 0, ..., p
617 autocovariances = [error_variance] # \Gamma_s
618 # \phi_{s,k}, s = 1, ..., p
619 # k = 1, ..., s+1
620 forwards = []
621 # \phi_{s,k}^*
622 backwards = []
624 error_variance_factor = linalg.cholesky(error_variance, lower=True)
626 forward_factors = [error_variance_factor]
627 backward_factors = [error_variance_factor]
629 # We fill in the entries as follows:
630 # [1,1]
631 # [2,2], [2,1]
632 # [3,3], [3,1], [3,2]
633 # ...
634 # [p,p], [p,1], ..., [p,p-1]
635 # the last row, correctly ordered, is then used as the coefficients
636 for s in range(order): # s = 0, ..., p-1
637 prev_forwards = forwards
638 prev_backwards = backwards
639 forwards = []
640 backwards = []
642 # Create the "last" (k = s+1) matrix
643 # Note: this is for k = s+1. However, below we then have to fill
644 # in for k = 1, ..., s in order.
645 # P L*^{-1} = x
646 # x L* = P
647 # L*' x' = P'
648 forwards.append(
649 linalg.solve_triangular(
650 backward_factors[s], partial_autocorrelations[s].T,
651 lower=True, trans='T'))
652 forwards[0] = np.dot(forward_factors[s], forwards[0].T)
654 # P' L^{-1} = x
655 # x L = P'
656 # L' x' = P
657 backwards.append(
658 linalg.solve_triangular(
659 forward_factors[s], partial_autocorrelations[s],
660 lower=True, trans='T'))
661 backwards[0] = np.dot(backward_factors[s], backwards[0].T)
663 # Update the variance
664 # Note: if s >= 1, this will be further updated in the for loop
665 # below
666 # Also, this calculation will be re-used in the forward variance
667 tmp = np.dot(forwards[0], backward_variances[s])
668 autocovariances.append(tmp.copy().T)
670 # Create the remaining k = 1, ..., s matrices,
671 # only has an effect if s >= 1
672 for k in range(s):
673 forwards.insert(k, prev_forwards[k] - np.dot(
674 forwards[-1], prev_backwards[s-(k+1)]))
676 backwards.insert(k, prev_backwards[k] - np.dot(
677 backwards[-1], prev_forwards[s-(k+1)]))
679 autocovariances[s+1] += np.dot(autocovariances[k+1],
680 prev_forwards[s-(k+1)].T)
682 # Create forward and backwards variances
683 forward_variances.append(
684 forward_variances[s] - np.dot(tmp, forwards[s].T)
685 )
686 backward_variances.append(
687 backward_variances[s] -
688 np.dot(
689 np.dot(backwards[s], forward_variances[s]),
690 backwards[s].T
691 )
692 )
694 # Cholesky factors
695 forward_factors.append(
696 linalg.cholesky(forward_variances[s+1], lower=True)
697 )
698 backward_factors.append(
699 linalg.cholesky(backward_variances[s+1], lower=True)
700 )
702 # If we do not want to use the transformed variance, we need to
703 # adjust the constrained matrices, as presented in Lemma 2.3, see above
704 variance = forward_variances[-1]
705 if not transform_variance:
706 # Here, we need to construct T such that:
707 # variance = T * initial_variance * T'
708 # To do that, consider the Cholesky of variance (L) and
709 # input_variance (M) to get:
710 # L L' = T M M' T' = (TM) (TM)'
711 # => L = T M
712 # => L M^{-1} = T
713 initial_variance_factor = np.linalg.cholesky(initial_variance)
714 transformed_variance_factor = np.linalg.cholesky(variance)
715 transform = np.dot(initial_variance_factor,
716 np.linalg.inv(transformed_variance_factor))
717 inv_transform = np.linalg.inv(transform)
719 for i in range(order):
720 forwards[i] = (
721 np.dot(np.dot(transform, forwards[i]), inv_transform)
722 )
724 return forwards, variance
727def constrain_stationary_multivariate_python(unconstrained, error_variance,
728 transform_variance=False,
729 prefix=None):
730 r"""
731 Transform unconstrained parameters used by the optimizer to constrained
732 parameters used in likelihood evaluation for a vector autoregression.
734 Parameters
735 ----------
736 unconstrained : array or list
737 Arbitrary matrices to be transformed to stationary coefficient matrices
738 of the VAR. If a list, should be a list of length `order`, where each
739 element is an array sized `k_endog` x `k_endog`. If an array, should be
740 the matrices horizontally concatenated and sized
741 `k_endog` x `k_endog * order`.
742 error_variance : ndarray
743 The variance / covariance matrix of the error term. Should be sized
744 `k_endog` x `k_endog`. This is used as input in the algorithm even if
745 is not transformed by it (when `transform_variance` is False). The
746 error term variance is required input when transformation is used
747 either to force an autoregressive component to be stationary or to
748 force a moving average component to be invertible.
749 transform_variance : bool, optional
750 Whether or not to transform the error variance term. This option is
751 not typically used, and the default is False.
752 prefix : {'s','d','c','z'}, optional
753 The appropriate BLAS prefix to use for the passed datatypes. Only
754 use if absolutely sure that the prefix is correct or an error will
755 result.
757 Returns
758 -------
759 constrained : array or list
760 Transformed coefficient matrices leading to a stationary VAR
761 representation. Will match the type of the passed `unconstrained`
762 variable (so if a list was passed, a list will be returned).
764 Notes
765 -----
766 In the notation of [1]_, the arguments `(variance, unconstrained)` are
767 written as :math:`(\Sigma, A_1, \dots, A_p)`, where :math:`p` is the order
768 of the vector autoregression, and is here determined by the length of
769 the `unconstrained` argument.
771 There are two steps in the constraining algorithm.
773 First, :math:`(A_1, \dots, A_p)` are transformed into
774 :math:`(P_1, \dots, P_p)` via Lemma 2.2 of [1]_.
776 Second, :math:`(\Sigma, P_1, \dots, P_p)` are transformed into
777 :math:`(\Sigma, \phi_1, \dots, \phi_p)` via Lemmas 2.1 and 2.3 of [1]_.
779 If `transform_variance=True`, then only Lemma 2.1 is applied in the second
780 step.
782 While this function can be used even in the univariate case, it is much
783 slower, so in that case `constrain_stationary_univariate` is preferred.
785 References
786 ----------
787 .. [1] Ansley, Craig F., and Robert Kohn. 1986.
788 "A Note on Reparameterizing a Vector Autoregressive Moving Average Model
789 to Enforce Stationarity."
790 Journal of Statistical Computation and Simulation 24 (2): 99-106.
791 .. [*] Ansley, Craig F, and Paul Newbold. 1979.
792 "Multivariate Partial Autocorrelations."
793 In Proceedings of the Business and Economic Statistics Section, 349-53.
794 American Statistical Association
795 """
797 use_list = type(unconstrained) == list
798 if not use_list:
799 k_endog, order = unconstrained.shape
800 order //= k_endog
802 unconstrained = [
803 unconstrained[:k_endog, i*k_endog:(i+1)*k_endog]
804 for i in range(order)
805 ]
807 order = len(unconstrained)
808 k_endog = unconstrained[0].shape[0]
810 # Step 1: convert from arbitrary matrices to those with singular values
811 # less than one.
812 sv_constrained = _constrain_sv_less_than_one_python(
813 unconstrained, order, k_endog)
815 # Step 2: convert matrices from our "partial autocorrelation matrix" space
816 # (matrices with singular values less than one) to the space of stationary
817 # coefficient matrices
818 constrained, var = _compute_coefficients_from_multivariate_pacf_python(
819 sv_constrained, error_variance, transform_variance, order, k_endog)
821 if not use_list:
822 constrained = np.concatenate(constrained, axis=1).reshape(
823 k_endog, k_endog * order)
825 return constrained, var
828@Appender(constrain_stationary_multivariate_python.__doc__)
829def constrain_stationary_multivariate(unconstrained, variance,
830 transform_variance=False,
831 prefix=None):
833 use_list = type(unconstrained) == list
834 if use_list:
835 unconstrained = np.concatenate(unconstrained, axis=1)
837 k_endog, order = unconstrained.shape
838 order //= k_endog
840 if order < 1:
841 raise ValueError('Must have order at least 1')
842 if k_endog < 1:
843 raise ValueError('Must have at least 1 endogenous variable')
845 if prefix is None:
846 prefix, dtype, _ = find_best_blas_type(
847 [unconstrained, variance])
848 dtype = prefix_dtype_map[prefix]
850 unconstrained = np.asfortranarray(unconstrained, dtype=dtype)
851 variance = np.asfortranarray(variance, dtype=dtype)
853 # Step 1: convert from arbitrary matrices to those with singular values
854 # less than one.
855 # sv_constrained = _constrain_sv_less_than_one(unconstrained, order,
856 # k_endog, prefix)
857 sv_constrained = prefix_sv_map[prefix](unconstrained, order, k_endog)
859 # Step 2: convert matrices from our "partial autocorrelation matrix"
860 # space (matrices with singular values less than one) to the space of
861 # stationary coefficient matrices
862 constrained, variance = prefix_pacf_map[prefix](
863 sv_constrained, variance, transform_variance, order, k_endog)
865 constrained = np.array(constrained, dtype=dtype)
866 variance = np.array(variance, dtype=dtype)
868 if use_list:
869 constrained = [
870 constrained[:k_endog, i*k_endog:(i+1)*k_endog]
871 for i in range(order)
872 ]
874 return constrained, variance
877def _unconstrain_sv_less_than_one(constrained, order=None, k_endog=None):
878 """
879 Transform matrices with singular values less than one to arbitrary
880 matrices.
882 Parameters
883 ----------
884 constrained : list
885 The partial autocorrelation matrices. Should be a list of length
886 `order`, where each element is an array sized `k_endog` x `k_endog`.
887 order : int, optional
888 The order of the autoregression.
889 k_endog : int, optional
890 The dimension of the data vector.
892 Returns
893 -------
894 unconstrained : list
895 Unconstrained matrices. A list of length `order`, where each element is
896 an array sized `k_endog` x `k_endog`.
898 Notes
899 -----
900 Corresponds to the inverse of Lemma 2.2 in Ansley and Kohn (1986). See
901 `unconstrain_stationary_multivariate` for more details.
902 """
903 from scipy import linalg
905 unconstrained = [] # A_s, s = 1, ..., p
906 if order is None:
907 order = len(constrained)
908 if k_endog is None:
909 k_endog = constrained[0].shape[0]
911 eye = np.eye(k_endog)
912 for i in range(order):
913 P = constrained[i]
914 # B^{-1} B^{-1}' = I - P P'
915 B_inv, lower = linalg.cho_factor(eye - np.dot(P, P.T), lower=True)
916 # A = BP
917 # B^{-1} A = P
918 unconstrained.append(linalg.solve_triangular(B_inv, P, lower=lower))
919 return unconstrained
922def _compute_multivariate_sample_acovf(endog, maxlag):
923 r"""
924 Computer multivariate sample autocovariances
926 Parameters
927 ----------
928 endog : array_like
929 Sample data on which to compute sample autocovariances. Shaped
930 `nobs` x `k_endog`.
932 Returns
933 -------
934 sample_autocovariances : list
935 A list of the first `maxlag` sample autocovariance matrices. Each
936 matrix is shaped `k_endog` x `k_endog`.
938 Notes
939 -----
940 This function computes the forward sample autocovariances:
942 .. math::
944 \hat \Gamma(s) = \frac{1}{n} \sum_{t=1}^{n-s}
945 (Z_t - \bar Z) (Z_{t+s} - \bar Z)'
947 See page 353 of Wei (1990). This function is primarily implemented for
948 checking the partial autocorrelation functions below, and so is quite slow.
950 References
951 ----------
952 .. [*] Wei, William. 1990.
953 Time Series Analysis : Univariate and Multivariate Methods.
954 Boston: Pearson.
955 """
956 # Get the (demeaned) data as an array
957 endog = np.array(endog)
958 if endog.ndim == 1:
959 endog = endog[:, np.newaxis]
960 endog -= np.mean(endog, axis=0)
962 # Dimensions
963 nobs, k_endog = endog.shape
965 sample_autocovariances = []
966 for s in range(maxlag + 1):
967 sample_autocovariances.append(np.zeros((k_endog, k_endog)))
968 for t in range(nobs - s):
969 sample_autocovariances[s] += np.outer(endog[t], endog[t+s])
970 sample_autocovariances[s] /= nobs
972 return sample_autocovariances
975def _compute_multivariate_acovf_from_coefficients(
976 coefficients, error_variance, maxlag=None,
977 forward_autocovariances=False):
978 r"""
979 Compute multivariate autocovariances from vector autoregression coefficient
980 matrices
982 Parameters
983 ----------
984 coefficients : array or list
985 The coefficients matrices. If a list, should be a list of length
986 `order`, where each element is an array sized `k_endog` x `k_endog`. If
987 an array, should be the coefficient matrices horizontally concatenated
988 and sized `k_endog` x `k_endog * order`.
989 error_variance : ndarray
990 The variance / covariance matrix of the error term. Should be sized
991 `k_endog` x `k_endog`.
992 maxlag : int, optional
993 The maximum autocovariance to compute. Default is `order`-1. Can be
994 zero, in which case it returns the variance.
995 forward_autocovariances : bool, optional
996 Whether or not to compute forward autocovariances
997 :math:`E(y_t y_{t+j}')`. Default is False, so that backward
998 autocovariances :math:`E(y_t y_{t-j}')` are returned.
1000 Returns
1001 -------
1002 autocovariances : list
1003 A list of the first `maxlag` autocovariance matrices. Each matrix is
1004 shaped `k_endog` x `k_endog`.
1006 Notes
1007 -----
1008 Computes
1010 .. math::
1012 \Gamma(j) = E(y_t y_{t-j}')
1014 for j = 1, ..., `maxlag`, unless `forward_autocovariances` is specified,
1015 in which case it computes:
1017 .. math::
1019 E(y_t y_{t+j}') = \Gamma(j)'
1021 Coefficients are assumed to be provided from the VAR model:
1023 .. math::
1024 y_t = A_1 y_{t-1} + \dots + A_p y_{t-p} + \varepsilon_t
1026 Autocovariances are calculated by solving the associated discrete Lyapunov
1027 equation of the state space representation of the VAR process.
1028 """
1029 from scipy import linalg
1031 # Convert coefficients to a list of matrices, for use in
1032 # `companion_matrix`; get dimensions
1033 if type(coefficients) == list:
1034 order = len(coefficients)
1035 k_endog = coefficients[0].shape[0]
1036 else:
1037 k_endog, order = coefficients.shape
1038 order //= k_endog
1040 coefficients = [
1041 coefficients[:k_endog, i*k_endog:(i+1)*k_endog]
1042 for i in range(order)
1043 ]
1045 if maxlag is None:
1046 maxlag = order-1
1048 # Start with VAR(p): w_{t+1} = phi_1 w_t + ... + phi_p w_{t-p+1} + u_{t+1}
1049 # Then stack the VAR(p) into a VAR(1) in companion matrix form:
1050 # z_{t+1} = F z_t + v_t
1051 companion = companion_matrix(
1052 [1] + [-np.squeeze(coefficients[i]) for i in range(order)]
1053 ).T
1055 # Compute the error variance matrix for the stacked form: E v_t v_t'
1056 selected_variance = np.zeros(companion.shape)
1057 selected_variance[:k_endog, :k_endog] = error_variance
1059 # Compute the unconditional variance of z_t: E z_t z_t'
1060 stacked_cov = linalg.solve_discrete_lyapunov(companion, selected_variance)
1062 # The first (block) row of the variance of z_t gives the first p-1
1063 # autocovariances of w_t: \Gamma_i = E w_t w_t+i with \Gamma_0 = Var(w_t)
1064 # Note: these are okay, checked against ArmaProcess
1065 autocovariances = [
1066 stacked_cov[:k_endog, i*k_endog:(i+1)*k_endog]
1067 for i in range(min(order, maxlag+1))
1068 ]
1070 for i in range(maxlag - (order-1)):
1071 stacked_cov = np.dot(companion, stacked_cov)
1072 autocovariances += [
1073 stacked_cov[:k_endog, -k_endog:]
1074 ]
1076 if forward_autocovariances:
1077 for i in range(len(autocovariances)):
1078 autocovariances[i] = autocovariances[i].T
1080 return autocovariances
1083def _compute_multivariate_sample_pacf(endog, maxlag):
1084 """
1085 Computer multivariate sample partial autocorrelations
1087 Parameters
1088 ----------
1089 endog : array_like
1090 Sample data on which to compute sample autocovariances. Shaped
1091 `nobs` x `k_endog`.
1092 maxlag : int
1093 Maximum lag for which to calculate sample partial autocorrelations.
1095 Returns
1096 -------
1097 sample_pacf : list
1098 A list of the first `maxlag` sample partial autocorrelation matrices.
1099 Each matrix is shaped `k_endog` x `k_endog`.
1100 """
1101 sample_autocovariances = _compute_multivariate_sample_acovf(endog, maxlag)
1103 return _compute_multivariate_pacf_from_autocovariances(
1104 sample_autocovariances)
1107def _compute_multivariate_pacf_from_autocovariances(autocovariances,
1108 order=None, k_endog=None):
1109 """
1110 Compute multivariate partial autocorrelations from autocovariances.
1112 Parameters
1113 ----------
1114 autocovariances : list
1115 Autocorrelations matrices. Should be a list of length `order` + 1,
1116 where each element is an array sized `k_endog` x `k_endog`.
1117 order : int, optional
1118 The order of the autoregression.
1119 k_endog : int, optional
1120 The dimension of the data vector.
1122 Returns
1123 -------
1124 pacf : list
1125 List of first `order` multivariate partial autocorrelations.
1127 Notes
1128 -----
1129 Note that this computes multivariate partial autocorrelations.
1131 Corresponds to the inverse of Lemma 2.1 in Ansley and Kohn (1986). See
1132 `unconstrain_stationary_multivariate` for more details.
1134 Notes
1135 -----
1136 Computes sample partial autocorrelations if sample autocovariances are
1137 given.
1138 """
1139 from scipy import linalg
1141 if order is None:
1142 order = len(autocovariances)-1
1143 if k_endog is None:
1144 k_endog = autocovariances[0].shape[0]
1146 # Now apply the Ansley and Kohn (1986) algorithm, except that instead of
1147 # calculating phi_{s+1, s+1} = L_s P_{s+1} {L_s^*}^{-1} (which requires
1148 # the partial autocorrelation P_{s+1} which is what we're trying to
1149 # calculate here), we calculate it as in Ansley and Newbold (1979), using
1150 # the autocovariances \Gamma_s and the forwards and backwards residual
1151 # variances \Sigma_s, \Sigma_s^*:
1152 # phi_{s+1, s+1} = [ \Gamma_{s+1}' - \phi_{s,1} \Gamma_s' - ... -
1153 # \phi_{s,s} \Gamma_1' ] {\Sigma_s^*}^{-1}
1155 # Forward and backward variances
1156 forward_variances = [] # \Sigma_s
1157 backward_variances = [] # \Sigma_s^*, s = 0, ..., p
1158 # \phi_{s,k}, s = 1, ..., p
1159 # k = 1, ..., s+1
1160 forwards = []
1161 # \phi_{s,k}^*
1162 backwards = []
1164 forward_factors = [] # L_s
1165 backward_factors = [] # L_s^*, s = 0, ..., p
1167 # Ultimately we want to construct the partial autocorrelation matrices
1168 # Note that this is "1-indexed" in the sense that it stores P_1, ... P_p
1169 # rather than starting with P_0.
1170 partial_autocorrelations = []
1172 # We fill in the entries of phi_{s,k} as follows:
1173 # [1,1]
1174 # [2,2], [2,1]
1175 # [3,3], [3,1], [3,2]
1176 # ...
1177 # [p,p], [p,1], ..., [p,p-1]
1178 # the last row, correctly ordered, should be the same as the coefficient
1179 # matrices provided in the argument `constrained`
1180 for s in range(order): # s = 0, ..., p-1
1181 prev_forwards = list(forwards)
1182 prev_backwards = list(backwards)
1183 forwards = []
1184 backwards = []
1186 # Create forward and backwards variances Sigma_s, Sigma*_s
1187 forward_variance = autocovariances[0].copy()
1188 backward_variance = autocovariances[0].T.copy()
1190 for k in range(s):
1191 forward_variance -= np.dot(prev_forwards[k],
1192 autocovariances[k+1])
1193 backward_variance -= np.dot(prev_backwards[k],
1194 autocovariances[k+1].T)
1196 forward_variances.append(forward_variance)
1197 backward_variances.append(backward_variance)
1199 # Cholesky factors
1200 forward_factors.append(
1201 linalg.cholesky(forward_variances[s], lower=True)
1202 )
1203 backward_factors.append(
1204 linalg.cholesky(backward_variances[s], lower=True)
1205 )
1207 # Create the intermediate sum term
1208 if s == 0:
1209 # phi_11 = \Gamma_1' \Gamma_0^{-1}
1210 # phi_11 \Gamma_0 = \Gamma_1'
1211 # \Gamma_0 phi_11' = \Gamma_1
1212 forwards.append(linalg.cho_solve(
1213 (forward_factors[0], True), autocovariances[1]).T)
1214 # backwards.append(forwards[-1])
1215 # phi_11_star = \Gamma_1 \Gamma_0^{-1}
1216 # phi_11_star \Gamma_0 = \Gamma_1
1217 # \Gamma_0 phi_11_star' = \Gamma_1'
1218 backwards.append(linalg.cho_solve(
1219 (backward_factors[0], True), autocovariances[1].T).T)
1220 else:
1221 # G := \Gamma_{s+1}' -
1222 # \phi_{s,1} \Gamma_s' - .. - \phi_{s,s} \Gamma_1'
1223 tmp_sum = autocovariances[s+1].T.copy()
1225 for k in range(s):
1226 tmp_sum -= np.dot(prev_forwards[k], autocovariances[s-k].T)
1228 # Create the "last" (k = s+1) matrix
1229 # Note: this is for k = s+1. However, below we then have to
1230 # fill in for k = 1, ..., s in order.
1231 # phi = G Sigma*^{-1}
1232 # phi Sigma* = G
1233 # Sigma*' phi' = G'
1234 # Sigma* phi' = G'
1235 # (because Sigma* is symmetric)
1236 forwards.append(linalg.cho_solve(
1237 (backward_factors[s], True), tmp_sum.T).T)
1239 # phi = G' Sigma^{-1}
1240 # phi Sigma = G'
1241 # Sigma' phi' = G
1242 # Sigma phi' = G
1243 # (because Sigma is symmetric)
1244 backwards.append(linalg.cho_solve(
1245 (forward_factors[s], True), tmp_sum).T)
1247 # Create the remaining k = 1, ..., s matrices,
1248 # only has an effect if s >= 1
1249 for k in range(s):
1250 forwards.insert(k, prev_forwards[k] - np.dot(
1251 forwards[-1], prev_backwards[s-(k+1)]))
1252 backwards.insert(k, prev_backwards[k] - np.dot(
1253 backwards[-1], prev_forwards[s-(k+1)]))
1255 # Partial autocorrelation matrix: P_{s+1}
1256 # P = L^{-1} phi L*
1257 # L P = (phi L*)
1258 partial_autocorrelations.append(linalg.solve_triangular(
1259 forward_factors[s], np.dot(forwards[s], backward_factors[s]),
1260 lower=True))
1262 return partial_autocorrelations
1265def _compute_multivariate_pacf_from_coefficients(constrained, error_variance,
1266 order=None, k_endog=None):
1267 r"""
1268 Transform matrices corresponding to a stationary (or invertible) process
1269 to matrices with singular values less than one.
1271 Parameters
1272 ----------
1273 constrained : array or list
1274 The coefficients matrices. If a list, should be a list of length
1275 `order`, where each element is an array sized `k_endog` x `k_endog`. If
1276 an array, should be the coefficient matrices horizontally concatenated
1277 and sized `k_endog` x `k_endog * order`.
1278 error_variance : ndarray
1279 The variance / covariance matrix of the error term. Should be sized
1280 `k_endog` x `k_endog`.
1281 order : int, optional
1282 The order of the autoregression.
1283 k_endog : int, optional
1284 The dimension of the data vector.
1286 Returns
1287 -------
1288 pacf : list
1289 List of first `order` multivariate partial autocorrelations.
1291 Notes
1292 -----
1293 Note that this computes multivariate partial autocorrelations.
1295 Corresponds to the inverse of Lemma 2.1 in Ansley and Kohn (1986). See
1296 `unconstrain_stationary_multivariate` for more details.
1298 Notes
1299 -----
1301 Coefficients are assumed to be provided from the VAR model:
1303 .. math::
1304 y_t = A_1 y_{t-1} + \dots + A_p y_{t-p} + \varepsilon_t
1305 """
1307 if type(constrained) == list:
1308 order = len(constrained)
1309 k_endog = constrained[0].shape[0]
1310 else:
1311 k_endog, order = constrained.shape
1312 order //= k_endog
1314 # Get autocovariances for the process; these are defined to be
1315 # E z_t z_{t-j}'
1316 # However, we want E z_t z_{t+j}' = (E z_t z_{t-j}')'
1317 _acovf = _compute_multivariate_acovf_from_coefficients
1319 autocovariances = [
1320 autocovariance.T for autocovariance in
1321 _acovf(constrained, error_variance, maxlag=order)]
1323 return _compute_multivariate_pacf_from_autocovariances(autocovariances)
1326def unconstrain_stationary_multivariate(constrained, error_variance):
1327 """
1328 Transform constrained parameters used in likelihood evaluation
1329 to unconstrained parameters used by the optimizer
1331 Parameters
1332 ----------
1333 constrained : array or list
1334 Constrained parameters of, e.g., an autoregressive or moving average
1335 component, to be transformed to arbitrary parameters used by the
1336 optimizer. If a list, should be a list of length `order`, where each
1337 element is an array sized `k_endog` x `k_endog`. If an array, should be
1338 the coefficient matrices horizontally concatenated and sized
1339 `k_endog` x `k_endog * order`.
1340 error_variance : ndarray
1341 The variance / covariance matrix of the error term. Should be sized
1342 `k_endog` x `k_endog`. This is used as input in the algorithm even if
1343 is not transformed by it (when `transform_variance` is False).
1345 Returns
1346 -------
1347 unconstrained : ndarray
1348 Unconstrained parameters used by the optimizer, to be transformed to
1349 stationary coefficients of, e.g., an autoregressive or moving average
1350 component. Will match the type of the passed `constrained`
1351 variable (so if a list was passed, a list will be returned).
1353 Notes
1354 -----
1355 Uses the list representation internally, even if an array is passed.
1357 References
1358 ----------
1359 .. [*] Ansley, Craig F., and Robert Kohn. 1986.
1360 "A Note on Reparameterizing a Vector Autoregressive Moving Average Model
1361 to Enforce Stationarity."
1362 Journal of Statistical Computation and Simulation 24 (2): 99-106.
1363 """
1364 use_list = type(constrained) == list
1365 if not use_list:
1366 k_endog, order = constrained.shape
1367 order //= k_endog
1369 constrained = [
1370 constrained[:k_endog, i*k_endog:(i+1)*k_endog]
1371 for i in range(order)
1372 ]
1373 else:
1374 order = len(constrained)
1375 k_endog = constrained[0].shape[0]
1377 # Step 1: convert matrices from the space of stationary
1378 # coefficient matrices to our "partial autocorrelation matrix" space
1379 # (matrices with singular values less than one)
1380 partial_autocorrelations = _compute_multivariate_pacf_from_coefficients(
1381 constrained, error_variance, order, k_endog)
1383 # Step 2: convert from arbitrary matrices to those with singular values
1384 # less than one.
1385 unconstrained = _unconstrain_sv_less_than_one(
1386 partial_autocorrelations, order, k_endog)
1388 if not use_list:
1389 unconstrained = np.concatenate(unconstrained, axis=1)
1391 return unconstrained, error_variance
1394def validate_matrix_shape(name, shape, nrows, ncols, nobs):
1395 """
1396 Validate the shape of a possibly time-varying matrix, or raise an exception
1398 Parameters
1399 ----------
1400 name : str
1401 The name of the matrix being validated (used in exception messages)
1402 shape : array_like
1403 The shape of the matrix to be validated. May be of size 2 or (if
1404 the matrix is time-varying) 3.
1405 nrows : int
1406 The expected number of rows.
1407 ncols : int
1408 The expected number of columns.
1409 nobs : int
1410 The number of observations (used to validate the last dimension of a
1411 time-varying matrix)
1413 Raises
1414 ------
1415 ValueError
1416 If the matrix is not of the desired shape.
1417 """
1418 ndim = len(shape)
1420 # Enforce dimension
1421 if ndim not in [2, 3]:
1422 raise ValueError('Invalid value for %s matrix. Requires a'
1423 ' 2- or 3-dimensional array, got %d dimensions' %
1424 (name, ndim))
1425 # Enforce the shape of the matrix
1426 if not shape[0] == nrows:
1427 raise ValueError('Invalid dimensions for %s matrix: requires %d'
1428 ' rows, got %d' % (name, nrows, shape[0]))
1429 if not shape[1] == ncols:
1430 raise ValueError('Invalid dimensions for %s matrix: requires %d'
1431 ' columns, got %d' % (name, ncols, shape[1]))
1433 # If we do not yet know `nobs`, do not allow time-varying arrays
1434 if nobs is None and not (ndim == 2 or shape[-1] == 1):
1435 raise ValueError('Invalid dimensions for %s matrix: time-varying'
1436 ' matrices cannot be given unless `nobs` is specified'
1437 ' (implicitly when a dataset is bound or else set'
1438 ' explicity)' % name)
1440 # Enforce time-varying array size
1441 if ndim == 3 and nobs is not None and not shape[-1] in [1, nobs]:
1442 raise ValueError('Invalid dimensions for time-varying %s'
1443 ' matrix. Requires shape (*,*,%d), got %s' %
1444 (name, nobs, str(shape)))
1447def validate_vector_shape(name, shape, nrows, nobs):
1448 """
1449 Validate the shape of a possibly time-varying vector, or raise an exception
1451 Parameters
1452 ----------
1453 name : str
1454 The name of the vector being validated (used in exception messages)
1455 shape : array_like
1456 The shape of the vector to be validated. May be of size 1 or (if
1457 the vector is time-varying) 2.
1458 nrows : int
1459 The expected number of rows (elements of the vector).
1460 nobs : int
1461 The number of observations (used to validate the last dimension of a
1462 time-varying vector)
1464 Raises
1465 ------
1466 ValueError
1467 If the vector is not of the desired shape.
1468 """
1469 ndim = len(shape)
1470 # Enforce dimension
1471 if ndim not in [1, 2]:
1472 raise ValueError('Invalid value for %s vector. Requires a'
1473 ' 1- or 2-dimensional array, got %d dimensions' %
1474 (name, ndim))
1475 # Enforce the shape of the vector
1476 if not shape[0] == nrows:
1477 raise ValueError('Invalid dimensions for %s vector: requires %d'
1478 ' rows, got %d' % (name, nrows, shape[0]))
1480 # If we do not yet know `nobs`, do not allow time-varying arrays
1481 if nobs is None and not (ndim == 1 or shape[-1] == 1):
1482 raise ValueError('Invalid dimensions for %s vector: time-varying'
1483 ' vectors cannot be given unless `nobs` is specified'
1484 ' (implicitly when a dataset is bound or else set'
1485 ' explicity)' % name)
1487 # Enforce time-varying array size
1488 if ndim == 2 and not shape[1] in [1, nobs]:
1489 raise ValueError('Invalid dimensions for time-varying %s'
1490 ' vector. Requires shape (*,%d), got %s' %
1491 (name, nobs, str(shape)))
1494def reorder_missing_matrix(matrix, missing, reorder_rows=False,
1495 reorder_cols=False, is_diagonal=False,
1496 inplace=False, prefix=None):
1497 """
1498 Reorder the rows or columns of a time-varying matrix where all non-missing
1499 values are in the upper left corner of the matrix.
1501 Parameters
1502 ----------
1503 matrix : array_like
1504 The matrix to be reordered. Must have shape (n, m, nobs).
1505 missing : array_like of bool
1506 The vector of missing indices. Must have shape (k, nobs) where `k = n`
1507 if `reorder_rows is True` and `k = m` if `reorder_cols is True`.
1508 reorder_rows : bool, optional
1509 Whether or not the rows of the matrix should be re-ordered. Default
1510 is False.
1511 reorder_cols : bool, optional
1512 Whether or not the columns of the matrix should be re-ordered. Default
1513 is False.
1514 is_diagonal : bool, optional
1515 Whether or not the matrix is diagonal. If this is True, must also have
1516 `n = m`. Default is False.
1517 inplace : bool, optional
1518 Whether or not to reorder the matrix in-place.
1519 prefix : {'s', 'd', 'c', 'z'}, optional
1520 The Fortran prefix of the vector. Default is to automatically detect
1521 the dtype. This parameter should only be used with caution.
1523 Returns
1524 -------
1525 reordered_matrix : array_like
1526 The reordered matrix.
1527 """
1528 if prefix is None:
1529 prefix = find_best_blas_type((matrix,))[0]
1530 reorder = prefix_reorder_missing_matrix_map[prefix]
1532 if not inplace:
1533 matrix = np.copy(matrix, order='F')
1535 reorder(matrix, np.asfortranarray(missing), reorder_rows, reorder_cols,
1536 is_diagonal)
1538 return matrix
1541def reorder_missing_vector(vector, missing, inplace=False, prefix=None):
1542 """
1543 Reorder the elements of a time-varying vector where all non-missing
1544 values are in the first elements of the vector.
1546 Parameters
1547 ----------
1548 vector : array_like
1549 The vector to be reordered. Must have shape (n, nobs).
1550 missing : array_like of bool
1551 The vector of missing indices. Must have shape (n, nobs).
1552 inplace : bool, optional
1553 Whether or not to reorder the matrix in-place. Default is False.
1554 prefix : {'s', 'd', 'c', 'z'}, optional
1555 The Fortran prefix of the vector. Default is to automatically detect
1556 the dtype. This parameter should only be used with caution.
1558 Returns
1559 -------
1560 reordered_vector : array_like
1561 The reordered vector.
1562 """
1563 if prefix is None:
1564 prefix = find_best_blas_type((vector,))[0]
1565 reorder = prefix_reorder_missing_vector_map[prefix]
1567 if not inplace:
1568 vector = np.copy(vector, order='F')
1570 reorder(vector, np.asfortranarray(missing))
1572 return vector
1575def copy_missing_matrix(A, B, missing, missing_rows=False, missing_cols=False,
1576 is_diagonal=False, inplace=False, prefix=None):
1577 """
1578 Copy the rows or columns of a time-varying matrix where all non-missing
1579 values are in the upper left corner of the matrix.
1581 Parameters
1582 ----------
1583 A : array_like
1584 The matrix from which to copy. Must have shape (n, m, nobs) or
1585 (n, m, 1).
1586 B : array_like
1587 The matrix to copy to. Must have shape (n, m, nobs).
1588 missing : array_like of bool
1589 The vector of missing indices. Must have shape (k, nobs) where `k = n`
1590 if `reorder_rows is True` and `k = m` if `reorder_cols is True`.
1591 missing_rows : bool, optional
1592 Whether or not the rows of the matrix are a missing dimension. Default
1593 is False.
1594 missing_cols : bool, optional
1595 Whether or not the columns of the matrix are a missing dimension.
1596 Default is False.
1597 is_diagonal : bool, optional
1598 Whether or not the matrix is diagonal. If this is True, must also have
1599 `n = m`. Default is False.
1600 inplace : bool, optional
1601 Whether or not to copy to B in-place. Default is False.
1602 prefix : {'s', 'd', 'c', 'z'}, optional
1603 The Fortran prefix of the vector. Default is to automatically detect
1604 the dtype. This parameter should only be used with caution.
1606 Returns
1607 -------
1608 copied_matrix : array_like
1609 The matrix B with the non-missing submatrix of A copied onto it.
1610 """
1611 if prefix is None:
1612 prefix = find_best_blas_type((A, B))[0]
1613 copy = prefix_copy_missing_matrix_map[prefix]
1615 if not inplace:
1616 B = np.copy(B, order='F')
1618 # We may have been given an F-contiguous memoryview; in that case, we do
1619 # not want to alter it or convert it to a numpy array
1620 try:
1621 if not A.is_f_contig():
1622 raise ValueError()
1623 except (AttributeError, ValueError):
1624 A = np.asfortranarray(A)
1626 copy(A, B, np.asfortranarray(missing), missing_rows, missing_cols,
1627 is_diagonal)
1629 return B
1632def copy_missing_vector(a, b, missing, inplace=False, prefix=None):
1633 """
1634 Reorder the elements of a time-varying vector where all non-missing
1635 values are in the first elements of the vector.
1637 Parameters
1638 ----------
1639 a : array_like
1640 The vector from which to copy. Must have shape (n, nobs) or (n, 1).
1641 b : array_like
1642 The vector to copy to. Must have shape (n, nobs).
1643 missing : array_like of bool
1644 The vector of missing indices. Must have shape (n, nobs).
1645 inplace : bool, optional
1646 Whether or not to copy to b in-place. Default is False.
1647 prefix : {'s', 'd', 'c', 'z'}, optional
1648 The Fortran prefix of the vector. Default is to automatically detect
1649 the dtype. This parameter should only be used with caution.
1651 Returns
1652 -------
1653 copied_vector : array_like
1654 The vector b with the non-missing subvector of b copied onto it.
1655 """
1656 if prefix is None:
1657 prefix = find_best_blas_type((a, b))[0]
1658 copy = prefix_copy_missing_vector_map[prefix]
1660 if not inplace:
1661 b = np.copy(b, order='F')
1663 # We may have been given an F-contiguous memoryview; in that case, we do
1664 # not want to alter it or convert it to a numpy array
1665 try:
1666 if not a.is_f_contig():
1667 raise ValueError()
1668 except (AttributeError, ValueError):
1669 a = np.asfortranarray(a)
1671 copy(a, b, np.asfortranarray(missing))
1673 return b
1676def copy_index_matrix(A, B, index, index_rows=False, index_cols=False,
1677 is_diagonal=False, inplace=False, prefix=None):
1678 """
1679 Copy the rows or columns of a time-varying matrix where all non-index
1680 values are in the upper left corner of the matrix.
1682 Parameters
1683 ----------
1684 A : array_like
1685 The matrix from which to copy. Must have shape (n, m, nobs) or
1686 (n, m, 1).
1687 B : array_like
1688 The matrix to copy to. Must have shape (n, m, nobs).
1689 index : array_like of bool
1690 The vector of index indices. Must have shape (k, nobs) where `k = n`
1691 if `reorder_rows is True` and `k = m` if `reorder_cols is True`.
1692 index_rows : bool, optional
1693 Whether or not the rows of the matrix are a index dimension. Default
1694 is False.
1695 index_cols : bool, optional
1696 Whether or not the columns of the matrix are a index dimension.
1697 Default is False.
1698 is_diagonal : bool, optional
1699 Whether or not the matrix is diagonal. If this is True, must also have
1700 `n = m`. Default is False.
1701 inplace : bool, optional
1702 Whether or not to copy to B in-place. Default is False.
1703 prefix : {'s', 'd', 'c', 'z'}, optional
1704 The Fortran prefix of the vector. Default is to automatically detect
1705 the dtype. This parameter should only be used with caution.
1707 Returns
1708 -------
1709 copied_matrix : array_like
1710 The matrix B with the non-index submatrix of A copied onto it.
1711 """
1712 if prefix is None:
1713 prefix = find_best_blas_type((A, B))[0]
1714 copy = prefix_copy_index_matrix_map[prefix]
1716 if not inplace:
1717 B = np.copy(B, order='F')
1719 # We may have been given an F-contiguous memoryview; in that case, we do
1720 # not want to alter it or convert it to a numpy array
1721 try:
1722 if not A.is_f_contig():
1723 raise ValueError()
1724 except (AttributeError, ValueError):
1725 A = np.asfortranarray(A)
1727 copy(A, B, np.asfortranarray(index), index_rows, index_cols,
1728 is_diagonal)
1730 return B
1733def copy_index_vector(a, b, index, inplace=False, prefix=None):
1734 """
1735 Reorder the elements of a time-varying vector where all non-index
1736 values are in the first elements of the vector.
1738 Parameters
1739 ----------
1740 a : array_like
1741 The vector from which to copy. Must have shape (n, nobs) or (n, 1).
1742 b : array_like
1743 The vector to copy to. Must have shape (n, nobs).
1744 index : array_like of bool
1745 The vector of index indices. Must have shape (n, nobs).
1746 inplace : bool, optional
1747 Whether or not to copy to b in-place. Default is False.
1748 prefix : {'s', 'd', 'c', 'z'}, optional
1749 The Fortran prefix of the vector. Default is to automatically detect
1750 the dtype. This parameter should only be used with caution.
1752 Returns
1753 -------
1754 copied_vector : array_like
1755 The vector b with the non-index subvector of b copied onto it.
1756 """
1757 if prefix is None:
1758 prefix = find_best_blas_type((a, b))[0]
1759 copy = prefix_copy_index_vector_map[prefix]
1761 if not inplace:
1762 b = np.copy(b, order='F')
1764 # We may have been given an F-contiguous memoryview; in that case, we do
1765 # not want to alter it or convert it to a numpy array
1766 try:
1767 if not a.is_f_contig():
1768 raise ValueError()
1769 except (AttributeError, ValueError):
1770 a = np.asfortranarray(a)
1772 copy(a, b, np.asfortranarray(index))
1774 return b
1777def prepare_exog(exog):
1778 k_exog = 0
1779 if exog is not None:
1780 exog_is_using_pandas = _is_using_pandas(exog, None)
1781 if not exog_is_using_pandas:
1782 exog = np.asarray(exog)
1784 # Make sure we have 2-dimensional array
1785 if exog.ndim == 1:
1786 if not exog_is_using_pandas:
1787 exog = exog[:, None]
1788 else:
1789 exog = pd.DataFrame(exog)
1791 k_exog = exog.shape[1]
1792 return (k_exog, exog)
1795def prepare_trend_spec(trend):
1796 # Trend
1797 if trend is None or trend == 'n':
1798 polynomial_trend = np.ones(0)
1799 elif trend == 'c':
1800 polynomial_trend = np.r_[1]
1801 elif trend == 't':
1802 polynomial_trend = np.r_[0, 1]
1803 elif trend == 'ct':
1804 polynomial_trend = np.r_[1, 1]
1805 elif trend == 'ctt':
1806 # TODO deprecate ctt?
1807 polynomial_trend = np.r_[1, 1, 1]
1808 else:
1809 trend = np.array(trend)
1810 if trend.ndim > 0:
1811 polynomial_trend = (trend > 0).astype(int)
1812 else:
1813 raise ValueError('Invalid trend method.')
1815 # Note: k_trend is not the degree of the trend polynomial, because e.g.
1816 # k_trend = 1 corresponds to the degree zero polynomial (with only a
1817 # constant term).
1818 k_trend = int(np.sum(polynomial_trend))
1820 return polynomial_trend, k_trend
1823def prepare_trend_data(polynomial_trend, k_trend, nobs, offset=1):
1824 # Cache the arrays for calculating the intercept from the trend
1825 # components
1826 time_trend = np.arange(offset, nobs + offset)
1827 trend_data = np.zeros((nobs, k_trend))
1828 i = 0
1829 for k in polynomial_trend.nonzero()[0]:
1830 if k == 0:
1831 trend_data[:, i] = np.ones(nobs,)
1832 else:
1833 trend_data[:, i] = time_trend**k
1834 i += 1
1836 return trend_data