diff --git a/python/cuml/cuml/common/exceptions.py b/python/cuml/cuml/common/exceptions.py deleted file mode 100644 index a45b53bcc6..0000000000 --- a/python/cuml/cuml/common/exceptions.py +++ /dev/null @@ -1,23 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. -# SPDX-License-Identifier: Apache-2.0 -# -__all__ = ("NotFittedError",) # noqa - - -def __getattr__(name): - if name == "NotFittedError": - import warnings - - from sklearn.exceptions import NotFittedError - - warnings.warn( - "`cuml.common.exceptions.NotFittedError` was deprecated in 26.04 " - "and will be removed in 26.06. Please use " - "`sklearn.exceptions.NotFittedError` instead.", - FutureWarning, - stacklevel=2, - ) - return NotFittedError - else: - raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/python/cuml/cuml/internals/validation.py b/python/cuml/cuml/internals/validation.py index e0e46ab020..9bed8f48fa 100644 --- a/python/cuml/cuml/internals/validation.py +++ b/python/cuml/cuml/internals/validation.py @@ -83,9 +83,7 @@ def _get_n_features(X): ndim = len(shape) - if ndim != 2: - import cuml.accel - + if ndim < 2: if isinstance(X, (cudf.Series, pd.Series)): msg = ( f"Expected a 2-dimensional container but got {type(X).__name__} " @@ -100,37 +98,13 @@ def _get_n_features(X): "using array.reshape(-1, 1) if your data has a single feature, " "or array.reshape(1, -1) if it contains a single sample." ) + raise ValueError(msg) + elif ndim > 2: + raise ValueError(f"Expected 2D array, got {ndim}D array instead.") - if cuml.accel.enabled() or ndim > 2: - raise ValueError(msg) - else: - warnings.warn( - "Support for passing non-2-dimensional X was deprecated in 26.04 " - "and will be removed in version 26.06 of cuML. In version 26.06 this will error " - f"with the following message:\n\n{msg}", - FutureWarning, - ) - # Fallback to 1 feature until the deprecation is completed - return 1 return shape[1] -def _warn_or_error(exc_cls, msg): - """Errors if running in cuml.accel, otherwise warns that an error will be - raised in the future.""" - import cuml.accel - - if cuml.accel.enabled(): - raise exc_cls(msg) - else: - warnings.warn( - "cuml is adding support for `feature_names_in_` for validating " - "the feature names of dataframe-like inputs. In version 26.06 of cuML this " - f"will error with the following message:\n\n{msg}", - FutureWarning, - ) - - def _get_feature_names(X): """Get feature names from X. @@ -157,7 +131,7 @@ def _get_feature_names(X): if len(types) == 1 and types[0] == "str": return feature_names elif len(types) > 1 and "str" in types: - msg = ( + raise TypeError( "Feature names are only supported if all input features have string names, " f"but your input has {types} as feature name / column name types. " "If you want feature names to be stored and validated, you must convert " @@ -165,7 +139,6 @@ def _get_feature_names(X): "example. Otherwise you can remove feature / column names from your input " "data, or convert them all to a non-string data type." ) - _warn_or_error(TypeError, msg) return None @@ -241,7 +214,7 @@ def check_features(estimator, X, reset=False) -> None: ) msg = "\n".join(parts) - _warn_or_error(ValueError, msg) + raise ValueError(msg) # Then check n_features_in_ if n_features != estimator.n_features_in_: diff --git a/python/cuml/tests/test_sklearn_compatibility.py b/python/cuml/tests/test_sklearn_compatibility.py index 80176bc44b..91bf00b930 100644 --- a/python/cuml/tests/test_sklearn_compatibility.py +++ b/python/cuml/tests/test_sklearn_compatibility.py @@ -98,8 +98,6 @@ "check_dtype_object": "KMeans does not handle object dtype", "check_estimators_nan_inf": "KMeans does not check for NaN and inf", "check_transformer_data_not_an_array": "KMeans does not handle non-array data", - "check_fit1d": "KMeans does not raise ValueError for 1D input", - "check_fit2d_predict1d": "KMeans does not handle 1D prediction input gracefully", }, KernelRidge: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -115,8 +113,6 @@ "check_regressor_data_not_an_array": "KernelRidge does not handle non-array data", "check_supervised_y_2d": "KernelRidge does not handle 2D y", "check_supervised_y_no_nan": "KernelRidge does not check for NaN in y", - "check_fit1d": "KernelRidge does not raise ValueError for 1D input", - "check_fit2d_predict1d": "KernelRidge does not handle 1D prediction input gracefully", "check_requires_y_none": "KernelRidge does not handle y=None", }, LogisticRegression: { @@ -136,8 +132,6 @@ "check_supervised_y_2d": "LogisticRegression does not handle 2D y", "check_class_weight_classifiers": "LogisticRegression does not handle class weights properly", "check_fit2d_1sample": "LogisticRegression does not handle single sample", - "check_fit1d": "LogisticRegression does not raise ValueError for 1D input", - "check_fit2d_predict1d": "LogisticRegression does not handle 1D prediction input gracefully", "check_requires_y_none": "LogisticRegression does not handle y=None", }, LinearRegression: { @@ -154,8 +148,6 @@ "check_regressor_data_not_an_array": "LinearRegression does not handle non-array data", "check_supervised_y_no_nan": "LinearRegression does not check for NaN in y", "check_fit2d_1sample": "LinearRegression does not handle single sample", - "check_fit1d": "LinearRegression does not raise ValueError for 1D input", - "check_fit2d_predict1d": "LinearRegression does not handle 1D prediction input gracefully", "check_requires_y_none": "LinearRegression does not handle y=None", }, Ridge: { @@ -171,8 +163,6 @@ "check_regressor_data_not_an_array": "Ridge does not handle non-array data", "check_supervised_y_2d": "Ridge does not handle 2D y", "check_supervised_y_no_nan": "Ridge does not check for NaN in y", - "check_fit1d": "Ridge does not raise ValueError for 1D input", - "check_fit2d_predict1d": "Ridge does not handle 1D prediction input gracefully", "check_requires_y_none": "Ridge does not handle y=None", "check_non_transformer_estimators_n_iter": "Ridge `n_iter_` may be `None`", }, @@ -189,8 +179,6 @@ "check_supervised_y_2d": "RandomForestRegressor does not handle 2D y", "check_supervised_y_no_nan": "RandomForestRegressor does not check for NaN in y", "check_dict_unchanged": "RandomForestRegressor modifies input dictionaries", - "check_fit1d": "RandomForestRegressor does not raise ValueError for 1D input", - "check_fit2d_predict1d": "RandomForestRegressor does not handle 1D prediction input gracefully", "check_requires_y_none": "RandomForestRegressor does not handle y=None", }, KNeighborsClassifier: { @@ -203,7 +191,6 @@ "check_classifiers_train": "KNeighborsClassifier does not validate input data properly", "check_supervised_y_no_nan": "KNeighborsClassifier does not check for NaN in y", "check_supervised_y_2d": "KNeighborsClassifier does not handle 2D y", - "check_fit2d_predict1d": "KNeighborsClassifier does not handle 1D prediction input gracefully", "check_requires_y_none": "KNeighborsClassifier does not handle y=None", }, RandomForestClassifier: { @@ -219,8 +206,6 @@ "check_supervised_y_no_nan": "RandomForestClassifier does not check for NaN in y", "check_supervised_y_2d": "RandomForestClassifier does not handle 2D y", "check_dict_unchanged": "RandomForestClassifier modifies input dictionaries", - "check_fit1d": "RandomForestClassifier does not raise ValueError for 1D input", - "check_fit2d_predict1d": "RandomForestClassifier does not handle 1D prediction input gracefully", "check_requires_y_none": "RandomForestClassifier does not handle y=None", }, KNeighborsRegressor: { @@ -235,7 +220,6 @@ "check_regressor_data_not_an_array": "KNeighborsRegressor does not handle non-array data", "check_supervised_y_2d": "KNeighborsRegressor does not handle 2D y", "check_supervised_y_no_nan": "KNeighborsRegressor does not check for NaN in y", - "check_fit2d_predict1d": "KNeighborsRegressor does not handle 1D prediction input gracefully", "check_requires_y_none": "KNeighborsRegressor does not handle y=None", }, NearestNeighbors: { @@ -257,8 +241,6 @@ "check_classifiers_train(readonly_memmap=True)": "LinearSVC does not handle readonly memmap", "check_classifiers_train(readonly_memmap=True,X_dtype=float32)": "LinearSVC does not handle readonly memmap with float32", "check_supervised_y_2d": "LinearSVC does not handle 2D y", - "check_fit1d": "LinearSVC does not raise ValueError for 1D input", - "check_fit2d_predict1d": "LinearSVC does not handle 1D prediction input gracefully", "check_requires_y_none": "LinearSVC does not handle y=None", }, LinearSVR: { @@ -275,8 +257,6 @@ "check_regressor_data_not_an_array": "LinearSVR does not handle non-array data", "check_supervised_y_2d": "LinearSVR does not handle 2D y", "check_supervised_y_no_nan": "LinearSVR does not check for NaN in y", - "check_fit1d": "LinearSVR does not raise ValueError for 1D input", - "check_fit2d_predict1d": "LinearSVR does not handle 1D prediction input gracefully", "check_requires_y_none": "LinearSVR does not handle y=None", }, SVC: { @@ -294,7 +274,6 @@ "check_requires_y_none": "SVC does not handle y=None", "check_sample_weights_list": "SVC does not handle list sample weights", "check_supervised_y_2d": "SVC does not warn on 1 column 2D y", - "check_fit2d_predict1d": "SVC doesn't raise the expected error", }, SVR: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -311,7 +290,6 @@ "check_regressor_data_not_an_array": "SVR does not handle non-array data", "check_supervised_y_2d": "SVR does not handle 2D y", "check_supervised_y_no_nan": "SVR does not check for NaN in y", - "check_fit2d_predict1d": "SVR does not handle 1D prediction input gracefully", "check_requires_y_none": "SVR does not handle y=None", }, PCA: { @@ -322,8 +300,6 @@ "check_transformer_data_not_an_array": "PCA does not handle non-array data", "check_fit2d_1sample": "PCA does not handle single sample", "check_fit2d_1feature": "PCA does not handle single feature", - "check_fit1d": "PCA does not raise ValueError for 1D input", - "check_fit2d_predict1d": "PCA does not handle 1D prediction input gracefully", }, IncrementalPCA: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -331,7 +307,6 @@ "check_estimators_empty_data_messages": "IncrementalPCA does not handle empty data", "check_estimators_nan_inf": "IncrementalPCA does not check for NaN and inf", "check_transformer_data_not_an_array": "IncrementalPCA does not handle non-array data", - "check_fit2d_predict1d": "IncrementalPCA does not handle 1D prediction input gracefully", }, TruncatedSVD: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -341,8 +316,6 @@ "check_transformer_data_not_an_array": "TruncatedSVD does not handle non-array data", "check_fit2d_1sample": "TruncatedSVD does not handle single sample", "check_fit2d_1feature": "TruncatedSVD does not handle single feature", - "check_fit1d": "TruncatedSVD does not raise ValueError for 1D input", - "check_fit2d_predict1d": "TruncatedSVD does not handle 1D prediction input gracefully", }, TSNE: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -355,7 +328,7 @@ "check_methods_subset_invariance": "TSNE results depend on data subset", "check_fit2d_1sample": "TSNE does not handle single sample", "check_fit2d_1feature": "TSNE does not handle single feature", - "check_fit2d_predict1d": "TSNE does not handle 1D prediction input gracefully", + "check_fit2d_predict1d": "TSNE only supports n_components = 2", }, UMAP: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -364,7 +337,6 @@ "check_methods_sample_order_invariance": "UMAP results depend on sample order", "check_transformer_general": "UMAP does not have consistent fit_transform and transform outputs", "check_methods_subset_invariance": "UMAP results depend on data subset", - "check_fit2d_predict1d": "UMAP doesn't raise the expected error", }, Lasso: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -379,8 +351,6 @@ "check_regressor_data_not_an_array": "Lasso does not handle non-array data", "check_supervised_y_2d": "Lasso does not handle 2D y", "check_supervised_y_no_nan": "Lasso does not check for NaN in y", - "check_fit1d": "Lasso does not raise ValueError for 1D input", - "check_fit2d_predict1d": "Lasso does not handle 1D prediction input gracefully", "check_requires_y_none": "Lasso does not handle y=None", }, ElasticNet: { @@ -396,8 +366,6 @@ "check_regressor_data_not_an_array": "ElasticNet does not handle non-array data", "check_supervised_y_2d": "ElasticNet does not handle 2D y", "check_supervised_y_no_nan": "ElasticNet does not check for NaN in y", - "check_fit1d": "ElasticNet does not raise ValueError for 1D input", - "check_fit2d_predict1d": "ElasticNet does not handle 1D prediction input gracefully", "check_requires_y_none": "ElasticNet does not handle y=None", }, KernelDensity: { @@ -407,7 +375,6 @@ "check_all_zero_sample_weights_error": "KernelDensity does not validate all-zero sample weights", "check_dtype_object": "KernelDensity does not handle object dtype", "check_estimators_nan_inf": "KernelDensity does not check for NaN and inf", - "check_fit1d": "KernelDensity does not raise ValueError for 1D input", }, LedoitWolf: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -423,7 +390,6 @@ "check_dtype_object": "DBSCAN does not handle object dtype", "check_estimators_empty_data_messages": "DBSCAN does not handle empty data", "check_estimators_nan_inf": "DBSCAN does not check for NaN and inf", - "check_fit1d": "DBSCAN does not raise ValueError for 1D input", }, HDBSCAN: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -431,13 +397,11 @@ "check_estimators_empty_data_messages": "HDBSCAN does not handle empty data", "check_estimators_nan_inf": "HDBSCAN does not check for NaN and inf", "check_fit2d_1sample": "HDBSCAN does not handle single sample properly", - "check_fit1d": "HDBSCAN does not raise ValueError for 1D input", }, AgglomerativeClustering: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", "check_dtype_object": "AgglomerativeClustering does not handle object dtype", "check_estimators_nan_inf": "AgglomerativeClustering does not check for NaN and inf", - "check_fit1d": "AgglomerativeClustering does not raise ValueError for 1D input", }, SpectralClustering: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -463,8 +427,6 @@ "check_classifiers_regression_target": "GaussianNB does not handle regression targets", "check_supervised_y_no_nan": "GaussianNB does not check for NaN in y", "check_supervised_y_2d": "GaussianNB does not handle 2D y", - "check_fit1d": "GaussianNB does not raise ValueError for 1D input", - "check_fit2d_predict1d": "GaussianNB does not handle 1D prediction input gracefully", "check_requires_y_none": "GaussianNB does not handle y=None", "check_sample_weights_list": "GaussianNB does not handle list sample weights", }, @@ -475,7 +437,6 @@ "check_estimators_empty_data_messages": "GaussianRandomProjection doesn't check for empty data", "check_estimators_nan_inf": "GaussianRandomProjection does not check for NaN and inf", "check_transformer_data_not_an_array": "GaussianRandomProjection does not handle non-array data", - "check_fit2d_predict1d": "GaussianRandomProjection does not handle 1D prediction input gracefully", }, SparseRandomProjection: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -484,7 +445,6 @@ "check_estimators_empty_data_messages": "SparseRandomProjection doesn't check for empty data", "check_estimators_nan_inf": "SparseRandomProjection does not check for NaN and inf", "check_transformer_data_not_an_array": "SparseRandomProjection does not handle non-array data", - "check_fit2d_predict1d": "SparseRandomProjection does not handle 1D prediction input gracefully", }, BernoulliNB: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", @@ -505,7 +465,6 @@ "check_classifiers_regression_target": "BernoulliNB does not validate target is classification", "check_supervised_y_no_nan": "BernoulliNB does not check for NaN in y", "check_supervised_y_2d": "BernoulliNB does not handle 2D y input gracefully", - "check_fit2d_predict1d": "BernoulliNB does not handle 1D prediction input gracefully", "check_requires_y_none": "BernoulliNB does not require y for fit", }, ComplementNB: { @@ -527,7 +486,6 @@ "check_classifiers_regression_target": "ComplementNB does not validate target is classification", "check_supervised_y_no_nan": "ComplementNB does not check for NaN in y", "check_supervised_y_2d": "ComplementNB does not handle 2D y input gracefully", - "check_fit2d_predict1d": "ComplementNB does not handle 1D prediction input gracefully", "check_requires_y_none": "ComplementNB does not require y for fit", }, CategoricalNB: { @@ -549,7 +507,6 @@ "check_classifiers_regression_target": "CategoricalNB does not validate target is classification", "check_supervised_y_no_nan": "CategoricalNB does not check for NaN in y", "check_supervised_y_2d": "CategoricalNB does not handle 2D y input gracefully", - "check_fit2d_predict1d": "CategoricalNB does not handle 1D prediction input gracefully", "check_requires_y_none": "CategoricalNB does not require y for fit", }, MultinomialNB: { @@ -571,7 +528,6 @@ "check_classifiers_regression_target": "MultinomialNB does not validate target is classification", "check_supervised_y_no_nan": "MultinomialNB does not check for NaN in y", "check_supervised_y_2d": "MultinomialNB does not handle 2D y input gracefully", - "check_fit2d_predict1d": "MultinomialNB does not handle 1D prediction input gracefully", "check_requires_y_none": "MultinomialNB does not require y for fit", }, } diff --git a/python/cuml/tests/test_sklearn_import_export.py b/python/cuml/tests/test_sklearn_import_export.py index 9762ade6aa..9fd3a9c42c 100644 --- a/python/cuml/tests/test_sklearn_import_export.py +++ b/python/cuml/tests/test_sklearn_import_export.py @@ -964,9 +964,6 @@ def test_linear_svc(random_state): assert sk_score > 0.7 -@pytest.mark.filterwarnings( - "ignore:TargetEncoder currently returns 1D output:FutureWarning" -) def test_target_encoder(random_state): # Create simple categorical data X = np.array( diff --git a/python/cuml/tests/test_target_encoder.py b/python/cuml/tests/test_target_encoder.py index 9ff0b0787f..e191c1f44e 100644 --- a/python/cuml/tests/test_target_encoder.py +++ b/python/cuml/tests/test_target_encoder.py @@ -10,39 +10,11 @@ from cuml.preprocessing._target_encoder import TargetEncoder from cuml.testing.utils import array_equal -# Filter the combination mode deprecation warning for all tests in this module -pytestmark = pytest.mark.filterwarnings( - "ignore:TargetEncoder currently returns 1D output:FutureWarning" -) - # TODO: many of these tests use `output_type="numpy"` to work around # https://github.com/rapidsai/cuml/issues/7893. These can be # reverted once that's resolved. -def test_targetencoder_deprecated_1d_input(): - df = cudf.DataFrame( - {"category": ["a", "b", "b", "a"], "label": [1, 0, 1, 1]} - ) - - # Warns in fit_transform - encoder = TargetEncoder(output_type="numpy") - with pytest.warns(FutureWarning, match="non-2-dimensional X"): - encoded = encoder.fit_transform(df.category, df.label) - answer = np.array([1.0, 1.0, 0.0, 1.0])[:, None] - assert array_equal(encoded, answer) - - # Warns in fit - encoder = TargetEncoder(output_type="numpy") - with pytest.warns(FutureWarning, match="non-2-dimensional X"): - encoder.fit(df.category, df.label) - - # Warns in tarnsform - with pytest.warns(FutureWarning, match="non-2-dimensional X"): - encoded = encoder.transform(df.category) - assert array_equal(encoded, answer) - - def test_targetencoder_fit_transform(): train = cudf.DataFrame({"category": ["a", "b", "b", "a"]}) label = cudf.Series([1, 0, 1, 1]) diff --git a/python/cuml/tests/test_validation.py b/python/cuml/tests/test_validation.py index ee4481cb7d..ac09ca1084 100644 --- a/python/cuml/tests/test_validation.py +++ b/python/cuml/tests/test_validation.py @@ -97,9 +97,13 @@ def __cuda_array_interface__(self): ], ) def test_get_n_features_1D(X): - with pytest.warns(FutureWarning, match="non-2-dimensional"): - n_features = _get_n_features(X) - assert n_features == 1 + if isinstance(X, (pd.Series, cudf.Series)): + match = "Expected a 2-dimensional container" + else: + match = "Expected 2D array" + + with pytest.raises(ValueError, match=match): + _get_n_features(X) def test_get_feature_names(): @@ -142,9 +146,10 @@ def __dataframe__(self): # Mixed str & non-str names warn df_mixed = pd.DataFrame({"a": [1, 2], 1: [3, 4]}) - with pytest.warns(FutureWarning, match="feature_names_in_") as rec: + with pytest.raises( + TypeError, match="all input features have string names" + ): _get_feature_names(df_mixed) - assert "all input features have string names" in str(rec[0].message) class MyModel: @@ -209,7 +214,7 @@ def test_fit_and_predict_with_and_without_feature_names_warnings(): est_named.predict(X_unnamed) -def test_feature_names_mismatch_warnings(): +def test_feature_names_mismatch_errors(): X = pd.DataFrame({"a": [1], "b": [2], "c": [3]}) # Correct names are fine @@ -219,24 +224,18 @@ def test_feature_names_mismatch_warnings(): # Missing column bad = pd.DataFrame({"a": [1], "b": [2]}) - with pytest.warns(FutureWarning, match="feature_names_in_") as rec: - # TODO: in 26.06 the FutureWarning will become an error, - # and this raises check can go away. - with pytest.raises(ValueError, match="X has 2 features"): - model.predict(bad) - assert "Feature names seen at fit time" in str(rec[0].message) + with pytest.raises(ValueError, match="The feature names") as rec: + model.predict(bad) + assert "Feature names seen at fit time" in str(rec.value) # Extra column bad = pd.DataFrame({"a": [1], "b": [2], "c": [3], "d": [4]}) - with pytest.warns(FutureWarning, match="feature_names_in_") as rec: - # TODO: in 26.06 the FutureWarning will become an error, - # and this raises check can go away. - with pytest.raises(ValueError, match="X has 4 features"): - model.predict(bad) - assert "Feature names unseen at fit time" in str(rec[0].message) + with pytest.raises(ValueError, match="The feature names") as rec: + model.predict(bad) + assert "Feature names unseen at fit time" in str(rec.value) # Reordered columns bad = pd.DataFrame({"a": [1], "c": [3], "b": [2]}) - with pytest.warns(FutureWarning, match="feature_names_in_") as rec: + with pytest.raises(ValueError, match="The feature names") as rec: model.predict(bad) - assert "Feature names must be in the same order" in str(rec[0].message) + assert "Feature names must be in the same order" in str(rec.value)