def test_emstep1(matlab_results, run): # Test that our EM step gets params2 from params1 # Uses our default method for the observation equation, which is an # optimized version of the method presented in Bańbura and Modugno (2014) # (e.g. our version doesn't require the loop over T or the Kronecker # product) endog_M, endog_Q = matlab_results[:2] results1 = matlab_results[2][f'{run}1'] results2 = matlab_results[2][f'{run}2'] # Construct the model mod = dynamic_factor_mq.DynamicFactorMQ( endog_M.iloc[:, :results1['k_endog_M']], endog_quarterly=endog_Q, factors=results1['factors'], factor_orders=results1['factor_orders'], factor_multiplicities=results1['factor_multiplicities'], idiosyncratic_ar1=True, init_t0=True, obs_cov_diag=True, standardize=True) init = initialization.Initialization( mod.k_states, 'known', constant=results1['initial_state'], stationary_cov=results1['initial_state_cov']) res2, params2 = mod._em_iteration(results1['params'], init=init, mstep_method='missing') # Test parameters true2 = results2['params'] assert_allclose(params2[mod._p['loadings']], true2[mod._p['loadings']]) assert_allclose(params2[mod._p['factor_ar']], true2[mod._p['factor_ar']]) assert_allclose(params2[mod._p['factor_cov']], true2[mod._p['factor_cov']]) assert_allclose(params2[mod._p['idiosyncratic_ar1']], true2[mod._p['idiosyncratic_ar1']]) assert_allclose(params2[mod._p['idiosyncratic_var']], true2[mod._p['idiosyncratic_var']])
def test_structural(): # Many of the forms of UnobservedComponents are just special cases of the # most general model with parameter restrictions endog = macrodata['infl'] # Local linear trend is a pretty general model, so we can test fixing # parameters against other more specific models # Local level is local linear trend with sigma2.trend = 0 mod1 = structural.UnobservedComponents(endog, 'llevel') mod2 = structural.UnobservedComponents(endog, 'lltrend') # Note: have to reinitialize, so the estimate of the trend term stays at # zero. init = initialization.Initialization(mod2.k_states) init[0] = 'approximate_diffuse' init.set(1, 'known', constant=[0]) mod2.ssm.initialization = init mod2.ssm.loglikelihood_burn = 1 constraints = {'sigma2.trend': 0} # Start pretty close to optimum to speed up test start_params = [3.37, 0.74] res1 = mod1.fit(start_params, disp=False) res2 = mod2.fit_constrained(constraints, start_params=res1.params, includes_fixed=False, disp=False) # Check that the right parameters were fixed assert_equal(res1.fixed_params, []) assert_equal(res2.fixed_params, ['sigma2.trend']) # Check that MLE finds the same parameters in either case desired = np.r_[res1.params, 0] assert_allclose(res2.params, desired) # Now smooth at the actual parameters (to allow high precision testing # below, even if there are small differences between MLE fitted parameters) with mod2.fix_params(constraints): res2 = mod2.smooth(res1.params) check_results(res1, res2)
def __init__(self, endog, trend=False, damped_trend=False, seasonal=None, initialization_method='estimated', initial_level=None, initial_trend=None, initial_seasonal=None, bounds=None, concentrate_scale=True, dates=None, freq=None, missing='none'): # Model definition self.trend = bool_like(trend, 'trend') self.damped_trend = bool_like(damped_trend, 'damped_trend') self.seasonal_periods = int_like(seasonal, 'seasonal', optional=True) self.seasonal = self.seasonal_periods is not None self.initialization_method = string_like( initialization_method, 'initialization_method').lower() self.concentrate_scale = bool_like(concentrate_scale, 'concentrate_scale') # TODO: add validation for bounds (e.g. have all bounds, upper > lower) # TODO: add `bounds_method` argument to choose between "usual" and # "admissible" as in Hyndman et al. (2008) self.bounds = bounds if self.bounds is None: self.bounds = [(1e-4, 1-1e-4)] * 3 + [(0.8, 0.98)] # Validation if self.seasonal_periods == 1: raise ValueError('Cannot have a seasonal period of 1.') if self.seasonal and self.seasonal_periods is None: raise NotImplementedError('Unable to detect season automatically;' ' please specify `seasonal_periods`.') if self.initialization_method not in ['concentrated', 'estimated', 'simple', 'heuristic', 'known']: raise ValueError('Invalid initialization method "%s".' % initialization_method) if self.initialization_method == 'known': if initial_level is None: raise ValueError('`initial_level` argument must be provided' ' when initialization method is set to' ' "known".') if initial_trend is None and self.trend: raise ValueError('`initial_trend` argument must be provided' ' for models with a trend component when' ' initialization method is set to "known".') if initial_seasonal is None and self.seasonal: raise ValueError('`initial_seasonal` argument must be provided' ' for models with a seasonal component when' ' initialization method is set to "known".') # Initialize the state space model if not self.seasonal or self.seasonal_periods is None: self._seasonal_periods = 0 else: self._seasonal_periods = self.seasonal_periods k_states = 2 + int(self.trend) + self._seasonal_periods k_posdef = 1 init = ss_init.Initialization(k_states, 'known', constant=[0] * k_states) super(ExponentialSmoothing, self).__init__( endog, k_states=k_states, k_posdef=k_posdef, initialization=init, dates=dates, freq=freq, missing=missing) # Concentrate the scale out of the likelihood function if self.concentrate_scale: self.ssm.filter_concentrated = True # Setup fixed elements of the system matrices # Observation error self.ssm['design', 0, 0] = 1. self.ssm['selection', 0, 0] = 1. self.ssm['state_cov', 0, 0] = 1. # Level self.ssm['design', 0, 1] = 1. self.ssm['transition', 1, 1] = 1. # Trend if self.trend: self.ssm['transition', 1:3, 2] = 1. # Seasonal if self.seasonal: k = 2 + int(self.trend) self.ssm['design', 0, k] = 1. self.ssm['transition', k, -1] = 1. self.ssm['transition', k + 1:k_states, k:k_states - 1] = ( np.eye(self.seasonal_periods - 1)) # Initialization of the states if self.initialization_method != 'known': msg = ('Cannot give `%%s` argument when initialization is "%s"' % initialization_method) if initial_level is not None: raise ValueError(msg % 'initial_level') if initial_trend is not None: raise ValueError(msg % 'initial_trend') if initial_seasonal is not None: raise ValueError(msg % 'initial_seasonal') if self.initialization_method == 'simple': initial_level, initial_trend, initial_seasonal = ( es_init._initialization_simple( self.endog[:, 0], trend='add' if self.trend else None, seasonal='add' if self.seasonal else None, seasonal_periods=self.seasonal_periods)) elif self.initialization_method == 'heuristic': initial_level, initial_trend, initial_seasonal = ( es_init._initialization_heuristic( self.endog[:, 0], trend='add' if self.trend else None, seasonal='add' if self.seasonal else None, seasonal_periods=self.seasonal_periods)) elif self.initialization_method == 'known': initial_level = float_like(initial_level, 'initial_level') if self.trend: initial_trend = float_like(initial_trend, 'initial_trend') if self.seasonal: initial_seasonal = array_like(initial_seasonal, 'initial_seasonal') if len(initial_seasonal) == self.seasonal_periods - 1: initial_seasonal = np.r_[initial_seasonal, 0 - np.sum(initial_seasonal)] if len(initial_seasonal) != self.seasonal_periods: raise ValueError( 'Invalid length of initial seasonal values. Must be' ' one of s or s-1, where s is the number of seasonal' ' periods.') # Note that the simple and heuristic methods of computing initial # seasonal factors return estimated seasonal factors associated with # the first t = 1, 2, ..., `n_seasons` observations. To use these as # the initial state, we lag them by `n_seasons`. This yields, for # example for `n_seasons = 4`, the seasons lagged L3, L2, L1, L0. # As described above, the state vector in this model should have # seasonal factors ordered L0, L1, L2, L3, and as a result we need to # reverse the order of the computed initial seasonal factors from # these methods. methods = ['simple', 'heuristic'] if (self.initialization_method in methods and initial_seasonal is not None): initial_seasonal = initial_seasonal[::-1] self._initial_level = initial_level self._initial_trend = initial_trend self._initial_seasonal = initial_seasonal self._initial_state = None # Initialize now if possible (if we have a damped trend, then # initialization will depend on the phi parameter, and so has to be # done at each `update`) methods = ['simple', 'heuristic', 'known'] if not self.damped_trend and self.initialization_method in methods: self._initialize_constant_statespace(initial_level, initial_trend, initial_seasonal) # Save keys for kwarg initialization self._init_keys += ['trend', 'damped_trend', 'seasonal', 'initialization_method', 'initial_level', 'initial_trend', 'initial_seasonal', 'bounds', 'concentrate_scale', 'dates', 'freq', 'missing']
def fit_em(self, start_params=None, transformed=True, cov_type='none', cov_kwds=None, maxiter=500, tolerance=1e-6, em_initialization=True, mstep_method=None, full_output=True, return_params=False, low_memory=False): if self._has_fixed_params: raise NotImplementedError('Cannot fit using the EM algorithm while' ' holding some parameters fixed.') if low_memory: raise ValueError('Cannot fit using the EM algorithm when using' ' low_memory option.') if start_params is None: start_params = self.start_params transformed = True else: start_params = np.array(start_params, ndmin=1) if not transformed: start_params = self.transform_params(start_params) # Perform expectation-maximization llf = [] params = [start_params] init = None i = 0 delta = 0 while i < maxiter and (i < 2 or (delta > tolerance)): out = self._em_iteration(params[-1], init=init, mstep_method=mstep_method) llf.append(out[0].llf_obs.sum()) params.append(out[1]) if em_initialization: init = initialization.Initialization( self.k_states, 'known', constant=out[0].smoothed_state[..., 0], stationary_cov=out[0].smoothed_state_cov[..., 0]) if i > 0: delta = (2 * (llf[-1] - llf[-2]) / (np.abs(llf[-1]) + np.abs(llf[-2]))) i += 1 # Just return the fitted parameters if requested if return_params: result = params[-1] # Otherwise construct the results class if desired else: if em_initialization: base_init = self.ssm.initialization self.ssm.initialization = init result = self.smooth(params[-1], transformed=True, cov_type=cov_type, cov_kwds=cov_kwds) if em_initialization: self.ssm.initialization = base_init # Save the output if full_output: em_retvals = Bunch(**{'params': np.array(params), 'llf': np.array(llf), 'iter': i}) em_settings = Bunch(**{'tolerance': tolerance, 'maxiter': maxiter}) else: em_retvals = None em_settings = None result.mle_retvals = em_retvals result.mle_settings = em_settings return result