Repository: pulearn/pulearn

Issue:
ImportError: cannot import name 'indices_to_mask' from 'sklearn.utils'
Summary.

When I run `python ElkanotoPUClassifierExample.py` in `./pulearn/examples/`, an error occurred:

```
ImportError: cannot import name 'indices_to_mask' from 'sklearn.utils'
```

## Expected Result

The ElkanotoPUClassifierExample.py can run without errors.

## Actual Result

An import error:
```
ImportError: cannot import name 'indices_to_mask' from 'sklearn.utils'
```

## Reproduction Steps

```bash
virtualenv venv
source ./venv/bin/activate
pip install pulearn
cd ./pulearn/examples
python ElkanotoPuClassifiierExample.py
```

## System Information

```
python --version
Python 3.12.4
```

```
pip show scikit-learn
Name: scikit-learn
Version: 1.5.1
Summary: A set of python modules for machine learning and data mining
...
Requires: joblib, numpy, scipy, threadpoolctl
Required-by: pulearn
```

```
System Version: macOS 14.6.1 (23G93)
Kernel Version: Darwin 23.6.0
Boot Volume: Macintosh HD
```

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pulearn/pulearn/pull/40?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulearn) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.16%. Comparing base [(`7164449`)](https://app.codecov.io/gh/pulearn/pulearn/commit/716444913f5c539f1f9a3eeaa1533f959a6dccf1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulearn) to head [(`1072b80`)](https://app.codecov.io/gh/pulearn/pulearn/commit/1072b8014b2d9bd5c2f0a32fcd9ccc677d02f669?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulearn).
> Report is 1 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master      #40   +/-   ##
=======================================
  Coverage   99.16%   99.16%           
=======================================
  Files           5        5           
  Lines         358      359    +1     
  Branches       60       60           
=======================================
+ Hits          355      356    +1     
  Misses          2        2           
  Partials        1        1           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pulearn/pulearn/pull/40?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulearn).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulearn).

shaypal5: pre-commit.ci autofix

Patch:
diff --git a/.gitignore b/.gitignore
index ca9049f..b03293b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,7 @@ coverage.xml
 *,cover
 .hypothesis/
 .pytest_cache/
+cov.xml
 
 # Translations
 *.mo
diff --git a/README.rst b/README.rst
index 6bb64e0..39d6e87 100644
--- a/README.rst
+++ b/README.rst
@@ -104,7 +104,7 @@ Unlabeled examples are expected to be indicated by a number smaller than `1`, po
 Examples
 ========
 
-A nice code example of the classic Elkan-Noto classifier used for classification on the `Wisconsin breast cancer dataset <https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+(Diagnostic)>`_ , comparing it to a regular random forest classifer, can be found in the ``examples`` directory.
+A nice code example of the classic Elkan-Noto classifier used for classification on the `Wisconsin breast cancer dataset <https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+(Diagnostic)>`_ , comparing it to a regular random forest classifier, can be found in the ``examples`` directory.
 
 To run it, clone the repository, and run the following command from the root of the repository, with a python environment where ``pulearn`` is installed:
 
@@ -163,7 +163,7 @@ Tests reside under the ``tests`` directory in the root of the repository. Each m
 Code style
 ----------
 
-``pulearn`` code is written to adhere to the coding style dictated by `flake8 <http://flake8.pycqa.org/en/latest/>`_. Practically, this means that one of the jobs that runs on `the project's Travis <https://travis-ci.org/pulearn/pulearn>`_ for each commit and pull request checks for a successfull run of the ``flake8`` CLI command in the repository's root. Which means pull requests will be flagged red by the Travis bot if non-flake8-compliant code was added.
+``pulearn`` code is written to adhere to the coding style dictated by `flake8 <http://flake8.pycqa.org/en/latest/>`_. Practically, this means that one of the jobs that runs on `the project's Travis <https://travis-ci.org/pulearn/pulearn>`_ for each commit and pull request checks for a successful run of the ``flake8`` CLI command in the repository's root. Which means pull requests will be flagged red by the Travis bot if non-flake8-compliant code was added.
 
 To solve this, please run ``flake8`` on your code (whether through your text editor/IDE or using the command line) and fix all resulting errors. Thank you! :)
 
diff --git a/examples/BreastCancerElkanotoExample.py b/examples/BreastCancerElkanotoExample.py
index 81825f8..f918a0b 100644
--- a/examples/BreastCancerElkanotoExample.py
+++ b/examples/BreastCancerElkanotoExample.py
@@ -1,6 +1,4 @@
-"""A usage example for both Elkan & Noto PU learning methods on breast cancer
-data.
-"""
+"""Examples for both Elkan & Noto PU learning methods on breast cancer data."""
 
 import os
 
