Skip to content

Commit 0d01f86

Browse files
committed
add test for ElasticNet classifier and regressor
1 parent 0f9118f commit 0d01f86

1 file changed

Lines changed: 258 additions & 0 deletions

File tree

tests/test_sklearn_elasticnet.py

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
"""
2+
Tests for plq_ElasticNet_Classifier and plq_ElasticNet_Regressor.
3+
"""
4+
5+
import numpy as np
6+
import pytest
7+
from sklearn.datasets import make_classification, make_regression
8+
from sklearn.metrics import accuracy_score
9+
from sklearn.model_selection import cross_val_score, train_test_split
10+
from sklearn.pipeline import Pipeline
11+
from sklearn.preprocessing import StandardScaler
12+
13+
from rehline import plq_ElasticNet_Classifier, plq_ElasticNet_Regressor
14+
15+
16+
# ---------------------------------------------------------------------------
17+
# Dataset helpers
18+
# ---------------------------------------------------------------------------
19+
20+
def _binary_dataset(seed=42):
21+
return make_classification(
22+
n_samples=500, n_features=10, n_informative=5,
23+
n_redundant=2, n_classes=2, random_state=seed,
24+
)
25+
26+
27+
def _multiclass_dataset(n_classes=3, seed=42):
28+
return make_classification(
29+
n_samples=600, n_features=10, n_informative=6,
30+
n_redundant=2, n_classes=n_classes,
31+
n_clusters_per_class=1, random_state=seed,
32+
)
33+
34+
35+
def _reg_dataset(seed=42):
36+
return make_regression(
37+
n_samples=500, n_features=10, n_informative=7,
38+
noise=5.0, random_state=seed,
39+
)
40+
41+
42+
# ===========================================================================
43+
# plq_ElasticNet_Classifier — binary
44+
# ===========================================================================
45+
46+
def test_elasticnet_clf_binary_pipeline_fits_and_predicts():
47+
X, y = _binary_dataset()
48+
pipe = Pipeline([
49+
("scaler", StandardScaler()),
50+
("clf", plq_ElasticNet_Classifier(loss={"name": "svm"}, C=1.0, l1_ratio=0.5)),
51+
])
52+
pipe.fit(X, y)
53+
preds = pipe.predict(X)
54+
assert preds.shape == (len(y),)
55+
assert accuracy_score(y, preds) > 0.5
56+
57+
58+
def test_elasticnet_clf_binary_cross_val_score():
59+
X, y = _binary_dataset()
60+
pipe = Pipeline([
61+
("scaler", StandardScaler()),
62+
("clf", plq_ElasticNet_Classifier(loss={"name": "svm"}, C=1.0, l1_ratio=0.5)),
63+
])
64+
scores = cross_val_score(pipe, X, y, cv=3, scoring="accuracy")
65+
assert scores.shape == (3,)
66+
assert np.mean(scores) > 0.5
67+
68+
69+
def test_elasticnet_clf_binary_with_intercept():
70+
X, y = _binary_dataset()
71+
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)
72+
clf = plq_ElasticNet_Classifier(
73+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5,
74+
fit_intercept=True, intercept_scaling=1.0,
75+
)
76+
clf.fit(X_tr, y_tr)
77+
assert hasattr(clf, "intercept_")
78+
assert clf.coef_.shape == (X_tr.shape[1],)
79+
assert clf.predict(X_te).shape == (len(y_te),)
80+
81+
82+
def test_elasticnet_clf_binary_without_intercept():
83+
X, y = _binary_dataset()
84+
clf = plq_ElasticNet_Classifier(
85+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5, fit_intercept=False,
86+
)
87+
clf.fit(X, y)
88+
assert clf.intercept_ == 0.0
89+
assert clf.coef_.shape == (X.shape[1],)
90+
91+
92+
def test_elasticnet_clf_l1_ratio_zero():
93+
"""l1_ratio=0 is pure Ridge — should fit without error."""
94+
X, y = _binary_dataset()
95+
clf = plq_ElasticNet_Classifier(loss={"name": "svm"}, C=1.0, l1_ratio=0.0)
96+
clf.fit(X, y)
97+
assert clf.predict(X).shape == (len(y),)
98+
99+
100+
def test_elasticnet_clf_l1_ratio_invalid_raises():
101+
with pytest.raises(ValueError, match="l1_ratio"):
102+
plq_ElasticNet_Classifier(loss={"name": "svm"}, C=1.0, l1_ratio=1.0)
103+
104+
105+
# ===========================================================================
106+
# plq_ElasticNet_Classifier — multiclass OvR
107+
# ===========================================================================
108+
109+
def test_elasticnet_clf_ovr_fits_and_predicts():
110+
X, y = _multiclass_dataset(n_classes=3)
111+
clf = plq_ElasticNet_Classifier(
112+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5, multi_class="ovr"
113+
)
114+
clf.fit(X, y)
115+
preds = clf.predict(X)
116+
assert preds.shape == (len(y),)
117+
assert set(np.unique(preds)).issubset(set(np.unique(y)))
118+
assert accuracy_score(y, preds) > 1 / 3
119+
120+
121+
def test_elasticnet_clf_ovr_estimators_shape():
122+
X, y = _multiclass_dataset(n_classes=3)
123+
clf = plq_ElasticNet_Classifier(
124+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5, multi_class="ovr"
125+
)
126+
clf.fit(X, y)
127+
K = len(clf.classes_)
128+
assert len(clf.estimators_) == K
129+
assert clf.coef_.shape == (K, X.shape[1])
130+
assert clf.intercept_.shape == (K,)
131+
132+
133+
def test_elasticnet_clf_ovr_pipeline():
134+
X, y = _multiclass_dataset(n_classes=3)
135+
pipe = Pipeline([
136+
("scaler", StandardScaler()),
137+
("clf", plq_ElasticNet_Classifier(
138+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5, multi_class="ovr"
139+
)),
140+
])
141+
pipe.fit(X, y)
142+
assert pipe.predict(X).shape == (len(y),)
143+
144+
145+
# ===========================================================================
146+
# plq_ElasticNet_Classifier — multiclass OvO
147+
# ===========================================================================
148+
149+
def test_elasticnet_clf_ovo_fits_and_predicts():
150+
X, y = _multiclass_dataset(n_classes=3)
151+
clf = plq_ElasticNet_Classifier(
152+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5, multi_class="ovo"
153+
)
154+
clf.fit(X, y)
155+
preds = clf.predict(X)
156+
assert preds.shape == (len(y),)
157+
assert accuracy_score(y, preds) > 1 / 3
158+
159+
160+
def test_elasticnet_clf_ovo_estimators_shape():
161+
"""OvO: K*(K-1)/2 binary classifiers."""
162+
X, y = _multiclass_dataset(n_classes=3)
163+
clf = plq_ElasticNet_Classifier(
164+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5, multi_class="ovo"
165+
)
166+
clf.fit(X, y)
167+
K = len(clf.classes_)
168+
expected = K * (K - 1) // 2
169+
assert len(clf.estimators_) == expected
170+
assert clf.coef_.shape == (expected, X.shape[1])
171+
172+
173+
def test_elasticnet_clf_multiclass_invalid_strategy_raises():
174+
X, y = _multiclass_dataset(n_classes=3)
175+
clf = plq_ElasticNet_Classifier(
176+
loss={"name": "svm"}, C=1.0, l1_ratio=0.5, multi_class="bad"
177+
)
178+
with pytest.raises(ValueError, match="multi_class"):
179+
clf.fit(X, y)
180+
181+
182+
# ===========================================================================
183+
# plq_ElasticNet_Regressor
184+
# ===========================================================================
185+
186+
def test_elasticnet_reg_pipeline_fits_and_predicts():
187+
X, y = _reg_dataset()
188+
pipe = Pipeline([
189+
("scaler", StandardScaler()),
190+
("reg", plq_ElasticNet_Regressor(loss={"name": "QR", "qt": 0.5}, C=1.0, l1_ratio=0.5)),
191+
])
192+
pipe.fit(X, y)
193+
preds = pipe.predict(X)
194+
assert preds.shape == (len(y),)
195+
assert np.all(np.isfinite(preds))
196+
197+
198+
def test_elasticnet_reg_cross_val_score():
199+
X, y = _reg_dataset()
200+
pipe = Pipeline([
201+
("scaler", StandardScaler()),
202+
("reg", plq_ElasticNet_Regressor(loss={"name": "QR", "qt": 0.5}, C=1.0, l1_ratio=0.5)),
203+
])
204+
scores = cross_val_score(pipe, X, y, cv=3, scoring="r2")
205+
assert scores.shape == (3,)
206+
assert np.mean(scores) > 0.0
207+
208+
209+
def test_elasticnet_reg_multiple_losses():
210+
X, y = _reg_dataset()
211+
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=0)
212+
for loss in [{"name": "huber", "tau": 1.0}, {"name": "SVR", "epsilon": 0.1}]:
213+
reg = plq_ElasticNet_Regressor(loss=loss, C=1.0, l1_ratio=0.3)
214+
reg.fit(X_tr, y_tr)
215+
preds = reg.predict(X_te)
216+
assert preds.shape == (len(y_te),)
217+
assert np.all(np.isfinite(preds)), f"Non-finite predictions for loss={loss}"
218+
219+
220+
def test_elasticnet_reg_l1_ratio_zero():
221+
"""l1_ratio=0 is pure Ridge — should fit without error."""
222+
X, y = _reg_dataset()
223+
reg = plq_ElasticNet_Regressor(loss={"name": "QR", "qt": 0.5}, C=1.0, l1_ratio=0.0)
224+
reg.fit(X, y)
225+
assert reg.predict(X).shape == (len(y),)
226+
227+
228+
def test_elasticnet_reg_l1_ratio_invalid_raises():
229+
with pytest.raises(ValueError, match="l1_ratio"):
230+
plq_ElasticNet_Regressor(loss={"name": "QR", "qt": 0.5}, C=1.0, l1_ratio=1.0)
231+
232+
233+
def test_elasticnet_reg_intercept_on():
234+
X, y = _reg_dataset()
235+
reg = plq_ElasticNet_Regressor(
236+
loss={"name": "QR", "qt": 0.5}, C=1.0, l1_ratio=0.5, fit_intercept=True,
237+
)
238+
reg.fit(X, y)
239+
assert isinstance(reg.intercept_, float)
240+
assert reg.coef_.shape == (X.shape[1],)
241+
242+
243+
def test_elasticnet_reg_intercept_off():
244+
X, y = _reg_dataset()
245+
reg = plq_ElasticNet_Regressor(
246+
loss={"name": "QR", "qt": 0.5}, C=1.0, l1_ratio=0.5, fit_intercept=False,
247+
)
248+
reg.fit(X, y)
249+
assert reg.intercept_ == 0.0
250+
assert reg.coef_.shape == (X.shape[1],)
251+
252+
253+
def test_elasticnet_reg_predict_equals_decision_function():
254+
X, y = _reg_dataset()
255+
X_tr, X_te, y_tr, _ = train_test_split(X, y, test_size=0.2, random_state=0)
256+
reg = plq_ElasticNet_Regressor(loss={"name": "QR", "qt": 0.5}, C=1.0, l1_ratio=0.5)
257+
reg.fit(X_tr, y_tr)
258+
np.testing.assert_array_equal(reg.predict(X_te), reg.decision_function(X_te))

0 commit comments

Comments
 (0)