Skip to content

Commit

Permalink
Merge pull request #634 from bashtage/fix-sur-constraint-r-q
Browse files Browse the repository at this point in the history
BUG: Correct handling of q in SUR constraints
  • Loading branch information
bashtage authored Jan 8, 2025
2 parents 1969b5b + fffcae3 commit 9b3b559
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 11 deletions.
4 changes: 4 additions & 0 deletions ci/azure_template_posix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ jobs:
python.version: '3.12'
XXHASH: true
PYARROW: true
python313_latest:
python.version: '3.13'
XXHASH: true
PYARROW: true
python312_copy_on_write:
python.version: '3.12'
XXHASH: true
Expand Down
4 changes: 4 additions & 0 deletions ci/azure_template_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ jobs:
python.version: '3.10'
python311_win_latest:
python.version: '3.11'
python312_win_latest:
python.version: '3.12'
python313_win_latest:
python.version: '3.13'
maxParallel: 10

steps:
Expand Down
8 changes: 2 additions & 6 deletions linearmodels/panel/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,9 +771,7 @@ def _setup_clusters(
cat = Categorical(formatted_clusters.dataframe[col])
# TODO: Bug in pandas-stubs
# https://github.com/pandas-dev/pandas-stubs/issues/111
formatted_clusters.dataframe[col] = cat.codes.astype(
np.int64
) # type: ignore
formatted_clusters.dataframe[col] = cat.codes.astype(np.int64) # type: ignore
clusters_frame = formatted_clusters.dataframe

cluster_entity = bool(cov_config_upd.pop("cluster_entity", False))
Expand Down Expand Up @@ -2184,9 +2182,7 @@ def _setup_clusters(
cluster_max.T, index=index, columns=clusters_panel.vars
)
# TODO: Bug in pandas-stubs prevents using Hashable | None
clusters_frame = clusters_frame.loc[reindex].astype(
np.int64
) # type: ignore
clusters_frame = clusters_frame.loc[reindex].astype(np.int64) # type: ignore
cov_config_upd["clusters"] = clusters_frame

return cov_config_upd
Expand Down
2 changes: 1 addition & 1 deletion linearmodels/shared/hypotheses.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def _parse_single(constraint: str) -> tuple[str, float]:


def _reparse_constraint_formula(
formula: str | list[str] | dict[str, float]
formula: str | list[str] | dict[str, float],
) -> str | dict[str, float]:
# TODO: Test against variable names constaining , or =
if isinstance(formula, Mapping):
Expand Down
3 changes: 2 additions & 1 deletion linearmodels/system/_utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ def __init__(
raise TypeError("q must be a Series or an array")
if r.shape[0] != q.shape[0]:
raise ValueError("Constraint inputs are not shape compatible")
q_pd = pd.Series(q, index=r_pd.index)
q_pd = pd.Series(q)
q_pd.index = r_pd.index
else:
q_pd = pd.Series(np.zeros(r_pd.shape[0]), index=r_pd.index)
self._q_pd = q_pd
Expand Down
2 changes: 1 addition & 1 deletion linearmodels/system/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@


def _missing_weights(
weights: Mapping[str, linearmodels.typing.data.ArrayLike | None]
weights: Mapping[str, linearmodels.typing.data.ArrayLike | None],
) -> None:
"""Raise warning if missing weighs found"""
missing = [key for key in weights if weights[key] is None]
Expand Down
34 changes: 34 additions & 0 deletions linearmodels/tests/system/test_sur.py
Original file line number Diff line number Diff line change
Expand Up @@ -909,3 +909,37 @@ def test_unknown_method():
mod = SUR(generate_data(k=3))
with pytest.raises(ValueError, match="method must be 'ols' or 'gls'"):
mod.fit(method="other")


def test_sur_contraint_with_value():
n = 100
rg = np.random.RandomState(np.random.MT19937(12345))
x1 = rg.normal(size=n)
x2 = rg.normal(size=n)
x3 = rg.normal(size=n)

y1 = 3 + 1.5 * x1 - 2.0 * x2 + np.random.normal(size=n)
y2 = -1 + 0.5 * x2 + 1.2 * x3 + np.random.normal(size=n)

data = DataFrame({"x1": x1, "x2": x2, "x3": x3, "y1": y1, "y2": y2})

equations = {"eq1": "y1 ~ x1 + x2", "eq2": "y2 ~ x2 + x3"}

model = SUR.from_formula(equations, data)

# coefficients of eq1_x1 and eq2_x2 are equal
r = DataFrame(
[[0] * 4], columns=model.param_names, index=["rest"], dtype=np.float64
)
r.iloc[0, 0] = -1
r.iloc[0, 2] = 1

q = Series([0])
model.add_constraints(r, q)
result = model.fit()

# No error without q
model = SUR.from_formula(equations, data)
model.add_constraints(r)
result_without_q = model.fit()
assert_allclose(result.params, result_without_q.params)
4 changes: 2 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
xarray>=0.16
mypy>=1.3
black[jupyter]==24.4.0
black[jupyter]==24.10.0
pytest>=7.3.0,<8
isort>=5.12
ipython
matplotlib
ruff
ruff>=0.8.6
jupyterlab-code-formatter
flake8
jupyter
Expand Down

0 comments on commit 9b3b559

Please sign in to comment.