class FullIndex(BaseIndex):

    """Vacuous indexer to be used with final layers.

    FullIndex is a compatibility class to be used with meta layers. It stores
    the sample size to be predicted for use with the
    :class:`ParallelProcessing` job manager, and yields a ``None, None``
    index when `generate` is called.
    """

    def __init__(self, X=None):
        super(FullIndex, self).__init__()
        if X is not None:
            self.fit(X)

    def fit(self, X, y=None, job=None):
        """Store dimensionality data about X."""
        self.n_samples = X.shape[0]
        self.n_test_samples = X.shape[0]
        self.__fitted__ = True

    def _gen_indices(self):
        """Vacuous generator to ensure training data is not sliced."""
        yield None, None class FoldIndex(BaseIndex):

    """Indexer that generates the full size of ``X``.

    K-Fold iterator that generates fold index tuples.

    FoldIndex creates a generator that returns a tuple of stop and start
    positions to be used for numpy array slicing [stop:start]. Note that
    slicing works well for the test set, but for the training set it is
    recommended to concatenate the index for training data that comes before
    the current test set with the index for the training data that comes after.
    This can easily be achieved with::

        for train_tup, test_tup in self.generate():
            train_slice = numpy.hstack([numpy.arange(t0, t1) for t0, t1 in
                                      train_tup])

            xtrain, xtest = X[train_slice], X[test_tup[0]:test_tup[1]]

    Warnings
    --------
    Simple clicing (i.e. ``X[start:stop]`` generally does not work for the
    train set, which often requires concatenating the train index range
    below the current test set, and the train index range above the current
    test set. To build get a training index, use ::

        ``hstack([np.arange(t0, t1) for t0, t1 in train_index_tuples])``.

    See Also
    --------
    :class:`BlendIndex`, :class:`SubsetIndex`

    Parameters
    ----------
    folds : int (default = 2)
        Number of splits to create in each partition. ``folds`` can
        not be 1 if ``n_partition > 1``. Note that if ``folds = 1``,
        both the train and test set will index the full data.

    X : array-like of shape [n_samples,] , optional
        the training set to partition. The training label array is also,
        accepted, as only the first dimension is used. If ``X`` is not
        passed at instantiation, the ``fit`` method must be called before
        ``generate``, or ``X`` must be passed as an argument of
        ``generate``.

    raise_on_exception : bool (default = True)
        whether to warn on suspicious slices or raise an error.

    Examples
    --------

    Creating arrays of folds and checking overlap

    >>> import numpy as np
    >>> from mlens.index import FoldIndex
    >>> X = np.arange(10)
    >>> print("Data set: %r" % X)
    >>> print()
    >>>
    >>> idx = FoldIndex(4, X)
    >>>
    >>> for train, test in idx.generate(as_array=True):
    ...     print('TRAIN IDX: %32r | TEST IDX: %16r' % (train, test))
    >>>
    >>> print()
    >>>
    >>> for train, test in idx.generate(as_array=True):
    ...     print('TRAIN SET: %32r | TEST SET: %16r' % (X[train], X[test]))
    >>>
    >>> for train_idx, test_idx in idx.generate(as_array=True):
    ...     assert not any([i in X[test_idx] for i in X[train_idx]])
    >>>
    >>> print()
    >>>
    >>> print("No overlap between train set and test set.")
    Data set: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    TRAIN IDX:     array([3, 4, 5, 6, 7, 8, 9]) | TEST IDX: array([0, 1, 2])
    TRAIN IDX:     array([0, 1, 2, 6, 7, 8, 9]) | TEST IDX: array([3, 4, 5])
    TRAIN IDX:  array([0, 1, 2, 3, 4, 5, 8, 9]) | TEST IDX:    array([6, 7])
    TRAIN IDX:  array([0, 1, 2, 3, 4, 5, 6, 7]) | TEST IDX:    array([8, 9])
    TRAIN SET:     array([3, 4, 5, 6, 7, 8, 9]) | TEST SET: array([0, 1, 2])
    TRAIN SET:     array([0, 1, 2, 6, 7, 8, 9]) | TEST SET: array([3, 4, 5])
    TRAIN SET:  array([0, 1, 2, 3, 4, 5, 8, 9]) | TEST SET:    array([6, 7])
    TRAIN SET:  array([0, 1, 2, 3, 4, 5, 6, 7]) | TEST SET:    array([8, 9])
    No overlap between train set and test set.

    Passing ``folds = 1`` without raising exception:

    >>> import numpy as np
    >>> from mlens.index import FoldIndex
    >>> X = np.arange(3)
    >>> print("Data set: %r" % X)
    >>> print()
    >>>
    >>> idx = FoldIndex(1, X, raise_on_exception=False)
    >>>
    >>> for train, test in idx.generate(as_array=True):
    ...     print('TRAIN IDX: %10r | TEST IDX: %10r' % (train, test))
    /../mlens/base/indexer.py:167: UserWarning: 'folds' is 1, will return
    full index as both training set and test set.
    warnings.warn("'folds' is 1, will return full index as "
    Data set: array([0, 1, 2])
    TRAIN IDX: array([0, 1, 2]) | TEST IDX: array([0, 1, 2])
    """

    def __init__(self, folds=2, X=None, raise_on_exception=True):
        super(FoldIndex, self).__init__()
        self.folds = folds
        self.raise_on_exception = raise_on_exception

        if X is not None:
            self.fit(X)

    def fit(self, X, y=None, job=None):
        """Method for storing array data.

        Parameters
        ----------
        X : array-like of shape [n_samples, optional]
            array to _collect dimension data from.
        y : None
            for compatibility
        job : None
            for compatibility

        Returns
        -------
        instance :
            indexer with stores sample size data.
        """
        n = X.shape[0]
        check_full_index(n, self.folds, self.raise_on_exception)

        self.n_test_samples = self.n_samples = n
        self.__fitted__ = True
        return self

    def _gen_indices(self):
        """Generate K-Fold iterator."""
        return super(FoldIndex, self)._gen_indices() class Data(object):

    """Class for getting data."""

    def __init__(self, cls, proba, preprocessing, is_learner=False,
                 *args, **kwargs):
        self.lr = is_learner
        self.proba = proba
        self.preprocessing = preprocessing
        self.cls = cls
        self.indexer = INDEXERS[cls](*args, **kwargs)

    def get_data(self, shape, m):
        """Generate X and y data with X.
        Parameters
        ----------
        shape : tuple
            shape of data to be generated
        m : int
            length of step function for y
        Returns
        -------
        train : ndarray
            generated as a sequence of  reshaped to (LEN, WIDTH)
        labels : ndarray
            generated as a step-function with a step every ``m``. As such,
            each prediction fold during cross-validation have
            a unique level value.
        """
        s = shape[0]
        w = shape[1]

        train = np.array(range(int(s * w)), dtype='float').reshape((s, w))
        train += 1

        labels = np.zeros(train.shape[0])

        if not self.proba:
            increment = 10
            for i in range(0, s, m):
                labels[i:i + m] += increment

                increment += 10

        else:
            labels = np.arange(train.shape[0]) % 2

        return train, labels

    def ground_truth(self, X, y, subsets=1, feature_prop=None):
        """Set up an experiment ground truth.
        Returns
        -------
        F : ndarray
            Full prediction array (train errors)
        P : ndarray
            Folded prediction array (test errors)
        Raises
        ------
        AssertionError :
            Raises assertion error if any weight vectors overlap or any
            predictions (as measured by columns in F and P) are judged to be
            equal.
        """
        P, weights_p = self._full_ests(X, y, subsets)
        if feature_prop:
            P = np.hstack([X[:, :feature_prop], P])

        if self.indexer.__class__.__name__.lower() == 'fullindex':
            return (P, weights_p), (P, weights_p)

        F, weights_f = self._folded_ests(X, y, subsets)
        if feature_prop:
            r = X.shape[0] - F.shape[0]
            F = np.hstack([X[r:][:, :feature_prop], F])

        return (F, weights_f), (P, weights_p)

    def _set_up_est(self, y):
        """Get estimators, preprocessing, num_ests, predict attr to use."""
        attr = 'predict_proba' if self.proba else 'predict'
        labels = len(np.unique(y)) if self.proba else 1

        if self.preprocessing:
            if self.lr:
                est = OLS() if not self.proba else LogisticRegression()
                ests = {'sc': [('est', est)]}
                prep = {'sc': [('scale', Scale())]}
            else:
                ests = ESTIMATORS_PROBA if self.proba else ESTIMATORS
                prep = PREPROCESSING

            n_ests = 0
            for case in ests:
                for _ in ests[case]:
                    n_ests += 1

        else:
            if self.lr:
                est = OLS() if not self.proba else LogisticRegression()
                ests = {'no-case': [('ols', est)]}
            else:
                ests = {'no-case': ECM_PROBA if self.proba else ECM}
            prep = {'no-case': []}

            n_ests = len(ests['no-case'])

        self.classes_ = labels
        self.n_pred = n_ests
        if self.cls == 'subsemble':
            self.n_pred *= self.indexer.partitions

        return ests, prep, n_ests, attr, labels

    def _folded_ests(self, X, y, subsets=1):
        """Build ground truth for each fold."""
        ests, prep, n_ests, attr, labels = self._set_up_est(y)

        t = [t for _, t in self.indexer.generate(X, True)]
        t = np.unique(np.hstack(t))
        t.sort()

        weights = []
        F = np.zeros((len(t), n_ests * subsets * labels), dtype=np.float)

        col_id = {}
        col_ass = 0

        # Sort at every occasion
        vps = [(c, e[0])
               for c in prep.keys()
               for e in ests[c]]

        for meta_key in sorted(vps):
            key, est_name = meta_key
            est = dict(ests[key])[est_name]
            for i, (tri, tei) in enumerate(self.indexer.generate(X, True)):

                if subsets > 1:
                    i = i // self.indexer.folds
                else:
                    i = 0

                if '%s-%s-%s' % (i, key, est_name) not in col_id:
                    col_id['%s-%s-%s' % (i, key, est_name)] = col_ass
                    col_ass += labels

                xtrain = X[tri]
                xtest = X[tei]

                # Transform inputs
                for _, tr in prep[key]:
                    t = clone(tr)
                    xtrain = t.fit_transform(xtrain)
                    xtest = t.transform(xtest)

                # Fit estimator
                e = clone(est).fit(xtrain, y[tri])
                w = e.coef_
                weights.append(w)

                # Get out-of-sample predictions
                p = getattr(e, attr)(xtest)

                rebase = X.shape[0] - F.shape[0]
                fix = tei - rebase

                if labels == 1:
                    F[fix, col_id['%s-%s-%s' % (i, key, est_name)]] = p
                else:
                    c = col_id['%s-%s-%s' % (i, key, est_name)]
                    F[np.ix_(fix, np.arange(c, c + labels))] = p

        return F, weights

    def _full_ests(self, X, y, subsets=1):
        """Get ground truth for train and predict on full data."""
        ests, prep, n_ests, attr, labels = self._set_up_est(y)
        if subsets == 1:
            indexer = DummyPartition(np.arange(X.shape[0]))
        else:
            indexer = self.indexer
            indexer.fit(X)

        P = np.zeros((X.shape[0], n_ests * subsets * labels), dtype=np.float)

        weights = list()
        col_id = {}
        col_ass = 0

        vps = ['%s__%s' % (c, e[0])
               for c in prep.keys()
               for e in ests[c]]
        for meta_key in sorted(vps):
            key, est_name = meta_key.split('__')
            est = dict(ests[key])[est_name]
            for i, tri in enumerate(indexer.partition(as_array=True)):
                if '%s-%s-%s' % (i, key, est_name) not in col_id:
                    col_id['%s-%s-%s' % (i, key, est_name)] = col_ass
                    col_ass += labels

                # Transform input
                xtrain = X[tri]
                ytrain = y[tri]

                xtest = X

                for _, tr in prep[key]:
                    t = clone(tr)
                    xtrain = t.fit_transform(xtrain)
                    xtest = t.transform(xtest)

                # Fit est
                e = clone(est).fit(xtrain, ytrain)
                w = e.coef_
                weights.append(w.tolist())

                # Predict
                p = getattr(e, attr)(xtest)
                c = col_id['%s-%s-%s' % (i, key, est_name)]
                if labels == 1:
                    P[:, c] = p
                else:
                    P[:, c:c + labels] = p

        return P, weights ESTIMATORS = {'sc': [('offs', OLS(offset=2)), ('null', OLS())],
              'no': [('offs', OLS(offset=2)), ('null', OLS())]} PREPROCESSING = {'no': [], 'sc': [('scale', Scale())]} class OLS(BaseEstimator):

    """No frills vanilla OLS estimator implemented through the normal equation.

    MWE of a Scikit-learn estimator.

    OLS is a simple estimator designed to allow for total control over
    predictions in unit testing. It implements OLS through the Normal
    Equation, no learning takes place. The ``offset`` option allows
    the user to offset weights by a scalar value, if different instances
    should be differentiated in their predictions.

    Parameters
    ----------
    offset : float (default = 0)
        scalar value to add to the coefficient vector after fitting.

    Examples
    --------

    Asserting the OLS passes the Scikit-learn estimator test

    >>> from sklearn.utils.estimator_checks import check_estimator
    >>> from mlens.utils.dummy import OLS
    >>> check_estimator(OLS)

    OLS comparison with Scikit-learn's LinearRegression

    >>> from numpy.testing import assert_array_equal
    >>> from mlens.utils.dummy import OLS
    >>> from sklearn.linear_model import LinearRegression
    >>> from sklearn.datasets import load_boston
    >>> X, y = load_boston(True)
    >>>
    >>> lr = LinearRegression(False)
    >>> lr.fit(X, y)
    >>>
    >>> ols = OLS()
    >>> ols.fit(X, y)
    >>>
    >>> assert_array_equal(lr.coef_, ols.coef_)
    """

    def __init__(self, offset=0):
        self.offset = offset

    def fit(self, X, y):
        """Fit coefficient vector."""
        X, y = check_X_y(X, y, dtype='float64')
        y = np.asarray(y, dtype=X.dtype)

        O = np.linalg.lstsq(X, y, rcond=None)

        self.coef_ = O[0] + self.offset
        self.resid_ = O[1]

        return self

    def predict(self, X):
        """Predict with fitted weights."""
        if not hasattr(self, 'coef_'):
            raise NotFittedError("Estimator not fitted. Call 'fit' first.")

        X = check_array(X, accept_sparse=False)

        return np.dot(X, self.coef_.T) class Scale(BaseEstimator, TransformerMixin):

    """Removes the a learnt mean in a column-wise manner in an array.

    MWE of a Scikit-learn transformer, to be used for unit-tests of ensemble
    classes.

    Parameters
    ----------
    copy : bool (default = True)
        Whether to copy X before transforming.

    Examples
    --------
    Asserting :class:`Scale` passes the Scikit-learn estimator test

    >>> from sklearn.utils.estimator_checks import check_estimator
    >>> from mlens.utils.dummy import Scale
    >>> check_estimator(Scale)

    Scaling elements

    >>> from numpy import arange
    >>> from mlens.utils.dummy import Scale
    >>> X = arange(6).reshape(3, 2)
    >>> X[:, 1] *= 2
    >>> print('X:')
    >>> print('%r' % X)
    >>> print('Scaled:')
    >>> S = Scale().fit_transform(X)
    >>> print('%r' % S)
    X:
    array([[ 0,  2],
           [ 2,  6],
           [ 4, 10]])
    Scaled:
    array([[-2., -4.],
           [ 0.,  0.],
           [ 2.,  4.]])
    """

    def __init__(self, copy=True):
        self.copy = copy
        self.__is_fitted__ = False

    __init__.deprecated_original = __init__

    def fit(self, X, y=None):
        """Estimate mean.

        Parameters
        ----------
        X : array-like
            training data to fit transformer on.

        y : array-like or None
            pass through for pipeline.
        """
        X = check_array(X, accept_sparse=False)
        self.__is_fitted__ = True
        self.mean_ = X.mean(axis=0)
        return self

    def transform(self, X):
        """Transform array by adjusting all elements with scale.

        Parameters
        ----------
        X : ndarray
            matrix to transform.
        """
        if not self.__is_fitted__:
            raise NotFittedError("Estimator not fitted.")
        X = check_array(X, accept_sparse=False)
        Xt = X.copy() if self.copy else X
        return Xt - self.mean_ class NotFittedError(ValueError, AttributeError):

    """Error class for an ensemble or estimator that is not fitted yet

    Raised when some method has been called that expects the instance to be
    fitted.
    """ class ParameterChangeWarning(UserWarning):

    """Warning for different params in blueprint estimator and fitted copy.

    .. versionadded:: 0.2.2
    """ def make_group(indexer, estimators, preprocessing,
               learner_kwargs=None, transformer_kwargs=None, name=None):
    """Creating a :class:`Group` from a set learners and transformers

    Utility function for creating mapping a set of estimators and
    preprocessing pipelines to a :class:`Group` of
    :class:`~mlens.parallel.learner.Learner` and
    :class:`~mlens.parallel.learner.Transformer` instances.

    Parameters
    ----------
    indexer : instance or None, default = None
        Indexer instance to use. See :obj:`~mlens.index` for details.

    estimators : dict of lists or list of estimators.
        If ``preprocessing`` is ``None`` or ``list``, ``estimators`` should
        be a ``list``. The list can either contain estimator instances,
        named tuples of estimator instances, or a combination of both. ::

            option_1 = [estimator_1, estimator_2]
            option_2 = [("est-1", estimator_1), ("est-2", estimator_2)]
            option_3 = [estimator_1, ("est-2", estimator_2)]

        If different preprocessing pipelines are desired, a dictionary
        that maps estimators to preprocessing pipelines must be passed.
        The names of the estimator dictionary must correspond to the
        names of the estimator dictionary. ::

            preprocessing_cases = {"case-1": [trans_1, trans_2].
                                   "case-2": [alt_trans_1, alt_trans_2]}

            estimators = {"case-1": [est_a, est_b].
                          "case-2": [est_c, est_d]}

        The lists for each dictionary entry can be any of ``option_1``,
        ``option_2`` and ``option_3``.

    preprocessing : dict of lists or list, optional, default = None
        preprocessing pipelines for given layer. If
        the same preprocessing applies to all estimators, ``preprocessing``
        should be a list of transformer instances. The list can contain the
        instances directly, named tuples of transformers,
        or a combination of both. ::

            option_1 = [transformer_1, transformer_2]
            option_2 = [("trans-1", transformer_1),
                        ("trans-2", transformer_2)]
            option_3 = [transformer_1, ("trans-2", transformer_2)]

        If different preprocessing pipelines are desired, a dictionary
        that maps preprocessing pipelines must be passed. The names of the
        preprocessing dictionary must correspond to the names of the
        estimator dictionary. ::

            preprocessing_cases = {"case-1": [trans_1, trans_2].
                                   "case-2": [alt_trans_1, alt_trans_2]}

            estimators = {"case-1": [est_a, est_b].
                          "case-2": [est_c, est_d]}

        The lists for each dictionary entry can be any of ``option_1``,
        ``option_2`` and ``option_3``.

    transformer_kwargs : dict, optional
        Keyword arguments to pass to the
        :class:`~mlens.parallel.learner.Transformer` instances.

    learner_kwargs : dict, optional
        Keyword arguments to pass to the
        :class:`~mlens.parallel.learner.Learner` instances.

    name : str, optional
        Name of group. Should be unique.

    """
    preprocessing, estimators = check_instances(estimators, preprocessing)

    if learner_kwargs is None:
        learner_kwargs = {}
    if transformer_kwargs is None:
        transformer_kwargs = {}

    transformers = [Transformer(estimator=Pipeline(tr, return_y=True),
                                name=case_name, **transformer_kwargs)
                    for case_name, tr in preprocessing]

    learners = [Learner(estimator=est, preprocess=case_name,
                        name=learner_name, **learner_kwargs)
                for case_name, learner_name, est in estimators]

    group = Group(indexer=indexer, learners=learners,
                  transformers=transformers, name=name)
    return group class LearnerEstimator(BaseEstimator):

    """Learner estimator

    Wraps an estimator in a cross-validation strategy.

    Parameters
    __________
    estimator: obj
        estimator to construct learner from.

    indexer: str, obj
        a cross-validation strategy. Either a :mod`mlens.index` indexer
        instance or a string.

    name: str, optional
        name of learner. If ``preprocessing`` is not ``None``,
        the name of the transformer will be prepended. If not specified, the
        name of the learner will be the name of the estimator in lower case.

    scorer: func, optional
        function to use for scoring predictions during cross-validated
        fitting.

    verbose: bool, int (default = False)
        whether to report completed fits.

    backend: str (default='threading')
        parallel backend. One of ``'treading'``, ``'multiprocessing'`` and
        ``'sequential'``.

    n_jobs: int (default=-1)
        degree of concurrency. Set to ``-1`` for maximum and ``1`` for
        sequential processing.
    """

    def __init__(self, estimator, indexer, verbose=False, scorer=None,
                 backend=None, n_jobs=-1, dtype=None):
        super(LearnerEstimator, self).__init__()

        self.estimator = estimator
        self.indexer = indexer
        self.verbose = verbose
        self.scorer = scorer
        self.backend = backend
        self.n_jobs = n_jobs
        self.dtype = dtype

        self.__static__.extend(['estimator', 'indexer'])

    __init__.deprecated_original = __init__

    def _build(self):
        """Build backend"""
        lr_kwargs = {'attr': 'predict', 'verbose': self.verbose,
                     'scorer': self.scorer, 'backend': self.backend,
                     'n_jobs': self.n_jobs, 'dtype': self.dtype}

        idx = clone(self.indexer)
        est = clone(self.estimator)
        group = make_group(idx, est, None, learner_kwargs=lr_kwargs)

        self._backend = group.learners[0]
        self._store_static_params() class TransformerEstimator(BaseEstimator):
    """Transformer estimator

    Wraps an preprocessing pipeline in a cross-validation strategy.

    Parameters
    __________
    preprocessing: obj
        preprocessing pipeline to construct transformer from.

    indexer: str, obj
        a cross-validation strategy. Either a :mod`mlens.index` indexer
        instance or a string.

    name: str
        name of transformer.

    verbose: bool, int (default = False)
        whether to report completed fits.

    backend: str (default='threading')
        parallel backend. One of ``'treading'``, ``'multiprocessing'`` and
        ``'sequential'``.

    n_jobs: int (default=-1)
        degree of concurrency. Set to ``-1`` for maximum and ``1`` for
        sequential processing.
    """

    def __init__(self, preprocessing, indexer, verbose=False,
                 backend=None, n_jobs=-1, dtype=None):
        super(TransformerEstimator, self).__init__()

        self.preprocessing = preprocessing
        self.indexer = indexer
        self.verbose = verbose
        self.backend = backend
        self.n_jobs = n_jobs
        self.dtype = dtype

        self.__static__.extend(['preprocessing', 'indexer'])

    __init__.deprecated_original = __init__

    def _build(self):
        """Build backend"""
        tr_kwargs = {'backend': self.backend,
                     'n_jobs': self.n_jobs, 'verbose': self.verbose,
                     'dtype': self.dtype}

        idx = clone(self.indexer)
        prep = clone(self.preprocessing)

        group = make_group(idx, None, prep, transformer_kwargs=tr_kwargs)
        self._backend = group.transformers[0]
        self._store_static_params() class LayerEnsemble(BaseEstimator):

    """One-layer ensemble

    An ensemble of estimators and preprocessing pipelines in one layer. Takes
    an input X and generates an output P. Assumes all preprocessing pipelines
    and all estimators are independent.

    Parameters
    ----------
    groups: list,
        list of :class:`Group` instances to build layer with.

    proba: bool (default = False)
        whether to call `predict_proba` on the estimators in the layer when
        predicting.

    propagate_features: list, range, optional
        Features to propagate from the input array to the output array.
        Carries input features to the output of the layer, useful for
        propagating original data through several stacked layers. Propagated
        features are stored in the left-most columns.

    verbose: int or bool (default = False)
        level of verbosity.

            - ``verbose = 0`` silent (same as ``verbose = False``)

            - ``verbose = 1`` messages at start and finish \
              (same as ``verbose = True``)

            - ``verbose = 2`` messages for preprocessing and estimators

            - ``verbose = 3`` messages for completed job

        If ``verbose >= 10`` prints to ``sys.stderr``, else ``sys.stdout``.

    shuffle: bool (default = False)
        Whether to shuffle data before fitting layer.

    random_state: obj, int, optional
        Random seed number to use for shuffling inputs
    """

    def __init__(
            self, groups, propagate_features=None, shuffle=False, n_jobs=-1,
            backend=None, verbose=False, random_state=None, dtype=None):
        super(LayerEnsemble, self).__init__()
        self.groups = groups if isinstance(groups, list) else [groups]
        self.propagate_features = propagate_features
        self.shuffle = shuffle
        self.random_state = random_state
        self.verbose = verbose
        self.backend = backend
        self.n_jobs = n_jobs
        self.dtype = dtype

        self.__static__.extend(['groups'])

    __init__.deprecated_original = __init__

    def _build(self):
        """Build Backend"""
        self._backend = Layer(
            propagate_features=self.propagate_features, dtype=self.dtype,
            shuffle=self.shuffle, random_state=self.random_state,
            verbose=self.verbose, stack=clone(self.groups))
        self._store_static_params()

    def push(self, *groups):
        """Push more groups onto stack

        Parameters
        ----------
        *groups : objects
            :class:`Group` instance(s) to push onto stack. Order is preserved
            (see pop).
        """
        check_stack(groups, self.groups)
        for group in groups:
            self.groups.append(group)
        return self

    def pop(self, idx):
        """Pop group

        Parameters
        ----------
        idx : int
            index of group to be popped in stack (0-based indexing).
            Equivalent to [group_0, group_1, ..., group_n].pop(idx)
        """
        return self.groups.pop(idx)

    def get_params(self, deep=True):
        out = super(LayerEnsemble, self).get_params(deep=deep)
        if not deep:
            return out

        for group in self.groups:
            for g in group:
                for key, value in g.get_params(deep=deep).items():
                    out["%s__%s" % (g.name, key)] = value
                    out[g.name] = g
        return out def clone(estimator, safe=True):
    """Constructs a new estimator with the same parameters.
    Clone does a deep copy of the model in an estimator
    without actually copying attached data. It yields a new estimator
    with the same parameters that has not been fit on any data.
    Parameters
    ----------
    estimator : estimator object, or list, tuple or set of objects
        The estimator or group of estimators to be cloned
    safe : boolean, optional
        If safe is false, clone will fall back to a deep copy on objects
        that are not estimators.
    """
    estimator_type = type(estimator)
    # XXX: not handling dictionaries
    if estimator_type in (list, tuple, set, frozenset):
        return estimator_type([clone(e, safe=safe) for e in estimator])
    elif not hasattr(estimator, 'get_params'):
        if not safe:
            return copy.deepcopy(estimator)
        else:
            raise TypeError("Cannot clone object '%s' (type %s): "
                            "it does not seem to be a scikit-learn estimator "
                            "as it does not implement a 'get_params' methods."
                            % (repr(estimator), type(estimator)))
    klass = estimator.__class__
    new_object_params = estimator.get_params(deep=False)
    for name, param in six.iteritems(new_object_params):
        new_object_params[name] = clone(param, safe=False)
    new_object = klass(**new_object_params)
    params_set = new_object.get_params(deep=False)

    # quick sanity check of the parameters of the clone
    for name in new_object_params:
        param1 = new_object_params[name]
        param2 = params_set[name]
        if param1 is param2:
            # this should always happen
            continue
        if isinstance(param1, np.ndarray):
            # For most ndarrays, we do not test for complete equality
            if not isinstance(param2, type(param1)):
                equality_test = False
            elif (param1.ndim > 0
                    and param1.shape[0] > 0
                    and isinstance(param2, np.ndarray)
                    and param2.ndim > 0
                    and param2.shape[0] > 0):
                equality_test = (
                    param1.shape == param2.shape
                    and param1.dtype == param2.dtype
                    and (_first_and_last_element(param1) ==
                         _first_and_last_element(param2))
                )
            else:
                equality_test = np.all(param1 == param2)
        elif sparse.issparse(param1):
            # For sparse matrices equality doesn't work
            if not sparse.issparse(param2):
                equality_test = False
            elif param1.size == 0 or param2.size == 0:
                equality_test = (
                    param1.__class__ == param2.__class__
                    and param1.size == 0
                    and param2.size == 0
                )
            else:
                equality_test = (
                    param1.__class__ == param2.__class__
                    and (_first_and_last_element(param1) ==
                         _first_and_last_element(param2))
                    and param1.nnz == param2.nnz
                    and param1.shape == param2.shape
                )
        else:
            # fall back on standard equality
            equality_test = param1 == param2
        if equality_test:
            warnings.warn("Estimator %s modifies parameters in __init__."
                          " This behavior is deprecated as of 0.18 and "
                          "support for this behavior will be removed in 0.20."
                          % type(estimator).__name__, DeprecationWarning)
        else:
            raise RuntimeError('Cannot clone object %s, as the constructor '
                               'does not seem to set parameter %s' %
                               (estimator, name))

    return new_object
import numpy as np
from mlens.index import FullIndex, FoldIndex
from mlens.testing import Data
from mlens.testing.dummy import ESTIMATORS, PREPROCESSING
from mlens.utils.dummy import OLS, Scale
from mlens.utils.exceptions import NotFittedError, ParameterChangeWarning
from mlens.parallel import make_group
from mlens.estimators import LearnerEstimator, TransformerEstimator, LayerEnsemble
from mlens.externals.sklearn.base import clone
    from sklearn.utils.estimator_checks import check_estimator
    from sklearn.utils.validation import check_X_y, check_array
"""ML-ENSEMBLE

Test classes.
"""

try:
    run_sklearn = True
except ImportError:
    check_estimator = None
    run_sklearn = False

data = Data('stack', False, True)
X, y = data.get_data((25, 4), 3)
(F, wf), (P, wp) = data.ground_truth(X, y)

Est = LayerEnsemble

est = LayerEnsemble(make_group(FoldIndex(), ESTIMATORS, PREPROCESSING),