From d91a4cd7983049af203ed28f3f45d5b8621e2c5d Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Mon, 1 Apr 2024 13:19:59 +0200 Subject: [PATCH 1/8] DOC improve rendering of the documentation (#1076) --- .circleci/config.yml | 1 + build_tools/circle/build_doc.sh | 1 + doc/_static/css/imbalanced-learn.css | 61 +++---- doc/_static/img/logo_wide_dark.png | Bin 0 -> 25947 bytes doc/_static/index_api.svg | 97 +++++++++++ doc/_static/index_examples.svg | 76 +++++++++ doc/_static/index_getting_started.svg | 66 ++++++++ doc/_static/index_user_guide.svg | 67 ++++++++ doc/conf.py | 13 +- doc/index.rst | 150 +++++++++--------- examples/api/plot_sampling_strategy_usage.py | 2 +- .../plot_comparison_ensemble_classifier.py | 2 +- imblearn/_min_dependencies.py | 1 + 13 files changed, 424 insertions(+), 113 deletions(-) create mode 100644 doc/_static/img/logo_wide_dark.png create mode 100644 doc/_static/index_api.svg create mode 100644 doc/_static/index_examples.svg create mode 100644 doc/_static/index_getting_started.svg create mode 100644 doc/_static/index_user_guide.svg diff --git a/.circleci/config.yml b/.circleci/config.yml index b5f679af6..8990d3f22 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,6 +24,7 @@ jobs: - NUMPYDOC_VERSION: 'latest' - SPHINXCONTRIB_BIBTEX_VERSION: 'latest' - PYDATA_SPHINX_THEME_VERSION: 'latest' + - SPHINX_DESIGN_VERSION: 'latest' steps: - add_ssh_keys: fingerprints: diff --git a/build_tools/circle/build_doc.sh b/build_tools/circle/build_doc.sh index 32699e8a8..9601b44aa 100755 --- a/build_tools/circle/build_doc.sh +++ b/build_tools/circle/build_doc.sh @@ -114,6 +114,7 @@ mamba create -n $CONDA_ENV_NAME --yes --quiet \ "$(get_dep sphinxcontrib-bibtex $SPHINXCONTRIB_BIBTEX_VERSION)" \ "$(get_dep sphinx-copybutton $SPHINXCONTRIB_BIBTEX_VERSION)" \ "$(get_dep pydata-sphinx-theme $PYDATA_SPHINX_THEME_VERSION)" \ + "$(get_dep sphinx-design $SPHINX_DESIGN_VERSION)" \ memory_profiler packaging seaborn pytest coverage compilers tensorflow source activate $CONDA_ENV_NAME diff --git a/doc/_static/css/imbalanced-learn.css b/doc/_static/css/imbalanced-learn.css index 6c778540b..3778ee94c 100644 --- a/doc/_static/css/imbalanced-learn.css +++ b/doc/_static/css/imbalanced-learn.css @@ -21,39 +21,44 @@ /* Override some aspects of the pydata-sphinx-theme */ -/* Getting started index page */ +/* Main index page overview cards */ .intro-card { - background: #fff; - border-radius: 0; - padding: 30px 10px 10px 10px; - margin: 10px 0px; -} - -.intro-card .card-text { - margin: 20px 0px; - /*min-height: 150px; */ -} - -.custom-button { - background-color: #dcdcdc; - border: none; - color: #484848; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 0.9rem; - border-radius: 0.5rem; + padding: 30px 10px 20px 10px; +} + +.intro-card .sd-card-img-top { + margin: 10px; + height: 52px; + background: none !important; +} + +.intro-card .sd-card-title { + color: var(--pst-color-primary); + font-size: var(--pst-font-size-h5); + padding: 1rem 0rem 0.5rem 0rem; +} + +.intro-card .sd-card-footer { + border: none !important; +} + +.intro-card .sd-card-footer p.sd-card-text { max-width: 220px; - padding: 0.5rem 0rem; + margin-left: auto; + margin-right: auto; +} + +.intro-card .sd-btn-secondary { + background-color: #6c757d !important; + border-color: #6c757d !important; } -.custom-button a { - color: #484848; +.intro-card .sd-btn-secondary:hover { + background-color: #5a6268 !important; + border-color: #545b62 !important; } -.custom-button p { - margin-top: 0; - margin-bottom: 0rem; - color: #484848; +.card, .card img { + background-color: var(--pst-color-background); } diff --git a/doc/_static/img/logo_wide_dark.png b/doc/_static/img/logo_wide_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..38f9978862615edb9955e9b47ca508ea221ce70a GIT binary patch literal 25947 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 literal 0 HcmV?d00001 diff --git a/doc/_static/index_api.svg b/doc/_static/index_api.svg new file mode 100644 index 000000000..69f7ba1d2 --- /dev/null +++ b/doc/_static/index_api.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/doc/_static/index_examples.svg b/doc/_static/index_examples.svg new file mode 100644 index 000000000..de3d90237 --- /dev/null +++ b/doc/_static/index_examples.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/doc/_static/index_getting_started.svg b/doc/_static/index_getting_started.svg new file mode 100644 index 000000000..2d36622cb --- /dev/null +++ b/doc/_static/index_getting_started.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/_static/index_user_guide.svg b/doc/_static/index_user_guide.svg new file mode 100644 index 000000000..bd1705351 --- /dev/null +++ b/doc/_static/index_user_guide.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/conf.py b/doc/conf.py index a6361eafd..5561808ab 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -43,6 +43,7 @@ "sphinx_issues", "sphinx_gallery.gen_gallery", "sphinx_copybutton", + "sphinx_design", ] # Specify how to identify the prompt when copying code snippets @@ -106,10 +107,12 @@ html_theme_options = { "external_links": [], "github_url": "https://github.com/scikit-learn-contrib/imbalanced-learn", - # "twitter_url": "https://twitter.com/pandas_dev", "use_edit_page_button": True, "show_toc_level": 1, # "navbar_align": "right", # For testing that the navbar items align properly + "logo": { + "image_dark": "https://imbalanced-learn.org/stable/_static/img/logo_wide_dark.png" + }, } html_context = { @@ -323,15 +326,7 @@ def generate_min_dependency_substitutions(app): # -- Additional temporary hacks ----------------------------------------------- -# Temporary work-around for spacing problem between parameter and parameter -# type in the doc, see https://github.com/numpy/numpydoc/issues/215. The bug -# has been fixed in sphinx (https://github.com/sphinx-doc/sphinx/pull/5976) but -# through a change in sphinx basic.css except rtd_theme does not use basic.css. -# In an ideal world, this would get fixed in this PR: -# https://github.com/readthedocs/sphinx_rtd_theme/pull/747/files - def setup(app): app.connect("builder-inited", generate_min_dependency_table) app.connect("builder-inited", generate_min_dependency_substitutions) - app.add_css_file("basic.css") diff --git a/doc/index.rst b/doc/index.rst index aa3d7a9b2..238786314 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -21,80 +21,82 @@ Imbalanced-learn (imported as :mod:`imblearn`) is an open source, MIT-licensed library relying on scikit-learn (imported as :mod:`sklearn`) and provides tools when dealing with classification with imbalanced classes. -.. raw:: html - -
-
-
-
-
- -
Getting started
-

Check out the getting started guides to install imbalanced-learn. - Some extra information to get started with a new contribution is also provided.

- -.. container:: custom-button - - :ref:`To the installation guideline` - -.. raw:: html - -
-
-
-
-
-
- -
User guide
-

The user guide provides in-depth information on the - key concepts of imbalanced-learn with useful background information and explanation.

- -.. container:: custom-button - - :ref:`To the user guide` - -.. raw:: html - -
-
-
-
-
-
- -
API reference
-

The reference guide contains a detailed description of - the imbalanced-learn API. To known more about methods parameters.

- -.. container:: custom-button - - :ref:`To the reference guide` - -.. raw:: html - -
-
-
-
-
-
- -
Examples
-

The gallery of examples is a good place to see imbalanced-learn in action. - Select an example and dive in.

- -.. container:: custom-button - - :ref:`To the gallery of examples` - -.. raw:: html - -
-
-
-
-
+.. grid:: 1 2 2 2 + :gutter: 4 + :padding: 2 2 0 0 + :class-container: sd-text-center + + .. grid-item-card:: Getting started + :img-top: _static/index_getting_started.svg + :class-card: intro-card + :shadow: md + + Check out the getting started guides to install `imbalanced-learn`. + Some extra information to get started with a new contribution is also provided. + + +++ + + .. button-ref:: getting_started + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the installation guideline + + .. grid-item-card:: User guide + :img-top: _static/index_user_guide.svg + :class-card: intro-card + :shadow: md + + The user guide provides in-depth information on the key concepts of + `imbalanced-learn` with useful background information and explanation. + + +++ + + .. button-ref:: user_guide + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the user guide + + .. grid-item-card:: API reference + :img-top: _static/index_api.svg + :class-card: intro-card + :shadow: md + + The reference guide contains a detailed description of + the `imbalanced-learn` API. To known more about methods parameters. + + +++ + + .. button-ref:: api + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the reference guide + + .. grid-item-card:: Examples + :img-top: _static/index_examples.svg + :class-card: intro-card + :shadow: md + + The gallery of examples is a good place to see `imbalanced-learn` in action. + Select an example and dive in. + + +++ + + .. button-ref:: general_examples + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the gallery of examples .. toctree:: diff --git a/examples/api/plot_sampling_strategy_usage.py b/examples/api/plot_sampling_strategy_usage.py index dbb52fcdf..1c76a06b2 100644 --- a/examples/api/plot_sampling_strategy_usage.py +++ b/examples/api/plot_sampling_strategy_usage.py @@ -129,7 +129,7 @@ # %% [markdown] # `sampling_strategy` as a `dict` -# ------------------------------ +# ------------------------------- # # When `sampling_strategy` is a `dict`, the keys correspond to the targeted # classes. The values correspond to the desired number of samples for each diff --git a/examples/ensemble/plot_comparison_ensemble_classifier.py b/examples/ensemble/plot_comparison_ensemble_classifier.py index 602e477e5..8c318e5bc 100644 --- a/examples/ensemble/plot_comparison_ensemble_classifier.py +++ b/examples/ensemble/plot_comparison_ensemble_classifier.py @@ -197,7 +197,7 @@ from imblearn.ensemble import EasyEnsembleClassifier, RUSBoostClassifier -estimator = AdaBoostClassifier(n_estimators=10) +estimator = AdaBoostClassifier(n_estimators=10, algorithm="SAMME") eec = EasyEnsembleClassifier(n_estimators=10, estimator=estimator) eec.fit(X_train, y_train) y_pred_eec = eec.predict(X_test) diff --git a/imblearn/_min_dependencies.py b/imblearn/_min_dependencies.py index 497688765..ec1f5dedb 100644 --- a/imblearn/_min_dependencies.py +++ b/imblearn/_min_dependencies.py @@ -37,6 +37,7 @@ "numpydoc": ("1.5.0", "docs"), "sphinxcontrib-bibtex": ("2.4.1", "docs"), "pydata-sphinx-theme": ("0.13.3", "docs"), + "sphinx-design": ("0.5.0", "docs"), } From 78d94a53fdde68880a669d2da0b380be518450bb Mon Sep 17 00:00:00 2001 From: Christos Aridas Date: Sun, 31 Mar 2024 22:22:23 +0300 Subject: [PATCH 2/8] MAINT Improve pipeline's validation readability (#1066) Co-authored-by: Guillaume Lemaitre --- imblearn/pipeline.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/imblearn/pipeline.py b/imblearn/pipeline.py index 01eead7ea..f98d58434 100644 --- a/imblearn/pipeline.py +++ b/imblearn/pipeline.py @@ -163,11 +163,12 @@ def _validate_steps(self): for t in transformers: if t is None or t == "passthrough": continue - if not ( - hasattr(t, "fit") - or hasattr(t, "fit_transform") - or hasattr(t, "fit_resample") - ) or not (hasattr(t, "transform") or hasattr(t, "fit_resample")): + + is_transfomer = hasattr(t, "fit") and hasattr(t, "transform") + is_sampler = hasattr(t, "fit_resample") + is_not_transfomer_or_sampler = not (is_transfomer or is_sampler) + + if is_not_transfomer_or_sampler: raise TypeError( "All intermediate steps of the chain should " "be estimators that implement fit and transform or " @@ -175,9 +176,7 @@ def _validate_steps(self): "'%s' (type %s) doesn't)" % (t, type(t)) ) - if hasattr(t, "fit_resample") and ( - hasattr(t, "fit_transform") or hasattr(t, "transform") - ): + if is_transfomer and is_sampler: raise TypeError( "All intermediate steps of the chain should " "be estimators that implement fit and transform or " From 7c26d56797e69df7d19e74c04071b008ed7a5c19 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Mon, 1 Apr 2024 11:49:04 +0200 Subject: [PATCH 3/8] MAINT compatibility sklearn 1.5 (#1074) --- imblearn/ensemble/_bagging.py | 2 +- imblearn/ensemble/_easy_ensemble.py | 14 +++++----- imblearn/over_sampling/_smote/base.py | 9 ++++++- imblearn/pipeline.py | 10 ++++++- imblearn/utils/_metadata_requests.py | 38 ++++++++++++++++++++------- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/imblearn/ensemble/_bagging.py b/imblearn/ensemble/_bagging.py index 2808239a7..acb0c70fa 100644 --- a/imblearn/ensemble/_bagging.py +++ b/imblearn/ensemble/_bagging.py @@ -386,7 +386,7 @@ def _fit(self, X, y, max_samples=None, max_depth=None, sample_weight=None): self.sampler_ = clone(self.sampler) # RandomUnderSampler is not supporting sample_weight. We need to pass # None. - return super()._fit(X, y, self.max_samples, sample_weight=None) + return super()._fit(X, y, self.max_samples) # TODO: remove when minimum supported version of scikit-learn is 1.1 @available_if(_estimator_has("decision_function")) diff --git a/imblearn/ensemble/_easy_ensemble.py b/imblearn/ensemble/_easy_ensemble.py index 1da81d93c..e3c85741c 100644 --- a/imblearn/ensemble/_easy_ensemble.py +++ b/imblearn/ensemble/_easy_ensemble.py @@ -300,7 +300,7 @@ def _fit(self, X, y, max_samples=None, max_depth=None, sample_weight=None): check_target_type(y) # RandomUnderSampler is not supporting sample_weight. We need to pass # None. - return super()._fit(X, y, self.max_samples, sample_weight=None) + return super()._fit(X, y, self.max_samples) # TODO: remove when minimum supported version of scikit-learn is 1.1 @available_if(_estimator_has("decision_function")) @@ -365,9 +365,11 @@ def base_estimator_(self): raise error raise error - def _more_tags(self): + def _get_estimator(self): if self.estimator is None: - estimator = AdaBoostClassifier(algorithm="SAMME") - else: - estimator = self.estimator - return {"allow_nan": _safe_tags(estimator, "allow_nan")} + return AdaBoostClassifier(algorithm="SAMME") + return self.estimator + + # TODO: remove when minimum supported version of scikit-learn is 1.5 + def _more_tags(self): + return {"allow_nan": _safe_tags(self._get_estimator(), "allow_nan")} diff --git a/imblearn/over_sampling/_smote/base.py b/imblearn/over_sampling/_smote/base.py index 93b7e8a7b..8ef902920 100644 --- a/imblearn/over_sampling/_smote/base.py +++ b/imblearn/over_sampling/_smote/base.py @@ -11,16 +11,17 @@ import warnings import numpy as np +import sklearn from scipy import sparse from sklearn.base import clone from sklearn.exceptions import DataConversionWarning from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder from sklearn.utils import ( - _get_column_indices, _safe_indexing, check_array, check_random_state, ) +from sklearn.utils.fixes import parse_version from sklearn.utils.sparsefuncs_fast import ( csr_mean_variance_axis0, ) @@ -34,6 +35,12 @@ from ...utils.fixes import _is_pandas_df, _mode from ..base import BaseOverSampler +sklearn_version = parse_version(sklearn.__version__).base_version +if parse_version(sklearn_version) < parse_version("1.5"): + from sklearn.utils import _get_column_indices +else: + from sklearn.utils._indexing import _get_column_indices + class BaseSMOTE(BaseOverSampler): """Base class for the different SMOTE algorithms.""" diff --git a/imblearn/pipeline.py b/imblearn/pipeline.py index f98d58434..7453446ad 100644 --- a/imblearn/pipeline.py +++ b/imblearn/pipeline.py @@ -12,9 +12,11 @@ # Christos Aridas # Guillaume Lemaitre # License: BSD +import sklearn from sklearn import pipeline from sklearn.base import clone -from sklearn.utils import Bunch, _print_elapsed_time +from sklearn.utils import Bunch +from sklearn.utils.fixes import parse_version from sklearn.utils.metaestimators import available_if from sklearn.utils.validation import check_memory @@ -34,6 +36,12 @@ __all__ = ["Pipeline", "make_pipeline"] +sklearn_version = parse_version(sklearn.__version__).base_version +if parse_version(sklearn_version) < parse_version("1.5"): + from sklearn.utils import _print_elapsed_time +else: + from sklearn.utils._user_interface import _print_elapsed_time + class Pipeline(_ParamsValidationMixin, pipeline.Pipeline): """Pipeline of transforms and resamples with a final estimator. diff --git a/imblearn/utils/_metadata_requests.py b/imblearn/utils/_metadata_requests.py index 1150c7d75..c81aa4ff0 100644 --- a/imblearn/utils/_metadata_requests.py +++ b/imblearn/utils/_metadata_requests.py @@ -1086,9 +1086,12 @@ def _serialize(self): def __iter__(self): if self._self_request: - yield "$self_request", RouterMappingPair( - mapping=MethodMapping.from_str("one-to-one"), - router=self._self_request, + yield ( + "$self_request", + RouterMappingPair( + mapping=MethodMapping.from_str("one-to-one"), + router=self._self_request, + ), ) for name, route_mapping in self._route_mappings.items(): yield (name, route_mapping) @@ -1234,7 +1237,7 @@ def __init__(self, name, keys, validate_keys=True): def __get__(self, instance, owner): # we would want to have a method which accepts only the expected args - def func(**kw): + def func(*args, **kw): """Updates the request for provided parameters This docstring is overwritten below. @@ -1253,15 +1256,32 @@ def func(**kw): f"arguments are: {set(self.keys)}" ) - requests = instance._get_metadata_request() + # This makes it possible to use the decorated method as an unbound + # method, for instance when monkeypatching. + # https://github.com/scikit-learn/scikit-learn/issues/28632 + if instance is None: + _instance = args[0] + args = args[1:] + else: + _instance = instance + + # Replicating python's behavior when positional args are given other + # than `self`, and `self` is only allowed if this method is unbound. + if args: + raise TypeError( + f"set_{self.name}_request() takes 0 positional argument but" + f" {len(args)} were given" + ) + + requests = _instance._get_metadata_request() method_metadata_request = getattr(requests, self.name) for prop, alias in kw.items(): if alias is not UNCHANGED: method_metadata_request.add_request(param=prop, alias=alias) - instance._metadata_request = requests + _instance._metadata_request = requests - return instance + return _instance # Now we set the relevant attributes of the function so that it seems # like a normal method to the end user, with known expected arguments. @@ -1525,13 +1545,13 @@ def process_routing(_obj, _method, /, **kwargs): metadata to corresponding methods or corresponding child objects. The object names are those defined in `obj.get_metadata_routing()`. """ - if not _routing_enabled() and not kwargs: + if not kwargs: # If routing is not enabled and kwargs are empty, then we don't have to # try doing any routing, we can simply return a structure which returns # an empty dict on routed_params.ANYTHING.ANY_METHOD. class EmptyRequest: def get(self, name, default=None): - return default if default else {} + return Bunch(**{method: dict() for method in METHODS}) def __getitem__(self, name): return Bunch(**{method: dict() for method in METHODS}) From 482ced9497548ca652e01f514ebc021af08ddea1 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 28 May 2024 15:34:29 +0200 Subject: [PATCH 4/8] DOC change version for compatibility sklearn 1.5 --- doc/whats_new/v0.12.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/whats_new/v0.12.rst b/doc/whats_new/v0.12.rst index 1213f1f56..4a96de92c 100644 --- a/doc/whats_new/v0.12.rst +++ b/doc/whats_new/v0.12.rst @@ -1,5 +1,19 @@ .. _changes_0_12: +Version 0.12.3 +============== + +**May 28, 2024** + +Changelog +--------- + +Compatibility +............. + +- Compatibility with scikit-learn 1.5 + :pr:`1074` by :user:`Guillaume Lemaitre `. + Version 0.12.2 ============== From bb7e34a7ae93adc5c817c028a13ac87b4bf8656e Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 28 May 2024 16:28:56 +0200 Subject: [PATCH 5/8] MAINT avoid warning deprecation sklearn 1.5 (#1084) --- doc/under_sampling.rst | 3 +-- doc/whats_new/v0.12.rst | 2 +- examples/applications/plot_outlier_rejections.py | 4 ++-- imblearn/ensemble/tests/test_bagging.py | 4 ++-- imblearn/tests/test_docstring_parameters.py | 5 ----- imblearn/tests/test_pipeline.py | 4 ++-- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/doc/under_sampling.rst b/doc/under_sampling.rst index 499b5a3d9..8f8e7fbb8 100644 --- a/doc/under_sampling.rst +++ b/doc/under_sampling.rst @@ -497,8 +497,7 @@ The class can be used as:: >>> from sklearn.linear_model import LogisticRegression >>> from imblearn.under_sampling import InstanceHardnessThreshold >>> iht = InstanceHardnessThreshold(random_state=0, - ... estimator=LogisticRegression( - ... solver='lbfgs', multi_class='auto')) + ... estimator=LogisticRegression()) >>> X_resampled, y_resampled = iht.fit_resample(X, y) >>> print(sorted(Counter(y_resampled).items())) [(0, 64), (1, 64), (2, 64)] diff --git a/doc/whats_new/v0.12.rst b/doc/whats_new/v0.12.rst index 4a96de92c..fd95a4fb8 100644 --- a/doc/whats_new/v0.12.rst +++ b/doc/whats_new/v0.12.rst @@ -12,7 +12,7 @@ Compatibility ............. - Compatibility with scikit-learn 1.5 - :pr:`1074` by :user:`Guillaume Lemaitre `. + :pr:`1074` and :pr:`1084` by :user:`Guillaume Lemaitre `. Version 0.12.2 ============== diff --git a/examples/applications/plot_outlier_rejections.py b/examples/applications/plot_outlier_rejections.py index 55f03e273..985b9211a 100644 --- a/examples/applications/plot_outlier_rejections.py +++ b/examples/applications/plot_outlier_rejections.py @@ -109,12 +109,12 @@ def outlier_rejection(X, y): pipe = make_pipeline( FunctionSampler(func=outlier_rejection), - LogisticRegression(solver="lbfgs", multi_class="auto", random_state=rng), + LogisticRegression(random_state=rng), ) y_pred = pipe.fit(X_train, y_train).predict(X_test) print(classification_report(y_test, y_pred)) -clf = LogisticRegression(solver="lbfgs", multi_class="auto", random_state=rng) +clf = LogisticRegression(random_state=rng) y_pred = clf.fit(X_train, y_train).predict(X_test) print(classification_report(y_test, y_pred)) diff --git a/imblearn/ensemble/tests/test_bagging.py b/imblearn/ensemble/tests/test_bagging.py index 5705de553..382597183 100644 --- a/imblearn/ensemble/tests/test_bagging.py +++ b/imblearn/ensemble/tests/test_bagging.py @@ -174,7 +174,7 @@ def test_probability(): # Degenerate case, where some classes are missing ensemble = BalancedBaggingClassifier( - estimator=LogisticRegression(solver="lbfgs", multi_class="auto"), + estimator=LogisticRegression(solver="lbfgs"), random_state=0, max_samples=5, ) @@ -435,7 +435,7 @@ def test_estimators_samples(): # remap the y outside of the BalancedBaggingclassifier # _, y = np.unique(y, return_inverse=True) bagging = BalancedBaggingClassifier( - LogisticRegression(solver="lbfgs", multi_class="auto"), + LogisticRegression(), max_samples=0.5, max_features=0.5, random_state=1, diff --git a/imblearn/tests/test_docstring_parameters.py b/imblearn/tests/test_docstring_parameters.py index b595d77d7..1bd6ecf51 100644 --- a/imblearn/tests/test_docstring_parameters.py +++ b/imblearn/tests/test_docstring_parameters.py @@ -11,7 +11,6 @@ import pytest from sklearn.datasets import make_classification from sklearn.linear_model import LogisticRegression -from sklearn.utils import IS_PYPY from sklearn.utils._testing import ( _get_func_name, check_docstring_parameters, @@ -70,7 +69,6 @@ # Python 3.7 @pytest.mark.filterwarnings("ignore::FutureWarning") @pytest.mark.filterwarnings("ignore::DeprecationWarning") -@pytest.mark.skipif(IS_PYPY, reason="test segfaults on PyPy") def test_docstring_parameters(): # Test module docstring formatting @@ -154,9 +152,6 @@ def test_tabs(): for importer, modname, ispkg in walk_packages( imblearn.__path__, prefix="imblearn." ): - if IS_PYPY: - continue - # because we don't import mod = importlib.import_module(modname) diff --git a/imblearn/tests/test_pipeline.py b/imblearn/tests/test_pipeline.py index 409dbce41..d89e03a11 100644 --- a/imblearn/tests/test_pipeline.py +++ b/imblearn/tests/test_pipeline.py @@ -272,7 +272,7 @@ def test_pipeline_methods_anova(): X = iris.data y = iris.target # Test with Anova + LogisticRegression - clf = LogisticRegression(solver="lbfgs", multi_class="auto") + clf = LogisticRegression() filter1 = SelectKBest(f_classif, k=2) pipe = Pipeline([("anova", filter1), ("logistic", clf)]) pipe.fit(X, y) @@ -639,7 +639,7 @@ def test_classes_property(): clf = make_pipeline( SelectKBest(k=1), - LogisticRegression(solver="lbfgs", multi_class="auto", random_state=0), + LogisticRegression(), ) with raises(AttributeError): getattr(clf, "classes_") From 5f0ef2d379cbf97ca1f3a6d777fd2a5a7b9ca6d8 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 28 May 2024 17:08:10 +0200 Subject: [PATCH 6/8] bumpversion 0.12.3 --- imblearn/_version.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/imblearn/_version.py b/imblearn/_version.py index 2c4231c9c..19c405e4b 100644 --- a/imblearn/_version.py +++ b/imblearn/_version.py @@ -22,4 +22,4 @@ # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = "0.12.2" +__version__ = "0.12.3" diff --git a/setup.cfg b/setup.cfg index 862620367..671e45e70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.12.2 +current_version = 0.12.3 tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? serialize = From 0a0cb9204de271e515e60576ca42636a4380862b Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Fri, 4 Oct 2024 18:21:19 +0200 Subject: [PATCH 7/8] MAINT compatibility scikit-learn 1.5.2 and Numpy 2 (#1097) --- azure-pipelines.yml | 2 +- conftest.py | 7 +++++++ doc/whats_new/v0.12.rst | 14 ++++++++++++++ imblearn/metrics/pairwise.py | 2 +- imblearn/utils/estimator_checks.py | 2 +- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5c4218dec..98f2b4e11 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -255,7 +255,7 @@ jobs: - template: build_tools/azure/posix.yml parameters: name: macOS - vmImage: macOS-11 + vmImage: macOS-12 dependsOn: [linting, git_commit] condition: | and( diff --git a/conftest.py b/conftest.py index 45a5ce679..0dc6e5a23 100644 --- a/conftest.py +++ b/conftest.py @@ -7,7 +7,14 @@ import os +import numpy as np import pytest +from sklearn.utils.fixes import parse_version + +# use legacy numpy print options to avoid failures due to NumPy 2.+ scalar +# representation +if parse_version(np.__version__) > parse_version("2.0.0"): + np.set_printoptions(legacy="1.25") def pytest_runtest_setup(item): diff --git a/doc/whats_new/v0.12.rst b/doc/whats_new/v0.12.rst index fd95a4fb8..fb79497d8 100644 --- a/doc/whats_new/v0.12.rst +++ b/doc/whats_new/v0.12.rst @@ -1,5 +1,19 @@ .. _changes_0_12: +Version 0.12.4 +============== + +**October 4, 2024** + +Changelog +--------- + +Compatibility +............. + +- Compatibility with NumPy 2.0+ + :pr:`1097` by :user:`Guillaume Lemaitre `. + Version 0.12.3 ============== diff --git a/imblearn/metrics/pairwise.py b/imblearn/metrics/pairwise.py index 11f654f02..40f099258 100644 --- a/imblearn/metrics/pairwise.py +++ b/imblearn/metrics/pairwise.py @@ -161,7 +161,7 @@ def fit(self, X, y): f"elements in n_categories and {self.n_features_in_} in " f"X." ) - self.n_categories_ = np.array(self.n_categories, copy=False) + self.n_categories_ = np.asarray(self.n_categories) classes = unique_labels(y) # list of length n_features of ndarray (n_categories, n_classes) diff --git a/imblearn/utils/estimator_checks.py b/imblearn/utils/estimator_checks.py index 570427759..2fc893391 100644 --- a/imblearn/utils/estimator_checks.py +++ b/imblearn/utils/estimator_checks.py @@ -309,7 +309,7 @@ def check_samplers_sparse(name, sampler_orig): sampler = clone(sampler) X_res, y_res = sampler.fit_resample(X, y) assert sparse.issparse(X_res_sparse) - assert_allclose(X_res_sparse.A, X_res, rtol=1e-5) + assert_allclose(X_res_sparse.toarray(), X_res, rtol=1e-5) assert_allclose(y_res_sparse, y_res) From e32819e9d4b6162b3c369471c6d0caf6cde1e667 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Fri, 4 Oct 2024 18:51:49 +0200 Subject: [PATCH 8/8] bumpversion 0.12.4 --- imblearn/_version.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/imblearn/_version.py b/imblearn/_version.py index 19c405e4b..ff7e11ace 100644 --- a/imblearn/_version.py +++ b/imblearn/_version.py @@ -22,4 +22,4 @@ # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = "0.12.3" +__version__ = "0.12.4" diff --git a/setup.cfg b/setup.cfg index 671e45e70..5cd5d6139 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.12.3 +current_version = 0.12.4 tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? serialize =