Repository: scikit-activeml/scikit-activeml
Pull request #508 (2025-07-08 15:44:41)

Compatibility for python 3.13 and improved navigation for the API reference
This pull request adds:

- compatibility for python 3.13 but drops compatibility with 3.9, 
- compatibility with the newer Sphinx and Sphinx extension versions,
- improved navigation for the API reference


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index a80f6803..382fdec7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -8,7 +8,7 @@ jobs:
     strategy:
       max-parallel: 4
       matrix:
-        python-version: [ '3.9', '3.10', '3.11', '3.12' ]
+        python-version: [ '3.10', '3.11', '3.12', '3.13' ]
     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
@@ -33,7 +33,7 @@ jobs:
     strategy:
       max-parallel: 4
       matrix:
-        python-version: [ '3.9', '3.10', '3.11', '3.12' ]
+        python-version: [ '3.10', '3.11', '3.12', '3.13']
     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
@@ -45,12 +45,13 @@ jobs:
         sudo apt-get -qq update
         sudo apt-get install pandoc
         sudo apt-get install ghostscript
+        sudo apt-get install ffmpeg
         python -m pip install --upgrade pip
         pip install --upgrade -r requirements.txt -r requirements_max.txt
         pip install -r requirements_extra.txt
     - name: Test with pytest and newest dependencies
       run: |
-        pytest -n auto --cov=./skactiveml --cov-report=xml --cov-fail-under=100
+        pytest -n auto --cov=./skactiveml --cov-report=xml
     - name: Upload coverage to Codecov
       uses: codecov/codecov-action@v5
       with:
@@ -69,7 +70,7 @@ jobs:
     strategy:
       max-parallel: 4
       matrix:
-        python-version: [ '3.9', '3.10', '3.11', '3.12' ]
+        python-version: [ '3.10', '3.11', '3.12', '3.13' ]
     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
@@ -81,6 +82,7 @@ jobs:
         sudo apt-get -qq update
         sudo apt-get install pandoc
         sudo apt-get install ghostscript
+        sudo apt-get install ffmpeg
         python -m pip install --upgrade pip
         pip install --upgrade -r requirements.txt -r requirements_max.txt
         pip install -r requirements_extra.txt
@@ -101,7 +103,7 @@ jobs:
     strategy:
       max-parallel: 4
       matrix:
-        python-version: [ '3.9', '3.10', '3.11', '3.12' ]
+        python-version: [ '3.10', '3.11', '3.12', '3.13' ]
     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
@@ -113,6 +115,7 @@ jobs:
         sudo apt-get -qq update
         sudo apt-get install pandoc
         sudo apt-get install ghostscript
+        sudo apt-get install ffmpeg
         python -m pip install --upgrade pip
         pip install --upgrade -r requirements.txt -r requirements_max.txt
         pip install -r requirements_extra.txt
diff --git a/README.rst b/README.rst
index 19f83219..9b6fe9f0 100644
--- a/README.rst
+++ b/README.rst
@@ -20,14 +20,14 @@ scikit-activeml: A Library and Toolbox for Active Learning Algorithms
 .. |Codecov| image:: https://codecov.io/gh/scikit-activeml/scikit-activeml/branch/master/graph/badge.svg
    :target: https://app.codecov.io/gh/scikit-activeml/scikit-activeml
 
-.. |PythonVersion| image:: https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue.svg
-   :target: https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue
+.. |PythonVersion| image:: https://img.shields.io/badge/python-03.10%20%7C%203.11%20%7C%203.12%20%7C3.13-blue.svg
+   :target: https://pypi.org/project/scikit-activeml/
 
 .. |PyPi| image:: https://badge.fury.io/py/scikit-activeml.svg
-   :target: https://badge.fury.io/py/scikit-activeml
+   :target: https://pypi.org/project/scikit-activeml/
 
-.. |Paper| image:: https://img.shields.io/badge/paper-10.20944/preprints202103.0194.v1-blue.svg
-   :target: https://www.preprints.org/manuscript/202103.0194/v1
+.. |Paper| image:: https://img.shields.io/badge/paper-10.20944/preprints202507.0252.v1-blue.svg
+   :target: https://www.preprints.org/manuscript/202507.0252/v1
 
 .. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
    :target: https://github.com/psf/black
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000..4773dcf0
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,10 @@
+coverage:
+  precision: 2
+  round: down
+  status:
+    project:
+      default:
+        target: 100%
+        threshold: 0%
+        if_not_found: failure
+        informational: false
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 0cd95168..90d5c496 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -73,6 +73,11 @@ extensions = [
     "copy_sphinx_gallery_notebooks"
 ]
 
+nitpicky = True
+nitpick_ignore = [
+    ("py:class", "skactiveml.pool._bald._GeneralBALD")
+]
+
 # nbsphinx_execute = 'always'
 nbsphinx_execute = 'never'
 
@@ -90,7 +95,7 @@ templates_path = ["_templates"]
 # You can specify multiple suffix as a list of string:
 #
 # source_suffix = ['.rst', '.md']
-source_suffix = [".rst"]
+source_suffix = {'.rst': 'restructuredtext'}
 
 # The master toctree document.
 master_doc = "index"
