Repository: statsmodels/statsmodels

Issue:
VAR error upon retrieving aic
Hello,

I am trying to fit a VAR model on a 45x3 dataframe with a lag of 11 and when I try to retrieve the aic I get the following error

<details>
Traceback (most recent call last):
  File "C:/Projects/test/repro.py", line 8, in <module>
    print('AIC : ', result.aic)
  File "C:\Anaconda3\envs\testenv\lib\site-packages\statsmodels\base\wrapper.py", line 36, in __getattribute__
    obj = getattr(results, attr)
  File "C:\Anaconda3\envs\testenv\lib\site-packages\statsmodels\tsa\vector_ar\var_model.py", line 2092, in aic
    return self.info_criteria['aic']
  File "pandas\_libs\properties.pyx", line 34, in pandas._libs.properties.CachedProperty.__get__
  File "C:\Anaconda3\envs\testenv\lib\site-packages\statsmodels\tsa\vector_ar\var_model.py", line 2073, in info_criteria
    ld = logdet_symm(self.sigma_u_mle)
  File "C:\Anaconda3\envs\testenv\lib\site-packages\statsmodels\tools\linalg.py", line 28, in logdet_symm
    c, _ = linalg.cho_factor(m, lower=True)
  File "C:\Anaconda3\envs\testenv\lib\site-packages\scipy\linalg\decomp_cholesky.py", line 153, in cho_factor
    check_finite=check_finite)
  File "C:\Anaconda3\envs\testenv\lib\site-packages\scipy\linalg\decomp_cholesky.py", line 17, in _cholesky
    a1 = asarray_chkfinite(a) if check_finite else asarray(a)
  File "C:\Anaconda3\envs\testenv\lib\site-packages\numpy\lib\function_base.py", line 499, in asarray_chkfinite
    "array must not contain infs or NaNs")
ValueError: array must not contain infs or NaNs
</details>

I am guessing the reason is that I don't have enough data to train with such a large lag (shorter lags work), but this behavior is a really counterintuitive way of finding out this issue. A better design would be a more informative exception being thrown when trying to call the fit method.

I'm using version 0.11.1

code sample:
```python
import pandas as pd
from statsmodels.tsa.api import VAR

df_train = pd.read_csv('./df_error_lag(11).csv')
model = VAR(df_train.astype(float))
result = model.fit(11)
print('AIC : ', result.aic)
```

