Skip to content

Commit 064ec25

Browse files
authored
Merge pull request #677 from bashtage/fix-predict-none
BUG: Fix predict when exog or endog is None
2 parents 28af72e + d555aa0 commit 064ec25

File tree

3 files changed

+84
-6
lines changed

3 files changed

+84
-6
lines changed

linearmodels/iv/model.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,15 +291,24 @@ def predict(
291291
"Predictions can only be constructed using one "
292292
"of exog/endog or data, but not both."
293293
)
294-
if exog is not None or endog is not None:
294+
if exog is not None:
295295
exog = IVData(exog).pandas
296+
if endog is not None:
296297
endog = IVData(endog).pandas
297-
elif data is not None:
298+
if data is not None:
298299
parser = IVFormulaParser(self.formula, data, eval_env=eval_env)
299300
exog = parser.exog
300301
endog = parser.endog
301-
else:
302-
raise ValueError("exog and endog or data must be provided.")
302+
if all(a is None for a in (exog, endog, data)):
303+
raise ValueError("At least one of exog, endog, or data must be provided.")
304+
if exog is None:
305+
assert endog is not None
306+
exog = IVData(exog, nobs=endog.shape[0]).pandas
307+
exog.index = endog.index
308+
if endog is None:
309+
assert exog is not None
310+
endog = IVData(endog, nobs=exog.shape[0]).pandas
311+
endog.index = exog.index
303312
assert exog is not None
304313
assert endog is not None
305314
if exog.shape[0] != endog.shape[0]:

linearmodels/tests/iv/test_formulas.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ def test_predict_formula(data, model_and_func, formula):
210210
assert_frame_equal(pred, pred2)
211211
assert_allclose(res.fitted_values, pred)
212212

213-
with pytest.raises(ValueError, match=r"exog and endog or data must be provided"):
213+
with pytest.raises(
214+
ValueError, match=r"At least one of exog, endog, or data must be provided"
215+
):
214216
mod.predict(res.params)
215217

216218

@@ -404,3 +406,37 @@ def test_formula_categorical_equiv(data, model_and_func, dtype):
404406
"x2",
405407
"x3",
406408
]
409+
410+
411+
def test_predict_no_rhs(data, model_and_func):
412+
model, _ = model_and_func
413+
mod = model.from_formula("y ~", data)
414+
res = mod.fit()
415+
pred0 = res.predict()
416+
pred1 = res.predict(data=data)
417+
pred1.columns = pred0.columns
418+
assert_frame_equal(pred0, pred1)
419+
420+
421+
@pytest.mark.parametrize(
422+
"fmla",
423+
[
424+
"y ~ 1",
425+
"y ~ 1 + x3",
426+
"y ~ [x1 + x2 ~ 1 + z1 + z2 + z3]",
427+
"y ~ 1 + [x1 + x2 ~ z1 + z2 + z3]",
428+
],
429+
)
430+
def test_formula_single(data, model_and_func, fmla):
431+
model, func = model_and_func
432+
res = model.from_formula(fmla, data).fit()
433+
pred0 = res.predict()
434+
pred1 = res.predict(data=data)
435+
436+
mod2 = func(fmla, data)
437+
res2 = mod2.fit()
438+
pred2 = res2.predict(data=data)
439+
pred1.columns = pred0.columns
440+
pred2.columns = pred0.columns
441+
assert_frame_equal(pred1, pred2)
442+
assert_frame_equal(pred0, pred1)

linearmodels/tests/iv/test_results.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from numpy import asarray
22
from numpy.testing import assert_allclose
33
from pandas import DataFrame
4-
from pandas.testing import assert_series_equal
4+
from pandas.testing import assert_frame_equal, assert_series_equal
55
import pytest
66

77
from linearmodels.iv.data import IVData
@@ -96,3 +96,36 @@ def test_predict_no_selection(data, model):
9696
res = mod.fit()
9797
with pytest.raises(ValueError, match=r"At least one output must be selected"):
9898
res.predict(fitted=False, idiosyncratic=False, missing=True)
99+
100+
101+
@pytest.mark.parametrize("include_vars", ["exog", "both", "endog"])
102+
def test_fitted_predict_combinations(data, model, include_vars):
103+
args = (data.dep,)
104+
if include_vars in ("exog", "both"):
105+
args += (data.exog,)
106+
else:
107+
args += (None,)
108+
if include_vars in ("endog", "both"):
109+
args += (data.endog, data.instr)
110+
else:
111+
args += (None, None)
112+
113+
mod = model(*args)
114+
res = mod.fit()
115+
assert_series_equal(res.idiosyncratic, res.resids)
116+
y = mod.dependent.pandas
117+
expected = asarray(y) - asarray(res.resids)[:, None]
118+
expected = DataFrame(expected, y.index, ["fitted_values"])
119+
assert_frame_similar(expected, res.fitted_values)
120+
assert_allclose(expected, res.fitted_values)
121+
pred = res.predict()
122+
pred2 = res.predict(exog=args[1], endog=args[2])
123+
pred2.columns = pred.columns
124+
assert_frame_equal(pred, pred2)
125+
nobs = res.resids.shape[0]
126+
assert isinstance(pred, DataFrame)
127+
assert pred.shape == (nobs, 1)
128+
pred = res.predict(idiosyncratic=True, missing=True)
129+
nobs = IVData(data.dep).pandas.shape[0]
130+
assert pred.shape == (nobs, 2)
131+
assert list(pred.columns) == ["fitted_values", "residual"]

0 commit comments

Comments
 (0)