Skip to content

Commit 8d97da2

Browse files
feat(spatial.models): design-matrix helper (formulaic-backed)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 711cf03 commit 8d97da2

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Shared plumbing for spatial regression models."""
2+
from __future__ import annotations
3+
4+
from typing import List, Tuple
5+
6+
import numpy as np
7+
import pandas as pd
8+
9+
try:
10+
import formulaic
11+
except ImportError as e: # pragma: no cover
12+
raise ImportError(
13+
"formulaic is required for spatial regression formulas. "
14+
"It is already a core StatsPAI dependency — "
15+
"please reinstall with `pip install statspai`."
16+
) from e
17+
18+
19+
def build_design_matrix(
20+
formula: str, data: pd.DataFrame
21+
) -> Tuple[np.ndarray, np.ndarray, List[str]]:
22+
"""Parse `y ~ x1 + x2` into (y, X, column_names).
23+
24+
Column names come from the formulaic model matrix header and may include
25+
"Intercept" (when the intercept is present).
26+
"""
27+
y_mat, X_mat = formulaic.Formula(formula).get_model_matrix(data)
28+
y = np.asarray(y_mat).ravel().astype(float)
29+
X = np.asarray(X_mat, dtype=float)
30+
names = list(X_mat.columns)
31+
return y, X, names

tests/spatial/test_models_base.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import numpy as np
2+
import pandas as pd
3+
from statspai.spatial.models._base import build_design_matrix
4+
5+
6+
def test_build_design_matrix_adds_intercept():
7+
df = pd.DataFrame({"y": [1.0, 2.0, 3.0], "x1": [4.0, 5.0, 6.0]})
8+
y, X, names = build_design_matrix("y ~ x1", df)
9+
assert y.shape == (3,)
10+
assert X.shape == (3, 2)
11+
assert names[0] in {"Intercept", "(Intercept)", "1"} # formulaic convention
12+
assert "x1" in names
13+
14+
15+
def test_build_design_matrix_no_intercept():
16+
df = pd.DataFrame({"y": [1.0, 2.0, 3.0], "x1": [4.0, 5.0, 6.0]})
17+
y, X, names = build_design_matrix("y ~ x1 - 1", df)
18+
assert X.shape == (3, 1)
19+
assert names == ["x1"]

0 commit comments

Comments
 (0)