Prompt48 commited on
Commit
b5e959a
·
verified ·
1 Parent(s): 6819784

Upload edit\Qwen3-TTS-test\.venv\Lib\site-packages\sklearn\decomposition\tests\test_pca.py with huggingface_hub

Browse files
edit//Qwen3-TTS-test//.venv//Lib//site-packages//sklearn//decomposition//tests//test_pca.py ADDED
@@ -0,0 +1,1155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import warnings
4
+
5
+ import numpy as np
6
+ import pytest
7
+ import scipy as sp
8
+ from numpy.testing import assert_array_equal
9
+
10
+ from sklearn import config_context, datasets
11
+ from sklearn.base import clone
12
+ from sklearn.datasets import load_iris, make_classification, make_low_rank_matrix
13
+ from sklearn.decomposition import PCA
14
+ from sklearn.decomposition._pca import _assess_dimension, _infer_dimension
15
+ from sklearn.utils._array_api import (
16
+ _atol_for_type,
17
+ _convert_to_numpy,
18
+ _get_namespace_device_dtype_ids,
19
+ yield_namespace_device_dtype_combinations,
20
+ )
21
+ from sklearn.utils._array_api import device as array_device
22
+ from sklearn.utils._test_common.instance_generator import _get_check_estimator_ids
23
+ from sklearn.utils._testing import _array_api_for_tests, assert_allclose
24
+ from sklearn.utils.estimator_checks import (
25
+ check_array_api_input_and_values,
26
+ )
27
+ from sklearn.utils.fixes import CSC_CONTAINERS, CSR_CONTAINERS
28
+
29
+ iris = datasets.load_iris()
30
+ PCA_SOLVERS = ["full", "covariance_eigh", "arpack", "randomized", "auto"]
31
+
32
+ # `SPARSE_M` and `SPARSE_N` could be larger, but be aware:
33
+ # * SciPy's generation of random sparse matrix can be costly
34
+ # * A (SPARSE_M, SPARSE_N) dense array is allocated to compare against
35
+ SPARSE_M, SPARSE_N = 1000, 300 # arbitrary
36
+ SPARSE_MAX_COMPONENTS = min(SPARSE_M, SPARSE_N)
37
+
38
+
39
+ def _check_fitted_pca_close(pca1, pca2, rtol=1e-7, atol=1e-12):
40
+ assert_allclose(pca1.components_, pca2.components_, rtol=rtol, atol=atol)
41
+ assert_allclose(
42
+ pca1.explained_variance_, pca2.explained_variance_, rtol=rtol, atol=atol
43
+ )
44
+ assert_allclose(pca1.singular_values_, pca2.singular_values_, rtol=rtol, atol=atol)
45
+ assert_allclose(pca1.mean_, pca2.mean_, rtol=rtol, atol=atol)
46
+ assert_allclose(pca1.noise_variance_, pca2.noise_variance_, rtol=rtol, atol=atol)
47
+
48
+ assert pca1.n_components_ == pca2.n_components_
49
+ assert pca1.n_samples_ == pca2.n_samples_
50
+ assert pca1.n_features_in_ == pca2.n_features_in_
51
+
52
+
53
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
54
+ @pytest.mark.parametrize("n_components", range(1, iris.data.shape[1]))
55
+ def test_pca(svd_solver, n_components):
56
+ X = iris.data
57
+ pca = PCA(n_components=n_components, svd_solver=svd_solver)
58
+
59
+ # check the shape of fit.transform
60
+ X_r = pca.fit(X).transform(X)
61
+ assert X_r.shape[1] == n_components
62
+
63
+ # check the equivalence of fit.transform and fit_transform
64
+ X_r2 = pca.fit_transform(X)
65
+ assert_allclose(X_r, X_r2)
66
+ X_r = pca.transform(X)
67
+ assert_allclose(X_r, X_r2)
68
+
69
+ # Test get_covariance and get_precision
70
+ cov = pca.get_covariance()
71
+ precision = pca.get_precision()
72
+ assert_allclose(np.dot(cov, precision), np.eye(X.shape[1]), atol=1e-12)
73
+
74
+
75
+ @pytest.mark.parametrize("density", [0.01, 0.1, 0.30])
76
+ @pytest.mark.parametrize("n_components", [1, 2, 10])
77
+ @pytest.mark.parametrize("sparse_container", CSR_CONTAINERS + CSC_CONTAINERS)
78
+ @pytest.mark.parametrize("svd_solver", ["arpack", "covariance_eigh"])
79
+ @pytest.mark.parametrize("scale", [1, 10, 100])
80
+ def test_pca_sparse(
81
+ global_random_seed, svd_solver, sparse_container, n_components, density, scale
82
+ ):
83
+ """Check that the results are the same for sparse and dense input."""
84
+
85
+ # Set atol in addition of the default rtol to account for the very wide range of
86
+ # result values (1e-8 to 1e0).
87
+ atol = 1e-12
88
+ transform_atol = 1e-10
89
+
90
+ random_state = np.random.default_rng(global_random_seed)
91
+ X = sparse_container(
92
+ sp.sparse.random(
93
+ SPARSE_M,
94
+ SPARSE_N,
95
+ random_state=random_state,
96
+ density=density,
97
+ )
98
+ )
99
+ # Scale the data + vary the column means
100
+ scale_vector = random_state.random(X.shape[1]) * scale
101
+ X = X.multiply(scale_vector)
102
+
103
+ pca = PCA(
104
+ n_components=n_components,
105
+ svd_solver=svd_solver,
106
+ random_state=global_random_seed,
107
+ )
108
+ pca.fit(X)
109
+
110
+ Xd = X.toarray()
111
+ pcad = PCA(
112
+ n_components=n_components,
113
+ svd_solver=svd_solver,
114
+ random_state=global_random_seed,
115
+ )
116
+ pcad.fit(Xd)
117
+
118
+ # Fitted attributes equality
119
+ _check_fitted_pca_close(pca, pcad, atol=atol)
120
+
121
+ # Test transform
122
+ X2 = sparse_container(
123
+ sp.sparse.random(
124
+ SPARSE_M,
125
+ SPARSE_N,
126
+ random_state=random_state,
127
+ density=density,
128
+ )
129
+ )
130
+ X2d = X2.toarray()
131
+
132
+ assert_allclose(pca.transform(X2), pca.transform(X2d), atol=transform_atol)
133
+ assert_allclose(pca.transform(X2), pcad.transform(X2d), atol=transform_atol)
134
+
135
+
136
+ @pytest.mark.parametrize("sparse_container", CSR_CONTAINERS + CSC_CONTAINERS)
137
+ def test_pca_sparse_fit_transform(global_random_seed, sparse_container):
138
+ random_state = np.random.default_rng(global_random_seed)
139
+ X = sparse_container(
140
+ sp.sparse.random(
141
+ SPARSE_M,
142
+ SPARSE_N,
143
+ random_state=random_state,
144
+ density=0.01,
145
+ )
146
+ )
147
+ X2 = sparse_container(
148
+ sp.sparse.random(
149
+ SPARSE_M,
150
+ SPARSE_N,
151
+ random_state=random_state,
152
+ density=0.01,
153
+ )
154
+ )
155
+
156
+ pca_fit = PCA(n_components=10, svd_solver="arpack", random_state=global_random_seed)
157
+ pca_fit_transform = PCA(
158
+ n_components=10, svd_solver="arpack", random_state=global_random_seed
159
+ )
160
+
161
+ pca_fit.fit(X)
162
+ transformed_X = pca_fit_transform.fit_transform(X)
163
+
164
+ _check_fitted_pca_close(pca_fit, pca_fit_transform)
165
+ assert_allclose(transformed_X, pca_fit_transform.transform(X))
166
+ assert_allclose(transformed_X, pca_fit.transform(X))
167
+ assert_allclose(pca_fit.transform(X2), pca_fit_transform.transform(X2))
168
+
169
+
170
+ @pytest.mark.parametrize("svd_solver", ["randomized", "full"])
171
+ @pytest.mark.parametrize("sparse_container", CSR_CONTAINERS + CSC_CONTAINERS)
172
+ def test_sparse_pca_solver_error(global_random_seed, svd_solver, sparse_container):
173
+ random_state = np.random.RandomState(global_random_seed)
174
+ X = sparse_container(
175
+ sp.sparse.random(
176
+ SPARSE_M,
177
+ SPARSE_N,
178
+ random_state=random_state,
179
+ )
180
+ )
181
+ pca = PCA(n_components=30, svd_solver=svd_solver)
182
+ error_msg_pattern = (
183
+ 'PCA only support sparse inputs with the "arpack" and "covariance_eigh"'
184
+ f' solvers, while "{svd_solver}" was passed'
185
+ )
186
+ with pytest.raises(TypeError, match=error_msg_pattern):
187
+ pca.fit(X)
188
+
189
+
190
+ @pytest.mark.parametrize("sparse_container", CSR_CONTAINERS + CSC_CONTAINERS)
191
+ def test_sparse_pca_auto_arpack_singluar_values_consistency(
192
+ global_random_seed, sparse_container
193
+ ):
194
+ """Check that "auto" and "arpack" solvers are equivalent for sparse inputs."""
195
+ random_state = np.random.RandomState(global_random_seed)
196
+ X = sparse_container(
197
+ sp.sparse.random(
198
+ SPARSE_M,
199
+ SPARSE_N,
200
+ random_state=random_state,
201
+ )
202
+ )
203
+ pca_arpack = PCA(n_components=10, svd_solver="arpack").fit(X)
204
+ pca_auto = PCA(n_components=10, svd_solver="auto").fit(X)
205
+ assert_allclose(pca_arpack.singular_values_, pca_auto.singular_values_, rtol=5e-3)
206
+
207
+
208
+ def test_no_empty_slice_warning():
209
+ # test if we avoid numpy warnings for computing over empty arrays
210
+ n_components = 10
211
+ n_features = n_components + 2 # anything > n_comps triggered it in 0.16
212
+ X = np.random.uniform(-1, 1, size=(n_components, n_features))
213
+ pca = PCA(n_components=n_components)
214
+ with warnings.catch_warnings():
215
+ warnings.simplefilter("error", RuntimeWarning)
216
+ pca.fit(X)
217
+
218
+
219
+ @pytest.mark.parametrize("copy", [True, False])
220
+ @pytest.mark.parametrize("solver", PCA_SOLVERS)
221
+ def test_whitening(solver, copy):
222
+ # Check that PCA output has unit-variance
223
+ rng = np.random.RandomState(0)
224
+ n_samples = 100
225
+ n_features = 80
226
+ n_components = 30
227
+ rank = 50
228
+
229
+ # some low rank data with correlated features
230
+ X = np.dot(
231
+ rng.randn(n_samples, rank),
232
+ np.dot(np.diag(np.linspace(10.0, 1.0, rank)), rng.randn(rank, n_features)),
233
+ )
234
+ # the component-wise variance of the first 50 features is 3 times the
235
+ # mean component-wise variance of the remaining 30 features
236
+ X[:, :50] *= 3
237
+
238
+ assert X.shape == (n_samples, n_features)
239
+
240
+ # the component-wise variance is thus highly varying:
241
+ assert X.std(axis=0).std() > 43.8
242
+
243
+ # whiten the data while projecting to the lower dim subspace
244
+ X_ = X.copy() # make sure we keep an original across iterations.
245
+ pca = PCA(
246
+ n_components=n_components,
247
+ whiten=True,
248
+ copy=copy,
249
+ svd_solver=solver,
250
+ random_state=0,
251
+ iterated_power=7,
252
+ )
253
+ # test fit_transform
254
+ X_whitened = pca.fit_transform(X_.copy())
255
+ assert X_whitened.shape == (n_samples, n_components)
256
+ X_whitened2 = pca.transform(X_)
257
+ assert_allclose(X_whitened, X_whitened2, rtol=5e-4)
258
+
259
+ assert_allclose(X_whitened.std(ddof=1, axis=0), np.ones(n_components))
260
+ assert_allclose(X_whitened.mean(axis=0), np.zeros(n_components), atol=1e-12)
261
+
262
+ X_ = X.copy()
263
+ pca = PCA(
264
+ n_components=n_components, whiten=False, copy=copy, svd_solver=solver
265
+ ).fit(X_.copy())
266
+ X_unwhitened = pca.transform(X_)
267
+ assert X_unwhitened.shape == (n_samples, n_components)
268
+
269
+ # in that case the output components still have varying variances
270
+ assert X_unwhitened.std(axis=0).std() == pytest.approx(74.1, rel=1e-1)
271
+ # we always center, so no test for non-centering.
272
+
273
+
274
+ @pytest.mark.parametrize(
275
+ "other_svd_solver", sorted(list(set(PCA_SOLVERS) - {"full", "auto"}))
276
+ )
277
+ @pytest.mark.parametrize("data_shape", ["tall", "wide"])
278
+ @pytest.mark.parametrize("rank_deficient", [False, True])
279
+ @pytest.mark.parametrize("whiten", [False, True])
280
+ def test_pca_solver_equivalence(
281
+ other_svd_solver,
282
+ data_shape,
283
+ rank_deficient,
284
+ whiten,
285
+ global_random_seed,
286
+ global_dtype,
287
+ ):
288
+ if data_shape == "tall":
289
+ n_samples, n_features = 100, 30
290
+ else:
291
+ n_samples, n_features = 30, 100
292
+ n_samples_test = 10
293
+
294
+ if rank_deficient:
295
+ rng = np.random.default_rng(global_random_seed)
296
+ rank = min(n_samples, n_features) // 2
297
+ X = rng.standard_normal(
298
+ size=(n_samples + n_samples_test, rank)
299
+ ) @ rng.standard_normal(size=(rank, n_features))
300
+ else:
301
+ X = make_low_rank_matrix(
302
+ n_samples=n_samples + n_samples_test,
303
+ n_features=n_features,
304
+ tail_strength=0.5,
305
+ random_state=global_random_seed,
306
+ )
307
+ # With a non-zero tail strength, the data is actually full-rank.
308
+ rank = min(n_samples, n_features)
309
+
310
+ X = X.astype(global_dtype, copy=False)
311
+ X_train, X_test = X[:n_samples], X[n_samples:]
312
+
313
+ if global_dtype == np.float32:
314
+ tols = dict(atol=3e-2, rtol=1e-5)
315
+ variance_threshold = 1e-5
316
+ else:
317
+ tols = dict(atol=1e-10, rtol=1e-12)
318
+ variance_threshold = 1e-12
319
+
320
+ extra_other_kwargs = {}
321
+ if other_svd_solver == "randomized":
322
+ # Only check for a truncated result with a large number of iterations
323
+ # to make sure that we can recover precise results.
324
+ n_components = 10
325
+ extra_other_kwargs = {"iterated_power": 50}
326
+ elif other_svd_solver == "arpack":
327
+ # Test all components except the last one which cannot be estimated by
328
+ # arpack.
329
+ n_components = np.minimum(n_samples, n_features) - 1
330
+ else:
331
+ # Test all components to high precision.
332
+ n_components = None
333
+
334
+ pca_full = PCA(n_components=n_components, svd_solver="full", whiten=whiten)
335
+ pca_other = PCA(
336
+ n_components=n_components,
337
+ svd_solver=other_svd_solver,
338
+ whiten=whiten,
339
+ random_state=global_random_seed,
340
+ **extra_other_kwargs,
341
+ )
342
+ X_trans_full_train = pca_full.fit_transform(X_train)
343
+ assert np.isfinite(X_trans_full_train).all()
344
+ assert X_trans_full_train.dtype == global_dtype
345
+ X_trans_other_train = pca_other.fit_transform(X_train)
346
+ assert np.isfinite(X_trans_other_train).all()
347
+ assert X_trans_other_train.dtype == global_dtype
348
+
349
+ assert (pca_full.explained_variance_ >= 0).all()
350
+ assert_allclose(pca_full.explained_variance_, pca_other.explained_variance_, **tols)
351
+ assert_allclose(
352
+ pca_full.explained_variance_ratio_,
353
+ pca_other.explained_variance_ratio_,
354
+ **tols,
355
+ )
356
+ reference_components = pca_full.components_
357
+ assert np.isfinite(reference_components).all()
358
+ other_components = pca_other.components_
359
+ assert np.isfinite(other_components).all()
360
+
361
+ # For some choice of n_components and data distribution, some components
362
+ # might be pure noise, let's ignore them in the comparison:
363
+ stable = pca_full.explained_variance_ > variance_threshold
364
+ assert stable.sum() > 1
365
+ assert_allclose(reference_components[stable], other_components[stable], **tols)
366
+
367
+ # As a result the output of fit_transform should be the same:
368
+ assert_allclose(
369
+ X_trans_other_train[:, stable], X_trans_full_train[:, stable], **tols
370
+ )
371
+
372
+ # And similarly for the output of transform on new data (except for the
373
+ # last component that can be underdetermined):
374
+ X_trans_full_test = pca_full.transform(X_test)
375
+ assert np.isfinite(X_trans_full_test).all()
376
+ assert X_trans_full_test.dtype == global_dtype
377
+ X_trans_other_test = pca_other.transform(X_test)
378
+ assert np.isfinite(X_trans_other_test).all()
379
+ assert X_trans_other_test.dtype == global_dtype
380
+ assert_allclose(X_trans_other_test[:, stable], X_trans_full_test[:, stable], **tols)
381
+
382
+ # Check that inverse transform reconstructions for both solvers are
383
+ # compatible.
384
+ X_recons_full_test = pca_full.inverse_transform(X_trans_full_test)
385
+ assert np.isfinite(X_recons_full_test).all()
386
+ assert X_recons_full_test.dtype == global_dtype
387
+ X_recons_other_test = pca_other.inverse_transform(X_trans_other_test)
388
+ assert np.isfinite(X_recons_other_test).all()
389
+ assert X_recons_other_test.dtype == global_dtype
390
+
391
+ if pca_full.components_.shape[0] == pca_full.components_.shape[1]:
392
+ # In this case, the models should have learned the same invertible
393
+ # transform. They should therefore both be able to reconstruct the test
394
+ # data.
395
+ assert_allclose(X_recons_full_test, X_test, **tols)
396
+ assert_allclose(X_recons_other_test, X_test, **tols)
397
+ elif pca_full.components_.shape[0] < rank:
398
+ # In the absence of noisy components, both models should be able to
399
+ # reconstruct the same low-rank approximation of the original data.
400
+ assert pca_full.explained_variance_.min() > variance_threshold
401
+ assert_allclose(X_recons_full_test, X_recons_other_test, **tols)
402
+ else:
403
+ # When n_features > n_samples and n_components is larger than the rank
404
+ # of the training set, the output of the `inverse_transform` function
405
+ # is ill-defined. We can only check that we reach the same fixed point
406
+ # after another round of transform:
407
+ assert_allclose(
408
+ pca_full.transform(X_recons_full_test)[:, stable],
409
+ pca_other.transform(X_recons_other_test)[:, stable],
410
+ **tols,
411
+ )
412
+
413
+
414
+ @pytest.mark.parametrize(
415
+ "X",
416
+ [
417
+ np.random.RandomState(0).randn(100, 80),
418
+ datasets.make_classification(100, 80, n_informative=78, random_state=0)[0],
419
+ np.random.RandomState(0).randn(10, 100),
420
+ ],
421
+ ids=["random-tall", "correlated-tall", "random-wide"],
422
+ )
423
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
424
+ def test_pca_explained_variance_empirical(X, svd_solver):
425
+ pca = PCA(n_components=2, svd_solver=svd_solver, random_state=0)
426
+ X_pca = pca.fit_transform(X)
427
+ assert_allclose(pca.explained_variance_, np.var(X_pca, ddof=1, axis=0))
428
+
429
+ expected_result = np.linalg.eig(np.cov(X, rowvar=False))[0]
430
+ expected_result = sorted(expected_result, reverse=True)[:2]
431
+ assert_allclose(pca.explained_variance_, expected_result, rtol=5e-3)
432
+
433
+
434
+ @pytest.mark.parametrize("svd_solver", ["arpack", "randomized"])
435
+ def test_pca_singular_values_consistency(svd_solver):
436
+ rng = np.random.RandomState(0)
437
+ n_samples, n_features = 100, 80
438
+ X = rng.randn(n_samples, n_features)
439
+
440
+ pca_full = PCA(n_components=2, svd_solver="full", random_state=rng)
441
+ pca_other = PCA(n_components=2, svd_solver=svd_solver, random_state=rng)
442
+
443
+ pca_full.fit(X)
444
+ pca_other.fit(X)
445
+
446
+ assert_allclose(pca_full.singular_values_, pca_other.singular_values_, rtol=5e-3)
447
+
448
+
449
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
450
+ def test_pca_singular_values(svd_solver):
451
+ rng = np.random.RandomState(0)
452
+ n_samples, n_features = 100, 80
453
+ X = rng.randn(n_samples, n_features)
454
+
455
+ pca = PCA(n_components=2, svd_solver=svd_solver, random_state=rng)
456
+ X_trans = pca.fit_transform(X)
457
+
458
+ # compare to the Frobenius norm
459
+ assert_allclose(
460
+ np.sum(pca.singular_values_**2), np.linalg.norm(X_trans, "fro") ** 2
461
+ )
462
+ # Compare to the 2-norms of the score vectors
463
+ assert_allclose(pca.singular_values_, np.sqrt(np.sum(X_trans**2, axis=0)))
464
+
465
+ # set the singular values and see what er get back
466
+ n_samples, n_features = 100, 110
467
+ X = rng.randn(n_samples, n_features)
468
+
469
+ pca = PCA(n_components=3, svd_solver=svd_solver, random_state=rng)
470
+ X_trans = pca.fit_transform(X)
471
+ X_trans /= np.sqrt(np.sum(X_trans**2, axis=0))
472
+ X_trans[:, 0] *= 3.142
473
+ X_trans[:, 1] *= 2.718
474
+ X_hat = np.dot(X_trans, pca.components_)
475
+ pca.fit(X_hat)
476
+ assert_allclose(pca.singular_values_, [3.142, 2.718, 1.0])
477
+
478
+
479
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
480
+ def test_pca_check_projection(svd_solver):
481
+ # Test that the projection of data is correct
482
+ rng = np.random.RandomState(0)
483
+ n, p = 100, 3
484
+ X = rng.randn(n, p) * 0.1
485
+ X[:10] += np.array([3, 4, 5])
486
+ Xt = 0.1 * rng.randn(1, p) + np.array([3, 4, 5])
487
+
488
+ Yt = PCA(n_components=2, svd_solver=svd_solver).fit(X).transform(Xt)
489
+ Yt /= np.sqrt((Yt**2).sum())
490
+
491
+ assert_allclose(np.abs(Yt[0][0]), 1.0, rtol=5e-3)
492
+
493
+
494
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
495
+ def test_pca_check_projection_list(svd_solver):
496
+ # Test that the projection of data is correct
497
+ X = [[1.0, 0.0], [0.0, 1.0]]
498
+ pca = PCA(n_components=1, svd_solver=svd_solver, random_state=0)
499
+ X_trans = pca.fit_transform(X)
500
+ assert X_trans.shape, (2, 1)
501
+ assert_allclose(X_trans.mean(), 0.00, atol=1e-12)
502
+ assert_allclose(X_trans.std(), 0.71, rtol=5e-3)
503
+
504
+
505
+ @pytest.mark.parametrize("svd_solver", ["full", "arpack", "randomized"])
506
+ @pytest.mark.parametrize("whiten", [False, True])
507
+ def test_pca_inverse(svd_solver, whiten):
508
+ # Test that the projection of data can be inverted
509
+ rng = np.random.RandomState(0)
510
+ n, p = 50, 3
511
+ X = rng.randn(n, p) # spherical data
512
+ X[:, 1] *= 0.00001 # make middle component relatively small
513
+ X += [5, 4, 3] # make a large mean
514
+
515
+ # same check that we can find the original data from the transformed
516
+ # signal (since the data is almost of rank n_components)
517
+ pca = PCA(n_components=2, svd_solver=svd_solver, whiten=whiten).fit(X)
518
+ Y = pca.transform(X)
519
+ Y_inverse = pca.inverse_transform(Y)
520
+ assert_allclose(X, Y_inverse, rtol=5e-6)
521
+
522
+
523
+ @pytest.mark.parametrize(
524
+ "data", [np.array([[0, 1, 0], [1, 0, 0]]), np.array([[0, 1, 0], [1, 0, 0]]).T]
525
+ )
526
+ @pytest.mark.parametrize(
527
+ "svd_solver, n_components, err_msg",
528
+ [
529
+ ("arpack", 0, r"must be between 1 and min\(n_samples, n_features\)"),
530
+ ("randomized", 0, r"must be between 1 and min\(n_samples, n_features\)"),
531
+ ("arpack", 2, r"must be strictly less than min"),
532
+ (
533
+ "auto",
534
+ 3,
535
+ (
536
+ r"n_components=3 must be between 0 and min\(n_samples, "
537
+ r"n_features\)=2 with svd_solver='full'"
538
+ ),
539
+ ),
540
+ ],
541
+ )
542
+ def test_pca_validation(svd_solver, data, n_components, err_msg):
543
+ # Ensures that solver-specific extreme inputs for the n_components
544
+ # parameter raise errors
545
+ smallest_d = 2 # The smallest dimension
546
+ pca_fitted = PCA(n_components, svd_solver=svd_solver)
547
+
548
+ with pytest.raises(ValueError, match=err_msg):
549
+ pca_fitted.fit(data)
550
+
551
+ # Additional case for arpack
552
+ if svd_solver == "arpack":
553
+ n_components = smallest_d
554
+
555
+ err_msg = (
556
+ "n_components={}L? must be strictly less than "
557
+ r"min\(n_samples, n_features\)={}L? with "
558
+ "svd_solver='arpack'".format(n_components, smallest_d)
559
+ )
560
+ with pytest.raises(ValueError, match=err_msg):
561
+ PCA(n_components, svd_solver=svd_solver).fit(data)
562
+
563
+
564
+ @pytest.mark.parametrize(
565
+ "solver, n_components_",
566
+ [
567
+ ("full", min(iris.data.shape)),
568
+ ("arpack", min(iris.data.shape) - 1),
569
+ ("randomized", min(iris.data.shape)),
570
+ ],
571
+ )
572
+ @pytest.mark.parametrize("data", [iris.data, iris.data.T])
573
+ def test_n_components_none(data, solver, n_components_):
574
+ pca = PCA(svd_solver=solver)
575
+ pca.fit(data)
576
+ assert pca.n_components_ == n_components_
577
+
578
+
579
+ @pytest.mark.parametrize("svd_solver", ["auto", "full"])
580
+ def test_n_components_mle(svd_solver):
581
+ # Ensure that n_components == 'mle' doesn't raise error for auto/full
582
+ rng = np.random.RandomState(0)
583
+ n_samples, n_features = 600, 10
584
+ X = rng.randn(n_samples, n_features)
585
+ pca = PCA(n_components="mle", svd_solver=svd_solver)
586
+ pca.fit(X)
587
+ assert pca.n_components_ == 1
588
+
589
+
590
+ @pytest.mark.parametrize("svd_solver", ["arpack", "randomized"])
591
+ def test_n_components_mle_error(svd_solver):
592
+ # Ensure that n_components == 'mle' will raise an error for unsupported
593
+ # solvers
594
+ rng = np.random.RandomState(0)
595
+ n_samples, n_features = 600, 10
596
+ X = rng.randn(n_samples, n_features)
597
+ pca = PCA(n_components="mle", svd_solver=svd_solver)
598
+ err_msg = "n_components='mle' cannot be a string with svd_solver='{}'".format(
599
+ svd_solver
600
+ )
601
+ with pytest.raises(ValueError, match=err_msg):
602
+ pca.fit(X)
603
+
604
+
605
+ def test_pca_dim():
606
+ # Check automated dimensionality setting
607
+ rng = np.random.RandomState(0)
608
+ n, p = 100, 5
609
+ X = rng.randn(n, p) * 0.1
610
+ X[:10] += np.array([3, 4, 5, 1, 2])
611
+ pca = PCA(n_components="mle", svd_solver="full").fit(X)
612
+ assert pca.n_components == "mle"
613
+ assert pca.n_components_ == 1
614
+
615
+
616
+ def test_infer_dim_1():
617
+ # TODO: explain what this is testing
618
+ # Or at least use explicit variable names...
619
+ n, p = 1000, 5
620
+ rng = np.random.RandomState(0)
621
+ X = (
622
+ rng.randn(n, p) * 0.1
623
+ + rng.randn(n, 1) * np.array([3, 4, 5, 1, 2])
624
+ + np.array([1, 0, 7, 4, 6])
625
+ )
626
+ pca = PCA(n_components=p, svd_solver="full")
627
+ pca.fit(X)
628
+ spect = pca.explained_variance_
629
+ ll = np.array([_assess_dimension(spect, k, n) for k in range(1, p)])
630
+ assert ll[1] > ll.max() - 0.01 * n
631
+
632
+
633
+ def test_infer_dim_2():
634
+ # TODO: explain what this is testing
635
+ # Or at least use explicit variable names...
636
+ n, p = 1000, 5
637
+ rng = np.random.RandomState(0)
638
+ X = rng.randn(n, p) * 0.1
639
+ X[:10] += np.array([3, 4, 5, 1, 2])
640
+ X[10:20] += np.array([6, 0, 7, 2, -1])
641
+ pca = PCA(n_components=p, svd_solver="full")
642
+ pca.fit(X)
643
+ spect = pca.explained_variance_
644
+ assert _infer_dimension(spect, n) > 1
645
+
646
+
647
+ def test_infer_dim_3():
648
+ n, p = 100, 5
649
+ rng = np.random.RandomState(0)
650
+ X = rng.randn(n, p) * 0.1
651
+ X[:10] += np.array([3, 4, 5, 1, 2])
652
+ X[10:20] += np.array([6, 0, 7, 2, -1])
653
+ X[30:40] += 2 * np.array([-1, 1, -1, 1, -1])
654
+ pca = PCA(n_components=p, svd_solver="full")
655
+ pca.fit(X)
656
+ spect = pca.explained_variance_
657
+ assert _infer_dimension(spect, n) > 2
658
+
659
+
660
+ @pytest.mark.parametrize(
661
+ "X, n_components, n_components_validated",
662
+ [
663
+ (iris.data, 0.95, 2), # row > col
664
+ (iris.data, 0.01, 1), # row > col
665
+ (np.random.RandomState(0).rand(5, 20), 0.5, 2),
666
+ ], # row < col
667
+ )
668
+ def test_infer_dim_by_explained_variance(X, n_components, n_components_validated):
669
+ pca = PCA(n_components=n_components, svd_solver="full")
670
+ pca.fit(X)
671
+ assert pca.n_components == pytest.approx(n_components)
672
+ assert pca.n_components_ == n_components_validated
673
+
674
+
675
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
676
+ def test_pca_score(svd_solver):
677
+ # Test that probabilistic PCA scoring yields a reasonable score
678
+ n, p = 1000, 3
679
+ rng = np.random.RandomState(0)
680
+ X = rng.randn(n, p) * 0.1 + np.array([3, 4, 5])
681
+ pca = PCA(n_components=2, svd_solver=svd_solver)
682
+ pca.fit(X)
683
+
684
+ ll1 = pca.score(X)
685
+ h = -0.5 * np.log(2 * np.pi * np.exp(1) * 0.1**2) * p
686
+ assert_allclose(ll1 / h, 1, rtol=5e-2)
687
+
688
+ ll2 = pca.score(rng.randn(n, p) * 0.2 + np.array([3, 4, 5]))
689
+ assert ll1 > ll2
690
+
691
+ pca = PCA(n_components=2, whiten=True, svd_solver=svd_solver)
692
+ pca.fit(X)
693
+ ll2 = pca.score(X)
694
+ assert ll1 > ll2
695
+
696
+
697
+ def test_pca_score3():
698
+ # Check that probabilistic PCA selects the right model
699
+ n, p = 200, 3
700
+ rng = np.random.RandomState(0)
701
+ Xl = rng.randn(n, p) + rng.randn(n, 1) * np.array([3, 4, 5]) + np.array([1, 0, 7])
702
+ Xt = rng.randn(n, p) + rng.randn(n, 1) * np.array([3, 4, 5]) + np.array([1, 0, 7])
703
+ ll = np.zeros(p)
704
+ for k in range(p):
705
+ pca = PCA(n_components=k, svd_solver="full")
706
+ pca.fit(Xl)
707
+ ll[k] = pca.score(Xt)
708
+
709
+ assert ll.argmax() == 1
710
+
711
+
712
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
713
+ def test_pca_sanity_noise_variance(svd_solver):
714
+ # Sanity check for the noise_variance_. For more details see
715
+ # https://github.com/scikit-learn/scikit-learn/issues/7568
716
+ # https://github.com/scikit-learn/scikit-learn/issues/8541
717
+ # https://github.com/scikit-learn/scikit-learn/issues/8544
718
+ X, _ = datasets.load_digits(return_X_y=True)
719
+ pca = PCA(n_components=30, svd_solver=svd_solver, random_state=0)
720
+ pca.fit(X)
721
+ assert np.all((pca.explained_variance_ - pca.noise_variance_) >= 0)
722
+
723
+
724
+ @pytest.mark.parametrize("svd_solver", ["arpack", "randomized"])
725
+ def test_pca_score_consistency_solvers(svd_solver):
726
+ # Check the consistency of score between solvers
727
+ X, _ = datasets.load_digits(return_X_y=True)
728
+ pca_full = PCA(n_components=30, svd_solver="full", random_state=0)
729
+ pca_other = PCA(n_components=30, svd_solver=svd_solver, random_state=0)
730
+ pca_full.fit(X)
731
+ pca_other.fit(X)
732
+ assert_allclose(pca_full.score(X), pca_other.score(X), rtol=5e-6)
733
+
734
+
735
+ # arpack raises ValueError for n_components == min(n_samples, n_features)
736
+ @pytest.mark.parametrize("svd_solver", ["full", "randomized"])
737
+ def test_pca_zero_noise_variance_edge_cases(svd_solver):
738
+ # ensure that noise_variance_ is 0 in edge cases
739
+ # when n_components == min(n_samples, n_features)
740
+ n, p = 100, 3
741
+ rng = np.random.RandomState(0)
742
+ X = rng.randn(n, p) * 0.1 + np.array([3, 4, 5])
743
+
744
+ pca = PCA(n_components=p, svd_solver=svd_solver)
745
+ pca.fit(X)
746
+ assert pca.noise_variance_ == 0
747
+ # Non-regression test for gh-12489
748
+ # ensure no divide-by-zero error for n_components == n_features < n_samples
749
+ pca.score(X)
750
+
751
+ pca.fit(X.T)
752
+ assert pca.noise_variance_ == 0
753
+ # Non-regression test for gh-12489
754
+ # ensure no divide-by-zero error for n_components == n_samples < n_features
755
+ pca.score(X.T)
756
+
757
+
758
+ @pytest.mark.parametrize(
759
+ "n_samples, n_features, n_components, expected_solver",
760
+ [
761
+ # case: n_samples < 10 * n_features and max(X.shape) <= 500 => 'full'
762
+ (10, 50, 5, "full"),
763
+ # case: n_samples > 10 * n_features and n_features < 500 => 'covariance_eigh'
764
+ (1000, 50, 50, "covariance_eigh"),
765
+ # case: n_components >= .8 * min(X.shape) => 'full'
766
+ (1000, 500, 400, "full"),
767
+ # n_components >= 1 and n_components < .8*min(X.shape) => 'randomized'
768
+ (1000, 500, 10, "randomized"),
769
+ # case: n_components in (0,1) => 'full'
770
+ (1000, 500, 0.5, "full"),
771
+ ],
772
+ )
773
+ def test_pca_svd_solver_auto(n_samples, n_features, n_components, expected_solver):
774
+ data = np.random.RandomState(0).uniform(size=(n_samples, n_features))
775
+ pca_auto = PCA(n_components=n_components, random_state=0)
776
+ pca_test = PCA(
777
+ n_components=n_components, svd_solver=expected_solver, random_state=0
778
+ )
779
+ pca_auto.fit(data)
780
+ assert pca_auto._fit_svd_solver == expected_solver
781
+ pca_test.fit(data)
782
+ assert_allclose(pca_auto.components_, pca_test.components_)
783
+
784
+
785
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
786
+ def test_pca_deterministic_output(svd_solver):
787
+ rng = np.random.RandomState(0)
788
+ X = rng.rand(10, 10)
789
+
790
+ transformed_X = np.zeros((20, 2))
791
+ for i in range(20):
792
+ pca = PCA(n_components=2, svd_solver=svd_solver, random_state=rng)
793
+ transformed_X[i, :] = pca.fit_transform(X)[0]
794
+ assert_allclose(transformed_X, np.tile(transformed_X[0, :], 20).reshape(20, 2))
795
+
796
+
797
+ @pytest.mark.parametrize("svd_solver", PCA_SOLVERS)
798
+ def test_pca_dtype_preservation(svd_solver, global_random_seed):
799
+ check_pca_float_dtype_preservation(svd_solver, global_random_seed)
800
+ check_pca_int_dtype_upcast_to_double(svd_solver)
801
+
802
+
803
+ def check_pca_float_dtype_preservation(svd_solver, seed):
804
+ # Ensure that PCA does not upscale the dtype when input is float32
805
+ X = np.random.RandomState(seed).rand(1000, 4)
806
+ X_float64 = X.astype(np.float64, copy=False)
807
+ X_float32 = X.astype(np.float32)
808
+
809
+ pca_64 = PCA(n_components=3, svd_solver=svd_solver, random_state=seed).fit(
810
+ X_float64
811
+ )
812
+ pca_32 = PCA(n_components=3, svd_solver=svd_solver, random_state=seed).fit(
813
+ X_float32
814
+ )
815
+
816
+ assert pca_64.components_.dtype == np.float64
817
+ assert pca_32.components_.dtype == np.float32
818
+ assert pca_64.transform(X_float64).dtype == np.float64
819
+ assert pca_32.transform(X_float32).dtype == np.float32
820
+
821
+ # The atol and rtol are set such that the test passes for all random seeds
822
+ # on all supported platforms on our CI and conda-forge with the default
823
+ # random seed.
824
+ assert_allclose(pca_64.components_, pca_32.components_, rtol=1e-3, atol=1e-3)
825
+
826
+
827
+ def check_pca_int_dtype_upcast_to_double(svd_solver):
828
+ # Ensure that all int types will be upcast to float64
829
+ X_i64 = np.random.RandomState(0).randint(0, 1000, (1000, 4))
830
+ X_i64 = X_i64.astype(np.int64, copy=False)
831
+ X_i32 = X_i64.astype(np.int32, copy=False)
832
+
833
+ pca_64 = PCA(n_components=3, svd_solver=svd_solver, random_state=0).fit(X_i64)
834
+ pca_32 = PCA(n_components=3, svd_solver=svd_solver, random_state=0).fit(X_i32)
835
+
836
+ assert pca_64.components_.dtype == np.float64
837
+ assert pca_32.components_.dtype == np.float64
838
+ assert pca_64.transform(X_i64).dtype == np.float64
839
+ assert pca_32.transform(X_i32).dtype == np.float64
840
+
841
+ assert_allclose(pca_64.components_, pca_32.components_, rtol=1e-4)
842
+
843
+
844
+ def test_pca_n_components_mostly_explained_variance_ratio():
845
+ # when n_components is the second highest cumulative sum of the
846
+ # explained_variance_ratio_, then n_components_ should equal the
847
+ # number of features in the dataset #15669
848
+ X, y = load_iris(return_X_y=True)
849
+ pca1 = PCA().fit(X, y)
850
+
851
+ n_components = pca1.explained_variance_ratio_.cumsum()[-2]
852
+ pca2 = PCA(n_components=n_components).fit(X, y)
853
+ assert pca2.n_components_ == X.shape[1]
854
+
855
+
856
+ def test_assess_dimension_bad_rank():
857
+ # Test error when tested rank not in [1, n_features - 1]
858
+ spectrum = np.array([1, 1e-30, 1e-30, 1e-30])
859
+ n_samples = 10
860
+ for rank in (0, 5):
861
+ with pytest.raises(ValueError, match=r"should be in \[1, n_features - 1\]"):
862
+ _assess_dimension(spectrum, rank, n_samples)
863
+
864
+
865
+ def test_small_eigenvalues_mle():
866
+ # Test rank associated with tiny eigenvalues are given a log-likelihood of
867
+ # -inf. The inferred rank will be 1
868
+ spectrum = np.array([1, 1e-30, 1e-30, 1e-30])
869
+
870
+ assert _assess_dimension(spectrum, rank=1, n_samples=10) > -np.inf
871
+
872
+ for rank in (2, 3):
873
+ assert _assess_dimension(spectrum, rank, 10) == -np.inf
874
+
875
+ assert _infer_dimension(spectrum, 10) == 1
876
+
877
+
878
+ def test_mle_redundant_data():
879
+ # Test 'mle' with pathological X: only one relevant feature should give a
880
+ # rank of 1
881
+ X, _ = datasets.make_classification(
882
+ n_features=20,
883
+ n_informative=1,
884
+ n_repeated=18,
885
+ n_redundant=1,
886
+ n_clusters_per_class=1,
887
+ random_state=42,
888
+ )
889
+ pca = PCA(n_components="mle").fit(X)
890
+ assert pca.n_components_ == 1
891
+
892
+
893
+ def test_fit_mle_too_few_samples():
894
+ # Tests that an error is raised when the number of samples is smaller
895
+ # than the number of features during an mle fit
896
+ X, _ = datasets.make_classification(n_samples=20, n_features=21, random_state=42)
897
+
898
+ pca = PCA(n_components="mle", svd_solver="full")
899
+ with pytest.raises(
900
+ ValueError,
901
+ match="n_components='mle' is only supported if n_samples >= n_features",
902
+ ):
903
+ pca.fit(X)
904
+
905
+
906
+ def test_mle_simple_case():
907
+ # non-regression test for issue
908
+ # https://github.com/scikit-learn/scikit-learn/issues/16730
909
+ n_samples, n_dim = 1000, 10
910
+ X = np.random.RandomState(0).randn(n_samples, n_dim)
911
+ X[:, -1] = np.mean(X[:, :-1], axis=-1) # true X dim is ndim - 1
912
+ pca_skl = PCA("mle", svd_solver="full")
913
+ pca_skl.fit(X)
914
+ assert pca_skl.n_components_ == n_dim - 1
915
+
916
+
917
+ def test_assess_dimesion_rank_one():
918
+ # Make sure assess_dimension works properly on a matrix of rank 1
919
+ n_samples, n_features = 9, 6
920
+ X = np.ones((n_samples, n_features)) # rank 1 matrix
921
+ _, s, _ = np.linalg.svd(X, full_matrices=True)
922
+ # except for rank 1, all eigenvalues are 0 resp. close to 0 (FP)
923
+ assert_allclose(s[1:], np.zeros(n_features - 1), atol=1e-12)
924
+
925
+ assert np.isfinite(_assess_dimension(s, rank=1, n_samples=n_samples))
926
+ for rank in range(2, n_features):
927
+ assert _assess_dimension(s, rank, n_samples) == -np.inf
928
+
929
+
930
+ def test_pca_randomized_svd_n_oversamples():
931
+ """Check that exposing and setting `n_oversamples` will provide accurate results
932
+ even when `X` as a large number of features.
933
+
934
+ Non-regression test for:
935
+ https://github.com/scikit-learn/scikit-learn/issues/20589
936
+ """
937
+ rng = np.random.RandomState(0)
938
+ n_features = 100
939
+ X = rng.randn(1_000, n_features)
940
+
941
+ # The default value of `n_oversamples` will lead to inaccurate results
942
+ # We force it to the number of features.
943
+ pca_randomized = PCA(
944
+ n_components=1,
945
+ svd_solver="randomized",
946
+ n_oversamples=n_features,
947
+ random_state=0,
948
+ ).fit(X)
949
+ pca_full = PCA(n_components=1, svd_solver="full").fit(X)
950
+ pca_arpack = PCA(n_components=1, svd_solver="arpack", random_state=0).fit(X)
951
+
952
+ assert_allclose(np.abs(pca_full.components_), np.abs(pca_arpack.components_))
953
+ assert_allclose(np.abs(pca_randomized.components_), np.abs(pca_arpack.components_))
954
+
955
+
956
+ def test_feature_names_out():
957
+ """Check feature names out for PCA."""
958
+ pca = PCA(n_components=2).fit(iris.data)
959
+
960
+ names = pca.get_feature_names_out()
961
+ assert_array_equal([f"pca{i}" for i in range(2)], names)
962
+
963
+
964
+ @pytest.mark.parametrize("copy", [True, False])
965
+ def test_variance_correctness(copy):
966
+ """Check the accuracy of PCA's internal variance calculation"""
967
+ rng = np.random.RandomState(0)
968
+ X = rng.randn(1000, 200)
969
+ pca = PCA().fit(X)
970
+ pca_var = pca.explained_variance_ / pca.explained_variance_ratio_
971
+ true_var = np.var(X, ddof=1, axis=0).sum()
972
+ np.testing.assert_allclose(pca_var, true_var)
973
+
974
+
975
+ def check_array_api_get_precision(name, estimator, array_namespace, device, dtype_name):
976
+ xp = _array_api_for_tests(array_namespace, device)
977
+ iris_np = iris.data.astype(dtype_name)
978
+ iris_xp = xp.asarray(iris_np, device=device)
979
+
980
+ estimator.fit(iris_np)
981
+ precision_np = estimator.get_precision()
982
+ covariance_np = estimator.get_covariance()
983
+
984
+ rtol = 2e-4 if iris_np.dtype == "float32" else 2e-7
985
+ with config_context(array_api_dispatch=True):
986
+ estimator_xp = clone(estimator).fit(iris_xp)
987
+ precision_xp = estimator_xp.get_precision()
988
+ assert precision_xp.shape == (4, 4)
989
+ assert precision_xp.dtype == iris_xp.dtype
990
+
991
+ assert_allclose(
992
+ _convert_to_numpy(precision_xp, xp=xp),
993
+ precision_np,
994
+ rtol=rtol,
995
+ atol=_atol_for_type(dtype_name),
996
+ )
997
+ covariance_xp = estimator_xp.get_covariance()
998
+ assert covariance_xp.shape == (4, 4)
999
+ assert covariance_xp.dtype == iris_xp.dtype
1000
+
1001
+ assert_allclose(
1002
+ _convert_to_numpy(covariance_xp, xp=xp),
1003
+ covariance_np,
1004
+ rtol=rtol,
1005
+ atol=_atol_for_type(dtype_name),
1006
+ )
1007
+
1008
+
1009
+ @pytest.mark.parametrize(
1010
+ "array_namespace, device, dtype_name",
1011
+ yield_namespace_device_dtype_combinations(),
1012
+ ids=_get_namespace_device_dtype_ids,
1013
+ )
1014
+ @pytest.mark.parametrize(
1015
+ "check",
1016
+ [check_array_api_input_and_values, check_array_api_get_precision],
1017
+ ids=_get_check_estimator_ids,
1018
+ )
1019
+ @pytest.mark.parametrize(
1020
+ "estimator",
1021
+ [
1022
+ PCA(n_components=2, svd_solver="full"),
1023
+ PCA(n_components=2, svd_solver="full", whiten=True),
1024
+ PCA(n_components=0.1, svd_solver="full", whiten=True),
1025
+ PCA(n_components=2, svd_solver="covariance_eigh"),
1026
+ PCA(n_components=2, svd_solver="covariance_eigh", whiten=True),
1027
+ PCA(
1028
+ n_components=2,
1029
+ svd_solver="randomized",
1030
+ power_iteration_normalizer="QR",
1031
+ random_state=0, # how to use global_random_seed here?
1032
+ ),
1033
+ ],
1034
+ ids=_get_check_estimator_ids,
1035
+ )
1036
+ def test_pca_array_api_compliance(
1037
+ estimator, check, array_namespace, device, dtype_name
1038
+ ):
1039
+ name = estimator.__class__.__name__
1040
+ estimator = clone(estimator)
1041
+ check(name, estimator, array_namespace, device=device, dtype_name=dtype_name)
1042
+
1043
+
1044
+ @pytest.mark.parametrize(
1045
+ "array_namespace, device, dtype_name",
1046
+ yield_namespace_device_dtype_combinations(),
1047
+ ids=_get_namespace_device_dtype_ids,
1048
+ )
1049
+ @pytest.mark.parametrize(
1050
+ "check",
1051
+ [check_array_api_get_precision],
1052
+ ids=_get_check_estimator_ids,
1053
+ )
1054
+ @pytest.mark.parametrize(
1055
+ "estimator",
1056
+ [
1057
+ # PCA with mle cannot use check_array_api_input_and_values because of
1058
+ # rounding errors in the noisy (low variance) components. Even checking
1059
+ # the shape of the `components_` is problematic because the number of
1060
+ # components depends on trimming threshold of the mle algorithm which
1061
+ # can depend on device-specific rounding errors.
1062
+ PCA(n_components="mle", svd_solver="full"),
1063
+ ],
1064
+ ids=_get_check_estimator_ids,
1065
+ )
1066
+ def test_pca_mle_array_api_compliance(
1067
+ estimator, check, array_namespace, device, dtype_name
1068
+ ):
1069
+ name = estimator.__class__.__name__
1070
+ check(name, estimator, array_namespace, device=device, dtype_name=dtype_name)
1071
+
1072
+ # Simpler variant of the generic check_array_api_input checker tailored for
1073
+ # the specific case of PCA with mle-trimmed components.
1074
+ xp = _array_api_for_tests(array_namespace, device)
1075
+
1076
+ X, y = make_classification(random_state=42)
1077
+ X = X.astype(dtype_name, copy=False)
1078
+ atol = _atol_for_type(X.dtype)
1079
+
1080
+ est = clone(estimator)
1081
+
1082
+ X_xp = xp.asarray(X, device=device)
1083
+ y_xp = xp.asarray(y, device=device)
1084
+
1085
+ est.fit(X, y)
1086
+
1087
+ components_np = est.components_
1088
+ explained_variance_np = est.explained_variance_
1089
+
1090
+ est_xp = clone(est)
1091
+ with config_context(array_api_dispatch=True):
1092
+ est_xp.fit(X_xp, y_xp)
1093
+ components_xp = est_xp.components_
1094
+ assert array_device(components_xp) == array_device(X_xp)
1095
+ components_xp_np = _convert_to_numpy(components_xp, xp=xp)
1096
+
1097
+ explained_variance_xp = est_xp.explained_variance_
1098
+ assert array_device(explained_variance_xp) == array_device(X_xp)
1099
+ explained_variance_xp_np = _convert_to_numpy(explained_variance_xp, xp=xp)
1100
+
1101
+ assert components_xp_np.dtype == components_np.dtype
1102
+ assert components_xp_np.shape[1] == components_np.shape[1]
1103
+ assert explained_variance_xp_np.dtype == explained_variance_np.dtype
1104
+
1105
+ # Check that the explained variance values match for the
1106
+ # common components:
1107
+ min_components = min(components_xp_np.shape[0], components_np.shape[0])
1108
+ assert_allclose(
1109
+ explained_variance_xp_np[:min_components],
1110
+ explained_variance_np[:min_components],
1111
+ atol=atol,
1112
+ )
1113
+
1114
+ # If the number of components differ, check that the explained variance of
1115
+ # the trimmed components is very small.
1116
+ if components_xp_np.shape[0] != components_np.shape[0]:
1117
+ reference_variance = explained_variance_np[-1]
1118
+ extra_variance_np = explained_variance_np[min_components:]
1119
+ extra_variance_xp_np = explained_variance_xp_np[min_components:]
1120
+ assert all(np.abs(extra_variance_np - reference_variance) < atol)
1121
+ assert all(np.abs(extra_variance_xp_np - reference_variance) < atol)
1122
+
1123
+
1124
+ @pytest.mark.skipif(
1125
+ os.environ.get("SCIPY_ARRAY_API") != "1", reason="SCIPY_ARRAY_API not set to 1."
1126
+ )
1127
+ def test_array_api_error_and_warnings_on_unsupported_params():
1128
+ xp = pytest.importorskip("array_api_strict")
1129
+ iris_xp = xp.asarray(iris.data)
1130
+
1131
+ pca = PCA(n_components=2, svd_solver="arpack", random_state=0)
1132
+ expected_msg = re.escape(
1133
+ "PCA with svd_solver='arpack' is not supported for Array API inputs."
1134
+ )
1135
+ with pytest.raises(ValueError, match=expected_msg):
1136
+ with config_context(array_api_dispatch=True):
1137
+ pca.fit(iris_xp)
1138
+
1139
+ pca.set_params(svd_solver="randomized", power_iteration_normalizer="LU")
1140
+ expected_msg = re.escape(
1141
+ "Array API does not support LU factorization. Set"
1142
+ " `power_iteration_normalizer='QR'` instead."
1143
+ )
1144
+ with pytest.raises(ValueError, match=expected_msg):
1145
+ with config_context(array_api_dispatch=True):
1146
+ pca.fit(iris_xp)
1147
+
1148
+ pca.set_params(svd_solver="randomized", power_iteration_normalizer="auto")
1149
+ expected_msg = re.escape(
1150
+ "Array API does not support LU factorization, falling back to QR instead. Set"
1151
+ " `power_iteration_normalizer='QR'` explicitly to silence this warning."
1152
+ )
1153
+ with pytest.warns(UserWarning, match=expected_msg):
1154
+ with config_context(array_api_dispatch=True):
1155
+ pca.fit(iris_xp)