Repository: alfaro96/scikit-lr (language: python)

Issue:
[MNT] Improve coding and testing for the modules
#### Description

Even if the code has been carefully implemented and tested, it still needs to be reviewed since it is not aesthetic as desired. Therefore, it would be interesting to handle this issue in order the fulfil the desired criteria.

Also, it is necessary to review the remaining work (workflows, coverage, etc.).

* [x] `sklr._build_utils`
* [ ] `sklr._types`
* [x] `sklr.base`
* [ ] `sklr.consensus`
* [x] `sklr.datasets`
* [ ] `sklr.dummy`
* [x] `sklr.exceptions`
* [ ] `sklr.metrics`
* [x] `sklr.neighbors`
* [ ] `sklr.tree`
* [ ] `sklr.utils`


Discussion / hints:

Fix patch:
diff --git a/sklr/__init__.py b/sklr/__init__.py
index bc70d6b..4678c08 100755
--- a/sklr/__init__.py
+++ b/sklr/__init__.py
@@ -44,9 +44,7 @@
     "consensus",
     "datasets",
     "dummy",
-    "ensemble",
     "metrics",
-    "miss",
     "neighbors",
     "tree",
     "utils"
diff --git a/sklr/ensemble/__init__.py b/sklr/ensemble/__init__.py
deleted file mode 100644
index 9ad0b44..0000000
--- a/sklr/ensemble/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""
-The :mod:`sklr.ensemble` module includes ensemble-based methods for
-Label Ranking and Partial Label Ranking problems.
-"""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Local application
-from ._base import BaseEnsemble
-from ._bagging import BaggingLabelRanker, BaggingPartialLabelRanker
-from ._forest import RandomForestLabelRanker, RandomForestPartialLabelRanker
-
-
-# =============================================================================
-# Public objects
-# =============================================================================
-
-# Set the classes that are accessible
-# from the module sklr.ensemble
-__all__ = [
-    "BaggingLabelRanker", "BaggingPartialLabelRanker",
-    "BaseEnsemble",
-    "RandomForestLabelRanker", "RandomForestPartialLabelRanker"
-]
diff --git a/sklr/ensemble/_bagging.py b/sklr/ensemble/_bagging.py
deleted file mode 100644
index f81cc1c..0000000
--- a/sklr/ensemble/_bagging.py
+++ /dev/null
@@ -1,567 +0,0 @@
-"""Bagging meta-estimator."""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Standard
-from abc import ABC, abstractmethod
-from numbers import Real, Integral
-
-# Third party
-import numpy as np
-from sklearn.utils.validation import check_is_fitted
-
-# Local application
-from ._base import BaseEnsemble
-from ._base import _indexes_to_mask
-from ._base import MAX_RAND_SEED
-from ..base import LabelRankerMixin, PartialLabelRankerMixin
-from ..tree import DecisionTreeLabelRanker, DecisionTreePartialLabelRanker
-from ..utils.validation import check_random_state, has_fit_parameter
-
-
-# =============================================================================
-# Methods
-# =============================================================================
-
-def _generate_indexes(random_state, bootstrap, n_population, n_samples):
-    """Draw randomly sampled indexes."""
-    return random_state.choice(n_population, n_samples, bootstrap)
-
-
-def _generate_bagging_indexes(random_state, bootstrap_features,
-                              bootstrap_samples, n_features, n_samples,
-                              max_features, max_samples):
-    """Randomly draw feature and sample indexes."""
-    # Obtain the random state
-    random_state = check_random_state(random_state)
-
-    # Draw random features
-    feature_indexes = _generate_indexes(
-        random_state, bootstrap_features,
-        n_features, max_features)
-
-    # Draw random samples
-    sample_indexes = _generate_indexes(
-        random_state, bootstrap_samples,
-        n_samples, max_samples)
-
-    # Return the randomly
-    # drawn features and samples
-    return (feature_indexes, sample_indexes)
-
-
-def _build_estimators(n_estimators, ensemble, X, Y, sample_weight, seeds):
-    """Private function used to build estimators."""
-    # Initialize some values from the input arrays
-    (n_samples, n_features) = (
-        X.shape)
-    (max_samples, max_features) = (
-        ensemble._max_samples, ensemble._max_features)
-    (bootstrap, bootstrap_features) = (
-        ensemble.bootstrap, ensemble.bootstrap_features)
-
-    # Check whether the base estimator of
-    # the ensemble support sample weighting,
-    # raising the proper error when they
-    # are provided and the base estimator
-    # does not supports it
-    support_sample_weight = has_fit_parameter(
-        ensemble.base_estimator_, "sample_weight")
-    if not support_sample_weight and sample_weight is not None:
-        raise ValueError("The base estimator does not support sample weight.")
-
-    # Initialize the estimators and the
-    # features used for building the estimators
-    estimators = []
-    estimators_features = []
-
-    # Build the list of
-    # estimators for the ensemble
-    for i in range(n_estimators):
-        # Make an estimator, properly
-        # seeding for reproducible experiments
-        estimator = ensemble._make_estimator(
-            append=False, random_state=seeds[i])
-        # Draw random features and samples
-        (features, indexes) = _generate_bagging_indexes(
-            seeds[i],
-            bootstrap_features, bootstrap,
-            n_features, n_samples,
-            max_features, max_samples)
-        # If the base estimator supports sample weights,
-        # then, obtain the sample weights using the
-        # bootstrap indexes and then fit
-        if support_sample_weight:
-            # Check the sample weights, initializing them to an
-            # uniform distribution if they are not provided and,
-            # if provided, copying them to properly weight the
-            # samples according to the bootstrap indexes
-            if sample_weight is None:
-                curr_sample_weight = np.ones(n_samples, dtype=np.float64)
-            else:
-                curr_sample_weight = np.array(sample_weight, dtype=np.float64)
-            # Obtain the sample weights according to the bootstrap
-            # indexes. If the samples are obtained with replacement,
-            # then, weight the sample weights by the number of times
-            # that each sample appears on the indexes. Otherwise,
-            # mantain the sample weight but setting to zero the weight
-            # of the samples that have not been obtained in the indexes
-            if bootstrap:
-                sample_counts = np.bincount(indexes, minlength=n_samples)
-                curr_sample_weight *= sample_counts
-            else:
-                not_indexes_mask = ~_indexes_to_mask(indexes, n_samples)
-                curr_sample_weight[not_indexes_mask] = 0
-            # Fit the estimator using the sample weight
-            estimator.fit(X[:, features], Y, curr_sample_weight)
-        # Otherwise, directly fit the
-        # estimator using the indexes
-        else:
-            estimator.fit(X[indexes][:, features], Y[indexes])
-        # Append the built estimator to the list
-        # of estimators and the features employed
-        # to build the estimator to the list of features
-        estimators.append(estimator)
-        estimators_features.append(features)
-
-    # Return the built estimators
-    # and estimators features
-    return (estimators, estimators_features)
-
-
-def _predict_estimators(ensemble, X):
-    """Private function used to compute the predictions."""
-    # Obtain the prediction of each estimator over all the samples
-    predictions = np.array([
-        estimator.predict(X[:, features]) for (estimator, features)
-        in zip(ensemble.estimators_, ensemble.estimators_features_)])
-
-    # Aggregate the predictions of the estimators on each sample
-    predictions = np.array([
-        ensemble._rank_algorithm.aggregate(predictions[:, sample])
-        for sample in range(X.shape[0])])
-
-    # Return the predictions
-    return predictions
-
-
-# =============================================================================
-# Classes
-# =============================================================================
-
-# =============================================================================
-# Base Bagging
-# =============================================================================
-class BaseBagging(BaseEnsemble, ABC):
-    """Base class for Bagging meta-estimator.
-
-    Warning: This class should not be used directly. Use derived classes
-    instead.
-    """
-
-    @abstractmethod
-    def __init__(self,
-                 base_estimator=None,
-                 n_estimators=10,
-                 max_samples=1.0,
-                 max_features=1.0,
-                 bootstrap=True,
-                 bootstrap_features=False,
-                 random_state=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator=base_estimator,
-                         n_estimators=n_estimators)
-
-        # Initialize the hyperparameters
-        self.max_samples = max_samples
-        self.max_features = max_features
-        self.bootstrap = bootstrap
-        self.bootstrap_features = bootstrap_features
-        self.random_state = random_state
-
-    def _fit(self, X, Y, max_samples=None, sample_weight=None):
-        """Build a Bagging ensemble of estimators from the training set."""
-        # Check the training data, the training rankings
-        # and, if provided, the sample weights. In fact,
-        # they are not stored (only checked) since they
-        # will be modified when fitting the estimators
-        (X, Y) = self._validate_data(X, Y, multi_output=True)
-
-        (n_samples, _) = X.shape
-
-        # Obtain the random state
-        random_state = check_random_state(self.random_state)
-
-        # Check the estimator
-        self._validate_estimator()
-
-        # Check that the maximum number of samples
-        # is an integer type or floating type
-        # greater than zero and less than or equal
-        # the number of samples on the training dataset
-        if isinstance(max_samples, (Integral, np.integer)):
-            self._max_samples = max_samples
-        elif isinstance(max_samples, (Real, np.floating)):
-            self._max_samples = int(max_samples * n_samples)
-        else:
-            raise TypeError("max_samples must be int or float.")
-        if self._max_samples <= 0 or self._max_samples > n_samples:
-            raise ValueError("max_samples must be in (0, n_samples].")
-        self._max_samples = max(1, int(self._max_samples))
-
-        # Check that the maximum number of features
-        # is an integer type or floating type
-        # greater than zero and less than or equal
-        # the number of features on the training dataset
-        if isinstance(self.max_features, (Integral, np.integer)):
-            self._max_features = self.max_features
-        elif isinstance(self.max_features, (Real, np.floating)):
-            self._max_features = self.max_features * self.n_features_in_
-        else:
-            raise TypeError("max_features must be int or float.")
-        if self._max_features <= 0 or self._max_features > self.n_features_in_:
-            raise ValueError("max_features must be in (0, n_features].")
-        self._max_features = max(1, int(self._max_features))
-
-        # Obtain the seeds to ensure that
-        # the experiments are reproducible
-        self._seeds = random_state.randint(
-            MAX_RAND_SEED, size=self.n_estimators)
-
-        # Build the estimators, also obtaining the features used to fit
-        # the estimators (since they are needed in the predict method)
-        (self.estimators_, self.estimators_features_) = _build_estimators(
-            self.n_estimators, self, X, Y, sample_weight, self._seeds)
-
-        # Return the built Bagging ensemble
-        return self
-
-    def fit(self, X, Y, sample_weight=None):
-        """Build a Bagging ensemble of estimators from the training
-        set (X, Y).
-
-        Parameters
-        ----------
-        X : np.ndarray of shape (n_samples, n_features)
-            The training input samples.
-
-        Y : np.ndarray of shape (n_samples, n_classes)
-            The target rankings.
-
-        sample_weight : {None, np.ndarray} of shape (n_samples,),
-                optional (default=None)
-            Sample weights. If None, then samples are equally weighted.
-            Note that this is supported only if the base estimator supports
-            sample weighting.
-
-        Returns
-        -------
-        self : object
-        """
-        return self._fit(X, Y, self.max_samples, sample_weight=sample_weight)
-
-    def predict(self, X):
-        """Predict rankings for X.
-
-        The predicted ranking of an input sample is the aggregation
-        of the predicted rankings for each estimator of the ensemble.
-
-        Parameters
-        ----------
-        X : np.ndarray of shape (n_samples, n_features)
-            The training input samples.
-
-        Returns
-        -------
-        Y : np.ndarray of shape (n_samples, n_classes)
-            The predicted rankings.
-        """
-        check_is_fitted(self)
-
-        # Check the test data
-        X = self._validate_data(X, reset=False)
-
-        # Obtain the predictions (using the private
-        # method for future parallelism purposes)
-        predictions = _predict_estimators(self, X)
-
-        # Return the predictions
-        return predictions
-
-
-# =============================================================================
-# Bagging Label Ranker
-# =============================================================================
-class BaggingLabelRanker(LabelRankerMixin, BaseBagging):
-    """A Bagging Label Ranker.
-
-    A Bagging Label Ranker is an ensemble meta-estimator that fits base
-    Label Rankers each on random subsets of the original dataset and then
-    aggregate their individual predictions to form a final prediction.
-    Such a meta-estimator can typically be used as a way to reduce the
-    variance of a black-box estimator (e.g., a decision tree), by introducing
-    randomization into its construction procedure and then making an
-    ensemble out of it.
-
-    This algorithm encompasses several works from the literature. When random
-    subsets of the dataset are drawn as random subsets of the samples, then
-    this algorithm is known as Pasting [1]_. If samples are drawn with
-    replacement, then the method is known as Bagging [2]_. When random subsets
-    of the dataset are drawn as random subsets of the features, then the method
-    is known as Random Subspaces [3]_. Finally, when base estimators are built
-    on subsets of both samples and features, then the method is known as
-    Random Patches [4]_.
-
-    Hyperparamters
-    --------------
-    base_estimator : {object, None}, optional (default=None)
-        The base estimator to fit on random subsets of the dataset.
-        If None, then the base estimator is a decision tree.
-
-    n_estimators : int, optional (default=10)
-        The number of base estimators in the ensemble.
-
-    max_samples : {int, float}, optional (default=1.0)
-        The number of samples to draw from X to train each base estimator.
-
-            - If int, then draw `max_samples` samples.
-            - If float, then draw `max_samples * X.shape[0]` samples.
-
-    max_features : {int, float}, optional (default=1.0)
-        The number of features to draw from X to train each base estimator.
-
-        - If int, then draw `max_features` features.
-        - If float, then draw `max_features * X.shape[1]` features.
-
-    bootstrap : bool, optional (default=True)
-        Whether samples are drawn with replacement. If False, sampling
-        without replacement is performed.
-
-    bootstrap_features : bool, optional (default=False)
-        Whether features are drawn with replacement.
-
-    random_state : {int, RandomState instance, None}, optional (default=None)
-        - If int, random_state is the seed used by the random number generator.
-        - If RandomState instance, random_state is the random number generator.
-        - If None, the random number generator is the RandomState instance used
-          by `np.random`.
-
-    Attributes
-    ----------
-    base_estimator_ : estimator
-        The base estimator from which the ensemble is grown.
-
-    n_samples_ : int
-        The number of samples when ``fit`` is performed.
-
-    n_features_ : int
-        The number of features when ``fit`` is performed.
-
-    n_classes_ : int
-        The number of classes when ``fit`` is performed.
-
-    estimators_ : list of estimators
-        The collection of fitted base estimators.
-
-    estimators_features_ : list of np.ndarrays
-        The subset of drawn features for each base estimator.
-
-    See also
-    --------
-    BaggingPartialLabelRanker
-
-    References
-    ----------
-    .. [1] `L. Breiman, "Pasting small votes for classification in large
-            databases and on-line", Machine Learning, vol. 36, pp. 85-103,
-            1999.`_
-
-    .. [2] `L. Breiman, "Bagging predictors", Machine Learning, vol. 24,
-            pp. 123-140, 1996.`_
-
-    .. [3] `T. Ho, "The random subspace method for constructing decision
-            forests", Pattern Analysis and Machine Intelligence, vol. 20,
-            pp. 832-844, 1998.`_
-
-    .. [4] `G. Louppe and P. Geurts, "Ensembles on Random Patches", In
-            Proceedings of teh Joint European Conference on Machine Learning
-            and Knowledge Discovery in Databases, 2012, pp. 346-361.`_
-
-    .. [5] `Juan A. Aledo, José A. Gámez and D. Molina, "Tackling the
-            supervised label ranking problem by bagging weak learners",
-            Information Fusion, vol. 35, pp. 38-50, 2017.`_
-
-    Examples
-    --------
-    >>> import numpy as np
-    >>> from sklr.ensemble import BaggingLabelRanker
-    >>> X = np.array([[0, 1, 0], [1, 0, 1], [1, 1, 0], [0, 1, 0]])
-    >>> Y = np.array([[1, 2, 3], [2, 1, 3], [1, 2, 3], [3, 1, 2]])
-    >>> model = BaggingLabelRanker(random_state=0)
-    >>> clf = model.fit(X, Y)
-    >>> clf.predict(np.array([[0, 1, 0]]))
-    np.array([[2, 1, 3]])
-    """
-
-    def __init__(self,
-                 base_estimator=None,
-                 n_estimators=10,
-                 max_samples=1.0,
-                 max_features=1.0,
-                 bootstrap=True,
-                 bootstrap_features=False,
-                 random_state=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator,
-                         n_estimators=n_estimators,
-                         max_samples=max_samples,
-                         max_features=max_features,
-                         bootstrap=bootstrap,
-                         bootstrap_features=bootstrap_features,
-                         random_state=random_state)
-
-    def _validate_estimator(self):
-        """Check the estimator and set the base_estimator_ attribute."""
-        # Call to the method of the parent with a Decision
-        # Tree Label Ranking as default estimator
-        super()._validate_estimator(default=DecisionTreeLabelRanker())
-
-
-# =============================================================================
-# Bagging Partial Label Ranker
-# =============================================================================
-class BaggingPartialLabelRanker(PartialLabelRankerMixin, BaseBagging):
-    """A Bagging Partial Label Ranker.
-
-    A Bagging Partial Label Ranker is an ensemble meta-estimator that fits base
-    Partial Label Rankers each on random subsets of the original dataset and
-    then aggregate their individual predictions to form a final prediction.
-    Such a meta-estimator can typically be used as a way to reduce the variance
-    of a black-box estimator (e.g., a decision tree), by introducing
-    randomization into its construction procedure and then making an ensemble
-    out of it.
-
-    This algorithm encompasses several works from the literature. When random
-    subsets of the dataset are drawn as random subsets of the samples, then
-    this algorithm is known as Pasting [1]_. If samples are drawn with
-    replacement, then the method is known as Bagging [2]_. When random subsets
-    of the dataset are drawn as random subsets of the features, then the method
-    is known as Random Subspaces [3]_. Finally, when base estimators are built
-    on subsets of both samples and features, then the method is known as
-    Random Patches [4]_.
-
-    Hyperparamters
-    --------------
-    base_estimator : {object, None}, optional (default=None)
-        The base estimator to fit on random subsets of the dataset.
-        If None, then the base estimator is a decision tree.
-
-    n_estimators : int, optional (default=10)
-        The number of base estimators in the ensemble.
-
-    max_samples : {int, float}, optional (default=1.0)
-        The number of samples to draw from X to train each base estimator.
-
-            - If int, then draw `max_samples` samples.
-            - If float, then draw `max_samples * X.shape[0]` samples.
-
-    max_features : {int, float}, optional (default=1.0)
-        The number of features to draw from X to train each base estimator.
-
-        - If int, then draw `max_features` features.
-        - If float, then draw `max_features * X.shape[1]` features.
-
-    bootstrap : bool, optional (default=True)
-        Whether samples are drawn with replacement. If False, sampling
-        without replacement is performed.
-
-    bootstrap_features : bool, optional (default=False)
-        Whether features are drawn with replacement.
-
-    random_state : {int, RandomState instance, None}, optional (default=None)
-        - If int, random_state is the seed used by the random number generator.
-        - If RandomState instance, random_state is the random number generator.
-        - If None, the random number generator is the RandomState instance used
-          by `np.random`.
-
-    Attributes
-    ----------
-    base_estimator_ : estimator
-        The base estimator from which the ensemble is grown.
-
-    n_samples_ : int
-        The number of samples when ``fit`` is performed.
-
-    n_features_ : int
-        The number of features when ``fit`` is performed.
-
-    n_classes_ : int
-        The number of classes when ``fit`` is performed.
-
-    estimators_ : list of estimators
-        The collection of fitted base estimators.
-
-    estimators_features_ : list of np.ndarrays
-        The subset of drawn features for each base estimator.
-
-    See also
-    --------
-    BaggingLabelRanker
-
-    References
-    ----------
-    .. [1] `L. Breiman, "Pasting small votes for classification in large
-            databases and on-line", Machine Learning, vol. 36, pp. 85-103,
-            1999.`_
-
-    .. [2] `L. Breiman, "Bagging predictors", Machine Learning, vol. 24,
-            pp. 123-140, 1996.`_
-
-    .. [3] `T. Ho, "The random subspace method for constructing decision
-            forests", Pattern Analysis and Machine Intelligence, vol. 20,
-            pp. 832-844, 1998.`_
-
-    .. [4] `G. Louppe and P. Geurts, "Ensembles on Random Patches", In
-            Proceedings of teh Joint European Conference on Machine Learning
-            and Knowledge Discovery in Databases, 2012, pp. 346-361.`_
-
-    Examples
-    --------
-    >>> import numpy as np
-    >>> from sklr.ensemble import BaggingPartialLabelRanker
-    >>> X = np.array([[0, 1, 0], [1, 0, 1], [1, 1, 0], [0, 1, 0]])
-    >>> Y = np.array([[1, 2, 3], [2, 1, 3], [1, 2, 3], [3, 1, 2]])
-    >>> model = BaggingPartialLabelRanker(random_state=0)
-    >>> clf = model.fit(X, Y)
-    >>> clf.predict(np.array([[0, 1, 0]]))
-    np.array([[1, 1, 2]])
-    """
-
-    def __init__(self,
-                 base_estimator=None,
-                 n_estimators=10,
-                 max_samples=1.0,
-                 max_features=1.0,
-                 bootstrap=True,
-                 bootstrap_features=False,
-                 random_state=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator,
-                         n_estimators=n_estimators,
-                         max_samples=max_samples,
-                         max_features=max_features,
-                         bootstrap=bootstrap,
-                         bootstrap_features=bootstrap_features,
-                         random_state=random_state)
-
-    def _validate_estimator(self):
-        """Check the estimator and set the base_estimator_ attribute."""
-        # Call to the method of the parent with a Decision
-        # Tree Partial Label Ranking as base estimator
-        super()._validate_estimator(default=DecisionTreePartialLabelRanker())
diff --git a/sklr/ensemble/_base.py b/sklr/ensemble/_base.py
deleted file mode 100644
index 4d79da1..0000000
--- a/sklr/ensemble/_base.py
+++ /dev/null
@@ -1,180 +0,0 @@
-"""Base class for ensemble-based estimators."""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Standard
-from abc import ABCMeta, abstractmethod
-from copy import deepcopy
-from numbers import Integral
-
-# Third party
-import numpy as np
-from sklearn.base import BaseEstimator, MetaEstimatorMixin
-
-# Local application
-from ..utils.validation import check_random_state
-
-
-# =============================================================================
-# Constants
-# =============================================================================
-
-# Maximum random number that
-# can be used for seeding
-MAX_RAND_SEED = np.iinfo(np.int32).max
-
-
-# =============================================================================
-# Methods
-# =============================================================================
-
-def _set_random_states(estimator, random_state=None):
-    """Sets fixed random_state parameters for an estimator.
-
-    Find all parameters ending ``random_state`` and sets them to integers
-    derived from ``random_state``.
-
-    Notes
-    -----
-    This does not necessarily set *all* ``random_state`` attributes that
-    control an estimator's randomness, only those accessible through
-    ``estimator.get_params()``.
-    """
-    # Obtain the random state
-    random_state = check_random_state(random_state)
-
-    # Initialize the dictionary where the
-    # random states are going to be put on.
-    # In case that the estimator does not
-    # needed random state, this dictionary
-    # will be empty and the process of
-    # setting the seed skipped
-    to_set = {}
-
-    # Introduce the random state for
-    # this estimator in the dictionary
-    # (only if it is supported)
-    for key in sorted(estimator.get_params(deep=True)):
-        if key == "random_state" or key.endswith("__random_state"):
-            to_set[key] = random_state.randint(MAX_RAND_SEED)
-
-    # Set the random state
-    # in the estimator
-    if to_set:
-        estimator.set_params(**to_set)
-
-
-def _indexes_to_mask(indexes, mask_length):
-    """Convert list of indices to boolean mask."""
-    # Initialize the mask of boolean values
-    mask = np.zeros(mask_length, dtype=np.bool)
-
-    # Set "True" in the given indexes
-    mask[indexes] = True
-
-    # Return the built mask
-    return mask
-
-
-# =============================================================================
-# Classes
-# =============================================================================
-
-# =============================================================================
-# Base ensemble
-# =============================================================================
-class BaseEnsemble(MetaEstimatorMixin, BaseEstimator, metaclass=ABCMeta):
-    """Base class for all ensemble classes.
-
-    Warning: This class should not be used directly. Use derived classes
-    instead.
-
-    Hyperparameters
-    ---------------
-    base_estimator : {None, object}, optional (default=None)
-        The base estimator from which the ensemble is built.
-
-    n_estimators : int
-        The number of estimators in the ensemble.
-
-    estimator_hyperparams : list of str
-        The list of attributes to use as hyperparameters when instantiating a
-        new base estimator.
-        If none are given, default hyperparameters are used.
-
-    Attributes
-    ----------
-    base_estimator_ : BaseEstimator
-        The base estimator from which the ensemble is grown.
-
-    estimators_ : list of BaseEstimator
-        The collection of fitted base estimators.
-    """
-
-    # Overwrite _required_parameters from
-    # MetaEstimatorMixin to an empty list,
-    # since the "estimator" object is not required
-    _required_parameters = []
-
-    @abstractmethod
-    def __init__(self, base_estimator, n_estimators=10,
-                 estimator_hyperparams=tuple()):
-        """Constructor."""
-        # Initialize the hyperparameters
-        self.base_estimator = base_estimator
-        self.n_estimators = n_estimators
-        self.estimator_hyperparams = estimator_hyperparams
-
-    def _validate_estimator(self, default=None):
-        """Check the estimator and the n_estimator hyperparameter,
-        set the `base_estimator_` attribute."""
-        # Check that the number of estimators
-        # is an integer type greater than zero
-        if not isinstance(self.n_estimators, (Integral, np.integer)):
-            raise TypeError("n_estimators must be an integer. "
-                            "Got {}."
-                            .format(type(self.n_estimators)))
-        if self.n_estimators <= 0:
-            raise ValueError("n_estimators must be greater than zero. "
-                             "Got {}."
-                             .format(self.n_estimators))
-
-        # Check the base estimator, initializing to
-        # the default one when it is not provided
-        if self.base_estimator is not None:
-            self.base_estimator_ = self.base_estimator
-        else:
-            self.base_estimator_ = default
-
-    def _make_estimator(self, append=True, random_state=None):
-        """Make and configure a copy of the `base_estimator_` attribute.
-
-        Warning: This method should be used to properly instantiate new
-        sub-estimators.
-        """
-        # Copy the base estimator to avoid
-        # that the original one is modified
-        estimator = deepcopy(self.base_estimator_)
-
-        # Set the required hyperparameters
-        # in the built estimator
-        estimator.set_params(**{
-                p: getattr(self, p) for p in self.estimator_hyperparams
-            })
-
-        # Set the random state in the estimator.
-        # In fact, if the seed is not provided,
-        # then, setting the random state is not necessary
-        if random_state is not None:
-            _set_random_states(estimator, random_state)
-
-        # Include the estimator in the
-        # list of estimators (if specified)
-        if append:
-            self.estimators_.append(estimator)
-
-        # Return the made estimator
-        return estimator
diff --git a/sklr/ensemble/_forest.py b/sklr/ensemble/_forest.py
deleted file mode 100644
index 40a559d..0000000
--- a/sklr/ensemble/_forest.py
+++ /dev/null
@@ -1,639 +0,0 @@
-"""Forest of trees-based ensemble methods.
-
-Those methods include random forests.
-
-The module structure is the following:
-
-- The ``BaseForest`` base class implements a common ``fit`` method for all
-  the estimators in the module. The ``fit`` method of the base ``Forest``
-  class calls the ``fit`` method of each sub-estimator on random samples
-  (with replacement, a.k.a. bootstrap) of the training set.
-
-  The init of the sub-estimator is further delegated to the
-  ``BaseEnsemble`` constructor.
-
-- The ``ForestLabelRanker`` and ``ForestPartialLabelRanker`` base classes
-  further implement the prediction logic by aggregating the predicted
-  outcomes of the sub-estimators.
-
-- The ``RandomForestLabelRanker`` and ``RandomForestPartialLabelRanker``
-  derived classes provide the user with concrete implementations of
-  the forest ensemble method using classical, deterministic
-  ``DecisionTreeLabelRanker`` and ``DecisionTreePartialLabelRanker`` as
-  sub-estimator implementations.
-
-Label Ranking and Partial Label Ranking problems are both handled.
-"""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Standard
-from abc import ABC, abstractmethod
-from numbers import Integral, Real
-
-# Third party
-import numpy as np
-from sklearn.utils.validation import check_is_fitted
-
-# Local application
-from ._base import BaseEnsemble
-from ._base import MAX_RAND_SEED
-from ..base import LabelRankerMixin, PartialLabelRankerMixin
-from ..tree import DecisionTreeLabelRanker, DecisionTreePartialLabelRanker
-from ..utils.validation import check_random_state
-
-
-# =============================================================================
-# Methods
-# =============================================================================
-
-def _get_n_samples_bootstrap(n_samples, max_samples):
-    """Get the number of samples in a bootstrap sample."""
-    # Check that the number of samples is an integer type
-    # greater than or equal one and less than or equal
-    # the number of samples. Also, it is possible to
-    # provide a floating value greater than zero and
-    # less than one. If None, then, use the number
-    # of samples of the training dataset
-    if max_samples is None:
-        return n_samples
-    elif isinstance(max_samples, (Integral, np.integer)):
-        if max_samples < 1 or max_samples > n_samples:
-            raise ValueError("`max_samples` must be in range "
-                             "[1, {}]. Got {}."
-                             .format(n_samples, max_samples))
-        return max_samples
-    elif isinstance(max_samples, (Real, np.floating)):
-        if max_samples <= 0 or max_samples >= 1:
-            raise ValueError("`max_samples` must be in range "
-                             "(0, 1). Got {}."
-                             .format(max_samples))
-        return int(round(n_samples * max_samples))
-    else:
-        raise TypeError("`max_samples` should be int or float. "
-                        "Got '{}'."
-                        .format(type(max_samples).__name__))
-
-
-def _generate_sample_indexes(random_state, n_samples, n_samples_bootstrap):
-    """Private function used to _build_trees function."""
-    # Obtain the random state
-    random_state = check_random_state(random_state)
-
-    # Obtain the indexes for the samples taking
-    # into account the total number of samples
-    # and the number of samples to be taken
-    sample_indexes = random_state.randint(0, n_samples, n_samples_bootstrap)
-
-    # Return them
-    return sample_indexes
-
-
-def _build_trees(tree, forest, X, Y, sample_weight, tree_idx, n_trees,
-                 n_samples_bootstrap=None):
-    """Private function used to fit a single tree."""
-    # Initialize the number of samples input data
-    n_samples = X.shape[0]
-
-    # If the samples are drawn with replacement, then,
-    # weight the sample weights by the number of times
-    # that each sample appears on the indexes
-    if forest.bootstrap:
-        # Check the sample weights, initializing them to an
-        # uniform distribution if they are not provided and,
-        # if provided, copying them to properly weight the
-        # samples according to the bootstrap indexes
-        if sample_weight is None:
-            curr_sample_weight = np.ones(n_samples, dtype=np.float64)
-        else:
-            curr_sample_weight = np.array(sample_weight, dtype=np.float64)
-        # Obtain the sample weights
-        # from to the bootstrap indexes
-        indexes = _generate_sample_indexes(tree.random_state, n_samples,
-                                           n_samples_bootstrap)
-        sample_counts = np.bincount(indexes, minlength=n_samples)
-        curr_sample_weight *= sample_counts
-        # Fit the estimator using the sample weight
-        # obtained from the bootstrap indexes
-        tree.fit(X, Y, curr_sample_weight)
-    # Otherwise, directly use the sample
-    # weight provided in the fit method
-    else:
-        tree.fit(X, Y, sample_weight)
-
-    # Return the built tree
-    return tree
-
-
-# =============================================================================
-# Classes
-# =============================================================================
-
-# =============================================================================
-# Base Forest
-# =============================================================================
-class BaseForest(BaseEnsemble, ABC):
-    """Base class for forests of trees.
-
-    Warning: This class should not be used directly. Use derived classes
-    instead.
-    """
-
-    @abstractmethod
-    def __init__(self,
-                 base_estimator,
-                 n_estimators=100,
-                 estimator_hyperparams=tuple(),
-                 bootstrap=False,
-                 random_state=None,
-                 max_samples=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator=base_estimator,
-                         n_estimators=n_estimators,
-                         estimator_hyperparams=estimator_hyperparams)
-
-        # Initialize the hyperparameters
-        self.bootstrap = bootstrap
-        self.random_state = random_state
-        self.max_samples = max_samples
-
-    def fit(self, X, Y, sample_weight=None):
-        """Build a forest of trees from the training set (X, Y).
-
-        Parameters
-        ----------
-        X : np.ndarray of shape (n_samples, n_features)
-            The training input samples.
-
-        Y : np.ndarray of shape (n_samples, n_classes)
-            The target rankings.
-
-        sample_weight : np.ndarray of shape (n_samples,),
-                optional, (default=None)
-            Sample weights. If None, then samples are equally weighted. Splits
-            that would create child nodes with net zero or negative weight are
-            ignored while searching for a split in each node.
-
-        Returns
-        -------
-        self : object
-        """
-        # Check the training data and rankings
-        (X, Y) = self._validate_data(X, Y, multi_output=True)
-
-        # Obtain the random state
-        random_state = check_random_state(self.random_state)
-
-        # Check the estimator
-        self._validate_estimator()
-
-        # Get the sample size
-        # for the bootstrap indexes
-        n_samples_bootstrap = _get_n_samples_bootstrap(
-            n_samples=X.shape[0],
-            max_samples=self.max_samples)
-
-        # Generate the seeds to ensure that
-        # the experiments are reproducible
-        self._seeds = random_state.randint(
-            MAX_RAND_SEED, size=self.n_estimators)
-
-        # Configure the trees to the hyperparameters
-        self.estimators_ = [
-            self._make_estimator(append=False, random_state=self._seeds[i])
-            for i in range(self.n_estimators)
-        ]
-
-        # Build them (using the private method
-        # for future parallelism purposes)
-        self.estimators_ = [
-            _build_trees(
-                t, self, X, Y, sample_weight, i, len(self.estimators_),
-                n_samples_bootstrap)
-            for (i, t) in enumerate(self.estimators_)
-        ]
-
-        # Return the built forest ensemble
-        return self
-
-    def predict(self, X):
-        """Predict ranking for X.
-
-        The predicted ranking of an input sample is an aggregation
-        by the trees in the forest. That is, the predicted ranking
-        is the one obtained by aggregating the estimate across the trees.
-
-        Parameters
-        ----------
-        X: np.ndarray of shape (n_samples, n_features)
-            The input samples.
-
-        Returns
-        -------
-        Y: np.ndarray of shape (n_samples, n_classes)
-            The predicted rankings.
-        """
-        check_is_fitted(self)
-
-        # Check the test data
-        X = self._validate_data(X, reset=False)
-
-        # Obtain the prediction of each tree over all the samples
-        predictions = np.array([
-            tree.predict(X) for tree in self.estimators_])
-
-        # Aggregate the predictions of the trees on each sample
-        predictions = np.array([
-            self._rank_algorithm.aggregate(predictions[:, sample])
-            for sample in range(X.shape[0])])
-
-        # Return the predictions
-        return predictions
-
-
-# =============================================================================
-# Forest Label Ranker
-# =============================================================================
-class ForestLabelRanker(LabelRankerMixin, BaseForest, ABC):
-    """Base class for forest of trees-based Label Rankers.
-
-    Warning: This class should not be used directly. Use derived classes
-    instead.
-    """
-
-    @abstractmethod
-    def __init__(self,
-                 base_estimator,
-                 n_estimators=100,
-                 estimator_hyperparams=tuple(),
-                 bootstrap=False,
-                 random_state=None,
-                 max_samples=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator,
-                         n_estimators=n_estimators,
-                         estimator_hyperparams=estimator_hyperparams,
-                         bootstrap=bootstrap,
-                         random_state=random_state,
-                         max_samples=max_samples)
-
-
-# =============================================================================
-# Forest Partial Label Ranker
-# =============================================================================
-class ForestPartialLabelRanker(PartialLabelRankerMixin, BaseForest, ABC):
-    """Base class for forest of trees-based Partial Label Rankers.
-
-    Warning: This class should not be used directly. Use derived classes
-    instead.
-    """
-
-    @abstractmethod
-    def __init__(self,
-                 base_estimator,
-                 n_estimators=100,
-                 estimator_hyperparams=tuple(),
-                 bootstrap=False,
-                 random_state=None,
-                 max_samples=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator,
-                         n_estimators=n_estimators,
-                         estimator_hyperparams=estimator_hyperparams,
-                         bootstrap=bootstrap,
-                         random_state=random_state,
-                         max_samples=max_samples)
-
-
-# =============================================================================
-# Random Forest Label Ranker
-# =============================================================================
-class RandomForestLabelRanker(ForestLabelRanker):
-    """A random forest Label Ranker.
-
-    A random forest is a meta estimator that fits a number of decision tree
-    Label Rankers on various sub-samples of the dataset and uses aggregating to
-    improve the predictive power and control over-fitting.
-    The sub-sample size is always the same as the original
-    input sample size but the samples are drawn with replacement if
-    `bootstrap=True` (default).
-
-    Hyperparameters
-    ---------------
-    n_estimators : int, optional (default=100)
-        The number of trees in the forest.
-
-    criterion : str, optional (default="mallows")
-        The function to measure the quality of a split. Supported criteria are
-        "mallows" for the Mallows impurity.
-
-    distance : str, optional (default="kendall")
-        The distance function to measure the proximity between rankings.
-        Supported distances are "kendall" for the Kendall distance.
-        This is only employed if ``criterion="mallows"``.
-
-    splitter : str, optional (default="binary")
-        The strategy used to choose the split at each node. Supported
-        strategies are "binary" to choose the best binary split, "width"
-        to choose the best equal-width split and "frequency" to choose
-        the best equal-frequency split.
-
-    max_depth : {int, None}, optional (default=None)
-        The maximum depth of the tree. If None, then nodes are expanded until
-        all leaves are pure or until all leaves contain less than
-        min_samples_split samples.
-
-    min_samples_split : {int, float}, optional (default=2)
-        The minimum number of samples required to split an internal node:
-
-            - If int, then consider `min_samples_split` as the minimum number.
-            - If float, then `min_samples_split` is a fraction and
-              `ceil(min_samples_split * n_samples)` are the minimum
-              number of samples for each split.
-
-    max_features : {int, float, str, None}, optional (default=None)
-        The number of features to consider when looking for the best split:
-
-            - If int, then consider `max_features` features at each split.
-            - If float, then `max_features` is a fraction and
-              `int(max_features * n_features)` features are considered at each
-              split.
-            - If "auto", then `max_features=sqrt(n_features)`.
-            - If "sqrt", then `max_features=sqrt(n_features)`.
-            - If "log2", then `max_features=log2(n_features)`.
-            - If `None`, then `max_features=n_features`.
-
-        Note: The search for a split does not stop until at least one
-        valid partition of the node samples is found, even if it requires to
-        effectively inspect more than ``max_features`` features.
-
-    max_splits : int, optional (default=2)
-        The maximum number of splits.
-
-    random_state : {int, RandomState instance, None}, optional (default=None)
-        - If int, random_state is the seed used by the random number generator.
-        - If RandomState instance, random_state is the random number generator.
-        - If None, the random number generator is the RandomState instance used
-          by `np.random`.
-
-     max_samples: {int, float}, optional (default=None)
-        If bootstrap is True, the number of samples to draw from X
-        to train each base estimator.
-
-        - If None (default), then draw `X.shape[0]` samples.
-        - If int, then draw `max_samples` samples.
-        - If float, then draw `max_samples * X.shape[0]` samples. Thus,
-          `max_samples` should be in the interval `(0, 1)`.
-
-    Attributes
-    ----------
-    base_estimator_ : DecisionTreeLabelRanker
-        The child estimator template used to create the collection of fitted
-        sub-estimators.
-
-    estimators_ : list of DecisionTreeLabelRanker
-        The collection of fitted sub-estimators.
-
-    n_samples_ : int
-        The number of samples when ``fit`` is performed.
-
-    n_features_ : int
-        The number of features when ``fit`` is performed.
-
-    n_classes_ : int
-        The number of classes when ``fit`` is performed.
-
-    Notes
-    -----
-    The default values for the hyperparameters controlling the size of the
-    trees (e.g. ``max_depth``, ``min_samples_leaf``, etc.) lead to fully grown
-    and unpruned trees which can potentially be very large on some data sets.
-    To reduce memory consumption, the complexity and size of the trees should
-    be controlled by setting those hyperparameter values.
-
-    The features are always randomly permuted at each split. Therefore,
-    the best found split may vary, even with the same training data and
-    ``max_features=n_features``, if the improvement of the criterion is
-    identical for several splits enumerated during the search of the best
-    split. To obtain a deterministic behaviour during fitting,
-    ``random_state`` has to be fixed.
-
-    See also
-    --------
-    DecisionTreeLabelRanker, RandomForestPartialLabelRanker
-
-    References
-    ----------
-    .. [1] `L. Breiman, "Random Forests", Machine Learning, vol. 45, pp. 5-32,
-            2001.`_
-
-    Examples
-    --------
-    >>> import numpy as np
-    >>> from sklr.ensemble import RandomForestLabelRanker
-    >>> X = np.array([[0, 1, 0], [1, 0, 1], [1, 1, 0], [0, 1, 0]])
-    >>> Y = np.array([[1, 2, 3], [2, 1, 3], [1, 2, 3], [3, 1, 2]])
-    >>> model = RandomForestLabelRanker(random_state=0)
-    >>> clf = model.fit(X, Y)
-    >>> clf.predict(np.array([[0, 1, 0]]))
-    np.array([[2, 1, 3]])
-    """
-
-    def __init__(self,
-                 n_estimators=100,
-                 criterion="mallows",
-                 distance="kendall",
-                 splitter="binary",
-                 max_depth=None,
-                 min_samples_split=2,
-                 max_features="auto",
-                 bootstrap=True,
-                 random_state=None,
-                 max_samples=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator=DecisionTreeLabelRanker(),
-                         n_estimators=n_estimators,
-                         estimator_hyperparams=(
-                             "criterion", "distance", "splitter",
-                             "max_depth", "min_samples_split",
-                             "max_features", "random_state"
-                        ),
-                         bootstrap=bootstrap,
-                         random_state=random_state,
-                         max_samples=max_samples)
-
-        # Initialize the hyperparameters of the tree
-        self.criterion = criterion
-        self.distance = distance
-        self.splitter = splitter
-        self.max_depth = max_depth
-        self.min_samples_split = min_samples_split
-        self.max_features = max_features
-
-
-# =============================================================================
-# Random Forest Partial Label Ranker
-# =============================================================================
-class RandomForestPartialLabelRanker(ForestPartialLabelRanker):
-    """A random forest Partial Label Ranker.
-
-    A random forest is a meta estimator that fits a number of decision tree
-    Partial Label Rankers on various sub-samples of the dataset and uses
-    aggregating to improve the predictive power and control over-fitting.
-    The sub-sample size is always the same as the original
-    input sample size but the samples are drawn with replacement if
-    `bootstrap=True` (default).
-
-    Hyperparameters
-    ---------------
-    n_estimators : int, optional (default=100)
-        The number of trees in the forest.
-
-    criterion : str, optional (default="entropy")
-        The function to measure the quality of a split. Supported criteria are
-        disagreements for disagreements impurity, "distance" for distance
-        impurity and "entropy" for the entropy impurity.
-
-    splitter : str, optional (default="binary")
-        The strategy used to choose the split at each node. Supported
-        strategies are "binary" to choose the best binary split, "width"
-        to choose the best equal-width split and "frequency" to choose
-        the best equal-frequency split.
-
-    max_depth : {int, None}, optional (default=None)
-        The maximum depth of the tree. If None, then nodes are expanded until
-        all leaves are pure or until all leaves contain less than
-        min_samples_split samples.
-
-    min_samples_split : {int, float}, optional (default=2)
-        The minimum number of samples required to split an internal node:
-
-            - If int, then consider `min_samples_split` as the minimum number.
-            - If float, then `min_samples_split` is a fraction and
-              `ceil(min_samples_split * n_samples)` are the minimum
-              number of samples for each split.
-
-    max_features : {int, float, string, None}, optional (default=None)
-        The number of features to consider when looking for the best split:
-
-            - If int, then consider `max_features` features at each split.
-            - If float, then `max_features` is a fraction and
-              `int(max_features * n_features)` features are considered at each
-              split.
-            - If "auto", then `max_features=sqrt(n_features)`.
-            - If "sqrt", then `max_features=sqrt(n_features)`.
-            - If "log2", then `max_features=log2(n_features)`.
-            - If `None`, then `max_features=n_features`.
-
-        Note: The search for a split does not stop until at least one
-        valid partition of the node samples is found, even if it requires to
-        effectively inspect more than ``max_features`` features.
-
-    max_splits : int, optional (default=2)
-        The maximum number of splits.
-
-    random_state : {int, RandomState instance, None}, optional (default=None)
-        - If int, random_state is the seed used by the random number generator.
-        - If RandomState instance, random_state is the random number generator.
-        - If None, the random number generator is the RandomState instance used
-          by `np.random`.
-
-     max_samples : {int, float}, optional (default=None)
-        If bootstrap is True, the number of samples to draw from X
-        to train each base estimator.
-
-        - If None (default), then draw `X.shape[0]` samples.
-        - If int, then draw `max_samples` samples.
-        - If float, then draw `max_samples * X.shape[0]` samples. Thus,
-          `max_samples` should be in the interval `(0, 1)`.
-
-    Attributes
-    ----------
-    base_estimator_ : DecisionTreePartialLabelRanker
-        The child estimator template used to create the collection of fitted
-        sub-estimators.
-
-    estimators_ : list of DecisionTreePartialLabelRanker
-        The collection of fitted sub-estimators.
-
-    n_samples_ : int
-        The number of samples when ``fit`` is performed.
-
-    n_features_ : int
-        The number of features when ``fit`` is performed.
-
-    n_classes_ : int
-        The number of classes when ``fit`` is performed.
-
-    Notes
-    -----
-    The default values for the hyperparameters controlling the size of the
-    trees (e.g. ``max_depth``, ``min_samples_leaf``, etc.) lead to fully grown
-    and unpruned trees which can potentially be very large on some data sets.
-    To reduce memory consumption, the complexity and size of the trees should
-    be controlled by setting those hyperparameter values.
-
-    The features are always randomly permuted at each split. Therefore,
-    the best found split may vary, even with the same training data and
-    ``max_features=n_features``, if the improvement of the criterion is
-    identical for several splits enumerated during the search of the best
-    split. To obtain a deterministic behaviour during fitting,
-    ``random_state`` has to be fixed.
-
-    See also
-    --------
-    DecisionTreePartialLabelRanker, RandomForestLabelRanker
-
-    References
-    ----------
-    .. [1] `L. Breiman, "Random Forests", Machine Learning, vol. 45, pp. 5-32,
-            2001.`_
-
-    Examples
-    --------
-    >>> import numpy as np
-    >>> from sklr.ensemble import RandomForestPartialLabelRanker
-    >>> X = np.array([[0, 1, 0], [1, 0, 1], [1, 1, 0], [0, 1, 0]])
-    >>> Y = np.array([[1, 2, 3], [2, 1, 3], [1, 2, 3], [3, 1, 2]])
-    >>> model = RandomForestPartialLabelRanker(random_state=0)
-    >>> clf = model.fit(X, Y)
-    >>> clf.predict(np.array([[0, 1, 0]]))
-    np.array([[1, 1, 1]])
-    """
-
-    def __init__(self,
-                 n_estimators=100,
-                 criterion="entropy",
-                 splitter="binary",
-                 max_depth=None,
-                 min_samples_split=2,
-                 max_features="auto",
-                 bootstrap=True,
-                 random_state=None,
-                 max_samples=None):
-        """Constructor."""
-        # Call to the constructor of the parent
-        super().__init__(base_estimator=DecisionTreePartialLabelRanker(),
-                         n_estimators=n_estimators,
-                         estimator_hyperparams=(
-                             "criterion", "splitter",
-                             "max_depth", "min_samples_split",
-                             "max_features", "random_state"
-                         ),
-                         bootstrap=bootstrap,
-                         random_state=random_state,
-                         max_samples=max_samples)
-
-        # Initialize the hyperparameters of the tree
-        self.criterion = criterion
-        self.splitter = splitter
-        self.max_depth = max_depth
-        self.min_samples_split = min_samples_split
-        self.max_features = max_features
diff --git a/sklr/miss/__init__.py b/sklr/miss/__init__.py
deleted file mode 100644
index 488e842..0000000
--- a/sklr/miss/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
-The :mod:`sklr.miss` module includes transformers to miss classes from
-rankings.
-"""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Local application
-from ._base import SimpleMisser
-
-
-# =============================================================================
-# Module public objects
-# =============================================================================
-__all__ = ["SimpleMisser"]
diff --git a/sklr/miss/_base.py b/sklr/miss/_base.py
deleted file mode 100644
index a07f76f..0000000
--- a/sklr/miss/_base.py
+++ /dev/null
@@ -1,157 +0,0 @@
-"""Base classes for all transformers to miss classes from rankings."""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Standard
-from numbers import Integral, Real
-
-# Third party
-import numpy as np
-from sklearn.base import BaseEstimator
-from sklearn.utils.validation import check_array, check_is_fitted
-
-# Local application
-from ..base import TransformerMixin
-from ._base_fast import miss_classes
-from ._base_fast import STRATEGY_MAPPING
-from ..utils import check_random_state
-
-
-# =============================================================================
-# Classes
-# =============================================================================
-
-class SimpleMisser(TransformerMixin, BaseEstimator):
-    """Missing transformer for deleting classes from rankings.
-
-    Hyperparameters
-    ---------------
-    strategy : {"random", "top"}, default="random"
-        The missing strategy. Supported criteria are ``"random"``, to delete
-        the classes at random and ``"top"``, to delete the classes out of the
-        top-k.
-
-    probability : float, default=0.0
-        The probability for the deletion of a class.
-
-    random_state : int or RandomState instance, default=None
-        Controls the randomness for the deletion of the classes.
-        This parameter is useful only for the ``"random"`` strategy.
-
-    Attributes
-    ----------
-    random_state_ : RandomState instance
-        The random number generator to delete the classes.
-        This attribute is available only for the ``"random"`` strategy.
-
-    n_classes_miss_ : int
-        The number of classes to delete from each ranking.
-        This attribute is available only for the ``"top"`` strategy.
-
-    Notes
-    -----
-    The values codifying the randomly and top-k deleted classes are ``np.nan``
-    and ``np.inf`` (respectively) to maintain their underlying meaning.
-
-    Examples
-    --------
-    >>> import numpy as np
-    >>> from sklr.miss import SimpleMisser
-    >>> Y = np.array([[1, 2, 3], [2, 3, 1], [3, 1, 2]])
-    >>> misser = SimpleMisser("random", probability=0.6, random_state=0)
-    >>> misser.fit_transform(Y)
-    array([[nan,  1.,  2.],
-           [nan, nan,  1.],
-           [nan,  1.,  2.]])
-    >>> misser = SimpleMisser("top", probability=0.6)
-    >>> misser.fit_transform(Y)
-    array([[ 1.,  2., inf],
-           [ 2., inf,  1.],
-           [inf,  1.,  2.]])
-    """
-
-    def __init__(self, strategy="random", probability=0.0, random_state=None):
-        """Constructor."""
-        self.strategy = strategy
-        self.probability = probability
-        self.random_state = random_state
-
-    def fit(self, Y):
-        """Fit the misser on ``Y``.
-
-        Parameters
-        ----------
-        Y : ndarray of shape (n_samples, n_classes), dtype=np.int64
-            The input rankings.
-
-        Returns
-        -------
-        self : SimpleMisser
-            The fitted misser.
-        """
-        Y = check_array(Y)
-
-        (_, n_classes) = Y.shape
-
-        if self.strategy not in STRATEGY_MAPPING:
-            raise ValueError("The supported strategies are: {0}. Got '{1}'."
-                             .format(list(STRATEGY_MAPPING), self.strategy))
-
-        if (not isinstance(self.probability, (Integral, np.integer)) and
-                not isinstance(self.probability, (Real, np.floating))):
-            raise TypeError("The probability for the deletion of a "
-                            "class must be integer or floating type. "
-                            "Got {0}.".format(type(self.probability)))
-        elif self.probability < 0.0 or self.probability > 1.0:
-            raise ValueError("The probability for the deletion of a "
-                             "class must be greater than or equal "
-                             "zero and less than or equal one. "
-                             "Got {0}.".format(self.probability))
-
-        if self.strategy == "random":
-            # Store the random number generator to guarantee
-            # the reproducibility between successive fit calls
-            self.random_state_ = check_random_state(self.random_state)
-        else:
-            self.n_classes_miss_ = int(n_classes * self.probability)
-
-        return self
-
-    def transform(self, Y):
-        """Miss the classes in ``Y``.
-
-        Parameters
-        ----------
-        Y : ndarray of shape (n_samples, n_classes), dtype=np.int64
-            The input rankings from which the classes will be deleted.
-
-        Returns
-        -------
-        Yt : ndarray of shape (n_samples, n_classes), dtype=np.float64
-            The transformed rankings.
-        """
-        check_is_fitted(self)
-
-        Y = check_array(Y)
-
-        Yt = np.array(Y, dtype=np.float64)
-        Y = np.zeros(Y.shape, dtype=np.int64)
-
-        if self.strategy == "random":
-            # Use an uniform distribution to randomly delete the classes
-            masks = self.random_state_.rand(*Y.shape) < self.probability
-        else:
-            # Sort the rankings to mantain the arbitrariness for tied classes
-            masks = np.argsort(np.argsort(-Yt)) + 1 <= self.n_classes_miss_
-
-        # Transform the masks to unsigned integer
-        # since Cython cannot handle boolean arrays
-        masks = np.array(masks, dtype=np.uint8)
-        strategy = STRATEGY_MAPPING[self.strategy]
-
-        miss_classes(Y, Yt, masks, strategy)
-
-        return Yt
diff --git a/sklr/miss/_base_fast.pxd b/sklr/miss/_base_fast.pxd
deleted file mode 100644
index c446de5..0000000
--- a/sklr/miss/_base_fast.pxd
+++ /dev/null
@@ -1,31 +0,0 @@
-# cython: language_level=3
-# cython: cdivision=True
-# cython: boundscheck=False
-# cython: wraparound=False
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Third party
-cimport numpy as np
-
-# Local application
-from .._types cimport DTYPE_t_2D, INT64_t, INT64_t_2D, SIZE_t
-
-
-# =============================================================================
-# Types
-# =============================================================================
-
-ctypedef np.npy_uint8[:, :] UINT8_t_2D
-
-
-# =============================================================================
-# Enums
-# =============================================================================
-
-ctypedef enum STRATEGY:
-    RANDOM
-    TOP
diff --git a/sklr/miss/_base_fast.pyx b/sklr/miss/_base_fast.pyx
deleted file mode 100644
index c296ee9..0000000
--- a/sklr/miss/_base_fast.pyx
+++ /dev/null
@@ -1,64 +0,0 @@
-# cython: language_level=3
-# cython: cdivision=True
-# cython: boundscheck=False
-# cython: wraparound=False
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Third party
-from libc.math cimport NAN, INFINITY
-
-# Local application
-from ..utils._ranking cimport RANK_METHOD
-from ..utils._ranking cimport rank_data_view
-
-
-# =============================================================================
-# Constants
-# =============================================================================
-
-# Map the strategy string identifier
-# to the strategy integer identifier
-STRATEGY_MAPPING = {
-    "random": RANDOM,
-    "top": TOP
-}
-
-
-# =============================================================================
-# Methods
-# =============================================================================
-
-cpdef void miss_classes(INT64_t_2D Y, DTYPE_t_2D Yt,
-                        UINT8_t_2D masks, STRATEGY strategy) nogil:
-    """Miss the classes from the rankings using the given masks and
-    taking into account the strategy for the deletion of classes."""
-    cdef INT64_t n_samples = Y.shape[0]
-    cdef INT64_t n_classes = Y.shape[1]
-
-    cdef SIZE_t sample
-    cdef SIZE_t label
-
-    for sample in range(n_samples):
-        for label in range(n_classes):
-            if masks[sample, label]:
-                if strategy == RANDOM:
-                    Yt[sample, label] = NAN
-                else:
-                    Yt[sample, label] = INFINITY
-
-    # Rank the randomly missed classes to ensure that the
-    # rankings are properly formatted for the estimators
-    if strategy == RANDOM:
-        for sample in range(n_samples):
-            # Use the dense method to rank the classes
-            # since it works for all type of targets
-            rank_data_view(Yt[sample], Y[sample],
-                           method=RANK_METHOD.DENSE)
-
-            for label in range(n_classes):
-                if not masks[sample, label]:
-                    Yt[sample, label] = Y[sample, label]


