Aluode/PerceptionLabPortable
0
1from importlib import import_module
2from inspect import signature
3from numbers import Integral, Real
4
5import pytest
6
7from sklearn.utils._param_validation import (
8 Interval,
9 InvalidParameterError,
10 generate_invalid_param_val,
11 generate_valid_param,
12 make_constraint,
13)
14
15
16def _get_func_info(func_module):
17 module_name, func_name = func_module.rsplit(".", 1)
18 module = import_module(module_name)
19 func = getattr(module, func_name)
20
21 func_sig = signature(func)
22 func_params = [
23 p.name
24 for p in func_sig.parameters.values()
25 if p.kind not in (p.VAR_POSITIONAL, p.VAR_KEYWORD)
26 ]
27
28 # The parameters `*args` and `**kwargs` are ignored since we cannot generate
29 # constraints.
30 required_params = [
31 p.name
32 for p in func_sig.parameters.values()
33 if p.default is p.empty and p.kind not in (p.VAR_POSITIONAL, p.VAR_KEYWORD)
34 ]
35
36 return func, func_name, func_params, required_params
37
38
39def _check_function_param_validation(
40 func, func_name, func_params, required_params, parameter_constraints
41):
42 """Check that an informative error is raised when the value of a parameter does not
43 have an appropriate type or value.
44 """
45 # generate valid values for the required parameters
46 valid_required_params = {}
47 for param_name in required_params:
48 if parameter_constraints[param_name] == "no_validation":
49 valid_required_params[param_name] = 1
50 else:
51 valid_required_params[param_name] = generate_valid_param(
52 make_constraint(parameter_constraints[param_name][0])
53 )
54
55 # check that there is a constraint for each parameter
56 if func_params:
57 validation_params = parameter_constraints.keys()
58 unexpected_params = set(validation_params) - set(func_params)
59 missing_params = set(func_params) - set(validation_params)
60 err_msg = (
61 "Mismatch between _parameter_constraints and the parameters of"
62 f" {func_name}.\nConsider the unexpected parameters {unexpected_params} and"
63 f" expected but missing parameters {missing_params}\n"
64 )
65 assert set(validation_params) == set(func_params), err_msg
66
67 # this object does not have a valid type for sure for all params
68 param_with_bad_type = type("BadType", (), {})()
69
70 for param_name in func_params:
71 constraints = parameter_constraints[param_name]
72
73 if constraints == "no_validation":
74 # This parameter is not validated
75 continue
76
77 # Mixing an interval of reals and an interval of integers must be avoided.
78 if any(
79 isinstance(constraint, Interval) and constraint.type == Integral
80 for constraint in constraints
81 ) and any(
82 isinstance(constraint, Interval) and constraint.type == Real
83 for constraint in constraints
84 ):
85 raise ValueError(
86 f"The constraint for parameter {param_name} of {func_name} can't have a"
87 " mix of intervals of Integral and Real types. Use the type"
88 " RealNotInt instead of Real."
89 )
90
91 match = (
92 rf"The '{param_name}' parameter of {func_name} must be .* Got .* instead."
93 )
94
95 err_msg = (
96 f"{func_name} does not raise an informative error message when the "
97 f"parameter {param_name} does not have a valid type. If any Python type "
98 "is valid, the constraint should be 'no_validation'."
99 )
100
101 # First, check that the error is raised if param doesn't match any valid type.
102 with pytest.raises(InvalidParameterError, match=match):
103 func(**{**valid_required_params, param_name: param_with_bad_type})
104 pytest.fail(err_msg)
105
106 # Then, for constraints that are more than a type constraint, check that the
107 # error is raised if param does match a valid type but does not match any valid
108 # value for this type.
109 constraints = [make_constraint(constraint) for constraint in constraints]
110
111 for constraint in constraints:
112 try:
113 bad_value = generate_invalid_param_val(constraint)
114 except NotImplementedError:
115 continue
116
117 err_msg = (
118 f"{func_name} does not raise an informative error message when the "
119 f"parameter {param_name} does not have a valid value.\n"
120 "Constraints should be disjoint. For instance "
121 "[StrOptions({'a_string'}), str] is not a acceptable set of "
122 "constraint because generating an invalid string for the first "
123 "constraint will always produce a valid string for the second "
124 "constraint."
125 )
126
127 with pytest.raises(InvalidParameterError, match=match):
128 func(**{**valid_required_params, param_name: bad_value})
129 pytest.fail(err_msg)
130
131
132PARAM_VALIDATION_FUNCTION_LIST = [
133 "sklearn.calibration.calibration_curve",
134 "sklearn.cluster.cluster_optics_dbscan",
135 "sklearn.cluster.compute_optics_graph",
136 "sklearn.cluster.estimate_bandwidth",
137 "sklearn.cluster.kmeans_plusplus",
138 "sklearn.cluster.cluster_optics_xi",
139 "sklearn.cluster.ward_tree",
140 "sklearn.covariance.empirical_covariance",
141 "sklearn.covariance.ledoit_wolf_shrinkage",
142 "sklearn.covariance.log_likelihood",
143 "sklearn.covariance.shrunk_covariance",
144 "sklearn.datasets.clear_data_home",
145 "sklearn.datasets.dump_svmlight_file",
146 "sklearn.datasets.fetch_20newsgroups",
147 "sklearn.datasets.fetch_20newsgroups_vectorized",
148 "sklearn.datasets.fetch_california_housing",
149 "sklearn.datasets.fetch_covtype",
150 "sklearn.datasets.fetch_kddcup99",
151 "sklearn.datasets.fetch_lfw_pairs",
152 "sklearn.datasets.fetch_lfw_people",
153 "sklearn.datasets.fetch_olivetti_faces",
154 "sklearn.datasets.fetch_rcv1",
155 "sklearn.datasets.fetch_openml",
156 "sklearn.datasets.fetch_species_distributions",
157 "sklearn.datasets.get_data_home",
158 "sklearn.datasets.load_breast_cancer",
159 "sklearn.datasets.load_diabetes",
160 "sklearn.datasets.load_digits",
161 "sklearn.datasets.load_files",
162 "sklearn.datasets.load_iris",
163 "sklearn.datasets.load_linnerud",
164 "sklearn.datasets.load_sample_image",
165 "sklearn.datasets.load_svmlight_file",
166 "sklearn.datasets.load_svmlight_files",
167 "sklearn.datasets.load_wine",
168 "sklearn.datasets.make_biclusters",
169 "sklearn.datasets.make_blobs",
170 "sklearn.datasets.make_checkerboard",
171 "sklearn.datasets.make_circles",
172 "sklearn.datasets.make_classification",
173 "sklearn.datasets.make_friedman1",
174 "sklearn.datasets.make_friedman2",
175 "sklearn.datasets.make_friedman3",
176 "sklearn.datasets.make_gaussian_quantiles",
177 "sklearn.datasets.make_hastie_10_2",
178 "sklearn.datasets.make_low_rank_matrix",
179 "sklearn.datasets.make_moons",
180 "sklearn.datasets.make_multilabel_classification",
181 "sklearn.datasets.make_regression",
182 "sklearn.datasets.make_s_curve",
183 "sklearn.datasets.make_sparse_coded_signal",
184 "sklearn.datasets.make_sparse_spd_matrix",
185 "sklearn.datasets.make_sparse_uncorrelated",
186 "sklearn.datasets.make_spd_matrix",
187 "sklearn.datasets.make_swiss_roll",
188 "sklearn.decomposition.sparse_encode",
189 "sklearn.feature_extraction.grid_to_graph",
190 "sklearn.feature_extraction.img_to_graph",
191 "sklearn.feature_extraction.image.extract_patches_2d",
192 "sklearn.feature_extraction.image.reconstruct_from_patches_2d",
193 "sklearn.feature_selection.chi2",
194 "sklearn.feature_selection.f_classif",
195 "sklearn.feature_selection.f_regression",
196 "sklearn.feature_selection.mutual_info_classif",
197 "sklearn.feature_selection.mutual_info_regression",
198 "sklearn.feature_selection.r_regression",
199 "sklearn.inspection.partial_dependence",
200 "sklearn.inspection.permutation_importance",
201 "sklearn.isotonic.check_increasing",
202 "sklearn.isotonic.isotonic_regression",
203 "sklearn.linear_model.enet_path",
204 "sklearn.linear_model.lars_path",
205 "sklearn.linear_model.lars_path_gram",
206 "sklearn.linear_model.lasso_path",
207 "sklearn.linear_model.orthogonal_mp",
208 "sklearn.linear_model.orthogonal_mp_gram",
209 "sklearn.linear_model.ridge_regression",
210 "sklearn.manifold.locally_linear_embedding",
211 "sklearn.manifold.smacof",
212 "sklearn.manifold.spectral_embedding",
213 "sklearn.manifold.trustworthiness",
214 "sklearn.metrics.accuracy_score",
215 "sklearn.metrics.auc",
216 "sklearn.metrics.average_precision_score",
217 "sklearn.metrics.balanced_accuracy_score",
218 "sklearn.metrics.brier_score_loss",
219 "sklearn.metrics.calinski_harabasz_score",
220 "sklearn.metrics.check_scoring",
221 "sklearn.metrics.completeness_score",
222 "sklearn.metrics.class_likelihood_ratios",
223 "sklearn.metrics.classification_report",
224 "sklearn.metrics.cluster.adjusted_mutual_info_score",
225 "sklearn.metrics.cluster.contingency_matrix",
226 "sklearn.metrics.cluster.entropy",
227 "sklearn.metrics.cluster.fowlkes_mallows_score",
228 "sklearn.metrics.cluster.homogeneity_completeness_v_measure",
229 "sklearn.metrics.cluster.normalized_mutual_info_score",
230 "sklearn.metrics.cluster.silhouette_samples",
231 "sklearn.metrics.cluster.silhouette_score",
232 "sklearn.metrics.cohen_kappa_score",
233 "sklearn.metrics.confusion_matrix",
234 "sklearn.metrics.consensus_score",
235 "sklearn.metrics.coverage_error",
236 "sklearn.metrics.d2_absolute_error_score",
237 "sklearn.metrics.d2_log_loss_score",
238 "sklearn.metrics.d2_pinball_score",
239 "sklearn.metrics.d2_tweedie_score",
240 "sklearn.metrics.davies_bouldin_score",
241 "sklearn.metrics.dcg_score",
242 "sklearn.metrics.det_curve",
243 "sklearn.metrics.explained_variance_score",
244 "sklearn.metrics.f1_score",
245 "sklearn.metrics.fbeta_score",
246 "sklearn.metrics.get_scorer",
247 "sklearn.metrics.hamming_loss",
248 "sklearn.metrics.hinge_loss",
249 "sklearn.metrics.homogeneity_score",
250 "sklearn.metrics.jaccard_score",
251 "sklearn.metrics.label_ranking_average_precision_score",
252 "sklearn.metrics.label_ranking_loss",
253 "sklearn.metrics.log_loss",
254 "sklearn.metrics.make_scorer",
255 "sklearn.metrics.matthews_corrcoef",
256 "sklearn.metrics.max_error",
257 "sklearn.metrics.mean_absolute_error",
258 "sklearn.metrics.mean_absolute_percentage_error",
259 "sklearn.metrics.mean_gamma_deviance",
260 "sklearn.metrics.mean_pinball_loss",
261 "sklearn.metrics.mean_poisson_deviance",
262 "sklearn.metrics.mean_squared_error",
263 "sklearn.metrics.mean_squared_log_error",
264 "sklearn.metrics.mean_tweedie_deviance",
265 "sklearn.metrics.median_absolute_error",
266 "sklearn.metrics.multilabel_confusion_matrix",
267 "sklearn.metrics.mutual_info_score",
268 "sklearn.metrics.ndcg_score",
269 "sklearn.metrics.pair_confusion_matrix",
270 "sklearn.metrics.adjusted_rand_score",
271 "sklearn.metrics.pairwise.additive_chi2_kernel",
272 "sklearn.metrics.pairwise.chi2_kernel",
273 "sklearn.metrics.pairwise.cosine_distances",
274 "sklearn.metrics.pairwise.cosine_similarity",
275 "sklearn.metrics.pairwise.euclidean_distances",
276 "sklearn.metrics.pairwise.haversine_distances",
277 "sklearn.metrics.pairwise.laplacian_kernel",
278 "sklearn.metrics.pairwise.linear_kernel",
279 "sklearn.metrics.pairwise.manhattan_distances",
280 "sklearn.metrics.pairwise.nan_euclidean_distances",
281 "sklearn.metrics.pairwise.paired_cosine_distances",
282 "sklearn.metrics.pairwise.paired_distances",
283 "sklearn.metrics.pairwise.paired_euclidean_distances",
284 "sklearn.metrics.pairwise.paired_manhattan_distances",
285 "sklearn.metrics.pairwise.pairwise_distances_argmin_min",
286 "sklearn.metrics.pairwise.pairwise_kernels",
287 "sklearn.metrics.pairwise.polynomial_kernel",
288 "sklearn.metrics.pairwise.rbf_kernel",
289 "sklearn.metrics.pairwise.sigmoid_kernel",
290 "sklearn.metrics.pairwise_distances",
291 "sklearn.metrics.pairwise_distances_argmin",
292 "sklearn.metrics.pairwise_distances_chunked",
293 "sklearn.metrics.precision_recall_curve",
294 "sklearn.metrics.precision_recall_fscore_support",
295 "sklearn.metrics.precision_score",
296 "sklearn.metrics.r2_score",
297 "sklearn.metrics.rand_score",
298 "sklearn.metrics.recall_score",
299 "sklearn.metrics.roc_auc_score",
300 "sklearn.metrics.roc_curve",
301 "sklearn.metrics.root_mean_squared_error",
302 "sklearn.metrics.root_mean_squared_log_error",
303 "sklearn.metrics.top_k_accuracy_score",
304 "sklearn.metrics.v_measure_score",
305 "sklearn.metrics.zero_one_loss",
306 "sklearn.model_selection.cross_val_predict",
307 "sklearn.model_selection.cross_val_score",
308 "sklearn.model_selection.cross_validate",
309 "sklearn.model_selection.learning_curve",
310 "sklearn.model_selection.permutation_test_score",
311 "sklearn.model_selection.train_test_split",
312 "sklearn.model_selection.validation_curve",
313 "sklearn.neighbors.kneighbors_graph",
314 "sklearn.neighbors.radius_neighbors_graph",
315 "sklearn.neighbors.sort_graph_by_row_values",
316 "sklearn.preprocessing.add_dummy_feature",
317 "sklearn.preprocessing.binarize",
318 "sklearn.preprocessing.label_binarize",
319 "sklearn.preprocessing.normalize",
320 "sklearn.preprocessing.scale",
321 "sklearn.random_projection.johnson_lindenstrauss_min_dim",
322 "sklearn.svm.l1_min_c",
323 "sklearn.tree.export_graphviz",
324 "sklearn.tree.export_text",
325 "sklearn.tree.plot_tree",
326 "sklearn.utils.gen_batches",
327 "sklearn.utils.gen_even_slices",
328 "sklearn.utils.resample",
329 "sklearn.utils.safe_mask",
330 "sklearn.utils.extmath.randomized_svd",
331 "sklearn.utils.class_weight.compute_class_weight",
332 "sklearn.utils.class_weight.compute_sample_weight",
333 "sklearn.utils.graph.single_source_shortest_path_length",
334]
335
336
337@pytest.mark.parametrize("func_module", PARAM_VALIDATION_FUNCTION_LIST)
338def test_function_param_validation(func_module):
339 """Check param validation for public functions that are not wrappers around
340 estimators.
341 """
342 func, func_name, func_params, required_params = _get_func_info(func_module)
343
344 parameter_constraints = getattr(func, "_skl_parameter_constraints")
345
346 _check_function_param_validation(
347 func, func_name, func_params, required_params, parameter_constraints
348 )
349
350
351PARAM_VALIDATION_CLASS_WRAPPER_LIST = [
352 ("sklearn.cluster.affinity_propagation", "sklearn.cluster.AffinityPropagation"),
353 ("sklearn.cluster.dbscan", "sklearn.cluster.DBSCAN"),
354 ("sklearn.cluster.k_means", "sklearn.cluster.KMeans"),
355 ("sklearn.cluster.mean_shift", "sklearn.cluster.MeanShift"),
356 ("sklearn.cluster.spectral_clustering", "sklearn.cluster.SpectralClustering"),
357 ("sklearn.covariance.graphical_lasso", "sklearn.covariance.GraphicalLasso"),
358 ("sklearn.covariance.ledoit_wolf", "sklearn.covariance.LedoitWolf"),
359 ("sklearn.covariance.oas", "sklearn.covariance.OAS"),
360 ("sklearn.decomposition.dict_learning", "sklearn.decomposition.DictionaryLearning"),
361 (
362 "sklearn.decomposition.dict_learning_online",
363 "sklearn.decomposition.MiniBatchDictionaryLearning",
364 ),
365 ("sklearn.decomposition.fastica", "sklearn.decomposition.FastICA"),
366 ("sklearn.decomposition.non_negative_factorization", "sklearn.decomposition.NMF"),
367 ("sklearn.preprocessing.maxabs_scale", "sklearn.preprocessing.MaxAbsScaler"),
368 ("sklearn.preprocessing.minmax_scale", "sklearn.preprocessing.MinMaxScaler"),
369 ("sklearn.preprocessing.power_transform", "sklearn.preprocessing.PowerTransformer"),
370 (
371 "sklearn.preprocessing.quantile_transform",
372 "sklearn.preprocessing.QuantileTransformer",
373 ),
374 ("sklearn.preprocessing.robust_scale", "sklearn.preprocessing.RobustScaler"),
375]
376
377
378@pytest.mark.parametrize(
379 "func_module, class_module", PARAM_VALIDATION_CLASS_WRAPPER_LIST
380)
381def test_class_wrapper_param_validation(func_module, class_module):
382 """Check param validation for public functions that are wrappers around
383 estimators.
384 """
385 func, func_name, func_params, required_params = _get_func_info(func_module)
386
387 module_name, class_name = class_module.rsplit(".", 1)
388 module = import_module(module_name)
389 klass = getattr(module, class_name)
390
391 parameter_constraints_func = getattr(func, "_skl_parameter_constraints")
392 parameter_constraints_class = getattr(klass, "_parameter_constraints")
393 parameter_constraints = {
394 **parameter_constraints_class,
395 **parameter_constraints_func,
396 }
397 parameter_constraints = {
398 k: v for k, v in parameter_constraints.items() if k in func_params
399 }
400
401 _check_function_param_validation(
402 func, func_name, func_params, required_params, parameter_constraints
403 )
404 