Skip to content

Commit

Permalink
ENH/BUG: correct predict with offset
Browse files Browse the repository at this point in the history
  • Loading branch information
josef-pkt committed Sep 21, 2020
1 parent 7f392b9 commit 97101b3
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 13 deletions.
27 changes: 14 additions & 13 deletions statsmodels/miscmodels/ordinal_model.py
Expand Up @@ -245,21 +245,15 @@ def transform_reverse_threshold_params(self, params):
np.log(np.diff(start_ppf[:-1]))))
return thresh_params

def predict(self, params, exog=None):
def predict(self, params, exog=None, offset=None):
"""predicted probabilities for each level of the ordinal endog.
"""
if exog is None:
exog = self.exog
# structure of params = [beta, constants_or_thresholds]
# note, exog and offset handling is in linpred

# explicit in several steps to avoid bugs
th_params = params[-(self.k_levels - 1):]
thresh = np.concatenate((th_params[:1],
np.exp(th_params[1:]))).cumsum()
thresh = np.concatenate(([-np.inf], thresh, [np.inf]))
xb = exog.dot(params[:-(self.k_levels - 1)])[:, None]
thresh = self.transform_threshold_params(params)
xb = self._linpred(params, exog=exog, offset=offset)[:, None]
low = thresh[:-1] - xb
upp = thresh[1:] - xb
prob = self.prob(low, upp)
Expand All @@ -272,10 +266,17 @@ def _linpred(self, params, exog=None, offset=None):
"""
if exog is None:
exog = self.exog
if offset is None:
offset = self.offset
if offset is None:
offset = self.offset
else:
if offset is None:
offset = 0

if offset is not None:
offset = np.asarray(offset)

if exog is not None:
linpred = self.exog.dot(params[:-(self.k_levels - 1)])
linpred = exog.dot(params[:-(self.k_levels - 1)])
else: # means self.exog is also None
linpred = np.zeros(self.nobs)
if offset is not None:
Expand Down
37 changes: 37 additions & 0 deletions statsmodels/miscmodels/tests/test_ordinal_model.py
Expand Up @@ -240,6 +240,8 @@ def test_formula_categorical(self):
assert not hasattr(modf2, "frame")

with pytest.raises(ValueError):
# only ordered categorical or numerical endog are allowed
# string endog raises ValueError
OrderedModel.from_formula(
"apply ~ pared + public + gpa - 1",
data={"apply": np.asarray(data['apply']),
Expand All @@ -248,6 +250,41 @@ def test_formula_categorical(self):
"gpa": data['gpa']},
distr='probit')

def test_offset(self):

resp = self.resp
data = ds.df
offset = np.ones(len(data))

formula = "apply ~ pared + public + gpa - 1"
modf2 = OrderedModel.from_formula(formula, data, offset=offset,
distr='probit')
resf2 = modf2.fit(method='bfgs', disp=False)

assert_allclose(resf2.params[:3], resp.params[:3], atol=2e-4)
assert_allclose(resf2.params[3], resp.params[3] + 1, atol=2e-4)

fitted = resp.predict()
fitted2 = resf2.predict()
assert_allclose(fitted2, fitted, atol=2e-4)

pred_ones = resf2.predict(data[:6], offset=np.ones(6))
assert_allclose(pred_ones, fitted[:6], atol=2e-4)

# check default is 0. if exog provided
pred_zero1 = resf2.predict(data[:6])
pred_zero2 = resf2.predict(data[:6], offset=0)
assert_allclose(pred_zero1, pred_zero2, atol=2e-4)

# compare with equivalent results frp, no-offset model
pred_zero = resp.predict(data.iloc[:6, 1:], offset=-np.ones(6))
assert_allclose(pred_zero1, pred_zero, atol=2e-4)

params_adj = resp.params.copy()
params_adj[3] += 1
fitted_zero = resp.model.predict(params_adj)
assert_allclose(pred_zero1, fitted_zero[:6], atol=2e-4)


class TestLogitModelFormula():

Expand Down

0 comments on commit 97101b3

Please sign in to comment.