Repository: statsmodels/statsmodels

Issue:
BUG: Statespace: `smooth` in multivariate should return corresponding results objects
After the `fit` method was switched to return smoothed results by default, the multivariate models weren't updated to override `smooth` to return their corresponding results objects instead of the default one.



Discussion:

Resolving patch:
diff --git a/statsmodels/tsa/statespace/dynamic_factor.py b/statsmodels/tsa/statespace/dynamic_factor.py
--- a/statsmodels/tsa/statespace/dynamic_factor.py
+++ b/statsmodels/tsa/statespace/dynamic_factor.py
@@ -461,6 +461,31 @@ def filter(self, params, transformed=True, cov_type=None, return_ssm=False,
         return result
     filter.__doc__ = MLEModel.filter.__doc__
 
+    def smooth(self, params, transformed=True, cov_type=None, return_ssm=False,
+               **kwargs):
+        params = np.array(params, ndmin=1)
+
+        # Transform parameters if necessary
+        if not transformed:
+            params = self.transform_params(params)
+            transformed = True
+
+        # Get the state space output
+        result = super(DynamicFactor, self).smooth(params, transformed,
+                       cov_type, return_ssm=True, **kwargs)
+
+        # Wrap in a results object
+        if not return_ssm:
+            result_kwargs = {}
+            if cov_type is not None:
+                result_kwargs['cov_type'] = cov_type
+            result = DynamicFactorResultsWrapper(
+                DynamicFactorResults(self, params, result, **result_kwargs)
+            )
+
+        return result
+    smooth.__doc__ = MLEModel.smooth.__doc__
+
     @property
     def start_params(self):
         params = np.zeros(self.k_params, dtype=np.float64)
@@ -1032,9 +1057,10 @@ def factors(self):
                 smoothed=None, smoothed_cov=None,
                         offset=offset)
             if self.smoothed_state is not None:
-                out.smoothed = self.smoothed_state[offset]
+                out.smoothed = self.smoothed_state[offset:end]
             if self.smoothed_state_cov is not None:
-                out.smoothed_cov = self.smoothed_state_cov[offset, offset]
+                out.smoothed_cov = (
+                    self.smoothed_state_cov[offset:end, offset:end])
         return out
 
     @cache_readonly
diff --git a/statsmodels/tsa/statespace/varmax.py b/statsmodels/tsa/statespace/varmax.py
--- a/statsmodels/tsa/statespace/varmax.py
+++ b/statsmodels/tsa/statespace/varmax.py
@@ -304,7 +304,32 @@ def filter(self, params, transformed=True, cov_type=None, return_ssm=False,
             )
 
         return result
-        filter.__doc__ = MLEModel.filter.__doc__
+    filter.__doc__ = MLEModel.filter.__doc__
+
+    def smooth(self, params, transformed=True, cov_type=None, return_ssm=False,
+               **kwargs):
+        params = np.array(params, ndmin=1)
+
+        # Transform parameters if necessary
+        if not transformed:
+            params = self.transform_params(params)
+            transformed = True
+
+        # Get the state space output
+        result = super(VARMAX, self).smooth(params, transformed,
+                       cov_type, return_ssm=True, **kwargs)
+
+        # Wrap in a results object
+        if not return_ssm:
+            result_kwargs = {}
+            if cov_type is not None:
+                result_kwargs['cov_type'] = cov_type
+            result = VARMAXResultsWrapper(
+                VARMAXResults(self, params, result, **result_kwargs)
+            )
+
+        return result
+    smooth.__doc__ = MLEModel.smooth.__doc__
 
     @property
     def start_params(self):


Test patch:
diff --git a/statsmodels/tsa/statespace/tests/test_dynamic_factor.py b/statsmodels/tsa/statespace/tests/test_dynamic_factor.py
--- a/statsmodels/tsa/statespace/tests/test_dynamic_factor.py
+++ b/statsmodels/tsa/statespace/tests/test_dynamic_factor.py
@@ -55,7 +55,7 @@ def __init__(self, true, k_factors, factor_order, cov_type='oim',
                                                   **kwargs)
 
         if filter:
-            self.results = self.model.filter(true['params'], cov_type=cov_type)
+            self.results = self.model.smooth(true['params'], cov_type=cov_type)
 
     def test_params(self):
         # Smoke test to make sure the start_params are well-defined and
diff --git a/statsmodels/tsa/statespace/tests/test_varmax.py b/statsmodels/tsa/statespace/tests/test_varmax.py
--- a/statsmodels/tsa/statespace/tests/test_varmax.py
+++ b/statsmodels/tsa/statespace/tests/test_varmax.py
@@ -139,7 +139,7 @@ def __init__(self, true, order, trend, error_cov_type, cov_type='oim',
         self.model = varmax.VARMAX(endog, order=order, trend=trend,
                                    error_cov_type=error_cov_type, **kwargs)
 
-        self.results = self.model.filter(true['params'], cov_type=cov_type)
+        self.results = self.model.smooth(true['params'], cov_type=cov_type)
 
     def test_predict(self, **kwargs):
         super(CheckLutkepohl, self).test_predict(end='1982-10-01', **kwargs)
@@ -261,7 +261,7 @@ def __init__(self):
         # Create another filter results with positive measurement errors
         self.true_measurement_error_variances = [1., 2., 3.]
         params = np.r_[true['params'][:-3], self.true_measurement_error_variances]
-        self.results2 = self.model.filter(params)
+        self.results2 = self.model.smooth(params)
 
     def test_mle(self):
         # With the additional measurment error parameters, this wouldn't be
@@ -549,7 +549,7 @@ def __init__(self, true, order, trend, error_cov_type, cov_type='oim',
             self.model = varmax.VARMAX(endog, order=order, trend=trend,
                                        error_cov_type=error_cov_type, **kwargs)
 
-        self.results = self.model.filter(true['params'], cov_type=cov_type)
+        self.results = self.model.smooth(true['params'], cov_type=cov_type)
 
 
 class TestVARMA(CheckFREDManufacturing):