test-data:
[df_error_lag(11).zip](https://github.com/statsmodels/statsmodels/files/5553455/df_error_lag.11.zip)



Discussion:

Resolving patch:
diff --git a/statsmodels/tsa/vector_ar/var_model.py b/statsmodels/tsa/vector_ar/var_model.py
--- a/statsmodels/tsa/vector_ar/var_model.py
+++ b/statsmodels/tsa/vector_ar/var_model.py
@@ -6,6 +6,8 @@
 ----------
 Lütkepohl (2005) New Introduction to Multiple Time Series Analysis
 """
+from __future__ import annotations
+
 from statsmodels.compat.pandas import deprecate_kwarg
 from statsmodels.compat.python import lrange
 
@@ -40,6 +42,7 @@
 # -------------------------------------------------------------------------------
 # VAR process routines
 
+
 def ma_rep(coefs, maxn=10):
     r"""
     MA(\infty) representation of VAR(p) process
@@ -67,16 +70,16 @@ def ma_rep(coefs, maxn=10):
     phis : ndarray (maxn + 1 x k x k)
     """
     p, k, k = coefs.shape
-    phis = np.zeros((maxn+1, k, k))
+    phis = np.zeros((maxn + 1, k, k))
     phis[0] = np.eye(k)
 
     # recursively compute Phi matrices
     for i in range(1, maxn + 1):
-        for j in range(1, i+1):
+        for j in range(1, i + 1):
             if j > p:
                 break
 
-            phis[i] += np.dot(phis[i-j], coefs[j-1])
+            phis[i] += np.dot(phis[i - j], coefs[j - 1])
 
     return phis
 
@@ -98,7 +101,7 @@ def is_stable(coefs, verbose=False):
     eigs = np.linalg.eigvals(A_var1)
 
     if verbose:
-        print('Eigenvalues of VAR(1) rep')
+        print("Eigenvalues of VAR(1) rep")
         for val in np.abs(eigs):
             print(val)
 
@@ -141,7 +144,7 @@ def var_acf(coefs, sig_u, nlags=None):
         # G(h) = A_1 G(h-1) + ... + A_p G(h-p)
 
         for j in range(p):
-            result[h] += np.dot(coefs[j], result[h-j-1])
+            result[h] += np.dot(coefs[j], result[h - j - 1])
 
     return result
 
@@ -155,18 +158,18 @@ def _var_acf(coefs, sig_u):
     Lütkepohl (2005) p.29
     """
     p, k, k2 = coefs.shape
-    assert(k == k2)
+    assert k == k2
 
     A = util.comp_matrix(coefs)
     # construct VAR(1) noise covariance
-    SigU = np.zeros((k*p, k*p))
+    SigU = np.zeros((k * p, k * p))
     SigU[:k, :k] = sig_u
 
     # vec(ACF) = (I_(kp)^2 - kron(A, A))^-1 vec(Sigma_U)
-    vecACF = np.linalg.solve(np.eye((k*p)**2) - np.kron(A, A), vec(SigU))
+    vecACF = np.linalg.solve(np.eye((k * p) ** 2) - np.kron(A, A), vec(SigU))
 
     acf = unvec(vecACF)
-    acf = [acf[:k, k * i:k * (i + 1)] for i in range(p)]
+    acf = [acf[:k, k * i : k * (i + 1)] for i in range(p)]
     acf = np.array(acf)
 
     return acf
@@ -280,9 +283,10 @@ def _forecast_vars(steps, ma_coefs, sig_u):
     return covs[:, inds, inds]
 
 
-def forecast_interval(y, coefs, trend_coefs, sig_u, steps=5, alpha=0.05,
-                      exog=1):
-    assert(0 < alpha < 1)
+def forecast_interval(
+    y, coefs, trend_coefs, sig_u, steps=5, alpha=0.05, exog=1
+):
+    assert 0 < alpha < 1
     q = util.norm_signif_level(alpha)
 
     point_forecast = forecast(y, coefs, trend_coefs, steps, exog)
@@ -325,8 +329,8 @@ def var_loglike(resid, omega, nobs):
     """
     logdet = logdet_symm(np.asarray(omega))
     neqs = len(omega)
-    part1 = - (nobs * neqs / 2) * np.log(2 * np.pi)
-    part2 = - (nobs / 2) * (logdet + neqs)
+    part1 = -(nobs * neqs / 2) * np.log(2 * np.pi)
+    part2 = -(nobs / 2) * (logdet + neqs)
     return part1 + part2
 
 
@@ -338,12 +342,12 @@ def _reordered(self, order):
     sigma_u = self.sigma_u
     names = self.names
     k_ar = self.k_ar
-    endog_new = np.zeros([np.size(endog, 0), np.size(endog, 1)])
-    endog_lagged_new = np.zeros([np.size(endog_lagged, 0), np.size(endog_lagged, 1)])
-    params_new_inc, params_new = [np.zeros([np.size(params, 0), np.size(params, 1)])
-                                  for i in range(2)]
-    sigma_u_new_inc, sigma_u_new = [np.zeros([np.size(sigma_u, 0), np.size(sigma_u, 1)])
-                                    for i in range(2)]
+    endog_new = np.zeros_like(endog)
+    endog_lagged_new = np.zeros_like(endog_lagged)
+    params_new_inc = np.zeros_like(params)
+    params_new = np.zeros_like(params)
+    sigma_u_new_inc = np.zeros_like(sigma_u)
+    sigma_u_new = np.zeros_like(sigma_u)
     num_end = len(self.params[0])
     names_new = []
 
@@ -355,18 +359,29 @@ def _reordered(self, order):
             params_new_inc[0, i] = params[0, i]
             endog_lagged_new[:, 0] = endog_lagged[:, 0]
         for j in range(k_ar):
-            params_new_inc[i+j*num_end+k, :] = self.params[c+j*num_end+k, :]
-            endog_lagged_new[:, i+j*num_end+k] = endog_lagged[:, c+j*num_end+k]
+            params_new_inc[i + j * num_end + k, :] = self.params[
+                c + j * num_end + k, :
+            ]
+            endog_lagged_new[:, i + j * num_end + k] = endog_lagged[
+                :, c + j * num_end + k
+            ]
         sigma_u_new_inc[i, :] = sigma_u[c, :]
         names_new.append(names[c])
     for i, c in enumerate(order):
         params_new[:, i] = params_new_inc[:, c]
         sigma_u_new[:, i] = sigma_u_new_inc[:, c]
 
-    return VARResults(endog=endog_new, endog_lagged=endog_lagged_new,
-                      params=params_new, sigma_u=sigma_u_new,
-                      lag_order=self.k_ar, model=self.model,
-                      trend='c', names=names_new, dates=self.dates)
+    return VARResults(
+        endog=endog_new,
+        endog_lagged=endog_lagged_new,
+        params=params_new,
+        sigma_u=sigma_u_new,
+        lag_order=self.k_ar,
+        model=self.model,
+        trend="c",
+        names=names_new,
+        dates=self.dates,
+    )
 
 
 def orth_ma_rep(results, maxn=10, P=None):
@@ -414,7 +429,8 @@ def test_normality(results, signif=0.05):
 
     References
     ----------
-    .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series Analysis*. Springer.
+    .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series*
+       *Analysis*. Springer.
 
     .. [2] Kilian, L. & Demiroglu, U. (2000). "Residual-Based Tests for
        Normality in Autoregressions: Asymptotic Theory and Simulation
@@ -425,8 +441,8 @@ def test_normality(results, signif=0.05):
     Pinv = np.linalg.inv(np.linalg.cholesky(sig))
 
     w = np.dot(Pinv, resid_c.T)
-    b1 = (w**3).sum(1)[:, None] / results.nobs
-    b2 = (w**4).sum(1)[:, None] / results.nobs - 3
+    b1 = (w ** 3).sum(1)[:, None] / results.nobs
+    b2 = (w ** 4).sum(1)[:, None] / results.nobs - 3
 
     lam_skew = results.nobs * np.dot(b1.T, b1) / 6
     lam_kurt = results.nobs * np.dot(b2.T, b2) / 24
@@ -436,8 +452,9 @@ def test_normality(results, signif=0.05):
     omni_pvalue = float(omni_dist.sf(lam_omni))
     crit_omni = float(omni_dist.ppf(1 - signif))
 
-    return NormalityTestResults(lam_omni, crit_omni, omni_pvalue,
-                                results.neqs*2, signif)
+    return NormalityTestResults(
+        lam_omni, crit_omni, omni_pvalue, results.neqs * 2, signif
+    )
 
 
 class LagOrderResults:
@@ -462,6 +479,7 @@ class LagOrderResults:
     -----
     In case of a VECM the shown lags are lagged differences.
     """
+
     def __init__(self, ics, selected_orders, vecm=False):
         self.title = ("VECM" if vecm else "VAR") + " Order Selection"
         self.title += " (* highlights the minimums)"
@@ -475,21 +493,27 @@ def __init__(self, ics, selected_orders, vecm=False):
 
     def summary(self):  # basically copied from (now deleted) print_ic_table()
         cols = sorted(self.ics)  # ["aic", "bic", "hqic", "fpe"]
-        str_data = np.array([["%#10.4g" % v for v in self.ics[c]] for c in cols],
-                            dtype=object).T
+        str_data = np.array(
+            [["%#10.4g" % v for v in self.ics[c]] for c in cols], dtype=object
+        ).T
         # mark minimum with an asterisk
         for i, col in enumerate(cols):
             idx = int(self.selected_orders[col]), i
-            str_data[idx] += '*'
-        return SimpleTable(str_data, [col.upper() for col in cols],
-                           lrange(len(str_data)), title=self.title)
+            str_data[idx] += "*"
+        return SimpleTable(
+            str_data,
+            [col.upper() for col in cols],
+            lrange(len(str_data)),
+            title=self.title,
+        )
 
     def __str__(self):
-        return "<" + self.__module__ + "." + self.__class__.__name__ \
-                   + " object. Selected orders are: AIC -> " + str(self.aic) \
-                   + ", BIC -> " + str(self.bic)  \
-                   + ", FPE -> " + str(self.fpe) \
-                   + ", HQIC -> " + str(self.hqic) + ">"
+        return (
+            f"<{self.__module__}.{self.__class__.__name__} object. Selected "
+            f"orders are: AIC -> {str(self.aic)}, BIC -> {str(self.bic)}, "
+            f"FPE -> {str(self.fpe)}, HQIC ->  {str(self.hqic)}>"
+        )
+
 
 # -------------------------------------------------------------------------------
 # VARProcess class: for known or unknown VAR process
@@ -517,15 +541,16 @@ class VAR(TimeSeriesModel):
 
     y = deprecated_alias("y", "endog", remove_version="0.11.0")
 
-    def __init__(self, endog, exog=None, dates=None, freq=None,
-                 missing='none'):
+    def __init__(
+        self, endog, exog=None, dates=None, freq=None, missing="none"
+    ):
         super(VAR, self).__init__(endog, exog, dates, freq, missing=missing)
         if self.endog.ndim == 1:
             raise ValueError("Only gave one variable to VAR")
         self.neqs = self.endog.shape[1]
         self.n_totobs = len(endog)
 
-    def predict(self, params, start=None, end=None, lags=1, trend='c'):
+    def predict(self, params, start=None, end=None, lags=1, trend="c"):
         """
         Returns in-sample predictions or forecasts
         """
@@ -535,8 +560,12 @@ def predict(self, params, start=None, end=None, lags=1, trend='c'):
             start = lags
 
         # Handle start, end
-        start, end, out_of_sample, prediction_index = (
-            self._get_prediction_index(start, end))
+        (
+            start,
+            end,
+            out_of_sample,
+            prediction_index,
+        ) = self._get_prediction_index(start, end)
 
         if end < start:
             raise ValueError("end is before start")
@@ -553,7 +582,7 @@ def predict(self, params, start=None, end=None, lags=1, trend='c'):
             predictedvalues += intercept
 
         y = self.endog
-        x = util.get_var_endog(y, lags, trend=trend, has_constant='raise')
+        x = util.get_var_endog(y, lags, trend=trend, has_constant="raise")
         fittedvalues = np.dot(x, params)
 
         fv_start = start - k_ar
@@ -570,8 +599,14 @@ def predict(self, params, start=None, end=None, lags=1, trend='c'):
         predictedvalues[pv_end:] = forecast(y, coefs, intercept, out_of_sample)
         return predictedvalues
 
-    def fit(self, maxlags=None, method='ols', ic=None, trend='c',
-            verbose=False):
+    def fit(
+        self,
+        maxlags: int | None = None,
+        method="ols",
+        ic=None,
+        trend="c",
+        verbose=False,
+    ):
         # todo: this code is only supporting deterministic terms as exog.
         # This means that all exog-variables have lag 0. If dealing with
         # different exogs is necessary, a `lags_exog`-parameter might make
@@ -614,18 +649,20 @@ def fit(self, maxlags=None, method='ols', ic=None, trend='c',
         """
         lags = maxlags
 
-        if trend not in ['c', 'ct', 'ctt', 'nc', 'n']:
+        if trend not in ["c", "ct", "ctt", "nc", "n"]:
             raise ValueError("trend '{}' not supported for VAR".format(trend))
 
         if ic is not None:
             selections = self.select_order(maxlags=maxlags)
             if not hasattr(selections, ic):
-                raise ValueError("%s not recognized, must be among %s"
-                                 % (ic, sorted(selections)))
+                raise ValueError(
+                    "%s not recognized, must be among %s"
+                    % (ic, sorted(selections))
+                )
             lags = getattr(selections, ic)
             if verbose:
                 print(selections)
-                print('Using %d based on %s criterion' % (lags, ic))
+                print("Using %d based on %s criterion" % (lags, ic))
         else:
             if lags is None:
                 lags = 1
@@ -641,16 +678,20 @@ def fit(self, maxlags=None, method='ols', ic=None, trend='c',
             if orig_exog_names:
                 x_names_to_add = orig_exog_names
             else:
-                x_names_to_add = [("exog%d" % i)
-                                  for i in range(self.exog.shape[1])]
-            self.data.xnames = (self.data.xnames[:k_trend] +
-                                x_names_to_add +
-                                self.data.xnames[k_trend:])
-        self.data.cov_names = pd.MultiIndex.from_product((self.data.xnames,
-                                                          self.data.ynames))
+                x_names_to_add = [
+                    ("exog%d" % i) for i in range(self.exog.shape[1])
+                ]
+            self.data.xnames = (
+                self.data.xnames[:k_trend]
+                + x_names_to_add
+                + self.data.xnames[k_trend:]
+            )
+        self.data.cov_names = pd.MultiIndex.from_product(
+            (self.data.xnames, self.data.ynames)
+        )
         return self._estimate_var(lags, trend=trend)
 
-    def _estimate_var(self, lags, offset=0, trend='c'):
+    def _estimate_var(self, lags, offset=0, trend="c"):
         """
         lags : int
             Lags of the endogenous variable.
@@ -664,26 +705,26 @@ def _estimate_var(self, lags, offset=0, trend='c'):
         self.k_trend = k_trend = util.get_trendorder(trend)
 
         if offset < 0:  # pragma: no cover
-            raise ValueError('offset must be >= 0')
+            raise ValueError("offset must be >= 0")
 
         nobs = self.n_totobs - lags - offset
         endog = self.endog[offset:]
         exog = None if self.exog is None else self.exog[offset:]
-        z = util.get_var_endog(endog, lags, trend=trend,
-                               has_constant='raise')
+        z = util.get_var_endog(endog, lags, trend=trend, has_constant="raise")
         if exog is not None:
             # TODO: currently only deterministic terms supported (exoglags==0)
             # and since exoglags==0, x will be an array of size 0.
-            x = util.get_var_endog(exog[-nobs:], 0, trend="n",
-                                   has_constant="raise")
+            x = util.get_var_endog(
+                exog[-nobs:], 0, trend="n", has_constant="raise"
+            )
             x_inst = exog[-nobs:]
             x = np.column_stack((x, x_inst))
             del x_inst  # free memory
             temp_z = z
-            z = np.empty((x.shape[0], x.shape[1]+z.shape[1]))
-            z[:, :self.k_trend] = temp_z[:, :self.k_trend]
-            z[:, self.k_trend:self.k_trend+x.shape[1]] = x
-            z[:, self.k_trend+x.shape[1]:] = temp_z[:, self.k_trend:]
+            z = np.empty((x.shape[0], x.shape[1] + z.shape[1]))
+            z[:, : self.k_trend] = temp_z[:, : self.k_trend]
+            z[:, self.k_trend : self.k_trend + x.shape[1]] = x
+            z[:, self.k_trend + x.shape[1] :] = temp_z[:, self.k_trend :]
             del temp_z, x  # free memory
         # the following modification of z is necessary to get the same results
         # as JMulTi for the constant-term-parameter...
@@ -692,7 +733,7 @@ def _estimate_var(self, lags, offset=0, trend='c'):
                 z[:, i] += lags
             # make the same adjustment for the quadratic term
             if (np.diff(np.sqrt(z[:, i])) == 1).all():
-                z[:, i] = (np.sqrt(z[:, i]) + lags)**2
+                z[:, i] = (np.sqrt(z[:, i]) + lags) ** 2
 
         y_sample = endog[lags:]
         # Lütkepohl p75, about 5x faster than stated formula
@@ -713,11 +754,23 @@ def _estimate_var(self, lags, offset=0, trend='c'):
         df_resid = avobs - (self.neqs * lags + k_trend)
 
         sse = np.dot(resid.T, resid)
-        omega = sse / df_resid
-
-        varfit = VARResults(endog, z, params, omega, lags,
-                            names=self.endog_names, trend=trend,
-                            dates=self.data.dates, model=self, exog=self.exog)
+        if df_resid:
+            omega = sse / df_resid
+        else:
+            omega = np.full_like(sse, np.nan)
+
+        varfit = VARResults(
+            endog,
+            z,
+            params,
+            omega,
+            lags,
+            names=self.endog_names,
+            trend=trend,
+            dates=self.data.dates,
+            model=self,
+            exog=self.exog,
+        )
         return VARResultsWrapper(varfit)
 
     def select_order(self, maxlags=None, trend="c"):
@@ -742,7 +795,7 @@ def select_order(self, maxlags=None, trend="c"):
         ntrend = len(trend) if trend.startswith("c") else 0
         max_estimable = (self.n_totobs - self.neqs - ntrend) // (1 + self.neqs)
         if maxlags is None:
-            maxlags = int(round(12*(len(self.endog)/100.)**(1/4.)))
+            maxlags = int(round(12 * (len(self.endog) / 100.0) ** (1 / 4.0)))
             # TODO: This expression shows up in a bunch of places, but
             #  in some it is `int` and in others `np.ceil`.  Also in some
             #  it multiplies by 4 instead of 12.  Let's put these all in
@@ -768,14 +821,16 @@ def select_order(self, maxlags=None, trend="c"):
             for k, v in result.info_criteria.items():
                 ics[k].append(v)
 
-        selected_orders = dict((k, np.array(v).argmin() + p_min)
-                               for k, v in ics.items())
+        selected_orders = dict(
+            (k, np.array(v).argmin() + p_min) for k, v in ics.items()
+        )
 
         return LagOrderResults(ics, selected_orders, vecm=False)
 
     @classmethod
-    def from_formula(cls, formula, data, subset=None, drop_cols=None,
-                     *args, **kwargs):
+    def from_formula(
+        cls, formula, data, subset=None, drop_cols=None, *args, **kwargs
+    ):
         """
         Not implemented. Formulas are not supported for VAR models.
         """
@@ -802,7 +857,10 @@ class VARProcess(object):
         If it is None, then coefs_exog are assumed to be for the intercept and
         trend.
     """
-    def __init__(self, coefs, coefs_exog, sigma_u, names=None, _params_info=None):
+
+    def __init__(
+        self, coefs, coefs_exog, sigma_u, names=None, _params_info=None
+    ):
         self.k_ar = len(coefs)
         self.neqs = coefs.shape[1]
         self.coefs = coefs
@@ -813,13 +871,13 @@ def __init__(self, coefs, coefs_exog, sigma_u, names=None, _params_info=None):
 
         if _params_info is None:
             _params_info = {}
-        self.k_exog_user = _params_info.get('k_exog_user', 0)
+        self.k_exog_user = _params_info.get("k_exog_user", 0)
         if self.coefs_exog is not None:
             k_ex = self.coefs_exog.shape[0] if self.coefs_exog.ndim != 1 else 1
             k_c = k_ex - self.k_exog_user
         else:
             k_c = 0
-        self.k_trend = _params_info.get('k_trend', k_c)
+        self.k_trend = _params_info.get("k_trend", k_c)
         # TODO: we need to distinguish exog including trend and exog_user
         self.k_exog = self.k_trend + self.k_exog_user
 
@@ -836,10 +894,12 @@ def get_eq_index(self, name):
         return util.get_index(self.names, name)
 
     def __str__(self):
-        output = ('VAR(%d) process for %d-dimensional response y_t'
-                  % (self.k_ar, self.neqs))
-        output += '\nstable: %s' % self.is_stable()
-        output += '\nmean: %s' % self.mean()
+        output = "VAR(%d) process for %d-dimensional response y_t" % (
+            self.k_ar,
+            self.neqs,
+        )
+        output += "\nstable: %s" % self.is_stable()
+        output += "\nmean: %s" % self.mean()
 
         return output
 
@@ -891,8 +951,9 @@ def simulate_var(self, steps=None, offset=None, seed=None):
                 # if more than intercept
                 # endog_lagged contains all regressors, trend, exog_user
                 # and lagged endog, trimmed initial observations
-                offset = self.endog_lagged[:,:self.k_exog].dot(
-                                                     self.coefs_exog.T)
+                offset = self.endog_lagged[:, : self.k_exog].dot(
+                    self.coefs_exog.T
+                )
                 steps_ = self.endog_lagged.shape[0]
             else:
                 offset = self.intercept
@@ -907,10 +968,14 @@ def simulate_var(self, steps=None, offset=None, seed=None):
                 steps = steps_
         else:
             if steps_ is not None and steps != steps_:
-                raise ValueError('if exog or offset are used, then steps must'
-                                 'be equal to their length or None')
+                raise ValueError(
+                    "if exog or offset are used, then steps must"
+                    "be equal to their length or None"
+                )
 
-        y = util.varsim(self.coefs, offset, self.sigma_u, steps=steps, seed=seed)
+        y = util.varsim(
+            self.coefs, offset, self.sigma_u, steps=steps, seed=seed
+        )
         return y
 
     def plotsim(self, steps=None, offset=None, seed=None):
@@ -1028,8 +1093,9 @@ def acorr(self, nlags=None):
 
     def plot_acorr(self, nlags=10, linewidth=8):
         """Plot theoretical autocorrelation function"""
-        fig = plotting.plot_full_acorr(self.acorr(nlags=nlags),
-                                       linewidth=linewidth)
+        fig = plotting.plot_full_acorr(
+            self.acorr(nlags=nlags), linewidth=linewidth
+        )
         return fig
 
     def forecast(self, y, steps, exog_future=None):
@@ -1050,13 +1116,19 @@ def forecast(self, y, steps, exog_future=None):
         Lütkepohl pp 37-38
         """
         if self.exog is None and exog_future is not None:
-            raise ValueError("No exog in model, so no exog_future supported "
-                             "in forecast method.")
+            raise ValueError(
+                "No exog in model, so no exog_future supported "
+                "in forecast method."
+            )
         if self.exog is not None and exog_future is None:
-            raise ValueError("Please provide an exog_future argument to "
-                             "the forecast method.")
-
-        exog_future = array_like(exog_future, "exog_future", optional=True, ndim=2)
+            raise ValueError(
+                "Please provide an exog_future argument to "
+                "the forecast method."
+            )
+
+        exog_future = array_like(
+            exog_future, "exog_future", optional=True, ndim=2
+        )
         if exog_future is not None:
             if exog_future.shape[0] != steps:
                 err_msg = f"""\
@@ -1069,12 +1141,13 @@ def forecast(self, y, steps, exog_future=None):
         exogs = []
         if self.trend.startswith("c"):  # constant term
             exogs.append(np.ones(steps))
-        exog_lin_trend = np.arange(self.n_totobs + 1,
-                                   self.n_totobs + 1 + steps)
+        exog_lin_trend = np.arange(
+            self.n_totobs + 1, self.n_totobs + 1 + steps
+        )
         if "t" in self.trend:
             exogs.append(exog_lin_trend)
         if "tt" in self.trend:
-            exogs.append(exog_lin_trend**2)
+            exogs.append(exog_lin_trend ** 2)
         if exog_future is not None:
             exogs.append(exog_future)
 
@@ -1157,7 +1230,7 @@ def forecast_interval(self, y, steps, alpha=0.05, exog_future=None):
         Lütkepohl pp. 39-40
         """
         if not 0 < alpha < 1:
-            raise ValueError('alpha must be between 0 and 1')
+            raise ValueError("alpha must be between 0 and 1")
         q = util.norm_signif_level(alpha)
 
         point_forecast = self.forecast(y, steps, exog_future=exog_future)
@@ -1174,13 +1247,14 @@ def to_vecm(self):
         p = self.coefs.shape[0]
         A = self.coefs
         pi = -(np.identity(k) - np.sum(A, 0))
-        gamma = np.zeros((p-1, k, k))
-        for i in range(p-1):
-            gamma[i] = -(np.sum(A[i+1:], 0))
+        gamma = np.zeros((p - 1, k, k))
+        for i in range(p - 1):
+            gamma[i] = -(np.sum(A[i + 1 :], 0))
         gamma = np.concatenate(gamma, 1)
         return {"Gamma": gamma, "Pi": pi}
 
-# -------------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
 # VARResults class
 
 
@@ -1197,13 +1271,14 @@ class VARResults(VARProcess):
     model : VAR model instance
     trend : str {'nc', 'c', 'ct'}
     names : array_like
-        List of names of the endogenous variables in order of appearance in `endog`.
+        List of names of the endogenous variables in order of appearance in
+        `endog`.
     dates
     exog : ndarray
 
     Attributes
     ----------
-    coefs : ndarray (p x K x K)
+    params : ndarray (p x K x K)
         Estimated A_i matrices, A_i = coefs[i-1]
     dates
     endog
@@ -1221,14 +1296,25 @@ class VARResults(VARProcess):
         A_i matrices and intercept in stacked form [int A_1 ... A_p]
     names : list
         variables names
-    resid
     sigma_u : ndarray (K x K)
         Estimate of white noise process variance Var[u_t]
-    tvalues
     """
-    _model_type = 'VAR'
-    def __init__(self, endog, endog_lagged, params, sigma_u, lag_order,
-                 model=None, trend='c', names=None, dates=None, exog=None):
+
+    _model_type = "VAR"
+
+    def __init__(
+        self,
+        endog,
+        endog_lagged,
+        params,
+        sigma_u,
+        lag_order,
+        model=None,
+        trend="c",
+        names=None,
+        dates=None,
+        exog=None,
+    ):
 
         self.model = model
         self.endog = endog
@@ -1239,8 +1325,9 @@ def __init__(self, endog, endog_lagged, params, sigma_u, lag_order,
         self.nobs = self.n_totobs - lag_order
         self.trend = trend
         k_trend = util.get_trendorder(trend)
-        self.exog_names = util.make_lag_names(names, lag_order, k_trend,
-                                              model.data.orig_exog)
+        self.exog_names = util.make_lag_names(
+            names, lag_order, k_trend, model.data.orig_exog
+        )
         self.params = params
         self.exog = exog
 
@@ -1264,17 +1351,24 @@ def __init__(self, endog, endog_lagged, params, sigma_u, lag_order,
 
         # maybe change to params class, distinguish exog_all versus exog_user
         # see issue #4535
-        _params_info = {'k_trend': k_trend,
-                        'k_exog_user': k_exog_user,
-                        'k_ar': lag_order}
-        super(VARResults, self).__init__(coefs, self.coefs_exog, sigma_u,
-                                         names=names,
-                                         _params_info=_params_info)
+        _params_info = {
+            "k_trend": k_trend,
+            "k_exog_user": k_exog_user,
+            "k_ar": lag_order,
+        }
+        super(VARResults, self).__init__(
+            coefs,
+            self.coefs_exog,
+            sigma_u,
+            names=names,
+            _params_info=_params_info,
+        )
 
     def plot(self):
         """Plot input time series"""
-        return plotting.plot_mts(self.endog, names=self.names,
-                                 index=self.dates)
+        return plotting.plot_mts(
+            self.endog, names=self.names, index=self.dates
+        )
 
     @property
     def df_model(self):
@@ -1300,11 +1394,11 @@ def resid(self):
         """
         Residuals of response variable resulting from estimated coefficients
         """
-        return self.endog[self.k_ar:] - self.fittedvalues
+        return self.endog[self.k_ar :] - self.fittedvalues
 
     def sample_acov(self, nlags=1):
         """Sample acov"""
-        return _compute_acov(self.endog[self.k_ar:], nlags=nlags)
+        return _compute_acov(self.endog[self.k_ar :], nlags=nlags)
 
     def sample_acorr(self, nlags=1):
         """Sample acorr"""
@@ -1328,8 +1422,9 @@ def plot_sample_acorr(self, nlags=10, linewidth=8):
         Figure
             The figure that contains the plot axes.
         """
-        fig = plotting.plot_full_acorr(self.sample_acorr(nlags=nlags),
-                                       linewidth=linewidth)
+        fig = plotting.plot_full_acorr(
+            self.sample_acorr(nlags=nlags), linewidth=linewidth
+        )
         return fig
 
     def resid_acov(self, nlags=1):
@@ -1368,8 +1463,9 @@ def resid_corr(self):
 
     @cache_readonly
     def sigma_u_mle(self):
-        """(Biased) maximum likelihood estimate of noise process covariance
-        """
+        """(Biased) maximum likelihood estimate of noise process covariance"""
+        if not self.df_resid:
+            return np.zeros_like(self.sigma_u)
         return self.sigma_u * self.df_resid / self.nobs
 
     def cov_params(self):
@@ -1391,10 +1487,11 @@ def cov_ybar(self):
 
         .. math::
 
-            \sqrt(T) (\bar{y} - \mu) \rightarrow {\cal N}(0, \Sigma_{\bar{y}})\\
+            \sqrt(T) (\bar{y} - \mu) \rightarrow
+                  {\cal N}(0, \Sigma_{\bar{y}}) \\
 
-            \Sigma_{\bar{y}} = B \Sigma_u B^\prime, \text{where } B = (I_K - A_1
-            - \cdots - A_p)^{-1}
+            \Sigma_{\bar{y}} = B \Sigma_u B^\prime, \text{where }
+                  B = (I_K - A_1 - \cdots - A_p)^{-1}
 
         Notes
         -----
@@ -1439,10 +1536,9 @@ def llf(self):
 
     @cache_readonly
     def stderr(self):
-        """Standard errors of coefficients, reshaped to match in size
-        """
+        """Standard errors of coefficients, reshaped to match in size"""
         stderr = np.sqrt(np.diag(self.cov_params()))
-        return stderr.reshape((self.df_model, self.neqs), order='C')
+        return stderr.reshape((self.df_model, self.neqs), order="C")
 
     bse = stderr  # statsmodels interface?
 
@@ -1460,8 +1556,9 @@ def stderr_dt(self):
 
     @cache_readonly
     def tvalues(self):
-        """Compute t-statistics. Use Student-t(T - Kp - 1) = t(df_resid) to test
-        significance.
+        """
+        Compute t-statistics. Use Student-t(T - Kp - 1) = t(df_resid) to
+        test significance.
         """
         return self.params / self.stderr
 
@@ -1479,7 +1576,8 @@ def tvalues_dt(self):
 
     @cache_readonly
     def pvalues(self):
-        """Two-sided p-values for model coefficients from Student t-distribution
+        """
+        Two-sided p-values for model coefficients from Student t-distribution
         """
         # return stats.t.sf(np.abs(self.tvalues), self.df_resid)*2
         return 2 * stats.norm.sf(np.abs(self.tvalues))
@@ -1496,22 +1594,28 @@ def pvalues_dt(self):
         end = self.k_exog
         return self.pvalues[:end]
 
-    # todo: --------------------------------------------------------------------
+    # todo: ------------------------------------------------------------------
 
     def plot_forecast(self, steps, alpha=0.05, plot_stderr=True):
         """
         Plot forecast
         """
-        mid, lower, upper = self.forecast_interval(self.endog[-self.k_ar:],
-                                                   steps,
-                                                   alpha=alpha)
-        fig = plotting.plot_var_forc(self.endog, mid, lower, upper,
-                                     names=self.names, plot_stderr=plot_stderr)
+        mid, lower, upper = self.forecast_interval(
+            self.endog[-self.k_ar :], steps, alpha=alpha
+        )
+        fig = plotting.plot_var_forc(
+            self.endog,
+            mid,
+            lower,
+            upper,
+            names=self.names,
+            plot_stderr=plot_stderr,
+        )
         return fig
 
     # Forecast error covariance functions
 
-    def forecast_cov(self, steps=1, method='mse'):
+    def forecast_cov(self, steps=1, method="mse"):
         r"""Compute forecast covariance matrices for desired number of steps
 
         Parameters
@@ -1529,24 +1633,35 @@ def forecast_cov(self, steps=1, method='mse'):
         covs : ndarray (steps x k x k)
         """
         fc_cov = self.mse(steps)
-        if method == 'mse':
+        if method == "mse":
             pass
-        elif method == 'auto':
+        elif method == "auto":
             if self.k_exog == 1 and self.k_trend < 2:
                 # currently only supported if no exog and trend in ['nc', 'c']
                 fc_cov += self._omega_forc_cov(steps) / self.nobs
                 import warnings
-                warnings.warn('forecast cov takes parameter uncertainty into'
-                              'account', OutputWarning)
+
+                warnings.warn(
+                    "forecast cov takes parameter uncertainty into" "account",
+                    OutputWarning,
+                )
         else:
             raise ValueError("method has to be either 'mse' or 'auto'")
 
         return fc_cov
 
     # Monte Carlo irf standard errors
-    @deprecate_kwarg('T', 'steps')
-    def irf_errband_mc(self, orth=False, repl=1000, steps=10,
-                       signif=0.05, seed=None, burn=100, cum=False):
+    @deprecate_kwarg("T", "steps")
+    def irf_errband_mc(
+        self,
+        orth=False,
+        repl=1000,
+        steps=10,
+        signif=0.05,
+        seed=None,
+        burn=100,
+        cum=False,
+    ):
         """
         Compute Monte Carlo integrated error bands assuming normally
         distributed for impulse response functions
@@ -1576,8 +1691,9 @@ def irf_errband_mc(self, orth=False, repl=1000, steps=10,
         -------
         Tuple of lower and upper arrays of ma_rep monte carlo standard errors
         """
-        ma_coll = self.irf_resim(orth=orth, repl=repl, steps=steps,
-                                 seed=seed, burn=burn, cum=cum)
+        ma_coll = self.irf_resim(
+            orth=orth, repl=repl, steps=steps, seed=seed, burn=burn, cum=cum
+        )
 
         ma_sort = np.sort(ma_coll, axis=0)  # sort to get quantiles
         # python 2: round returns float
@@ -1587,9 +1703,10 @@ def irf_errband_mc(self, orth=False, repl=1000, steps=10,
         upper = ma_sort[upp_idx, :, :, :]
         return lower, upper
 
-    @deprecate_kwarg('T', 'steps')
-    def irf_resim(self, orth=False, repl=1000, steps=10,
-                  seed=None, burn=100, cum=False):
+    @deprecate_kwarg("T", "steps")
+    def irf_resim(
+        self, orth=False, repl=1000, steps=10, seed=None, burn=100, cum=False
+    ):
         """
         Simulates impulse response function, returning an array of simulations.
         Used for Sims-Zha error band calculation.
@@ -1632,13 +1749,20 @@ def irf_resim(self, orth=False, repl=1000, steps=10,
 
         def fill_coll(sim):
             ret = VAR(sim, exog=self.exog).fit(maxlags=k_ar, trend=self.trend)
-            ret = ret.orth_ma_rep(maxn=steps) if orth else ret.ma_rep(maxn=steps)
+            ret = (
+                ret.orth_ma_rep(maxn=steps) if orth else ret.ma_rep(maxn=steps)
+            )
             return ret.cumsum(axis=0) if cum else ret
 
         for i in range(repl):
             # discard first burn to eliminate correct for starting bias
-            sim = util.varsim(coefs, intercept, sigma_u,
-                              seed=seed, steps=nobs_original+burn)
+            sim = util.varsim(
+                coefs,
+                intercept,
+                sigma_u,
+                seed=seed,
+                steps=nobs_original + burn,
+            )
             sim = sim[burn:]
             ma_coll[i, :, :, :] = fill_coll(sim)
 
@@ -1667,29 +1791,34 @@ def bpow(i):
         omegas = np.zeros((steps, self.neqs, self.neqs))
         for h in range(1, steps + 1):
             if h == 1:
-                omegas[h-1] = self.df_model * self.sigma_u
+                omegas[h - 1] = self.df_model * self.sigma_u
                 continue
 
-            om = omegas[h-1]
+            om = omegas[h - 1]
             for i in range(h):
                 for j in range(h):
                     Bi = bpow(h - 1 - i)
                     Bj = bpow(h - 1 - j)
                     mult = np.trace(Bi.T @ Ginv @ Bj @ G)
                     om += mult * phis[i] @ sig_u @ phis[j].T
-            omegas[h-1] = om
+            omegas[h - 1] = om
 
         return omegas
 
     def _bmat_forc_cov(self):
         # B as defined on p. 96 of Lut
         upper = np.zeros((self.k_exog, self.df_model))
-        upper[:, :self.k_exog] = np.eye(self.k_exog)
+        upper[:, : self.k_exog] = np.eye(self.k_exog)
 
         lower_dim = self.neqs * (self.k_ar - 1)
         eye = np.eye(lower_dim)
-        lower = np.column_stack((np.zeros((lower_dim, self.k_exog)), eye,
-                                 np.zeros((lower_dim, self.neqs))))
+        lower = np.column_stack(
+            (
+                np.zeros((lower_dim, self.k_exog)),
+                eye,
+                np.zeros((lower_dim, self.neqs)),
+            )
+        )
 
         return np.vstack((upper, self.params.T, lower))
 
@@ -1709,8 +1838,8 @@ def irf(self, periods=10, var_decomp=None, var_order=None):
         ----------
         periods : int
         var_decomp : ndarray (k x k), lower triangular
-            Must satisfy Omega = P P', where P is the passed matrix. Defaults to
-            Cholesky decomposition of Omega
+            Must satisfy Omega = P P', where P is the passed matrix. Defaults
+            to Cholesky decomposition of Omega
         var_order : sequence
             Alternate variable order for Cholesky decomposition
 
@@ -1719,8 +1848,9 @@ def irf(self, periods=10, var_decomp=None, var_order=None):
         irf : IRAnalysis
         """
         if var_order is not None:
-            raise NotImplementedError('alternate variable order not implemented'
-                                      ' (yet)')
+            raise NotImplementedError(
+                "alternate variable order not implemented" " (yet)"
+            )
 
         return IRAnalysis(self, P=var_decomp, periods=periods)
 
@@ -1735,11 +1865,12 @@ def fevd(self, periods=10, var_decomp=None):
         return FEVD(self, P=var_decomp, periods=periods)
 
     def reorder(self, order):
-        """Reorder variables for structural specification
-        """
+        """Reorder variables for structural specification"""
         if len(order) != len(self.params[0, :]):
-            raise ValueError("Reorder specification length should match "
-                             "number of endogenous variables")
+            raise ValueError(
+                "Reorder specification length should match "
+                "number of endogenous variables"
+            )
         # This converts order to list of integers if given as strings
         if isinstance(order[0], str):
             order_new = []
@@ -1748,10 +1879,11 @@ def reorder(self, order):
             order = order_new
         return _reordered(self, order)
 
-    # --------------------------------------------------------------------------
-    # VAR Diagnostics: Granger-causality, whiteness of residuals, normality, etc
+    # -----------------------------------------------------------
+    # VAR Diagnostics: Granger-causality, whiteness of residuals,
+    #                  normality, etc
 
-    def test_causality(self, caused, causing=None, kind='f', signif=0.05):
+    def test_causality(self, caused, causing=None, kind="f", signif=0.05):
         """
         Test Granger causality
 
@@ -1797,7 +1929,8 @@ def test_causality(self, caused, causing=None, kind='f', signif=0.05):
 
         References
         ----------
-        .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series Analysis*. Springer.
+        .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series*
+           *Analysis*. Springer.
         """
         if not (0 < signif < 1):
             raise ValueError("signif has to be between 0 and 1")
@@ -1807,8 +1940,10 @@ def test_causality(self, caused, causing=None, kind='f', signif=0.05):
         if isinstance(caused, allowed_types):
             caused = [caused]
         if not all(isinstance(c, allowed_types) for c in caused):
-            raise TypeError("caused has to be of type string or int (or a "
-                            "sequence of these types).")
+            raise TypeError(
+                "caused has to be of type string or int (or a "
+                "sequence of these types)."
+            )
         caused = [self.names[c] if type(c) == int else c for c in caused]
         caused_ind = [util.get_index(self.names, c) for c in caused]
 
@@ -1817,12 +1952,13 @@ def test_causality(self, caused, causing=None, kind='f', signif=0.05):
             if isinstance(causing, allowed_types):
                 causing = [causing]
             if not all(isinstance(c, allowed_types) for c in causing):
-                raise TypeError("causing has to be of type string or int (or "
-                                "a sequence of these types) or None.")
+                raise TypeError(
+                    "causing has to be of type string or int (or "
+                    "a sequence of these types) or None."
+                )
             causing = [self.names[c] if type(c) == int else c for c in causing]
             causing_ind = [util.get_index(self.names, c) for c in causing]
-
-        if causing is None:
+        else:
             causing_ind = [i for i in range(self.neqs) if i not in caused_ind]
             causing = [self.names[c] for c in caused_ind]
 
@@ -1836,13 +1972,13 @@ def test_causality(self, caused, causing=None, kind='f', signif=0.05):
         num_det_terms = self.k_exog
 
         # Make restriction matrix
-        C = np.zeros((num_restr, k * num_det_terms + k**2 * p), dtype=float)
+        C = np.zeros((num_restr, k * num_det_terms + k ** 2 * p), dtype=float)
         cols_det = k * num_det_terms
         row = 0
         for j in range(p):
             for ing_ind in causing_ind:
                 for ed_ind in caused_ind:
-                    C[row, cols_det + ed_ind + k * ing_ind + k**2 * j] = 1
+                    C[row, cols_det + ed_ind + k * ing_ind + k ** 2 * j] = 1
                     row += 1
 
         # Lütkepohl 3.6.5
@@ -1852,22 +1988,30 @@ def test_causality(self, caused, causing=None, kind='f', signif=0.05):
         # wald statistic
         lam_wald = statistic = Cb @ middle @ Cb
 
-        if kind.lower() == 'wald':
+        if kind.lower() == "wald":
             df = num_restr
             dist = stats.chi2(df)
-        elif kind.lower() == 'f':
+        elif kind.lower() == "f":
             statistic = lam_wald / num_restr
             df = (num_restr, k * self.df_resid)
             dist = stats.f(*df)
         else:
-            raise ValueError('kind %s not recognized' % kind)
+            raise ValueError("kind %s not recognized" % kind)
 
         pvalue = dist.sf(statistic)
         crit_value = dist.ppf(1 - signif)
 
-        return CausalityTestResults(causing, caused, statistic,
-                                    crit_value, pvalue, df, signif,
-                                    test="granger", method=kind)
+        return CausalityTestResults(
+            causing,
+            caused,
+            statistic,
+            crit_value,
+            pvalue,
+            df,
+            signif,
+            test="granger",
+            method=kind,
+        )
 
     def test_inst_causality(self, causing, signif=0.05):
         """
@@ -1878,8 +2022,8 @@ def test_inst_causality(self, causing, signif=0.05):
         causing :
             If int or str, test whether the corresponding variable is causing
             the variable(s) specified in caused.
-            If sequence of int or str, test whether the corresponding variables
-            are causing the variable(s) specified in caused.
+            If sequence of int or str, test whether the corresponding
+            variables are causing the variable(s) specified in caused.
         signif : float between 0 and 1, default 5 %
             Significance level for computing critical values for test,
             defaulting to standard 0.05 level
@@ -1922,14 +2066,15 @@ def test_inst_causality(self, causing, signif=0.05):
         causing" causing), thus the naming of the parameters (which is chosen
         to be in accordance with test_granger_causality()) may be misleading.
 
-        This method is not returning the same result as JMulTi. This is because
-        the test is based on a VAR(k_ar) model in statsmodels (in accordance to
-        pp. 104, 320-321 in [1]_) whereas JMulTi seems to be using a
-        VAR(k_ar+1) model.
+        This method is not returning the same result as JMulTi. This is
+        because the test is based on a VAR(k_ar) model in statsmodels
+        (in accordance to pp. 104, 320-321 in [1]_) whereas JMulTi seems
+        to be using a VAR(k_ar+1) model.
 
         References
         ----------
-        .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series Analysis*. Springer.
+        .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series*
+           *Analysis*. Springer.
         """
         if not (0 < signif < 1):
             raise ValueError("signif has to be between 0 and 1")
@@ -1938,8 +2083,10 @@ def test_inst_causality(self, causing, signif=0.05):
         if isinstance(causing, allowed_types):
             causing = [causing]
         if not all(isinstance(c, allowed_types) for c in causing):
-            raise TypeError("causing has to be of type string or int (or a " +
-                            "a sequence of these types).")
+            raise TypeError(
+                "causing has to be of type string or int (or a "
+                + "a sequence of these types)."
+            )
         causing = [self.names[c] if type(c) == int else c for c in causing]
         causing_ind = [util.get_index(self.names, c) for c in causing]
 
@@ -1977,9 +2124,17 @@ def test_inst_causality(self, causing, signif=0.05):
         pvalue = dist.sf(wald_statistic)
         crit_value = dist.ppf(1 - signif)
 
-        return CausalityTestResults(causing, caused, wald_statistic,
-                                    crit_value, pvalue, df, signif,
-                                    test="inst", method="wald")
+        return CausalityTestResults(
+            causing,
+            caused,
+            wald_statistic,
+            crit_value,
+            pvalue,
+            df,
+            signif,
+            test="inst",
+            method="wald",
+        )
 
     def test_whiteness(self, nlags=10, signif=0.05, adjusted=False):
         """
@@ -2007,30 +2162,34 @@ def test_whiteness(self, nlags=10, signif=0.05, adjusted=False):
 
         References
         ----------
-        .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series Analysis*. Springer.
+        .. [1] Lütkepohl, H. 2005. *New Introduction to Multiple Time Series*
+           *Analysis*. Springer.
         """
         if nlags - self.k_ar <= 0:
-            raise ValueError("The whiteness test can only be used when nlags "
-                             "is larger than the number of lags included in "
-                             f"the model ({self.k_ar}).")
+            raise ValueError(
+                "The whiteness test can only be used when nlags "
+                "is larger than the number of lags included in "
+                f"the model ({self.k_ar})."
+            )
         statistic = 0
         u = np.asarray(self.resid)
         acov_list = _compute_acov(u, nlags)
         cov0_inv = np.linalg.inv(acov_list[0])
-        for t in range(1, nlags+1):
+        for t in range(1, nlags + 1):
             ct = acov_list[t]
             to_add = np.trace(ct.T @ cov0_inv @ ct @ cov0_inv)
             if adjusted:
-                to_add /= (self.nobs - t)
+                to_add /= self.nobs - t
             statistic += to_add
-        statistic *= self.nobs**2 if adjusted else self.nobs
-        df = self.neqs**2 * (nlags - self.k_ar)
+        statistic *= self.nobs ** 2 if adjusted else self.nobs
+        df = self.neqs ** 2 * (nlags - self.k_ar)
         dist = stats.chi2(df)
         pvalue = dist.sf(statistic)
         crit_value = dist.ppf(1 - signif)
 
-        return WhitenessTestResults(statistic, crit_value, pvalue, df, signif,
-                                    nlags, adjusted)
+        return WhitenessTestResults(
+            statistic, crit_value, pvalue, df, signif, nlags, adjusted
+        )
 
     def plot_acorr(self, nlags=10, resid=True, linewidth=8):
         r"""
@@ -2061,10 +2220,12 @@ def plot_acorr(self, nlags=10, resid=True, linewidth=8):
 
         bound = 2 / np.sqrt(self.nobs)
 
-        fig = plotting.plot_full_acorr(acorrs[1:],
-                                       xlabel=np.arange(1, nlags+1),
-                                       err_bound=bound,
-                                       linewidth=linewidth)
+        fig = plotting.plot_full_acorr(
+            acorrs[1:],
+            xlabel=np.arange(1, nlags + 1),
+            err_bound=bound,
+            linewidth=linewidth,
+        )
         fig.suptitle(r"ACF plots for residuals with $2 / \sqrt{T}$ bounds ")
         return fig
 
@@ -2107,27 +2268,27 @@ def info_criteria(self):
         neqs = self.neqs
         lag_order = self.k_ar
         free_params = lag_order * neqs ** 2 + neqs * self.k_exog
-
-        ld = logdet_symm(self.sigma_u_mle)
+        if self.df_resid:
+            ld = logdet_symm(self.sigma_u_mle)
+        else:
+            ld = -np.inf
 
         # See Lütkepohl pp. 146-150
 
-        aic = ld + (2. / nobs) * free_params
+        aic = ld + (2.0 / nobs) * free_params
         bic = ld + (np.log(nobs) / nobs) * free_params
-        hqic = ld + (2. * np.log(np.log(nobs)) / nobs) * free_params
-        fpe = ((nobs + self.df_model) / self.df_resid) ** neqs * np.exp(ld)
+        hqic = ld + (2.0 * np.log(np.log(nobs)) / nobs) * free_params
+        if self.df_resid:
+            fpe = ((nobs + self.df_model) / self.df_resid) ** neqs * np.exp(ld)
+        else:
+            fpe = np.inf
 
-        return {
-            'aic': aic,
-            'bic': bic,
-            'hqic': hqic,
-            'fpe': fpe
-            }
+        return {"aic": aic, "bic": bic, "hqic": hqic, "fpe": fpe}
 
     @property
     def aic(self):
         """Akaike information criterion"""
-        return self.info_criteria['aic']
+        return self.info_criteria["aic"]
 
     @property
     def fpe(self):
@@ -2135,17 +2296,17 @@ def fpe(self):
 
         Lütkepohl p. 147, see info_criteria
         """
-        return self.info_criteria['fpe']
+        return self.info_criteria["fpe"]
 
     @property
     def hqic(self):
         """Hannan-Quinn criterion"""
-        return self.info_criteria['hqic']
+        return self.info_criteria["hqic"]
 
     @property
     def bic(self):
         """Bayesian a.k.a. Schwarz info criterion"""
-        return self.info_criteria['bic']
+        return self.info_criteria["bic"]
 
     @cache_readonly
     def roots(self):
@@ -2160,23 +2321,31 @@ def roots(self):
         p = neqs * k_ar
         arr = np.zeros((p, p))
         arr[:neqs, :] = np.column_stack(self.coefs)
-        arr[neqs:, :-neqs] = np.eye(p-neqs)
-        roots = np.linalg.eig(arr)[0]**-1
+        arr[neqs:, :-neqs] = np.eye(p - neqs)
+        roots = np.linalg.eig(arr)[0] ** -1
         idx = np.argsort(np.abs(roots))[::-1]  # sort by reverse modulus
         return roots[idx]
 
 
 class VARResultsWrapper(wrap.ResultsWrapper):
-    _attrs = {'bse': 'columns_eq', 'cov_params': 'cov',
-              'params': 'columns_eq', 'pvalues': 'columns_eq',
-              'tvalues': 'columns_eq', 'sigma_u': 'cov_eq',
-              'sigma_u_mle': 'cov_eq',
-              'stderr': 'columns_eq'}
-    _wrap_attrs = wrap.union_dicts(TimeSeriesResultsWrapper._wrap_attrs,
-                                   _attrs)
-    _methods = {'conf_int': 'multivariate_confint'}
-    _wrap_methods = wrap.union_dicts(TimeSeriesResultsWrapper._wrap_methods,
-                                     _methods)
+    _attrs = {
+        "bse": "columns_eq",
+        "cov_params": "cov",
+        "params": "columns_eq",
+        "pvalues": "columns_eq",
+        "tvalues": "columns_eq",
+        "sigma_u": "cov_eq",
+        "sigma_u_mle": "cov_eq",
+        "stderr": "columns_eq",
+    }
+    _wrap_attrs = wrap.union_dicts(
+        TimeSeriesResultsWrapper._wrap_attrs, _attrs
+    )
+    _methods = {"conf_int": "multivariate_confint"}
+    _wrap_methods = wrap.union_dicts(
+        TimeSeriesResultsWrapper._wrap_methods, _methods
+    )
+
 
 wrap.populate_wrapper(VARResultsWrapper, VARResults)  # noqa:E305
 
@@ -2186,6 +2355,7 @@ class FEVD(object):
     Compute and plot Forecast error variance decomposition and asymptotic
     standard errors
     """
+
     def __init__(self, model, P=None, periods=None):
         self.periods = periods
 
@@ -2218,8 +2388,8 @@ def summary(self):
         for i in range(self.neqs):
             ppm = output.pprint_matrix(self.decomp[i], rng, self.names)
 
-            buf.write('FEVD for %s\n' % self.names[i])
-            buf.write(ppm + '\n')
+            buf.write("FEVD for %s\n" % self.names[i])
+            buf.write(ppm + "\n")
 
         print(buf.getvalue())
 
@@ -2246,13 +2416,13 @@ def plot(self, periods=None, figsize=(10, 10), **plot_kwds):
 
         fig, axes = plt.subplots(nrows=k, figsize=figsize)
 
-        fig.suptitle('Forecast error variance decomposition (FEVD)')
+        fig.suptitle("Forecast error variance decomposition (FEVD)")
 
         colors = [str(c) for c in np.arange(k, dtype=float) / k]
         ticks = np.arange(periods)
 
         limits = self.decomp.cumsum(2)
-
+        ax = axes[0]
         for i in range(k):
             ax = axes[i]
 
@@ -2263,9 +2433,14 @@ def plot(self, periods=None, figsize=(10, 10), **plot_kwds):
             for j in range(k):
                 lower = this_limits[j - 1] if j > 0 else 0
                 upper = this_limits[j]
-                handle = ax.bar(ticks, upper - lower, bottom=lower,
-                                color=colors[j], label=self.names[j],
-                                **plot_kwds)
+                handle = ax.bar(
+                    ticks,
+                    upper - lower,
+                    bottom=lower,
+                    color=colors[j],
+                    label=self.names[j],
+                    **plot_kwds,
+                )
 
                 handles.append(handle)
 
@@ -2273,10 +2448,11 @@ def plot(self, periods=None, figsize=(10, 10), **plot_kwds):
 
         # just use the last axis to get handles for plotting
         handles, labels = ax.get_legend_handles_labels()
-        fig.legend(handles, labels, loc='upper right')
+        fig.legend(handles, labels, loc="upper right")
         plotting.adjust_subplots(right=0.85)
         return fig
 
+
 # -------------------------------------------------------------------------------
 
 
@@ -2298,51 +2474,3 @@ def _compute_acov(x, nlags=1):
 def _acovs_to_acorrs(acovs):
     sd = np.sqrt(np.diag(acovs[0]))
     return acovs / np.outer(sd, sd)
-
-
-if __name__ == '__main__':
-    import statsmodels.tools.data as data_util
-    from statsmodels.tsa.vector_ar.util import parse_lutkepohl_data
-
-    np.set_printoptions(linewidth=140, precision=5)
-
-    sdata, dates = parse_lutkepohl_data('data/%s.dat' % 'e1')
-
-    names = sdata.dtype.names
-    data = data_util.struct_to_ndarray(sdata)
-    adj_data = np.diff(np.log(data), axis=0)
-    # est = VAR(adj_data, p=2, dates=dates[1:], names=names)
-    model = VAR(adj_data[:-16], dates=dates[1:-16], names=names)
-    # model = VAR(adj_data[:-16], dates=dates[1:-16], names=names)
-
-    est = model.fit(maxlags=2)
-    irf = est.irf()
-
-    y = est.endog[-2:]
-    """
-    # irf.plot_irf()
-
-    # i = 2; j = 1
-    # cv = irf.cum_effect_cov(orth=True)
-    # print np.sqrt(cv[:, j * 3 + i, j * 3 + i]) / 1e-2
-
-    # data = np.genfromtxt('Canada.csv', delimiter=',', names=True)
-    # data = data.view((float, 4))
-    """
-
-    '''
-    mdata = sm.datasets.macrodata.load().data
-    mdata2 = mdata[['realgdp','realcons','realinv']]
-    names = mdata2.dtype.names
-    data = mdata2.view((float,3))
-    data = np.diff(np.log(data), axis=0)
-
-    import pandas as pn
-    df = pn.DataFrame.fromRecords(mdata)
-    df = np.log(df.reindex(columns=names))
-    df = (df - df.shift(1)).dropna()
-
-    model = VAR(df)
-    est = model.fit(maxlags=2)
-    irf = est.irf()
-    '''


Test patch: