Skip to content

Commit

Permalink
Merge 631ac54 into 7d9d96b
Browse files Browse the repository at this point in the history
  • Loading branch information
ChadFulton committed Sep 3, 2020
2 parents 7d9d96b + 631ac54 commit b72ea0e
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 29 deletions.
7 changes: 4 additions & 3 deletions statsmodels/tsa/arima/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def __init__(self, endog, exog=None, order=(0, 0, 0),
seasonal_order=(0, 0, 0, 0), trend=None,
enforce_stationarity=True, enforce_invertibility=True,
concentrate_scale=False, trend_offset=1, dates=None,
freq=None, missing='none'):
freq=None, missing='none', validate_specification=True):
# Default for trend
# 'c' if there is no integration and 'n' otherwise
# TODO: if trend='c', then we could alternatively use `demean=True` in
Expand All @@ -131,7 +131,8 @@ def __init__(self, endog, exog=None, order=(0, 0, 0),
endog, exog=exog, order=order, seasonal_order=seasonal_order,
trend=trend, enforce_stationarity=None, enforce_invertibility=None,
concentrate_scale=concentrate_scale, trend_offset=trend_offset,
dates=dates, freq=freq, missing=missing)
dates=dates, freq=freq, missing=missing,
validate_specification=validate_specification)
exog = self._spec_arima._model.data.orig_exog

# Keep the given `exog` by removing the prepended trend variables
Expand All @@ -152,7 +153,7 @@ def __init__(self, endog, exog=None, order=(0, 0, 0),
enforce_stationarity=enforce_stationarity,
enforce_invertibility=enforce_invertibility,
concentrate_scale=concentrate_scale, dates=dates, freq=freq,
missing=missing)
missing=missing, validate_specification=validate_specification)
self.trend = trend

# Save the input exog and input exog names, so that we can refer to
Expand Down
53 changes: 30 additions & 23 deletions statsmodels/tsa/arima/specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def __init__(self, endog=None, exog=None, order=None,
seasonal_ma_order=None, seasonal_periods=None, trend=None,
enforce_stationarity=None, enforce_invertibility=None,
concentrate_scale=None, trend_offset=1, dates=None, freq=None,
missing='none'):
missing='none', validate_specification=True):

# Basic parameters
self.enforce_stationarity = enforce_stationarity
Expand Down Expand Up @@ -271,17 +271,20 @@ def __init__(self, endog=None, exog=None, order=None,
' with four elements.')

# Validate differencing parameters
if order[1] < 0:
raise ValueError('Cannot specify negative differencing.')
if order[1] != int(order[1]):
raise ValueError('Cannot specify fractional differencing.')
if seasonal_order[1] < 0:
raise ValueError('Cannot specify negative seasonal differencing.')
if seasonal_order[1] != int(seasonal_order[1]):
raise ValueError('Cannot specify fractional seasonal'
' differencing.')
if seasonal_order[3] < 0:
raise ValueError('Cannot specify negative seasonal periodicity.')
if validate_specification:
if order[1] < 0:
raise ValueError('Cannot specify negative differencing.')
if order[1] != int(order[1]):
raise ValueError('Cannot specify fractional differencing.')
if seasonal_order[1] < 0:
raise ValueError('Cannot specify negative seasonal'
' differencing.')
if seasonal_order[1] != int(seasonal_order[1]):
raise ValueError('Cannot specify fractional seasonal'
' differencing.')
if seasonal_order[3] < 0:
raise ValueError('Cannot specify negative seasonal'
' periodicity.')

# Standardize to integers or lists of integers
order = (
Expand All @@ -295,12 +298,15 @@ def __init__(self, endog=None, exog=None, order=None,
int(seasonal_order[3]))

# Validate seasonals
if seasonal_order[3] == 1:
raise ValueError('Seasonal periodicity must be greater than 1.')
if ((seasonal_order[0] != 0 or seasonal_order[1] != 0 or
seasonal_order[2] != 0) and seasonal_order[3] == 0):
raise ValueError('Must include nonzero seasonal periodicity if'
' including seasonal AR, MA, or differencing.')
if validate_specification:
if seasonal_order[3] == 1:
raise ValueError('Seasonal periodicity must be greater'
' than 1.')
if ((seasonal_order[0] != 0 or seasonal_order[1] != 0 or
seasonal_order[2] != 0) and seasonal_order[3] == 0):
raise ValueError('Must include nonzero seasonal periodicity if'
' including seasonal AR, MA, or'
' differencing.')

# Basic order
self.order = order
Expand Down Expand Up @@ -353,7 +359,7 @@ def __init__(self, endog=None, exog=None, order=None,
seasonal_ar_lags = set(np.array(self.seasonal_ar_lags)
* self.seasonal_periods)
duplicate_ar_lags = ar_lags.intersection(seasonal_ar_lags)
if len(duplicate_ar_lags) > 0:
if validate_specification and len(duplicate_ar_lags) > 0:
raise ValueError('Invalid model: autoregressive lag(s) %s are'
' in both the seasonal and non-seasonal'
' autoregressive components.'
Expand All @@ -363,7 +369,7 @@ def __init__(self, endog=None, exog=None, order=None,
seasonal_ma_lags = set(np.array(self.seasonal_ma_lags)
* self.seasonal_periods)
duplicate_ma_lags = ma_lags.intersection(seasonal_ma_lags)
if len(duplicate_ma_lags) > 0:
if validate_specification and len(duplicate_ma_lags) > 0:
raise ValueError('Invalid model: moving average lag(s) %s are'
' in both the seasonal and non-seasonal'
' moving average components.'
Expand All @@ -375,8 +381,8 @@ def __init__(self, endog=None, exog=None, order=None,

# Check for a constant column in the provided exog
exog_is_pandas = _is_using_pandas(exog, None)
if (exog is not None and len(self.trend_poly) > 0 and
self.trend_poly[0] == 1):
if (validate_specification and exog is not None and
len(self.trend_poly) > 0 and self.trend_poly[0] == 1):
# Figure out if we have any constant columns
x = np.asanyarray(exog)
ptp0 = np.ptp(x, axis=0)
Expand Down Expand Up @@ -443,7 +449,8 @@ def __init__(self, endog=None, exog=None, order=None,
self.exog = self._model.exog

# Validate endog shape
if not faux_endog and self.endog.ndim > 1 and self.endog.shape[1] > 1:
if (validate_specification and not faux_endog and
self.endog.ndim > 1 and self.endog.shape[1] > 1):
raise ValueError('SARIMAX models require univariate `endog`. Got'
' shape %s.' % str(self.endog.shape))

Expand Down
8 changes: 5 additions & 3 deletions statsmodels/tsa/statespace/sarimax.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,14 @@ def __init__(self, endog, exog=None, order=(1, 0, 0),
enforce_stationarity=True, enforce_invertibility=True,
hamilton_representation=False, concentrate_scale=False,
trend_offset=1, use_exact_diffuse=False, dates=None,
freq=None, missing='none', **kwargs):
freq=None, missing='none', validate_specification=True,
**kwargs):

self._spec = SARIMAXSpecification(
endog, exog=exog, order=order, seasonal_order=seasonal_order,
trend=trend, enforce_stationarity=None, enforce_invertibility=None,
concentrate_scale=concentrate_scale, dates=dates, freq=freq,
missing=missing)
missing=missing, validate_specification=validate_specification)
self._params = SARIMAXParams(self._spec)

# Save given orders
Expand Down Expand Up @@ -1734,10 +1735,11 @@ def _get_extension_time_varying_matrices(
if not self.simple_differencing and self._k_trend > 0:
extend_kwargs.setdefault(
'trend_offset', self.trend_offset + self.nobs)
extend_kwargs.setdefault('validate_specification', False)
mod_extend = self.clone(
endog=tmp_endog, exog=tmp_exog, **extend_kwargs)
mod_extend.update(params, transformed=transformed,
includes_fixed=includes_fixed)
includes_fixed=includes_fixed,)

# Retrieve the extensions to the time-varying system matrices
# and put them in kwargs
Expand Down
17 changes: 17 additions & 0 deletions statsmodels/tsa/statespace/tests/test_sarimax.py
Original file line number Diff line number Diff line change
Expand Up @@ -2763,3 +2763,20 @@ def test_sarimax_starting_values_few_obsevations(reset_randomstate):
assert np.all(
np.isfinite(sarimax_model.predict(start=len(y), end=len(y) + 11))
)


def test_sarimax_forecast_exog_trend(reset_randomstate):
# Test that an error is not raised that the given `exog` for the forecast
# period is a constant when forecating with an intercept
# GH 7019
y = np.zeros(10)
x = np.zeros(10)

mod = sarimax.SARIMAX(endog=y, exog=x, order=(1, 0, 0), trend='c')
res = mod.smooth([0.2, 0.4, 0.5, 1.0])

# Test for h=1
assert_allclose(res.forecast(1, exog=1), 0.2 + 0.4)

# Test for h=2
assert_allclose(res.forecast(2, exog=[1., 1.]), 0.2 + 0.4, 0.2 + 0.4 + 0.5)

0 comments on commit b72ea0e

Please sign in to comment.