Test patch:
diff --git a/sklr/ensemble/tests/__init__.py b/sklr/ensemble/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/sklr/ensemble/tests/test_bagging.py b/sklr/ensemble/tests/test_bagging.py
deleted file mode 100644
index e165785..0000000
--- a/sklr/ensemble/tests/test_bagging.py
+++ /dev/null
@@ -1,257 +0,0 @@
-"""Testing for the bagging ensemble module."""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Third party
-import numpy as np
-import pytest
-
-# Local application
-from sklr.ensemble import BaggingLabelRanker, BaggingPartialLabelRanker
-from sklr.neighbors import KNeighborsLabelRanker, KNeighborsPartialLabelRanker
-from sklr.tree import DecisionTreeLabelRanker, DecisionTreePartialLabelRanker
-from sklr.utils import check_random_state
-
-
-# =============================================================================
-# Initialization
-# =============================================================================
-
-# The seed and the random state generator
-# to always obtain the same results and,
-# so, ensure that the tests carried out
-# are always the same
-seed = 198075
-random_state = check_random_state(seed)
-
-# The following variables are required for some
-# test methods to work. Even if they will not be
-# used in all the tests, they are globally declared
-# to avoid that they are defined along several methods.
-# The extra memory overhead should not be an issue
-
-# The default estimators
-LR_DEFAULT_ESTIMATOR = DecisionTreeLabelRanker
-PLR_DEFAULT_ESTIMATOR = DecisionTreePartialLabelRanker
-DEFAULT_ESTIMATORS = [LR_DEFAULT_ESTIMATOR, PLR_DEFAULT_ESTIMATOR]
-
-# The base estimators
-LR_BASE_ESTIMATORS = [KNeighborsLabelRanker]
-PLR_BASE_ESTIMATORS = [KNeighborsPartialLabelRanker]
-BASE_ESTIMATORS = [*LR_BASE_ESTIMATORS, *PLR_BASE_ESTIMATORS]
-
-# The bagging rankers
-LR_BAGGING = [BaggingLabelRanker]
-PLR_BAGGING = [BaggingPartialLabelRanker]
-BAGGING = [*LR_BAGGING, *PLR_BAGGING]
-
-# A toy example to check that the forest rankers
-# are properly working. In fact, initialize two datasets,
-# one with training data and rankings and another one with
-# test data and rankings
-
-# Training
-
-# Data
-X_train = np.array([
-    [-2, -1],
-    [-1, -1],
-    [-1, -2],
-    [1, 1],
-    [1, 2],
-    [2, 1]])
-
-# Rankings
-Y_train = np.array([
-    [1, 2, 3],
-    [1, 2, 3],
-    [1, 2, 3],
-    [2, 1, 3],
-    [2, 1, 3],
-    [2, 1, 3]])
-
-# Test
-
-# Data
-X_test = np.array([
-    [-1, -1],
-    [2, 2],
-    [3, 2]])
-
-# Rankings
-Y_test = np.array([
-    [1, 2, 3],
-    [2, 1, 3],
-    [2, 1, 3]])
-
-
-# =============================================================================
-# Testing
-# =============================================================================
-
-@pytest.mark.toy_example
-@pytest.mark.parametrize("BaggingRanker", BAGGING)
-def test_toy_example(BaggingRanker):
-    """Test the bagging rankers on a toy dataset."""
-    # Initialize the bagging ranker
-    # using its default hyperparameters
-    model = BaggingRanker(random_state=seed)
-
-    # Fit the bagging ranker
-    # to the training dataset
-    clf = model.fit(X_train, Y_train)
-
-    # Obtain the predictions of the bagging ranker
-    Y_pred = clf.predict(X_test)
-
-    # Assert that the predictions are correct
-    np.testing.assert_array_equal(Y_pred, Y_test)
-
-    # For the sake of fully-tested code, apply
-    # the same procedure without boostrapping
-    clf = model.set_params(bootstrap=False).fit(X_train, Y_train)
-    Y_pred = clf.predict(X_test)
-    np.testing.assert_array_equal(Y_pred, Y_test)
-
-
-@pytest.mark.error
-@pytest.mark.parametrize("BaggingRanker", BAGGING)
-def test_error(BaggingRanker):
-    """Test that it gives proper exception on deficient input."""
-    # Initialize the bagging ranker
-    # using its default hyperparameters
-    model = BaggingRanker(random_state=seed)
-
-    # Assert that an error is raised when the maximum number
-    # of samples is an integer type less or equal than zero
-    with pytest.raises(ValueError):
-        model.set_params(max_samples=0).fit(X_train, Y_train)
-
-    # Assert that an error is raised when the maximum number of
-    # samples is an integer type greater than the number of samples
-    with pytest.raises(ValueError):
-        model.set_params(max_samples=100).fit(X_train, Y_train)
-
-    # Assert that an error is raised when the maximum number
-    # of samples is a floating type less than or equal zero
-    with pytest.raises(ValueError):
-        model.set_params(max_samples=0.0).fit(X_train, Y_train)
-
-    # Assert that an error is raised when the maximum number
-    # of samples is a floating type greater than one
-    with pytest.raises(ValueError):
-        model.set_params(max_samples=2.0).fit(X_train, Y_train)
-
-    # Asssert that an error is raised when the maximum
-    # number of samples in not an integer or floating type
-    with pytest.raises(TypeError):
-        model.set_params(max_samples="foo").fit(X_train, Y_train)
-
-    # The same procedure with the maximum number of features
-
-    # Initialize the bagging ranker using an small number of
-    # estimators to not introduce too much memory overhead
-    model = BaggingRanker(n_estimators=10, random_state=seed)
-
-    # Assert that an error is raised when the maximum number
-    # of features is an integer type less or equal than zero
-    with pytest.raises(ValueError):
-        model.set_params(max_features=0).fit(X_train, Y_train)
-
-    # Assert that an error is raised when the maximum number of
-    # features is an integer type greater than the number of features
-    with pytest.raises(ValueError):
-        model.set_params(max_features=10).fit(X_train, Y_train)
-
-    # Assert that an error is raised when the maximum number
-    # of features is a floating type less than or equal zero
-    with pytest.raises(ValueError):
-        model.set_params(max_features=0.0).fit(X_train, Y_train)
-
-    # Assert that an error is raised when the maximum number
-    # of features is a floating type greater than one
-    with pytest.raises(ValueError):
-        model.set_params(max_features=2.0).fit(X_train, Y_train)
-
-    # Asssert that an error is raised when the maximum
-    # number of features in not an integer or floating type
-    with pytest.raises(TypeError):
-        model.set_params(max_features="foo").fit(X_train, Y_train)
-
-
-@pytest.mark.base_estimator
-@pytest.mark.parametrize(
-    "BaseEstimator,DefaultEstimator,BaggingRanker",
-    zip(BASE_ESTIMATORS, DEFAULT_ESTIMATORS, BAGGING))
-def test_base_estimator(BaseEstimator, DefaultEstimator, BaggingRanker):
-    """Test base_estimator and its default values."""
-    # Initialize the bagging ranker using the None as base estimator
-    # and assert that the base estimator is the default estimator
-    model = BaggingRanker(base_estimator=None, random_state=seed)
-    clf = model.fit(X_train, Y_train)
-    assert isinstance(clf.base_estimator_, DefaultEstimator)
-
-    # Initialize the bagging ranker using the default estimator as base
-    # estimator and assert that the base estimator is the default estimator
-    model = BaggingRanker(base_estimator=DefaultEstimator(), random_state=seed)
-    clf = model.fit(X_train, Y_train)
-    assert isinstance(clf.base_estimator_, DefaultEstimator)
-
-    # Initialize the bagging ranker explictly given the
-    # base estimator and assert that it is properly set
-    model = BaggingRanker(base_estimator=BaseEstimator(), random_state=seed)
-    clf = model.fit(X_train, Y_train)
-    assert isinstance(clf.base_estimator_, BaseEstimator)
-
-
-@pytest.mark.bagging_sample_weight_unsupported_but_passed
-@pytest.mark.parametrize(
-    "BaseEstimator,BaggingRanker", zip(BASE_ESTIMATORS, BAGGING))
-def test_bagging_sample_weight_unsupported_but_passed(BaseEstimator,
-                                                      BaggingRanker):
-    """Test that error is raised when passing
-    sample weights to an unsupported estimator."""
-    # Initialize the bagging ranker, setting the base
-    # estimator that does not support sample weighting
-    model = BaggingRanker(base_estimator=BaseEstimator(),
-                          random_state=seed)
-
-    # Initialize a sample weight that will be
-    # used to fit the different bagging rankers
-    sample_weight = random_state.randint(10, size=(X_train.shape[0]))
-
-    # Assert than an error is raised when passing sample weights
-    # to a base estimator that does not support sample weighting
-    with pytest.raises(ValueError):
-        model.fit(X_train, Y_train, sample_weight)
-
-    # For the sake of coverage, test that no error is raised
-    # when sample weights are used with supported estimator
-    model = BaggingRanker(n_estimators=10, random_state=seed)
-    model.fit(X_train, Y_train, sample_weight)
-
-
-@pytest.mark.max_samples_consistency
-@pytest.mark.parametrize("BaggingRanker", BAGGING)
-def test_max_samples_consistency(BaggingRanker):
-    """Test to make sure validated max_samples and original max_samples
-    are identical when valid integer max_samples supplied by user."""
-    # Initialize the maximum number
-    # of samples to be tested
-    max_samples = 3
-
-    # Initialize the bagging ranker explictly
-    # given the maximum number of samples
-    model = BaggingRanker(max_samples=max_samples,
-                          random_state=seed)
-
-    # Fit the bagging ranker
-    # to the training dataset
-    clf = model.fit(X_train, Y_train)
-
-    # Assert that the maximum number of samples stored in the
-    # model and the given maximum number of samples is the same
-    assert clf._max_samples == max_samples
diff --git a/sklr/ensemble/tests/test_base.py b/sklr/ensemble/tests/test_base.py
deleted file mode 100644
index 6ddc4ca..0000000
--- a/sklr/ensemble/tests/test_base.py
+++ /dev/null
@@ -1,148 +0,0 @@
-"""Testing for the base module."""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Standard
-from numbers import Integral
-
-# Third party
-import numpy as np
-import pytest
-
-# Local application
-from sklr.ensemble import BaggingLabelRanker
-from sklr.ensemble._base import _set_random_states
-from sklr.neighbors import KNeighborsLabelRanker
-from sklr.tree import DecisionTreeLabelRanker
-from sklr.utils import check_random_state
-
-
-# =============================================================================
-# Initialization
-# =============================================================================
-
-# The seed and the random state generator
-# to always obtain the same results and,
-# so, ensure that the tests carried out
-# are always the same
-seed = 198075
-random_state = check_random_state(seed)
-
-# A toy example to check that the
-# base ensemble is properly working
-
-# Data
-X = np.array([
-    [-2, -1],
-    [-1, -1],
-    [-1, -2],
-    [1, 1],
-    [1, 2],
-    [2, 1]])
-
-# Rankings
-Y = np.array([
-    [1, 2, 3],
-    [1, 2, 3],
-    [1, 2, 3],
-    [2, 1, 3],
-    [2, 1, 3],
-    [2, 1, 3]])
-
-
-# =============================================================================
-# Testing
-# =============================================================================
-
-@pytest.mark.base
-def test_base():
-    """Test BaseEnsemble methods."""
-    # Initialize the ensemble
-    # using three base estimators
-    ensemble = BaggingLabelRanker(n_estimators=3, random_state=seed)
-
-    # Fit the ensemble to the training dataset for
-    # being able to manually set the estimators
-    clf = ensemble.fit(X, Y)
-
-    # Initialize an empty list of estimators
-    # that will be manually created
-    clf.estimators_ = []
-
-    # Manually create an estimator without random state,
-    # another two with an integer random state and another
-    # one without appending to the list of estimators
-    clf._make_estimator()
-    clf._make_estimator(random_state=seed)
-    clf._make_estimator(random_state=seed + 1)
-    clf._make_estimator(append=False)
-
-    # Assert that the length of the
-    # list of estimators is correct
-    assert 3 == len(ensemble.estimators_)
-
-    # Assert that the random states have
-    # been properly set for each estimator
-    assert clf.estimators_[0].random_state is None
-    assert isinstance(clf.estimators_[1].random_state, (Integral, np.integer))
-    assert isinstance(clf.estimators_[2].random_state, (Integral, np.integer))
-
-    # Assert that the random state of the
-    # second and third estimator are different
-    assert clf.estimators_[1].random_state != clf.estimators_[2].random_state
-
-
-@pytest.mark.base_zero_n_estimators
-def test_base_zero_n_estimators():
-    """Test that instantiating a BaseEnsemble
-    with n_estimators<=0 raises a ValueError."""
-    # Initialize the ensemble
-    # using zero base estimators
-    ensemble = BaggingLabelRanker(n_estimators=0, random_state=seed)
-
-    # Assert that an error is raised when the number
-    # of estimators is less than or equal zero
-    with pytest.raises(ValueError):
-        ensemble.fit(X, Y)
-
-
-@pytest.mark.base_not_int_n_estimators
-def test_base_not_int_n_estimators():
-    """Test that instantiating a BaseEnsemble
-    without an integer raises a TypeError."""
-    # Initialize the ensemble using a
-    # number of estimators of str type
-    ensemble = BaggingLabelRanker(n_estimators="foo", random_state=seed)
-
-    # Assert that an error is raised when the
-    # number of estimators is not an integer type
-    with pytest.raises(TypeError):
-        ensemble.fit(X, Y)
-
-
-@pytest.mark.set_random_states
-def test_set_random_states():
-    """Test the _set_random_states method."""
-    # Smoke test, KNeighborsLabelRanker does not have random
-    # state so that the process of setting the seed is bypassed
-    _set_random_states(KNeighborsLabelRanker(), random_state=seed)
-
-    # Assert that the random state as None in the base estimator
-    # still sets a random state in the base estimator
-    estimator1 = DecisionTreeLabelRanker(random_state=None)
-    assert estimator1.random_state is None
-    _set_random_states(estimator1, None)
-    assert isinstance(estimator1.random_state, (Integral, np.integer))
-
-    # Assert that the random state fixes results in consistent
-    # initialization, that is, even if another random state
-    # is set in the initialization of the base estimator, the
-    # random state passed to this method sets the random state
-    estimator2 = DecisionTreeLabelRanker(random_state=None)
-    _set_random_states(estimator1, random_state=seed)
-    assert isinstance(estimator1.random_state, (Integral, np.integer))
-    _set_random_states(estimator2, random_state=seed)
-    assert estimator1.random_state == estimator2.random_state
diff --git a/sklr/ensemble/tests/test_forest.py b/sklr/ensemble/tests/test_forest.py
deleted file mode 100644
index 4612cef..0000000
--- a/sklr/ensemble/tests/test_forest.py
+++ /dev/null
@@ -1,205 +0,0 @@
-"""Testing for the forest module."""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Standard
-from itertools import product, chain
-
-# Third party
-import numpy as np
-import pytest
-
-# Local application
-from sklr.ensemble import (
-    RandomForestLabelRanker, RandomForestPartialLabelRanker)
-from sklr.ensemble._forest import _get_n_samples_bootstrap
-from sklr.utils import check_random_state
-
-
-# =============================================================================
-# Initialization
-# =============================================================================
-
-# The seed and the random state generator
-# to always obtain the same results and,
-# so, ensure that the tests carried out
-# are always the same
-seed = 198075
-random_state = check_random_state(seed)
-
-# The following variables are required for some
-# test methods to work. Even if they will not be
-# used in all the tests, they are globally declared
-# to avoid that they are defined along several methods.
-# The extra memory overhead should not be an issue
-
-# The criteria for the Label Ranking problem
-# and the Partial Label Ranking problem
-LR_CRITERIA = ["mallows"]
-PLR_CRITERIA = ["disagreements", "distance", "entropy"]
-CRITERIA = [*LR_CRITERIA, *PLR_CRITERIA]
-
-# The distances required
-# for the Mallows criterion
-DISTANCES = ["kendall"]
-
-# The splitters that can be used to split
-# an internal node of the decision tree
-SPLITTERS = ["binary", "frequency", "width"]
-
-# The forest rankers
-LR_FORESTS = [RandomForestLabelRanker]
-PLR_FORESTS = [RandomForestPartialLabelRanker]
-FORESTS = [*LR_FORESTS, *PLR_FORESTS]
-
-# The possible combinations of forest
-# rankers, criteria, splitters and distance
-COMBINATIONS_LR = product(LR_FORESTS, LR_CRITERIA, SPLITTERS, DISTANCES)
-COMBINATIONS_PLR = product(PLR_FORESTS, PLR_CRITERIA, SPLITTERS, DISTANCES)
-COMBINATIONS = list(chain(COMBINATIONS_LR, COMBINATIONS_PLR))
-
-# A toy example to check that the forest rankers
-# are properly working. In fact, initialize two datasets,
-# one with training data and rankings and another one with
-# test data and rankings
-
-# Training
-
-# Data
-X_train = np.array([
-    [-2, -1],
-    [-1, -1],
-    [-1, -2],
-    [1, 1],
-    [1, 2],
-    [2, 1]])
-
-# Rankings
-Y_train = np.array([
-    [1, 2, 3],
-    [1, 2, 3],
-    [1, 2, 3],
-    [2, 1, 3],
-    [2, 1, 3],
-    [2, 1, 3]])
-
-# Test
-
-# Data
-X_test = np.array([
-    [-1, -1],
-    [2, 2],
-    [3, 2]])
-
-# Rankings
-Y_test = np.array([
-    [1, 2, 3],
-    [2, 1, 3],
-    [2, 1, 3]])
-
-
-# =============================================================================
-# Testing
-# =============================================================================
-
-@pytest.mark.toy_example
-@pytest.mark.parametrize(
-    "RandomForestRanker,criterion,splitter,distance", COMBINATIONS)
-def test_toy_example(RandomForestRanker, criterion, splitter, distance):
-    """Test the forest rankers on a toy dataset."""
-    # Initialize the forest ranker using the given
-    # criterion, splitter and (if correponds) distance,
-    # Also, set the maximum number of samples to consider
-    # to three quarters of the number of training samples
-    if RandomForestRanker is RandomForestLabelRanker:
-        model = RandomForestRanker(n_estimators=10,
-                                   criterion=criterion,
-                                   distance=distance,
-                                   splitter=splitter,
-                                   max_samples=0.75,
-                                   random_state=seed)
-    else:
-        model = RandomForestRanker(n_estimators=10,
-                                   criterion=criterion,
-                                   splitter=splitter,
-                                   max_samples=0.75,
-                                   random_state=seed)
-
-    # Initialize a sample weight that will be
-    # used to fit the different forest rankers
-    sample_weight = random_state.randint(10, size=(X_train.shape[0]))
-
-    # Fit the forest ranker to the training
-    # dataset also using the sample weight
-    clf = model.fit(X_train, Y_train, sample_weight)
-
-    # Obtain the predictions of the decision tree ranker
-    Y_pred = clf.predict(X_test)
-
-    # Assert that the predictions are correct
-    np.testing.assert_array_equal(Y_pred, Y_test)
-
-    # Now, apply the same procedure
-    # but only using one feature
-    model = model.set_params(max_features=1)
-    clf = model.fit(X_train, Y_train)
-    Y_pred = clf.predict(X_test)
-    np.testing.assert_array_equal(Y_pred, Y_test)
-
-    # The same as before but without bootstrapping
-    # for the sake of fully test the code (coverage)
-
-    # All features
-    model = model.set_params(bootstrap=False, max_features=None)
-    clf = model.fit(X_train, Y_train, sample_weight)
-    Y_pred = clf.predict(X_test)
-    np.testing.assert_array_equal(Y_pred, Y_test)
-
-    # One feature
-    model = model.set_params(bootstrap=False, max_features=1)
-    clf = model.fit(X_train, Y_train)
-    Y_pred = clf.predict(X_test)
-    np.testing.assert_array_equal(Y_pred, Y_test)
-
-
-@pytest.mark.max_samples_exceptions
-def test_max_samples_exceptions():
-    """Test invalid max_samples values."""
-    # Assert that an exception is raised when
-    # the number of bootstrapped samples is
-    # an integer type less than or equal zero
-    with pytest.raises(ValueError):
-        _get_n_samples_bootstrap(100, -1)
-
-    # Assert that an exception is raised when the
-    # number of bootstrapped samples is an integer
-    # type greater than the number of samples
-    with pytest.raises(ValueError):
-        _get_n_samples_bootstrap(100, 1000)
-
-    # Assert that an exception is raised when
-    # the number of bootstrapped samples is
-    # a floating type less than or equal zero
-    with pytest.raises(ValueError):
-        _get_n_samples_bootstrap(100, 0.0)
-
-    # Assert that an exception is raised when
-    # the number of bootstrapped samples is
-    # a floating type greater than or equal one
-    with pytest.raises(ValueError):
-        _get_n_samples_bootstrap(100, 1.0)
-
-    # Assert that an exception is raised when
-    # the number of bootstrapped samples is
-    # not an integer or floating type
-    with pytest.raises(TypeError):
-        _get_n_samples_bootstrap(100, "foo")
-
-    # Assert that no exception is raised
-    # with properly formatted samples
-    assert _get_n_samples_bootstrap(100, None) == 100
-    assert _get_n_samples_bootstrap(100, 50) == 50
-    assert _get_n_samples_bootstrap(100, 0.5) == 50
diff --git a/sklr/miss/tests/__init__.py b/sklr/miss/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/sklr/miss/tests/test_base.py b/sklr/miss/tests/test_base.py
deleted file mode 100644
index cad61f6..0000000
--- a/sklr/miss/tests/test_base.py
+++ /dev/null
@@ -1,70 +0,0 @@
-"""Testing of the base classes for missing classes from rankings."""
-
-
-# =============================================================================
-# Imports
-# =============================================================================
-
-# Third party
-import numpy as np
-import pytest
-
-# Local application
-from sklr.miss import SimpleMisser
-from sklr.datasets import load_iris
-
-
-# =============================================================================
-# Testing
-# =============================================================================
-
-class TestSimpleMisser:
-    """Testing of the class to miss classes from rankings."""
-
-    def setup(self):
-        """Setup the attributes for testing."""
-        # The strategy will be set in the corresponding testing method
-        self.misser = SimpleMisser(probability=0.6, random_state=198075)
-
-        (_, self.Y) = load_iris(problem="partial_label_ranking")
-
-    def test_gets_raised(self):
-        """Test that the handled errors are raised."""
-        with pytest.raises(ValueError, match="supported strategies"):
-            SimpleMisser(strategy="foo").fit(self.Y)
-
-        with pytest.raises(TypeError, match="integer or floating"):
-            SimpleMisser(probability="foo").fit(self.Y)
-
-        with pytest.raises(ValueError, match="less than or equal one"):
-            SimpleMisser(probability=1.5).fit(self.Y)
-
-        with pytest.raises(ValueError, match="greater than or equal zero"):
-            SimpleMisser(probability=-1.5).fit(self.Y)
-
-    def test_random(self):
-        """Test the random strategy.
-
-        In particular, asserts that the specified percentage of classes are
-        deleted (up to one decimal of precision).
-        """
-        self.probability = self.misser.probability
-        self.random_misser = self.misser.set_params(strategy="random")
-
-        Yt = self.random_misser.fit_transform(self.Y)
-        nan_mean = np.mean(np.isnan(Yt))
-
-        assert np.isclose(nan_mean, self.probability, rtol=1e-1)
-
-    def test_top(self):
-        """Test the top strategy.
-
-        In particular, asserts that the latest ranked classes are deleted
-        (maintaining the specified arbitrariness).
-        """
-        self.top_misser = self.misser.set_params(strategy="top")
-
-        Yt = self.top_misser.fit_transform(self.Y)
-        idx_last_ranked = np.argmax(self.Y, axis=1)
-
-        assert np.all(np.isinf(np.choose(idx_last_ranked, Yt.T)))