Repository: statsmodels/statsmodels

Issue:
seasonal_decompose() With Known Freq but Without DatetimeIndex()
In issue #3225, someone wrote "In 0.8, you should be able to specify freq as keyword argument to override the index." But I seem to still be having the same problems as the questioner there, with my data.

Here is a simple reproducible code example of my issue:
```
import statsmodels.api as sm
dta = pd.Series([x%3 for x in range(100)])
decomposed = sm.tsa.seasonal_decompose(dta, freq=3)
```

> AttributeError: 'RangeIndex' object has no attribute 'inferred_freq'

```
import statsmodels
print(statsmodels.__version__)
```

> 0.8.0

Should it be possible to use StatsModels seasonal_decompose() with a given frequency but without a DatetimeIndex? With my real data, I know it is of half-hour resolution, but I don't know the dates/times it corresponds to, so there's no neat/clean way of mapping it to a datetime index.

I've also posted this question on [StackOverflow](http://stackoverflow.com/questions/42425774/using-statsmodels-seasonal-decompose-without-datetimeindex-but-with-known-freq) and will add an answer there if I get one here. Thanks everyone for your time.


Discussion:
I haven't checked yet what is going on, but I get the same exception with master. I thought the index would be completely ignored.

workaround is to use a numpy array, i.e. either .values or np.asarray:

`decomposed = sm.tsa.seasonal_decompose(np.asarray(dta), freq=3)`
OK, it's in the wrapper code
`from .filters._utils import _maybe_get_pandas_wrapper_freq`

accesses the attribute `inferred_freq` without protection, even if we don't need the freq

adding prio-high because it's a popular function
my guess for a solution (based on a brief look) is that we should use either one of the following depending on whether `freq` is specified:

`_pandas_wrapper, pfreq = _maybe_get_pandas_wrapper_freq(x)`
or
`_pandas_wrapper, pfreq = _maybe_get_pandas_wrapper(x)`

The if `freq` is further down in the function, so there might be some reshuflling needed.

or we just change `_maybe_get_pandas_wrapper_freq` to return pfreq is None if attribute is not available, instead of letting it raise an exception
note getting freq is the only difference between the two _maybe_get_pandas_wrapper functions.
Also, if we release an 0.8.1, then this would be a backport candidate
Thanks Josef,

Looking over the source code, the first line of `seasonal_decompose` is:

`_pandas_wrapper, pfreq = _maybe_get_pandas_wrapper_freq(x)
`
and then in `_maybe_get_pandas_wrapper_freq`, if the `x` that was passed is a Pandas object, this line of code is always executed:
`freq = index.inferred_freq
`

Does the following seem like a viable solution to you?

What if `seasonal_decompose` also passed its own `freq` (`None` unless the caller specified one, as I did in this issue) to `_maybe_get_pandas_wrapper_freq`? Then the line of code I posted above could be changed to:
```
if freq is not None:
    freq = index.inferred_freq
```

`_maybe_get_pandas_wrapper_freq` could still have the same return statement: 
`return func, freq 
`

it would just return the `freq` it was given (if it was given one), or the `freq` it inferred if it was not given one (if it was given `freq=None`).
It's also a possible solution.

But I think all these solutions are a bit to roundabout for my. We missed this when we changed the behavior if freq is defined because that check is hiding in a utility function.
If possible, I prefer a solution where the behavior becomes clear from looking at seasonal_compose.
E.g. there is still the other if freq is None further down in seasonal compose.

If possible I would like a refactor to use something like this at the beginning of seasonal_decompose
```
if freq is not None:
      don't look at index at all (i.e. keep it just so we can return a pandas Series)
else:
     use implied freq or raise
```

That makes sense to me Josef. I agree the cleaner solution would be to move up the check for freq within `seasonal_compose()` as you suggest. I'll use your .values workaround for now. Thanks again.

Resolving patch:
diff --git a/statsmodels/tsa/filters/_utils.py b/statsmodels/tsa/filters/_utils.py
--- a/statsmodels/tsa/filters/_utils.py
+++ b/statsmodels/tsa/filters/_utils.py
@@ -35,7 +35,7 @@ def _maybe_get_pandas_wrapper(X, trim_head=None, trim_tail=None):
     if _is_using_pandas(X, None):
         return _get_pandas_wrapper(X, trim_head, trim_tail)
     else:
-        return
+        return lambda x : x
 
 
 def _maybe_get_pandas_wrapper_freq(X, trim=None):
diff --git a/statsmodels/tsa/seasonal.py b/statsmodels/tsa/seasonal.py
--- a/statsmodels/tsa/seasonal.py
+++ b/statsmodels/tsa/seasonal.py
@@ -4,7 +4,8 @@
 from statsmodels.compat.python import lmap, range, iteritems
 import numpy as np
 from pandas.core.nanops import nanmean as pd_nanmean
-from .filters._utils import _maybe_get_pandas_wrapper_freq
+from .filters._utils import (_maybe_get_pandas_wrapper_freq,
+                             _maybe_get_pandas_wrapper)
 from .filters.filtertools import convolution_filter
 from statsmodels.tsa.tsatools import freq_to_period
 
@@ -65,7 +66,11 @@ def seasonal_decompose(x, model="additive", filt=None, freq=None, two_sided=True
     statsmodels.tsa.filters.hp_filter.hpfilter
     statsmodels.tsa.filters.convolution_filter
     """
-    _pandas_wrapper, pfreq = _maybe_get_pandas_wrapper_freq(x)
+    if freq is None:
+        _pandas_wrapper, pfreq = _maybe_get_pandas_wrapper_freq(x)
+    else:
+        _pandas_wrapper = _maybe_get_pandas_wrapper(x)
+        pfreq = None
     x = np.asanyarray(x).squeeze()
     nobs = len(x)
 


Test patch:
diff --git a/statsmodels/tsa/tests/test_seasonal.py b/statsmodels/tsa/tests/test_seasonal.py
--- a/statsmodels/tsa/tests/test_seasonal.py
+++ b/statsmodels/tsa/tests/test_seasonal.py
@@ -1,5 +1,7 @@
 import numpy as np
-from numpy.testing import assert_almost_equal, assert_equal, assert_raises
+import pandas as pd
+from numpy.testing import (assert_almost_equal, assert_equal, assert_raises,
+                           assert_allclose)
 from statsmodels.tsa.seasonal import seasonal_decompose
 from pandas import DataFrame, DatetimeIndex
 
@@ -138,6 +140,21 @@ def test_pandas(self):
         assert_equal(res_mult.seasonal.index.values.squeeze(),
                             self.data.index.values)
 
+    def test_pandas_nofreq(self):
+        # issue #3503
+        nobs = 100
+        dta = pd.Series([x%3 for x in range(nobs)] + np.random.randn(nobs))
+        res_np = seasonal_decompose(dta.values, freq=3)
+        res = seasonal_decompose(dta, freq=3)
+
+        atol = 1e-8
+        rtol = 1e-10
+        assert_allclose(res.seasonal.values.squeeze(), res_np.seasonal,
+                        atol=atol, rtol=rtol)
+        assert_allclose(res.trend.values.squeeze(), res_np.trend,
+                        atol=atol, rtol=rtol)
+        assert_allclose(res.resid.values.squeeze(), res_np.resid,
+                        atol=atol, rtol=rtol)
 
     def test_filt(self):
         filt = np.array([1/8., 1/4., 1./4, 1/4., 1/8.])