Repository: statsmodels/statsmodels

Issue:
Avoid np.linalg.inv call in regression.rolling.RollingWLS.fit if output is not used
#### Problem description

In the documentation of `statsmodels.regression.rolling.RollingWLS.fit` the parameter `method` is described as 

> method{‘inv’, ‘lstsq’, ‘pinv’}
> Method to use when computing the the model parameters.
> ‘inv’ - use moving windows inner-products and matrix inversion. This method is the fastest, but may be less accurate than the other methods.
> ‘lstsq’ - Use numpy.linalg.lstsq
> ‘pinv’ - Use numpy.linalg.pinv. This method matches the default estimator in non-moving regression estimators.

An obvious reason that 'inv' is fastest in the current implementation to be that `np.linalg.inv` is called also for 'lstsq' and 'pinv':
![image](https://user-images.githubusercontent.com/8350047/97979299-31732e80-1dcf-11eb-8e4c-11e257af684a.png)

(ll. 222 in https://github.com/statsmodels/statsmodels/blob/master/statsmodels/regression/rolling.py)

If `params_only==True` then `wxpwxi` seems to be never used but has impact on performance. 

#### Proposed solution
Doing in the try-except block quoted above something like   
```python
    if method == "inv" or not params_only:
        wxspwxi = np.linalg.inv(wxpwx)
    [...]
```
should speed up the the method for `method=='lstsq'` and `method=='pinv'`. 



Discussion:

Resolving patch:
diff --git a/statsmodels/regression/rolling.py b/statsmodels/regression/rolling.py
--- a/statsmodels/regression/rolling.py
+++ b/statsmodels/regression/rolling.py
@@ -223,7 +223,8 @@ def _fit_single(self, idx, wxpwx, wxpwy, nobs, store, params_only, method):
         if nobs < self._min_nobs:
             return
         try:
-            wxpwxi = np.linalg.inv(wxpwx)
+            if (method == "inv") or not params_only:
+                wxpwxi = np.linalg.inv(wxpwx)
             if method == "inv":
                 params = wxpwxi @ wxpwy
             else:


Test patch:
diff --git a/statsmodels/regression/tests/test_rolling.py b/statsmodels/regression/tests/test_rolling.py
--- a/statsmodels/regression/tests/test_rolling.py
+++ b/statsmodels/regression/tests/test_rolling.py
@@ -268,16 +268,27 @@ def test_plot():
     res.plot_recursive_coefficient(alpha=None, figsize=(30, 7))
 
 
-def test_methods(basic_data):
+@pytest.mark.parametrize("params_only", [True, False])
+def test_methods(basic_data, params_only):
     y, x, _ = basic_data
     mod = RollingOLS(y, x, 150)
-    res_inv = mod.fit(method="inv")
-    res_lstsq = mod.fit(method="lstsq")
-    res_pinv = mod.fit(method="pinv")
+    res_inv = mod.fit(method="inv", params_only=params_only)
+    res_lstsq = mod.fit(method="lstsq", params_only=params_only)
+    res_pinv = mod.fit(method="pinv", params_only=params_only)
     assert_allclose(res_inv.params, res_lstsq.params)
     assert_allclose(res_inv.params, res_pinv.params)
 
 
+@pytest.mark.parametrize("method", ["inv", "lstsq", "pinv"])
+def test_params_only(basic_data, method):
+    y, x, _ = basic_data
+    mod = RollingOLS(y, x, 150)
+    res = mod.fit(method=method, params_only=False)
+    res_params_only = mod.fit(method=method, params_only=True)
+    # use assert_allclose to incorporate for numerical errors on x86 platforms
+    assert_allclose(res_params_only.params, res.params)
+
+
 def test_min_nobs(basic_data):
     y, x, w = basic_data
     if not np.any(np.isnan(np.asarray(x))):