@@ -128,7 +126,7 @@ if __name__ == "__main__":
     plt.title("Random forest with/without PU learning")
     plt.plot(n_sacrifice_iter, pu_f1_scores, label="PU Adapted Random Forest")
     plt.plot(n_sacrifice_iter, reg_f1_scores, label="Random Forest")
-    plt.xlabel("Number of positive examples hidden in the unlabled set")
+    plt.xlabel("Number of positive examples hidden in the unlabeled set")
     plt.ylabel("F1 Score")
     plt.legend()
     plt.show()
diff --git a/examples/ElkanotoPuClassifierExample.py b/examples/ElkanotoPuClassifierExample.py
index 485e1e6..1a7e117 100644
--- a/examples/ElkanotoPuClassifierExample.py
+++ b/examples/ElkanotoPuClassifierExample.py
@@ -40,19 +40,11 @@ if __name__ == "__main__":
     print("\nComparison of estimator and PUAdapter(estimator):")
     print(
         "Number of disagreements: {}".format(
-            len(
-                np.where(
-                    (pu_estimator.predict(X) == estimator.predict(X)) == False
-                )[0]
-            )
+            len(np.where(pu_estimator.predict(X) != estimator.predict(X))[0])
         )
     )
     print(
         "Number of agreements: {}".format(
-            len(
-                np.where(
-                    (pu_estimator.predict(X) == estimator.predict(X)) == True
-                )[0]
-            )
+            len(np.where(pu_estimator.predict(X) == estimator.predict(X))[0])
         )
     )
diff --git a/src/pulearn/__init__.py b/src/pulearn/__init__.py
index 81aeea1..90623b6 100644
--- a/src/pulearn/__init__.py
+++ b/src/pulearn/__init__.py
@@ -1,5 +1,7 @@
-"""The `pulearn` Python package provide a collection of scikit-learn wrappers
-to several positive-unlabled learning (PU-learning) methods.
+"""pulearn: Positive-unlabeled learning with Python.
+
+The `pulearn` Python package provide a collection of scikit-learn wrappers
+to several positive-unlabeled learning (PU-learning) methods.
 
 .. include:: ./documentation.md
 
diff --git a/src/pulearn/bagging.py b/src/pulearn/bagging.py
index 00513e7..27e4fa4 100644
--- a/src/pulearn/bagging.py
+++ b/src/pulearn/bagging.py
@@ -44,8 +44,8 @@ from sklearn.utils import (
     check_random_state,
     check_X_y,
     column_or_1d,
-    indices_to_mask,
 )
+from sklearn.utils._mask import indices_to_mask
 from sklearn.utils.metaestimators import available_if
 from sklearn.utils.multiclass import check_classification_targets
 from sklearn.utils.random import sample_without_replacement
@@ -264,6 +264,7 @@ class BaseBaggingPU(with_metaclass(ABCMeta, BaseEnsemble)):
         random_state=None,
         verbose=0,
     ):
+        """Initialize the Bagging meta-estimator."""
         super(BaseBaggingPU, self).__init__(
             estimator=estimator, n_estimators=n_estimators
         )
@@ -412,7 +413,8 @@ class BaseBaggingPU(with_metaclass(ABCMeta, BaseEnsemble)):
         if n_more_estimators == 0:
             warn(
                 "Warm-start fitting without increasing n_estimators does not "
-                "fit new trees."
+                "fit new trees.",
+                stacklevel=2,
             )
             return self
 
@@ -613,6 +615,7 @@ class BaggingPuClassifier(BaseBaggingPU, ClassifierMixin):
         random_state=None,
         verbose=0,
     ):
+        """Initialize the Bagging meta-estimator."""
         super(BaggingPuClassifier, self).__init__(
             estimator,
             n_estimators=n_estimators,
diff --git a/src/pulearn/elkanoto.py b/src/pulearn/elkanoto.py
index 4b4943b..011bb45 100644
--- a/src/pulearn/elkanoto.py
+++ b/src/pulearn/elkanoto.py
@@ -16,11 +16,12 @@ class ElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         predict_proba() methods.
     hold_out_ratio : float, default 0.1
        The ratio of training examples to set aside to estimate the probability
-       of an exmaple to be positive.
+       of an example to be positive.
 
     """
 
     def __init__(self, estimator, hold_out_ratio=0.1, random_state=None):
+        """Initialize the classifier."""
         self.estimator = estimator
         # c is the constant proba that a example is positive, init to 1
         self.c = 1.0
@@ -29,6 +30,7 @@ class ElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         self.estimator_fitted = False
 
     def __str__(self):
+        """Return a string representation of the classifier."""
         return "Estimator: {}\np(s=1|y=1,x) ~= {}\nFitted: {}".format(
             self.estimator,
             self.c,
@@ -109,7 +111,7 @@ class ElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         X : array-like of shape = [n_samples, n_features]
             The input samples.
         threshold : float, default 0.5
-            The decision threshold over probability to warrent a
+            The decision threshold over probability to warrant a
             positive label.
 
         Returns
@@ -149,7 +151,7 @@ class WeightedElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         The cardinality to attribute to the unlabeled training set.
     hold_out_ratio : float, default 0.1
        The ratio of training examples to set aside to estimate the probability
-       of an exmaple to be positive.
+       of an example to be positive.
 
     """
 
@@ -161,6 +163,7 @@ class WeightedElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         hold_out_ratio=0.1,
         random_state=None,
     ):
+        """Initialize the classifier."""
         self.estimator = estimator
         self.c = 1.0
         self.hold_out_ratio = hold_out_ratio
@@ -170,6 +173,7 @@ class WeightedElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         self.estimator_fitted = False
 
     def __str__(self):
+        """Return a string representation of the classifier."""
         return "Estimator: {}\np(s=1|y=1,x) ~= {}\nFitted: {}".format(
             self.estimator,
             self.c,
@@ -262,7 +266,7 @@ class WeightedElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         numerator = probabilistic_predictions * (self.c * yEstimate * m)
         return numerator / float(n)
 
-    def predict(self, X, treshold=0.5):
+    def predict(self, X, threshold=0.5):
         """Predict labels.
 
         Parameters
@@ -270,7 +274,7 @@ class WeightedElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
         X : array-like of shape = [n_samples, n_features]
             The input samples.
         threshold : float, default 0.5
-            The decision threshold over probability to warrent a
+            The decision threshold over probability to warrant a
             positive label.
 
         Returns
@@ -284,5 +288,8 @@ class WeightedElkanotoPuClassifier(BaseEstimator, ClassifierMixin):
                 "The estimator must be fitted before calling predict()."
             )
         return np.array(
-            [1.0 if p > treshold else 0.0 for p in self.predict_proba(X)[:, 1]]
+            [
+                1.0 if p > threshold else 0.0
+                for p in self.predict_proba(X)[:, 1]
+            ]
         )
diff --git a/src/pulearn/metrics.py b/src/pulearn/metrics.py
index 2381801..c799ca3 100644
--- a/src/pulearn/metrics.py
+++ b/src/pulearn/metrics.py
@@ -40,7 +40,7 @@ def recall(y_true: np.array, y_pred: np.array, threshold: float = 0.5):
         The recall score for the given input samples.
 
     """
-    # check if we need to treshold
+    # check if we need to threshold
     if np.issubdtype(y_pred.dtype, np.floating):
         y_pred = np.array([1 if p > threshold else -1 for p in y_pred])
 
@@ -77,7 +77,7 @@ def lee_liu_score(
     ----------
     y_true : np.array of shape = [n_samples]
         The true labels of the input samples.
-        Unlabled samples are assumed to be indicated with
+        Unlabeled samples are assumed to be indicated with
         numbers <1. Positive samples are assumed to be indicated
         with 1.
     y_pred : np.array of shape = [n_samples]


Test patch:
diff --git a/tests/test_elkanoto.py b/tests/test_elkanoto.py
index 54f562e..724706b 100644
--- a/tests/test_elkanoto.py
+++ b/tests/test_elkanoto.py
@@ -83,20 +83,12 @@ def test_elkanoto(dataset, cls_n_args, estimator_kind):
     print("\nComparison of estimator and PUAdapter(estimator):")
     print(
         "Number of disagreements: {}".format(
-            len(
-                np.where(
-                    (pu_estimator.predict(X) == estimator.predict(X)) == False
-                )[0]
-            )
+            len(np.where(pu_estimator.predict(X) != estimator.predict(X))[0])
         )
     )
     print(
         "Number of agreements: {}".format(
-            len(
-                np.where(
-                    (pu_estimator.predict(X) == estimator.predict(X)) == True
-                )[0]
-            )
+            len(np.where(pu_estimator.predict(X) == estimator.predict(X))[0])
         )
     )