Skip to content

Commit 80860d3

Browse files
committed
Fixing PCovR to work with 1D column vectors
1 parent 45681d5 commit 80860d3

File tree

6 files changed

+29
-11
lines changed

6 files changed

+29
-11
lines changed

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ known_first_party = "skmatter"
8787
[tool.pytest.ini_options]
8888
testpaths = ["tests"]
8989
addopts = ["--cov"]
90-
filterwarnings = ["error"]
9190

9291
[tool.ruff]
9392
exclude = ["docs/src/examples/"]

src/skmatter/decomposition/_kernel_pcovr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from sklearn.utils import check_random_state
1313
from sklearn.utils._arpack import _init_arpack_v0
1414
from sklearn.utils.extmath import randomized_svd, stable_cumsum, svd_flip
15-
from sklearn.utils.validation import check_is_fitted, validate_data, _check_n_features
15+
from sklearn.utils.validation import _check_n_features, check_is_fitted, validate_data
1616

1717
from ..preprocessing import KernelNormalizer
1818
from ..utils import check_krr_fit, pcovr_kernel

src/skmatter/decomposition/_pcovr.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def fit(self, X, Y, W=None):
310310
self._fit_sample_space(X, Y.reshape(Yhat.shape), Yhat, W)
311311

312312
self.pxy_ = self.pxt_ @ self.pty_
313-
if len(Y.shape) == 1:
313+
if len(Y.shape) == 1 or Y.shape[1] == 1:
314314
self.pxy_ = self.pxy_.reshape(
315315
X.shape[1],
316316
)
@@ -637,6 +637,10 @@ def score(self, X, y, T=None):
637637
"""
638638
X, y = validate_data(self, X, y, reset=False)
639639

640+
if y.ndim == 2:
641+
if y.shape[1] == 1:
642+
y = y.ravel()
643+
640644
if T is None:
641645
T = self.transform(X)
642646

src/skmatter/linear_model/_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import numpy as np
22
from scipy.linalg import orthogonal_procrustes
3-
from sklearn.base import MultiOutputMixin, RegressorMixin, BaseEstimator
3+
from sklearn.base import BaseEstimator, MultiOutputMixin, RegressorMixin
44
from sklearn.linear_model import LinearRegression
55
from sklearn.utils import check_array, check_X_y
66
from sklearn.utils.validation import check_is_fitted

src/skmatter/utils/_pcovr_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ def check_lr_fit(regressor, X, y):
4141
# Check compatibility with X
4242
validate_data(fitted_regressor, X, y, reset=False, multi_output=True)
4343

44+
if y.ndim == 2:
45+
if y.shape[1] == 1:
46+
y = y.ravel()
47+
4448
# Check compatibility with y
4549
if fitted_regressor.coef_.ndim != y.ndim:
4650
raise ValueError(

tests/test_pcovr.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_simple_reconstruction(self):
6969
def test_simple_prediction(self):
7070
"""
7171
Check that PCovR with a full eigendecomposition at mixing=0
72-
can fully reconstruct the input properties.
72+
can reproduce a linear regression result.
7373
"""
7474
for space in ["feature", "sample", "auto"]:
7575
with self.subTest(space=space):
@@ -91,13 +91,14 @@ def test_lr_with_x_errors(self):
9191
and that the prediction error increases with `mixing`
9292
"""
9393
prev_error = -1.0
94+
Ytrue = self.Y.ravel()
9495

9596
for mixing in np.linspace(0, 1, 11):
9697
pcovr = self.model(mixing=mixing, n_components=2, tol=1e-12)
9798
pcovr.fit(self.X, self.Y)
9899

99100
Yp = pcovr.predict(X=self.X)
100-
error = np.linalg.norm(self.Y - Yp) ** 2.0 / np.linalg.norm(self.Y) ** 2.0
101+
error = np.linalg.norm(Ytrue - Yp) ** 2.0 / np.linalg.norm(Ytrue) ** 2.0
101102

102103
with self.subTest(error=error):
103104
self.assertFalse(np.isnan(error))
@@ -111,14 +112,15 @@ def test_lr_with_t_errors(self):
111112
projection and that the prediction error increases with `mixing`.
112113
"""
113114
prev_error = -1.0
115+
Ytrue = self.Y.ravel()
114116

115117
for mixing in np.linspace(0, 1, 11):
116118
pcovr = self.model(mixing=mixing, n_components=2, tol=1e-12)
117119
pcovr.fit(self.X, self.Y)
118120

119121
T = pcovr.transform(self.X)
120122
Yp = pcovr.predict(T=T)
121-
error = np.linalg.norm(self.Y - Yp) ** 2.0 / np.linalg.norm(self.Y) ** 2.0
123+
error = np.linalg.norm(Ytrue - Yp) ** 2.0 / np.linalg.norm(Ytrue) ** 2.0
122124

123125
with self.subTest(error=error):
124126
self.assertFalse(np.isnan(error))
@@ -481,10 +483,10 @@ def test_none_regressor(self):
481483
self.assertTrue(pcovr.regressor is None)
482484
self.assertTrue(pcovr.regressor_ is not None)
483485

484-
def test_incompatible_coef_shape(self):
486+
def test_incompatible_coef_dim(self):
485487
# self.Y is 2D with one target
486488
# Don't need to test X shape, since this should
487-
# be caught by sklearn's _validate_data
489+
# be caught by sklearn's validate_data
488490
regressor = Ridge(alpha=1e-8, fit_intercept=False, tol=1e-12)
489491
regressor.fit(self.X, self.Y)
490492
pcovr = self.model(mixing=0.5, regressor=regressor)
@@ -499,14 +501,23 @@ def test_incompatible_coef_shape(self):
499501
"have dimension 2",
500502
)
501503

504+
def test_incompatible_coef_shape(self):
502505
# Shape mismatch (number of targets)
506+
Y_double = np.column_stack((self.Y, self.Y))
507+
Y_triple = np.column_stack((Y_double, self.Y))
508+
509+
regressor = Ridge(alpha=1e-8, fit_intercept=False, tol=1e-12)
510+
regressor.fit(self.X, Y_double)
511+
512+
pcovr = self.model(mixing=0.5, regressor=regressor)
513+
503514
with self.assertRaises(ValueError) as cm:
504-
pcovr.fit(self.X, np.column_stack((self.Y, self.Y)))
515+
pcovr.fit(self.X, Y_triple)
505516
self.assertEqual(
506517
str(cm.exception),
507518
"The regressor coefficients have a shape incompatible with the supplied "
508519
"target space. The coefficients have shape %r and the targets have shape %r"
509-
% (regressor.coef_.shape, np.column_stack((self.Y, self.Y)).shape),
520+
% (regressor.coef_.shape, Y_triple.shape),
510521
)
511522

512523

0 commit comments

Comments
 (0)