@@ -105,7 +110,17 @@ language = "en"
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 # This pattern also affects html_static_path and html_extra_path .
-exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"]
+exclude_patterns = [
+    "_build",
+    "Thumbs.db",
+    ".DS_Store",
+    "**.ipynb_checkpoints",
+    # ignore sphinx gallery example ipynb as they might interfere with
+    # including the generated .rst files
+    'generated/sphinx_gallery_examples/**/*.ipynb',
+    'examples/*',
+    'generated/examples/*'
+]
 
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = "sphinx"
@@ -224,8 +239,9 @@ bibtex_bibfiles = ["refs.bib"]
 intersphinx_mapping = {
     "numpy": ("https://numpy.org/doc/stable/", None),
     "python": ("https://docs.python.org/3/", None),
-    "scipy": ("https://docs.scipy.org/doc/scipy/reference/", None),
-    "matplotlib": ("https://matplotlib.org/", None),
+    'sklearn': ('https://scikit-learn.org/stable/', None),
+    "scipy": ("https://docs.scipy.org/doc/scipy/", None),
+    "matplotlib": ("https://matplotlib.org/stable/", None),
     "joblib": ("https://joblib.readthedocs.io/en/latest/", None),
     "iteration-utilities": (
         "https://iteration-utilities.readthedocs.io/en/latest/",
diff --git a/docs/examples/README.rst b/docs/examples/README.rst
index 7d4d0aae..8a0578f4 100644
--- a/docs/examples/README.rst
+++ b/docs/examples/README.rst
@@ -1,2 +1,4 @@
+:orphan:
+
 Examples
 ========
\ No newline at end of file
diff --git a/docs/examples/pool/README.rst b/docs/examples/pool/README.rst
index f82422b4..384ab533 100644
--- a/docs/examples/pool/README.rst
+++ b/docs/examples/pool/README.rst
@@ -1,10 +1,10 @@
-Pool Based AL Strategies
+Pool-based AL Strategies
 ------------------------
 
 **Legend for classification plots**
 
-.. image:: ../examples/pool_classification_legend.png
+.. image:: /examples/pool_classification_legend.png
 
 **Legend for regression plots**
 
-.. image:: ../examples/pool_regression_legend.png
+.. image:: /examples/pool_regression_legend.png
diff --git a/docs/examples/stream/README.rst b/docs/examples/stream/README.rst
index 28281f19..f6d861d1 100644
--- a/docs/examples/stream/README.rst
+++ b/docs/examples/stream/README.rst
@@ -1,5 +1,5 @@
-Stream Based AL Strategies
+Stream-based AL Strategies
 --------------------------
 **Legend for classification plots**
 
-.. image:: ../examples/stream_classification_legend.png
\ No newline at end of file
+.. image:: /examples/stream_classification_legend.png
\ No newline at end of file
diff --git a/docs/examples/template_pool.py b/docs/examples/template_pool.py
index 2fb30a9e..0b79133e 100644
--- a/docs/examples/template_pool.py
+++ b/docs/examples/template_pool.py
@@ -16,10 +16,7 @@
 "$install_dependencies|# !pip install scikit-activeml"
 
 # %%
-# .. raw:: html
-#
-#   <hr style="border-style: solid; border-top: 1px solid; border-right: 0; border-bottom: 0; border-left: 0;">
-#
+# ---
 
 # %%
 import numpy as np
diff --git a/docs/examples/template_pool_batch.py b/docs/examples/template_pool_batch.py
index 599d13ff..1c70a0d6 100644
--- a/docs/examples/template_pool_batch.py
+++ b/docs/examples/template_pool_batch.py
@@ -16,10 +16,7 @@
 "$install_dependencies|# !pip install scikit-activeml"
 
 # %%
-# .. raw:: html
-#
-#   <hr style="border-style: solid; border-top: 1px solid; border-right: 0; border-bottom: 0; border-left: 0;">
-#
+# ---
 
 # %%
 import numpy as np
diff --git a/docs/examples/template_pool_regression.py b/docs/examples/template_pool_regression.py
index 202061ba..6c001074 100644
--- a/docs/examples/template_pool_regression.py
+++ b/docs/examples/template_pool_regression.py
@@ -16,10 +16,7 @@
 "$install_dependencies|# !pip install scikit-activeml"
 
 # %%
-# .. raw:: html
-#
-#   <hr style="border-style: solid; border-top: 1px solid; border-right: 0; border-bottom: 0; border-left: 0;">
-#
+# ---
 
 # %%
 import numpy as np
diff --git a/docs/examples/template_pool_regression_batch.py b/docs/examples/template_pool_regression_batch.py
index 19c287c0..5c6da1b9 100644
--- a/docs/examples/template_pool_regression_batch.py
+++ b/docs/examples/template_pool_regression_batch.py
@@ -16,10 +16,7 @@
 "$install_dependencies|# !pip install scikit-activeml"
 
 # %%
-# .. raw:: html
-#
-#   <hr style="border-style: solid; border-top: 1px solid; border-right: 0; border-bottom: 0; border-left: 0;">
-#
+# ---
 
 # %%
 import numpy as np
diff --git a/docs/examples/template_stream.py b/docs/examples/template_stream.py
index 64e78a24..0433ba45 100644
--- a/docs/examples/template_stream.py
+++ b/docs/examples/template_stream.py
@@ -16,10 +16,7 @@
 "$install_dependencies|# !pip install scikit-activeml"
 
 # %%
-# .. raw:: html
-#
-#   <hr style="border-style: solid; border-top: 1px solid; border-right: 0; border-bottom: 0; border-left: 0;">
-#
+# ---
 
 # %%
 import numpy as np
diff --git a/docs/generate.py b/docs/generate.py
index 60455832..65689113 100644
--- a/docs/generate.py
+++ b/docs/generate.py
@@ -38,23 +38,10 @@ def generate_api_reference_rst(gen_path):
     path = os.path.join(os.path.basename(gen_path), "api_reference.rst")
     gen_path = os.path.join(os.path.basename(gen_path), "api")
     os.makedirs(os.path.abspath(gen_path), exist_ok=True)
-    with open(path, "w") as file:
-        file.write(".. _api_reference:\n")
-        file.write("\n")
-        file.write("=============\n")
-        file.write("API Reference\n")
-        file.write("=============\n")
-        file.write("\n")
-        file.write("This is an overview of the API.\n")
-        file.write("\n")
-        file.write(".. module:: skactiveml\n")
-        file.write("\n")
-        for item in skactiveml.__all__:
-            if inspect.ismodule(getattr(skactiveml, item)):
-                file.write(automodule(getattr(skactiveml, item)))
+    automodule(skactiveml, api_root_path=gen_path)
 
 
-def automodule(module, level=0):
+def automodule(module, api_root_path, level=0):
     """
     This function generates the restructured text for the api reference and the
      specified module.
@@ -70,6 +57,7 @@ def automodule(module, level=0):
     -------
         str : The restructured text
     """
+    header_str = ""
     rst_str = ""
     modules = []
     classes = []
@@ -91,21 +79,35 @@ def automodule(module, level=0):
             constants.append(item)
 
     title = f":mod:`{module.__name__}`"
-    rst_str += title + "\n"
-    rst_str += "".ljust(len(title), "=") + "\n\n"
+    header_str += title + "\n"
+    header_str += "".ljust(len(title), "=") + "\n\n"
+
+    header_str += f".. automodule:: {module.__name__}\n"
+    header_str += f"    :no-members:\n"
+    header_str += f"    :no-inherited-members:\n\n"
 
-    rst_str += f".. automodule:: {module.__name__}\n"
-    rst_str += f"    :no-members:\n"
-    rst_str += f"    :no-inherited-members:\n\n"
+    header_str += f'.. currentmodule:: {module.__name__.split(".")[0]}\n\n'
+
+    if modules:
+        rst_str += f"Submodules\n"
+        rst_str += f"----------\n\n"
+
+        rst_str += f".. list-table::\n"
+        rst_str += f"  :header-rows: 0\n"
+        rst_str += f"  :widths: 100\n"
+        rst_str += "\n"
+        for item in modules:
+            item_module_path = f"{module.__name__}.{item}"
+            rst_str += f"  * - :mod:`{item_module_path}`\n"
+        rst_str += "\n"
 
-    rst_str += f'.. currentmodule:: {module.__name__.split(".")[0]}\n\n'
     if classes:
         rst_str += f"Classes\n"
         rst_str += f"-------\n\n"
 
         rst_str += f".. autosummary::\n"
         rst_str += f"   :nosignatures:\n"
-        rst_str += f"   :toctree: api\n"
+        rst_str += f"   :toctree: .\n"
         rst_str += f"   :template: class.rst\n\n"
         for item in classes:
             name = module.__name__
@@ -123,7 +125,7 @@ def automodule(module, level=0):
 
         rst_str += f".. autosummary::\n"
         rst_str += f"   :nosignatures:\n"
-        rst_str += f"   :toctree: api\n"
+        rst_str += f"   :toctree: .\n"
         rst_str += f"   :template: function.rst\n\n"
         for item in functions:
             name = module.__name__
@@ -135,10 +137,44 @@ def automodule(module, level=0):
             rst_str += f"   {name}" + "\n"
         rst_str += "\n"
 
-    for item in modules:
-        rst_str += automodule(getattr(module, item), level=level + 1)
+    path = os.path.join(api_root_path, f"{module.__name__}.rst")
+    if level == 0:
+        with open(path, "w") as file:
+            file.write("=============\n")
+            file.write("API Reference\n")
+            file.write("=============\n")
+            file.write("\n")
+            file.write("This is an overview of the API.\n")
+            file.write("\n")
+            file.write(".. toctree::\n")
+            file.write("  :titlesonly:\n")
+            file.write("  :includehidden:\n")
+            file.write("  :maxdepth: 4\n")
 
-    return rst_str
+            file.write("\n")
+            for item in modules:
+                file.write(f"  {module.__name__}.{item}\n")
+            file.write("\n")
+            file.write("\n")
+            file.write(".. module:: skactiveml\n")
+    else:
+        with open(path, "w") as file:
+            file.write(header_str)
+            file.write("\n")
+            file.write(".. toctree::\n")
+            file.write("  :hidden:\n")
+            file.write("\n")
+            for item in modules:
+                file.write(f"  {module.__name__}.{item}\n")
+            file.write("\n")
+            file.write(rst_str)
+
+    for item in modules:
+        automodule(
+            getattr(module, item),
+            api_root_path=api_root_path,
+            level=level + 1
+        )
 
 
 def generate_strategy_overview_rst(gen_path, json_data):
@@ -868,6 +904,9 @@ def post_process_tutorials(
 
         if file_content is not None:
             processed_file_content = copy.copy(file_content)
+            processed_file_content = add_orphan_metadata(
+                processed_file_content
+            )
             processed_file_content = replace_colab_link(
                 processed_file_content,
                 file_path_colab,
@@ -887,6 +926,30 @@ def post_process_tutorials(
                     pass
 
 
+def add_orphan_metadata(
+        file_content
+):
+    """This function adds the orphan metadata that nbsphinx require so that the
+    notebooks don't need to appear in any toctree.
+
+    Parameters
+    ----------
+    file_content: str
+        The content of the jupyter notebook.
+
+    Returns
+    -------
+    output : str
+        The notebook with the added metadata.
+    """
+    notebook_json = json.loads(file_content)
+    nbsphinx_dict = notebook_json['metadata'].get('nbsphinx', {})
+    nbsphinx_dict['orphan'] = True
+    notebook_json['metadata']['nbsphinx'] = nbsphinx_dict
+    output = json.dumps(notebook_json)
+    return output
+
+
 def replace_colab_link(
         file_content,
         colab_path,
diff --git a/docs/index.rst b/docs/index.rst
index d8cb0640..0efb0ea2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -5,7 +5,7 @@
    Home <self>
    Tutorials <tutorials>
    Strategy Overview <generated/strategy_overview>
-   generated/api_reference
+   generated/api/skactiveml
    generated/sphinx_gallery_examples/index
    contributing
    Changelog <https://github.com/scikit-activeml/scikit-activeml/releases>
@@ -22,15 +22,10 @@
    :start-after: examples_start
    :end-before: examples_end
 
-.. include:: ../README.rst
-   :start-after: dev_start
-   :end-before: dev_end
-
 .. include:: ../README.rst
    :start-after: citing_start
    :end-before: citing_end
 
-
 Indices and tables
 ==================
 
diff --git a/docs/skactiveml.utils.MISSING_LABEL.rst b/docs/skactiveml.utils.MISSING_LABEL.rst
index 5c11c8d8..cdd29568 100644
--- a/docs/skactiveml.utils.MISSING_LABEL.rst
+++ b/docs/skactiveml.utils.MISSING_LABEL.rst
@@ -1,4 +1,6 @@
-﻿skactiveml.utils.MISSING\_LABEL
+﻿:orphan:
+
+skactiveml.utils.MISSING\_LABEL
 ===============================
 
 Define constant for missing label used throughout the package.
diff --git a/requirements.txt b/requirements.txt
index 23e1d2f3..4cb29c17 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 joblib>=1.4.0
-numpy>=1.26
-scipy>=1.11.3
+numpy>=2.1
+scipy>=1.14.1
 scikit-learn>=1.6.0
-matplotlib>=3.7.3
-iteration-utilities>=0.12.0
+matplotlib>=3.9.0
+iteration-utilities>=0.13.0
 makefun>=1.15.3
\ No newline at end of file
diff --git a/requirements_extra.txt b/requirements_extra.txt
index 70001943..e65f7f5b 100644
--- a/requirements_extra.txt
+++ b/requirements_extra.txt
@@ -3,13 +3,13 @@ pytest
 pytest-cov
 pytest-xdist
 jupyter
-sphinx==5.3.0
-sphinxcontrib-bibtex~=2.3
-numpydoc~=1.1
-nbsphinx~=0.8
-nbformat~=5.4.0
-sphinx-gallery~=0.11
-pybtex~=0.23
-pydata_sphinx_theme==0.12
+sphinx>=8.1, <=8.2
+sphinxcontrib-bibtex~=2.6
+numpydoc~=1.9
+nbsphinx~=0.9
+nbformat~=5.10
+sphinx-gallery~=0.19
+pybtex~=0.25
+pydata_sphinx_theme~=0.16
 gitpython
 black
\ No newline at end of file
diff --git a/skactiveml/base.py b/skactiveml/base.py
index 6c8142c0..fb7b655b 100644
--- a/skactiveml/base.py
+++ b/skactiveml/base.py
@@ -38,6 +38,7 @@ from .utils import (
 # '__all__' is necessary to create the sphinx docs.
 __all__ = [
     "QueryStrategy",
+    "PoolQueryStrategy",
     "SingleAnnotatorPoolQueryStrategy",
     "MultiAnnotatorPoolQueryStrategy",
     "BudgetManager",
@@ -104,7 +105,7 @@ class PoolQueryStrategy(QueryStrategy):
         X : array-like of shape (n_samples, n_features)
             Training data set, usually complete, i.e. including the labeled and
             unlabeled samples.
-        y : array-like of shape (n_samples, *)
+        y : array-like of shape (n_samples, ...)
             Labels of the training data set (possibly including unlabeled ones
             indicated by self.MISSING_LABEL.
         candidates : None or array-like of shape (n_candidates), dtype=int or \
@@ -114,7 +115,7 @@ class PoolQueryStrategy(QueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
@@ -133,7 +134,7 @@ class PoolQueryStrategy(QueryStrategy):
         -------
         X : np.ndarray of shape (n_samples, n_features)
             Checked training data set.
-        y : np.ndarray of shape (n_samples, *)
+        y : np.ndarray of shape (n_samples, ...)
             Checked labels of the training data set.
         candidates : None or np.ndarray of shape (n_candidates), dtype=int or\
                 np.ndarray of shape (n_candidates, n_features)
@@ -218,7 +219,7 @@ class SingleAnnotatorPoolQueryStrategy(PoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
@@ -282,7 +283,7 @@ class SingleAnnotatorPoolQueryStrategy(PoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
@@ -360,7 +361,7 @@ class SingleAnnotatorPoolQueryStrategy(PoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         X : np.ndarray of shape (n_samples, n_features)
diff --git a/skactiveml/classifier/_wrapper.py b/skactiveml/classifier/_wrapper.py
index 9dcc5ed0..c0c2a14d 100644
--- a/skactiveml/classifier/_wrapper.py
+++ b/skactiveml/classifier/_wrapper.py
@@ -91,7 +91,7 @@ class SklearnClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             The feature matrix representing the samples.
         y : array-like of shape (n_samples,) or (n_samples, n_outputs)
             It contains the class labels of the training samples. Missing
@@ -125,7 +125,7 @@ class SklearnClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             The feature matrix representing the samples.
         y : array-like of shape (n_samples,) or (n_samples, n_outputs)
             It contains the class labels of the training samples. Missing
@@ -159,7 +159,7 @@ class SklearnClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             Input samples.
         predict_kwargs : dict-like
             Further parameters as input to the `predict` method of the
@@ -198,7 +198,7 @@ class SklearnClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             Input samples.
         predict_proba_kwargs : dict-like
             Further parameters as input to the `predict_proba` method of the
@@ -416,7 +416,7 @@ class SlidingWindowClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             The feature matrix representing the samples.
         y : array-like of shape (n_samples,) or (n_samples, n_outputs)
             It contains the class labels of the training samples. Missing
@@ -477,7 +477,7 @@ class SlidingWindowClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             The feature matrix representing the samples.
         y : array-like of shape (n_samples,) or (n_samples, n_outputs)
             It contains the class labels of the training samples. Missing
@@ -670,7 +670,7 @@ class SlidingWindowClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             Input samples.
         predict_kwargs : dict-like
             Further parameters as input to the `predict` method of the
@@ -690,7 +690,7 @@ class SlidingWindowClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             Input samples.
         predict_proba_kwargs : dict-like
             Further parameters as input to the `predict_proba` method of the
@@ -712,7 +712,7 @@ class SlidingWindowClassifier(SkactivemlClassifier, MetaEstimatorMixin):
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             Test samples whose class frequencies are to be estimated.
 
         Returns
diff --git a/skactiveml/classifier/multiannotator/_annotator_ensemble_classifier.py b/skactiveml/classifier/multiannotator/_annotator_ensemble_classifier.py
index 0a7e39c7..27aaec16 100644
--- a/skactiveml/classifier/multiannotator/_annotator_ensemble_classifier.py
+++ b/skactiveml/classifier/multiannotator/_annotator_ensemble_classifier.py
@@ -5,7 +5,7 @@ Classifier Ensemble for Multiple Annotators
 from copy import deepcopy
 
 import numpy as np
-from sklearn.ensemble._base import _BaseHeterogeneousEnsemble
+from sklearn.base import MetaEstimatorMixin, is_classifier, is_regressor
 from sklearn.utils.validation import (
     check_array,
     check_is_fitted,
@@ -20,9 +20,7 @@ from ...utils import (
 )
 
 
-class AnnotatorEnsembleClassifier(
-    _BaseHeterogeneousEnsemble, SkactivemlClassifier
-):
+class AnnotatorEnsembleClassifier(MetaEstimatorMixin, SkactivemlClassifier):
     """Ensemble of Annotator-wise Classifier
 
     This strategy consists of fitting one classifier per annotator.
@@ -45,7 +43,7 @@ class AnnotatorEnsembleClassifier(
         Value to represent a missing label.
     cost_matrix : array-like of shape (n_classes, n_classes)
         Cost matrix with `cost_matrix[i,j]` indicating cost of predicting class
-        `classes[j]`  for a sample of class `classes[i]. Can be only set, if
+        `classes[j]`  for a sample of class `classes[i]`. Can be only set, if
         classes is not none.
     random_state : int or RandomState instance or None, default=None
         Determines random number for `predict` method. Pass an int for
@@ -57,11 +55,10 @@ class AnnotatorEnsembleClassifier(
         Holds the label for each class after fitting.
     cost_matrix_ : np.ndarray of shape (classes, classes)
         Cost matrix with `cost_matrix_[i,j]` indicating cost of predicting
-        class `classes_[j]`  for a sample of class `classes_[i].
+        class `classes_[j]`  for a sample of class `classes_[i]`.
     estimators_ : list of estimators
         The elements of the estimators parameter, having been fitted on the
-        training data. If an estimator has been set to `'drop'`, it will not
-        appear in `estimators_`.
+        training data.
     """
 
     def __init__(
@@ -73,7 +70,6 @@ class AnnotatorEnsembleClassifier(
         cost_matrix=None,
         random_state=None,
     ):
-        _BaseHeterogeneousEnsemble.__init__(self, estimators=estimators)
         SkactivemlClassifier.__init__(
             self,
             classes=classes,
@@ -81,6 +77,7 @@ class AnnotatorEnsembleClassifier(
             cost_matrix=cost_matrix,
             random_state=random_state,
         )
+        self.estimators = estimators
         self.voting = voting
 
     def fit(self, X, y, sample_weight=None):
@@ -88,7 +85,7 @@ class AnnotatorEnsembleClassifier(
 
         Parameters
         ----------
-        X : array-like of shape (n_samples, *)
+        X : array-like of shape (n_samples, ...)
             The feature matrix representing the samples.
         y : array-like of shape (n_samples, n_estimators)
             It contains the class labels of the training samples.
@@ -135,7 +132,7 @@ class AnnotatorEnsembleClassifier(
             f"{len(self.estimators)}) but has shape {y.shape}."
         )
         if (
-            self.named_estimators is not None
+            self.estimators is not None
             and y.ndim <= 1
             or y.shape[1] != len(self.estimators)
         ):
@@ -192,8 +189,26 @@ class AnnotatorEnsembleClassifier(
         return P
 
     def _validate_estimators(self):
-        _BaseHeterogeneousEnsemble._validate_estimators(self)
-        for name, est in self.estimators:
+        if len(self.estimators) == 0:
+            raise ValueError(
+                "The list of estimators `self.estimators`, needs to be a "
+                "non-empty list of (name, estimator) tuples."
+                f"Instead, {self.estimators} was passed."
+            )
+
+        for i_elem, elem in enumerate(self.estimators):
+            if (
+                not hasattr(elem, "__len__")
+                or len(elem) != 2
+                or not isinstance(elem[0], str)
+            ):
+                raise ValueError(
+                    "The list of estimators `self.estimators`, needs to be a"
+                    "non-empty list of (name, estimator) tuples. Instead, "
+                    f"{self.estimators} was passed with element {elem} at "
+                    f"position {i_elem}."
+                )
+            est = elem[1]
             if not isinstance(est, SkactivemlClassifier):
                 raise TypeError(f"'{est}' is not a 'SkactivemlClassifier'.")
             if self.voting == "soft" and not hasattr(est, "predict_proba"):
diff --git a/skactiveml/pool/__init__.py b/skactiveml/pool/__init__.py
index 3615c5eb..70b1cbe0 100644
--- a/skactiveml/pool/__init__.py
+++ b/skactiveml/pool/__init__.py
@@ -9,7 +9,11 @@ from ._bald import GreedyBALD, BatchBALD, batch_bald
 from ._cost_embedding_al import CostEmbeddingAL
 from ._discriminative_al import DiscriminativeAL
 from ._epistemic_uncertainty_sampling import EpistemicUncertaintySampling
-from ._expected_error_reduction import MonteCarloEER, ValueOfInformationEER
+from ._expected_error_reduction import (
+    ExpectedErrorReduction,
+    MonteCarloEER,
+    ValueOfInformationEER,
+)
 from ._expected_model_change_maximization import (
     ExpectedModelChangeMaximization,
 )
@@ -53,6 +57,7 @@ __all__ = [
     "uncertainty_scores",
     "expected_average_precision",
     "EpistemicUncertaintySampling",
+    "ExpectedErrorReduction",
     "MonteCarloEER",
     "ValueOfInformationEER",
     "QueryByCommittee",
diff --git a/skactiveml/pool/_badge.py b/skactiveml/pool/_badge.py
index 0b2082b5..e92fb29d 100644
--- a/skactiveml/pool/_badge.py
+++ b/skactiveml/pool/_badge.py
@@ -92,7 +92,7 @@ class Badge(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
diff --git a/skactiveml/pool/_bald.py b/skactiveml/pool/_bald.py
index 25813cdc..18779fdb 100644
--- a/skactiveml/pool/_bald.py
+++ b/skactiveml/pool/_bald.py
@@ -51,7 +51,7 @@ class _GeneralBALD(QueryByCommittee):
           performed.
         - If `sample_predictions_method_name` is not `None` and in the
           case of classification, the method is expected to take samples of
-          the shape `(n_samples, *)` as input and to output probabilities
+          the shape `(n_samples, ...)` as input and to output probabilities
           of the shape `(n_members, n_samples, n_classes)`, e.g.,
           `sample_proba` in `skactiveml.base.ClassFrequencyEstimator`.
     sample_predictions_dict : dict, default=None
@@ -142,7 +142,7 @@ class _GeneralBALD(QueryByCommittee):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
@@ -266,7 +266,7 @@ class BatchBALD(_GeneralBALD):
           performed.
         - If `sample_predictions_method_name` is not `None` and in the
           case of classification, the method is expected to take samples of
-          the shape `(n_samples, *)` as input and to output probabilities
+          the shape `(n_samples, ...)` as input and to output probabilities
           of the shape `(n_members, n_samples, n_classes)`, e.g.,
           `sample_proba` in `skactiveml.base.ClassFrequencyEstimator`.
     sample_predictions_dict : dict, default=None
@@ -334,7 +334,7 @@ class GreedyBALD(_GeneralBALD):
           performed.
         - If `sample_predictions_method_name` is not `None` and in the
           case of classification, the method is expected to take samples of
-          the shape `(n_samples, *)` as input and to output probabilities
+          the shape `(n_samples, ...)` as input and to output probabilities
           of the shape `(n_members, n_samples, n_classes)`, e.g.,
           `sample_proba` in `skactiveml.base.ClassFrequencyEstimator`.
     sample_predictions_dict : dict, default=None
diff --git a/skactiveml/pool/_contrastive_al.py b/skactiveml/pool/_contrastive_al.py
index 695cffa0..f9aad7ec 100644
--- a/skactiveml/pool/_contrastive_al.py
+++ b/skactiveml/pool/_contrastive_al.py
@@ -104,7 +104,7 @@ class ContrastiveAL(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
diff --git a/skactiveml/pool/_core_set.py b/skactiveml/pool/_core_set.py
index 08d9e356..0eea0b92 100644
--- a/skactiveml/pool/_core_set.py
+++ b/skactiveml/pool/_core_set.py
@@ -73,7 +73,7 @@ class CoreSet(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
diff --git a/skactiveml/pool/_cost_embedding_al.py b/skactiveml/pool/_cost_embedding_al.py
index 8695d3ad..ae41b158 100644
--- a/skactiveml/pool/_cost_embedding_al.py
+++ b/skactiveml/pool/_cost_embedding_al.py
@@ -122,7 +122,7 @@ class CostEmbeddingAL(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
diff --git a/skactiveml/pool/_discriminative_al.py b/skactiveml/pool/_discriminative_al.py
index c13c86f2..0f9180b7 100644
--- a/skactiveml/pool/_discriminative_al.py
+++ b/skactiveml/pool/_discriminative_al.py
@@ -86,7 +86,7 @@ class DiscriminativeAL(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
diff --git a/skactiveml/pool/_epistemic_uncertainty_sampling.py b/skactiveml/pool/_epistemic_uncertainty_sampling.py
index d2e51044..65a11ffa 100644
--- a/skactiveml/pool/_epistemic_uncertainty_sampling.py
+++ b/skactiveml/pool/_epistemic_uncertainty_sampling.py
@@ -80,7 +80,7 @@ class EpistemicUncertaintySampling(SingleAnnotatorPoolQueryStrategy):
         y : array-like of shape (n_samples,)
             Labels of the training data set (possibly including unlabeled ones
             indicated by `self.missing_label`).
-        clf : skactiveml.classifier.ParzenWindowClassifier or
+        clf : skactiveml.classifier.ParzenWindowClassifier or \
                 wrapped sklearn.linear_model.LogisticRegression
             Only the Parzen Window Classifier and a wrapped sklearn
             logistic regression are supported as classifiers.
@@ -94,7 +94,7 @@ class EpistemicUncertaintySampling(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
diff --git a/skactiveml/pool/_expected_error_reduction.py b/skactiveml/pool/_expected_error_reduction.py
index 3b01cc82..1df4c955 100644
--- a/skactiveml/pool/_expected_error_reduction.py
+++ b/skactiveml/pool/_expected_error_reduction.py
@@ -109,7 +109,7 @@ class ExpectedErrorReduction(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         sample_weight_candidates : array-like of shape (n_candidates,), \
@@ -744,7 +744,7 @@ class ValueOfInformationEER(ExpectedErrorReduction):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
diff --git a/skactiveml/pool/_expected_model_change_maximization.py b/skactiveml/pool/_expected_model_change_maximization.py
index 2ce5953a..ef3bc11c 100644
--- a/skactiveml/pool/_expected_model_change_maximization.py
+++ b/skactiveml/pool/_expected_model_change_maximization.py
@@ -106,8 +106,8 @@ class ExpectedModelChangeMaximization(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, `candidates` is
-              considered as the candidate samples in `(X,y)`.
+            - If `candidates` is of shape `(n_candidates, ...)`, `candidates`
+              is considered as the candidate samples in `(X,y)`.
         batch_size : int, default=1
             The number of samples to be selected in one AL cycle.
         return_utilities : bool, default=False
@@ -135,7 +135,7 @@ class ExpectedModelChangeMaximization(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
 
diff --git a/skactiveml/pool/_expected_model_output_change.py b/skactiveml/pool/_expected_model_output_change.py
index 0efbbcc9..7a26358b 100644
--- a/skactiveml/pool/_expected_model_output_change.py
+++ b/skactiveml/pool/_expected_model_output_change.py
@@ -98,7 +98,7 @@ class ExpectedModelOutputChange(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         X_eval : array-like of shape (n_eval_samples, n_features), default=None
diff --git a/skactiveml/pool/_expected_model_variance.py b/skactiveml/pool/_expected_model_variance.py
index 3e7257d9..16133cc4 100644
--- a/skactiveml/pool/_expected_model_variance.py
+++ b/skactiveml/pool/_expected_model_variance.py
@@ -81,7 +81,7 @@ class ExpectedModelVarianceReduction(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
diff --git a/skactiveml/pool/_falcun.py b/skactiveml/pool/_falcun.py
index 71a154c0..78bcc348 100644
--- a/skactiveml/pool/_falcun.py
+++ b/skactiveml/pool/_falcun.py
@@ -87,7 +87,7 @@ class Falcun(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
@@ -117,7 +117,7 @@ class Falcun(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
         # Check parameters.
diff --git a/skactiveml/pool/_four_ds.py b/skactiveml/pool/_four_ds.py
index c86eb976..381a3e35 100644
--- a/skactiveml/pool/_four_ds.py
+++ b/skactiveml/pool/_four_ds.py
@@ -85,8 +85,8 @@ class FourDs(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, `candidates` is
-              considered as the candidate samples in `(X,y)`.
+            - If `candidates` is of shape `(n_candidates, ...)`, `candidates`
+              is considered as the candidate samples in `(X,y)`.
         batch_size : int, default=1
             The number of samples to be selected in one AL cycle.
         return_utilities : bool, default=False
@@ -114,7 +114,7 @@ class FourDs(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
         # Check standard parameters.
diff --git a/skactiveml/pool/_greedy_sampling.py b/skactiveml/pool/_greedy_sampling.py
index a5332408..4a9f1de3 100644
--- a/skactiveml/pool/_greedy_sampling.py
+++ b/skactiveml/pool/_greedy_sampling.py
@@ -74,8 +74,8 @@ class GreedySamplingX(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, `candidates` is
-              considered as the candidate samples in `(X,y)`.
+            - If `candidates` is of shape `(n_candidates, ...)`, `candidates`
+              is considered as the candidate samples in `(X,y)`.
         batch_size : int, default=1
             The number of samples to be selected in one AL cycle.
         return_utilities : bool, default=False
@@ -103,7 +103,7 @@ class GreedySamplingX(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
         X, y, candidates, batch_size, return_utilities = self._validate_data(
@@ -240,8 +240,8 @@ class GreedySamplingTarget(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, `candidates` is
-              considered as the candidate samples in `(X,y)`.
+            - If `candidates` is of shape `(n_candidates, ...)`, `candidates`
+              is considered as the candidate samples in `(X,y)`.
         batch_size : int, default=1
             The number of samples to be selected in one AL cycle.
         return_utilities : bool, default=False
@@ -269,7 +269,7 @@ class GreedySamplingTarget(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
         X, y, candidates, batch_size, return_utilities = self._validate_data(
diff --git a/skactiveml/pool/_information_gain_maximization.py b/skactiveml/pool/_information_gain_maximization.py
index dca08025..e42391c3 100644
--- a/skactiveml/pool/_information_gain_maximization.py
+++ b/skactiveml/pool/_information_gain_maximization.py
@@ -98,7 +98,7 @@ class KLDivergenceMaximization(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
@@ -129,7 +129,7 @@ class KLDivergenceMaximization(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
         X, y, candidates, batch_size, return_utilities = self._validate_data(
diff --git a/skactiveml/pool/_probabilistic_al.py b/skactiveml/pool/_probabilistic_al.py
index 65a5d0e9..552f6dc5 100644
--- a/skactiveml/pool/_probabilistic_al.py
+++ b/skactiveml/pool/_probabilistic_al.py
@@ -111,8 +111,8 @@ class ProbabilisticAL(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, `candidates` is
-              considered as the candidate samples in `(X,y)`.
+            - If `candidates` is of shape `(n_candidates, ...)`, `candidates`
+              is considered as the candidate samples in `(X,y)`.
         batch_size : int, default=1
             The number of samples to be selected in one AL cycle.
         return_utilities : bool, default=False
@@ -140,7 +140,7 @@ class ProbabilisticAL(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
         # Validate input parameters.
diff --git a/skactiveml/pool/_query_by_committee.py b/skactiveml/pool/_query_by_committee.py
index 2a9f48e5..6cbda3ec 100644
--- a/skactiveml/pool/_query_by_committee.py
+++ b/skactiveml/pool/_query_by_committee.py
@@ -53,12 +53,12 @@ class QueryByCommittee(SingleAnnotatorPoolQueryStrategy):
           performed.
         - If `sample_predictions_method_name` is not `None` and in the
           case of classification, the method is expected to take samples of
-          the shape `(n_samples, *)` as input and to output probabilities
+          the shape `(n_samples, ...)` as input and to output probabilities
           of the shape `(n_members, n_samples, n_classes)`, e.g.,
           `sample_proba` in `skactiveml.base.ClassFrequencyEstimator`.
         - If `sample_predictions_method_name` is not `None` and in the
           case of regression, the method is expected to take samples of the
-          shape `(n_samples, *)` as input and to output numerical values of
+          shape `(n_samples, ...)` as input and to output numerical values of
           the shape `(n_members, n_samples)`, e.g., `sample_y` in
           `sklearn.gaussian_process.GaussianProcessRegressor`.
     sample_predictions_dict : dict, default=None
@@ -163,7 +163,7 @@ class QueryByCommittee(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
diff --git a/skactiveml/pool/_random_sampling.py b/skactiveml/pool/_random_sampling.py
index d88ab312..57e88c0a 100644
--- a/skactiveml/pool/_random_sampling.py
+++ b/skactiveml/pool/_random_sampling.py
@@ -43,7 +43,7 @@ class RandomSampling(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
diff --git a/skactiveml/pool/_regression_tree_based_al.py b/skactiveml/pool/_regression_tree_based_al.py
index 6acd8fdf..1be0de39 100644
--- a/skactiveml/pool/_regression_tree_based_al.py
+++ b/skactiveml/pool/_regression_tree_based_al.py
@@ -106,7 +106,7 @@ class RegressionTreeBasedAL(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
diff --git a/skactiveml/pool/_uncertainty_sampling.py b/skactiveml/pool/_uncertainty_sampling.py
index c87a1fd3..8b79ecb2 100644
--- a/skactiveml/pool/_uncertainty_sampling.py
+++ b/skactiveml/pool/_uncertainty_sampling.py
@@ -110,7 +110,7 @@ class UncertaintySampling(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`).
         batch_size : int, default=1
@@ -140,7 +140,7 @@ class UncertaintySampling(SingleAnnotatorPoolQueryStrategy):
               in `X`.
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `utilities` refers to the samples in `X`.
-            - If `candidates` is of shape `(n_candidates, *)`, `utilities`
+            - If `candidates` is of shape `(n_candidates, ...)`, `utilities`
               refers to the indexing in `candidates`.
         """
         # Validate input parameters.
diff --git a/skactiveml/pool/_wrapper.py b/skactiveml/pool/_wrapper.py
index 693d88fc..e39c51e4 100644
--- a/skactiveml/pool/_wrapper.py
+++ b/skactiveml/pool/_wrapper.py
@@ -87,7 +87,7 @@ class SubSamplingWrapper(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
@@ -359,7 +359,7 @@ class ParallelUtilityEstimationWrapper(SingleAnnotatorPoolQueryStrategy):
             - If `candidates` is of shape `(n_candidates,)` and of type
               `int`, `candidates` is considered as the indices of the
               samples in `(X,y)`.
-            - If `candidates` is of shape `(n_candidates, *)`, the
+            - If `candidates` is of shape `(n_candidates, ...)`, the
               candidate samples are directly given in `candidates` (not
               necessarily contained in `X`). This is not supported by all
               query strategies.
diff --git a/skactiveml/stream/__init__.py b/skactiveml/stream/__init__.py
index 0df62d5c..38414f68 100644
--- a/skactiveml/stream/__init__.py
+++ b/skactiveml/stream/__init__.py
@@ -6,6 +6,7 @@ stream-based active learning.
 from ._stream_baselines import StreamRandomSampling, PeriodicSampling
 from ._stream_probabilistic_al import StreamProbabilisticAL
 from ._uncertainty_zliobaite import (
+    UncertaintyZliobaite,
     FixedUncertainty,
     VariableUncertainty,
     Split,
@@ -24,6 +25,7 @@ __all__ = [
     "budgetmanager",
     "StreamRandomSampling",
     "PeriodicSampling",
+    "UncertaintyZliobaite",
     "FixedUncertainty",
     "VariableUncertainty",
     "Split",
diff --git a/skactiveml/stream/_density_uncertainty.py b/skactiveml/stream/_density_uncertainty.py
index 90db3b0b..3faf1cf6 100644
--- a/skactiveml/stream/_density_uncertainty.py
+++ b/skactiveml/stream/_density_uncertainty.py
@@ -1106,7 +1106,7 @@ class CognitiveDualQueryStrategyRan(CognitiveDualQueryStrategy):
     See Also
     --------
     .budgetmanager.RandomBudgetManager : The default budget manager.
-    .budgetmanager.CognitiveDualQueryStrategy : The base class for this
+    .CognitiveDualQueryStrategy : The base class for this
         strategy.
 
     References
@@ -1208,7 +1208,7 @@ class CognitiveDualQueryStrategyFixUn(CognitiveDualQueryStrategy):
     See Also
     --------
     .budgetmanager.FixedUncertaintyBudgetManager : The default budget manager
-    .budgetmanager.CognitiveDualQueryStrategy : The base class for this
+    .CognitiveDualQueryStrategy : The base class for this
         strategy.
 
     References
@@ -1322,7 +1322,7 @@ class CognitiveDualQueryStrategyVarUn(CognitiveDualQueryStrategy):
     See Also
     --------
     .budgetmanager.RandomBudgetManager : The default budget manager.
-    .budgetmanager.CognitiveDualQueryStrategy : The base class for this
+    .CognitiveDualQueryStrategy : The base class for this
         strategy.
 
     References
@@ -1423,7 +1423,7 @@ class CognitiveDualQueryStrategyRanVarUn(CognitiveDualQueryStrategy):
     See Also
     --------
     .budgetmanager.RandomBudgetManager : The default budget manager.
-    .budgetmanager.CognitiveDualQueryStrategy : The base class for this
+    .CognitiveDualQueryStrategy : The base class for this
         strategy.
 
     References
diff --git a/skactiveml/stream/_uncertainty_zliobaite.py b/skactiveml/stream/_uncertainty_zliobaite.py
index 168781ab..7b5725c9 100644
--- a/skactiveml/stream/_uncertainty_zliobaite.py
+++ b/skactiveml/stream/_uncertainty_zliobaite.py
@@ -381,7 +381,7 @@ class FixedUncertainty(UncertaintyZliobaite):
     based on the classifier's predictions. The sample is queried when the
     probability of the most likely class exceeds a threshold calculated based
     on the budget and the number of classes. See also
-    :class:`.FixedUncertaintyBudgetManager`
+    :class:`.budgetmanager.FixedUncertaintyBudgetManager`
 
     Parameters
     ----------
@@ -459,7 +459,7 @@ class VariableUncertainty(UncertaintyZliobaite):
     the classifier's predictions. The sample is queried when the probability
     of the most likely class exceeds a time-dependent threshold calculated
     based on the budget, number of observed and acquired samples. See also
-    :class:`.VariableUncertaintyBudgetManager`
+    :class:`.budgetmanager.VariableUncertaintyBudgetManager`
 
     Parameters
     ----------
@@ -512,14 +512,14 @@ class RandomVariableUncertainty(UncertaintyZliobaite):
     time-dependent threshold calculated based on the budget, and the number of
     observed and acquired samples. The threshold is randomized by being
     multiplied with a random number sampled from N(1,delta). See also
-    :class:`.RandomVariableUncertaintyBudgetManager`
+    :class:`.budgetmanager.RandomVariableUncertaintyBudgetManager`
 
     Parameters
     ----------
     budget_manager : BudgetManager, default=None
         The BudgetManager which models the budgeting constraint used in the
         stream-based active learning setting. if set to `None`,
-        `RandomVariableUncertaintyBudgetManager` will be used by default.  The
+        `RandomVariableUncertaintyBudgetManager` will be used by default. The
         budget manager will be initialized based on the following conditions:
 
         - If only a `budget` is given, the default budget manager is
@@ -564,7 +564,8 @@ class Split(UncertaintyZliobaite):
     exceeds a time-dependent threshold calculated based on the budget, number
     of observed and acquired samples. It is a hybrid strategy that combines
     `VariableUncertainty` with randomly sampling samples with a given
-    probability. See also :class:`.SplitBudgetManager`
+    probability. See also
+    :class:`.budgetmanager.SplitBudgetManager`
 
     Parameters
     ----------
diff --git a/skactiveml/stream/budgetmanager/_balanced_incremental_quantile_filter.py b/skactiveml/stream/budgetmanager/_balanced_incremental_quantile_filter.py
index fde2259b..fb9da6dd 100644
--- a/skactiveml/stream/budgetmanager/_balanced_incremental_quantile_filter.py
+++ b/skactiveml/stream/budgetmanager/_balanced_incremental_quantile_filter.py
@@ -12,12 +12,12 @@ class BalancedIncrementalQuantileFilter(BudgetManager):
 
     The Balanced Incremental Quantile Filter has been proposed together with
     Probabilistic Active Learning for Datastreams [1]_. It assesses whether a
-    given spatial utility (i.e., obtained via :class:`ProbabilisticAL`)
-    warrants to query the label in question. The spatial ultilities are
-    compared against a threshold that is derived from a quantile (`budget`) of
-    the last `w` observed utilities. To balance the number of queries, `w_tol`
-    is used to increase or decrease the threshold based on the number of
-    available acquisitions.
+    given spatial utility (i.e., obtained via
+    :class:`skactiveml.pool.ProbabilisticAL`) warrants to query the label in
+    question. The spatial ultilities are compared against a threshold that is
+    derived from a quantile (`budget`) of the last `w` observed utilities. To
+    balance the number of queries, `w_tol` is used to increase or decrease the
+    threshold based on the number of available acquisitions.
 
     Parameters
     ----------
@@ -28,9 +28,9 @@ class BalancedIncrementalQuantileFilter(BudgetManager):
         The window in which the number of acquisitions should stay within the
         budget. `w_tol` should be higher than 0.
     budget : float, default=None
-        Specifies the ratio of samples which are allowed to be sampled, with
-        `0 <= budget <= 1`. If `budget` is `None`, it is replaced with the
-        default budget 0.1.
+        Specifies the ratio of samples which are allowed to be sampled, with `0
+        <= budget <= 1`. If `budget` is `None`, it is replaced with the default
+        budget 0.1.
 
     References
     ----------
diff --git a/skactiveml/utils/_validation.py b/skactiveml/utils/_validation.py
index b8f90ff1..d614ae8e 100644
--- a/skactiveml/utils/_validation.py
+++ b/skactiveml/utils/_validation.py
@@ -765,7 +765,7 @@ def check_budget_manager(
     ----------
     budget : float, default=None
         Specifies the ratio of samples which are allowed to be queried, with
-        0 <= budget <= 1. See Also :class:`BudgetManager`.
+        0 <= budget <= 1. See Also :class:`skactiveml.base.BudgetManager`.
     budget_manager : BudgetManager, default=None
         Budget manager to be checked. If `budget_manager` is `None`, a new
         budget manager using the class `default_budget_manager_class` is
diff --git a/skactiveml/visualization/_feature_space.py b/skactiveml/visualization/_feature_space.py
index 333ef8df..7d969e8b 100644
--- a/skactiveml/visualization/_feature_space.py
+++ b/skactiveml/visualization/_feature_space.py
@@ -54,7 +54,7 @@ def plot_utilities(qs, X, y, candidates=None, **kwargs):
         - If `candidates` is of shape `(n_candidates,)` and of type
           `int`, `candidates` is considered as the indices of the
           samples in `(X,y)`.
-        - If `candidates` is of shape `(n_candidates, *)`, the
+        - If `candidates` is of shape `(n_candidates, ...)`, the
           candidate samples are directly given in `candidates` (not
           necessarily contained in `X`). This is not supported by all
           query strategies.
@@ -115,7 +115,7 @@ def plot_annotator_utilities(qs, X, y, candidates=None, **kwargs):
         - If `candidates` is of shape `(n_candidates,)` and of type
           `int`, `candidates` is considered as the indices of the
           samples in `(X,y)`.
-        - If `candidates` is of shape `(n_candidates, *)`, the
+        - If `candidates` is of shape `(n_candidates, ...)`, the
           candidate samples are directly given in `candidates` (not
           necessarily contained in `X`). This is not supported by all
           query strategies.
@@ -533,7 +533,7 @@ def _general_plot_utilities(qs, X, y, candidates=None, **kwargs):
         - If `candidates` is of shape `(n_candidates,)` and of type
           `int`, `candidates` is considered as the indices of the
           samples in `(X,y)`.
-        - If `candidates` is of shape `(n_candidates, *)`, the
+        - If `candidates` is of shape `(n_candidates, ...)`, the
           candidate samples are directly given in `candidates` (not
           necessarily contained in `X`). This is not supported by all
           query strategies.
diff --git a/tutorials/01_deep_pool_al_with_skorch.ipynb b/tutorials/01_deep_pool_al_with_skorch.ipynb
index 92661d65..5f8b2f5d 100644
--- a/tutorials/01_deep_pool_al_with_skorch.ipynb
+++ b/tutorials/01_deep_pool_al_with_skorch.ipynb
@@ -182,7 +182,7 @@
     "* adding noise,\n",
     "* random erasing.\n",
     "\n",
-    "For more information on data augmentation take a look at the [Data Augmentation Tutorial](https://www.tensorflow.org/tutorials/images/data_augmentation) of [TensorFlow](https://www.tensorflow.org) or visit [Transforming And Augmenting Images](https://pytorch.org/vision/stable/transforms.html) on [PyTorch](pytorch.org)."
+    "For more information on data augmentation take a look at the [Data Augmentation Tutorial](https://www.tensorflow.org/tutorials/images/data_augmentation) of [TensorFlow](https://www.tensorflow.org) or visit [Transforming And Augmenting Images](https://pytorch.org/vision/stable/transforms.html) on [PyTorch](https://pytorch.org)."
    ]
   },
   {
diff --git a/tutorials/03_pool_oracle_annotations.ipynb b/tutorials/03_pool_oracle_annotations.ipynb
index 2c7ac3df..03e68845 100644
--- a/tutorials/03_pool_oracle_annotations.ipynb
+++ b/tutorials/03_pool_oracle_annotations.ipynb
@@ -122,7 +122,7 @@
     "\n",
     "    Parameters\n",
     "    ----------\n",
-    "    X : array-like of shape (n_samples, *)\n",
+    "    X : array-like of shape (n_samples, ...)\n",
     "        Training data set, usually complete, i.e. including the labeled and\n",
     "        unlabeled samples.\n",
     "    y : array-like of shape (n_samples,)\n",


Test patch:
diff --git a/skactiveml/classifier/multiannotator/tests/test_annotator_ensemble_classifier.py b/skactiveml/classifier/multiannotator/tests/test_annotator_ensemble_classifier.py
index d35cd07d..0e581ccd 100644
--- a/skactiveml/classifier/multiannotator/tests/test_annotator_ensemble_classifier.py
+++ b/skactiveml/classifier/multiannotator/tests/test_annotator_ensemble_classifier.py
@@ -23,6 +23,8 @@ class TestAnnotatorEnsembleClassifier(unittest.TestCase):
         self.assertRaises(ValueError, clf.fit, X=self.X, y=self.y)
         clf = AnnotatorEnsembleClassifier(estimators=None)
         self.assertRaises(TypeError, clf.fit, X=self.X, y=self.y)
+        clf = AnnotatorEnsembleClassifier(estimators=[])
+        self.assertRaises(ValueError, clf.fit, X=self.X, y=self.y)
         clf = AnnotatorEnsembleClassifier(estimators=[("GNB", GaussianNB())])
         self.assertRaises(TypeError, clf.fit, X=self.X, y=self.y)
         clf = AnnotatorEnsembleClassifier(
diff --git a/skactiveml/regressor/tests/test_wrapper.py b/skactiveml/regressor/tests/test_wrapper.py
index 87c6ce1e..6778e0d5 100644
--- a/skactiveml/regressor/tests/test_wrapper.py
+++ b/skactiveml/regressor/tests/test_wrapper.py
@@ -73,13 +73,7 @@ class TestWrapper(TemplateSkactivemlRegressor, unittest.TestCase):
 
         X = np.array([[0], [1], [2], [3], [4]])
         y = np.array([3, 4, 1, 2, 1])
-
-        reg_2 = clone(reg_1)
         sample_weight = np.arange(1, len(y) + 1)
-        self.assertRaises(
-            TypeError, reg_1.fit, X, y, sample_weight=sample_weight
-        )
-        reg_2.fit(X, y)
 
         reg_1 = SklearnRegressor(estimator=LinearRegression())
         reg_2 = clone(reg_1)
diff --git a/skactiveml/tests/test_examples.py b/skactiveml/tests/test_examples.py
index 170571d5..168decb8 100644
--- a/skactiveml/tests/test_examples.py
+++ b/skactiveml/tests/test_examples.py
@@ -8,9 +8,12 @@ from os import path
 from docs.generate import generate_examples
 from skactiveml import pool, stream
 
-from skactiveml.stream import CognitiveDualQueryStrategy
+from skactiveml.pool import ExpectedErrorReduction
+from skactiveml.stream import UncertaintyZliobaite, CognitiveDualQueryStrategy
 
 QUERY_STRATEGY_EXCEPTIONS_LIST = [
+    ExpectedErrorReduction,
+    UncertaintyZliobaite,
     CognitiveDualQueryStrategy,
 ]