Repository: sktime/sktime

Issue:
[ENH] VAR and VECM models
@fkiraly update: almost done, all that is missing is `predict_interval` for VECM.

---

@fkiraly's summary of the discussion:

We should interface VAR and VECM from `statsmodels`.

- [x] VAR #1083, #1491
- [x] VECM #1141, #2829

---

Have you considered porting `Vector Autoregressions (VAR)` and `Vector Error Correction Models (VECM)` from [statsmodels](https://www.statsmodels.org/dev/vector_ar.html#)? This would be a great addition. Thank you


Discussion:
we first need to consolidate the multivariate forecasting interface, see #919 and #228  
but yes, important next step - join the dev days if interested #863 
I'm very glad to know, thank you for the feedback
As discussed in the latest mentoring meeting @thayeylolu will be working on this.

The interface will assume `pd.DataFrame` as input to `_fit`, and as return from `_predict`, no other types for the moment.

This means it should eventually be compatible irrespective of whichever of the two proposed designs #980 and #1074 we go with.

FYI, @mloning.
Other implementation notes

* we should start by writing a short user journey design
* the statsmodels adapter may be a good way to go, but needs investigating
* tests may also need looking at, e.g., multivariate input generation
##### USER JOURNEY DESIGN
-----
**Vector Auto Regressor**

**Primary use case** :Vector Auto Regression of a data frame with more one columns
**Input Type** : None

###### Methods
- fit (y,fh)
```python
-fit(y: dataframe, X:None, fh:np.ndarray, pd.Series)
- predict(self, fh, X=None, return_pred_int=False, alpha=DEFAULT_ALPHA)
```
   - expected input : `y`,`fh`
   - expected output: `self`
```python
- predict()
```
  - inherits from `_StatsModelsAdapter`
  - expected output: `pd.dataframe`


``` python

model = VAR()
model.fit(y=y, fh=fh, X=X)
model.predict()

```

#### Wrapped VAR

``` python
model = VAR(**params)
model.fit(y=y, fh=fh, X=X)
model.predict(X)
```
#### Statsmodel's VAR
```
model = VAR(endog = y, exog = X)
statvar = model.fit(**params)
statvar.forecast(y=forecast_input, steps=fh, exog_future = X)
```





this is not a complete user journey design - this should include specification, and usage (e.g., fitting, forecasting) in key use cases.

I also don't think that data should ever be passed to a constructor - look at the forecasting extension template, constructor arguments are reserved to be hyper-parameters and components (if any)
Hi @fkiraly , I have updated the User Journey
just confirming what I said verbally earlier, the user journey should probably look like this:
```params
model = VAR(**params)
model.fit(y=y, fh=fh, X=X)
model.predict(X_new)
```

where `model.predict(X_new)` returns a data frame at indices of `fh`, and both `y` and `X` are data frame
FYI @GitHunter0, VAR is now merged (after some back/forth related not to VAR itself but to the testing framework)
Thanks @fkiraly , it is great to know! 
VECM is still open, adding good first issue tag.
Hey @fkiraly, If open I would like to take up VECM
Great, @AurumnPegasus, all yours!
Hey @fkiraly ,
Had a doubt related to one of the variables used in VECM class of statsmodels:

```
exog_coint : ndarray (nobs_tot x neqs) or None.
Deterministic terms inside the cointegration relation.
```

This variables is used in `estimate_vecm_ml` function of statsmodel's VECM class, and is required for getting the desired result. Now, in the estimator template for forecaster, the `_fit` method only takes `x`, `y` and `fh`. Since the parameter does not seem to be model parameter, but more related to input data, I was wondering if I could add another parameter to the `_fit` method to represent `exog_coint` and what should the variable name be (I am not very sure what significance it has in statistics).

Resolving patch:
diff --git a/sktime/forecasting/vecm.py b/sktime/forecasting/vecm.py
--- a/sktime/forecasting/vecm.py
+++ b/sktime/forecasting/vecm.py
@@ -3,6 +3,7 @@
 
 """VECM Forecaster."""
 
+
 __all__ = ["VECM"]
 __author__ = ["thayeylolu", "AurumnPegasus"]
 
@@ -84,6 +85,7 @@ class VECM(_StatsModelsAdapter):
         "requires-fh-in-fit": False,
         "univariate-only": False,
         "ignores-exogeneous-X": False,
+        "capability:pred_int": True,
     }
 
     def __init__(
@@ -210,3 +212,70 @@ def _predict(self, fh, X=None):
         )
 
         return y_pred
+
+    def _predict_interval(self, fh, X=None, coverage=None):
+        """
+        Compute/return prediction quantiles for a forecast.
+
+        private _predict_interval containing the core logic,
+            called from predict_interval and possibly predict_quantiles
+        State required:
+            Requires state to be "fitted".
+        Accesses in self:
+            Fitted model attributes ending in "_"
+            self.cutoff
+
+        Parameters
+        ----------
+        fh : guaranteed to be ForecastingHorizon
+            The forecasting horizon with the steps ahead to to predict.
+        X : optional (default=None)
+            guaranteed to be of a type in self.get_tag("X_inner_mtype")
+            Exogeneous time series for the forecast
+        coverage : list of float (guaranteed not None and floats in [0,1] interval)
+           nominal coverage(s) of predictive interval(s)
+
+        Returns
+        -------
+        pred_int : pd.DataFrame
+            Column has multi-index: first level is variable name from y in fit,
+                second level coverage fractions for which intervals were computed.
+                    in the same order as in input `coverage`.
+                Third level is string "lower" or "upper", for lower/upper interval end.
+            Row index is fh, with additional (upper) levels equal to instance levels,
+                from y seen in fit, if y_inner_mtype is Panel or Hierarchical.
+            Entries are forecasts of lower/upper interval end,
+                for var in col index, at nominal coverage in second col index,
+                lower/upper depending on third col index, for the row index.
+                Upper/lower interval end forecasts are equivalent to
+                quantile forecasts at alpha = 0.5 - c/2, 0.5 + c/2 for c in coverage.
+        """
+        exog_fc = X.values if X is not None else None
+        fh_oos = fh.to_out_of_sample(self.cutoff)
+        var_names = (
+            self._y.index.name
+            if self._y.index.name is not None
+            else self._y.columns.values
+        )
+        int_idx = pd.MultiIndex.from_product([var_names, coverage, ["lower", "upper"]])
+        # pred_int = pd.DataFrame(index=int_idx)
+
+        for c in coverage:
+            alpha = 1 - c
+            _, y_lower, y_upper = self._fitted_forecaster.predict(
+                steps=fh_oos[-1],
+                exog_fc=exog_fc,
+                exog_coint_fc=self.exog_coint_fc,
+                alpha=alpha,
+            )
+            values = []
+            for v_idx in range(len(var_names)):
+                values.append(y_lower[0][v_idx])
+                values.append(y_upper[0][v_idx])
+                # pred_int.loc[(var_names[v_idx], c, "lower"), :] = (y_lower[0][v_idx])
+                # pred_int.loc[(var_names[v_idx], c, "upper"), :] = (y_upper[0][v_idx])
+        pred_int = pd.DataFrame(
+            [values], index=fh.to_absolute(self.cutoff), columns=int_idx
+        )
+
+        return pred_int


Test patch: