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 def check_ensemble_build(inst, attr='stack'):
    """Check that layers have been instantiated."""
    if not hasattr(inst, attr):
        # No layer container. This should not happen!

        msg = ("No layer class attached to instance (%s). (Cannot find a "
               "'Sequential' class instance as attribute [%s].)")

        raise AttributeError(msg % (inst.__class__.__name__, attr))

    if getattr(inst, attr) is None:
        # No layers instantiated.

        if not getattr(inst, 'raise_on_exception', True):

            msg = "No Layers in instance (%s). Nothing to fit / predict."
            warnings.warn(msg % inst.__class__.__name__,
                          LayerSpecificationWarning)
            # For PASSED flag on soft fail
            return False

        else:

            msg = ("No Layers in instance (%s). Add layers before calling "
                   "'fit' and 'predict'.")
            raise LayerSpecificationError(msg % inst.__class__.__name__)

    # For PASSED flag
    return True def assert_valid_estimator(instance):
    """Assert that an instance has a ``get_params`` and ``fit`` method."""
    has_get_params = hasattr(instance, 'get_params')
    has_fit = hasattr(instance, 'fit')

    if not has_get_params:
        raise TypeError("'%s' does not appear to be a valid"
                        " estimator as it does not implement a "
                        "'get_params' method. Type: "
                        "%s" % (instance, type(instance)))

    if not has_fit:
        raise TypeError("'%s' does not appear to be a valid"
                        " estimator as it does not implement a "
                        "'fit' method. Type: "
                        "%s" % (instance, type(instance)))

    try:
        instance.get_params()
    except TypeError:
        raise TypeError("'%s' does not appear to be an instance of an "
                        "estimator class, but the class itself." % instance) def assert_correct_format(estimators, preprocessing):
    """Initial check to assert layer can be constructed."""
    if (preprocessing is None) or (not isinstance(preprocessing, dict)):
        if isinstance(estimators, dict):
            # Either no or uniform preprocessing, estimators should be list
            msg = ("Preprocessing is either 'None' or 'list': 'estimators' "
                   "must be of type 'list'. Got %s.")
            raise LayerSpecificationError(msg % type(estimators))
    else:
        # Check that both estimators and preprocessing are dicts
        if not isinstance(preprocessing, dict):
            msg = ("Unexpected format of 'preprocessing'. Needs to be "
                   " of type 'None' or 'list' or 'dict'. Got %s .")
            raise LayerSpecificationError(msg % type(preprocessing))

        if not isinstance(estimators, dict):
            msg = ("Unexpected format of 'estimators'. Needs to be "
                   "'dict' when preprocessing is 'dict'. Got %s.")
            raise LayerSpecificationError(msg % type(estimators))

        # Check that keys overlap
        prep_check = [key in preprocessing for key in estimators]
        est_check = [key in estimators for key in preprocessing]

        if not all(est_check):
            msg = ("Too few estimator cases to match preprocessing cases:\n"
                   "estimator cases:     %r\npreprocessing cases: %r")
            raise LayerSpecificationError(msg % (list(estimators),
                                                 list(preprocessing)))
        if not all(prep_check):
            msg = ("Too few preprocessing cases to match estimators cases:\n"
                   "preprocessing cases: %r\nestimator cases:     %r")
            raise LayerSpecificationError(msg % (list(preprocessing),
                                                 list(estimators))) def check_initialized(inst):
    """Check if a ParallelProcessing instance is initialized properly."""
    if not inst.__initialized__:
        msg = "ParallelProcessing is not initialized. Call " \
              "'initialize' before calling 'fit'."
        raise ParallelProcessingError(msg)

    if getattr(inst, '__fitted__', None):
        if inst.layers.raise_on_exception:
            raise ParallelProcessingError("This instance is already "
                                          "fitted and its parallel "
                                          "processing jobs has not been "
                                          "terminated. To refit "
                                          "instance, call 'terminate' "
                                          "before calling 'fit'.")
        else:
            warnings.warn("This instance is already "
                          "fitted and its parallel "
                          "processing job has not been "
                          "terminated. Will refit using previous job's cache.",
                          ParallelProcessingWarning) 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 EstimatorContainer(object):

    """Class for generating architectures of various types."""

    def __init__(self):
        pass

    def get_learner(self, kls, proba, preprocessing, *args, **kwargs):
        """Generate learner and transformer

        Parameters
        ----------
        kls : str
            class type

        proba : bool
            whether to set ``proba`` to ``True``

        preprocessing : bool
            layer with preprocessing cases
        """
        indexer, kwargs = self.load_indexer(kls, args, kwargs)
        est = OLS() if not proba else LogisticRegression()

        if preprocessing:
            transformer = Transformer(
                estimator=Pipeline(Scale(), return_y=True),
                indexer=indexer, name='sc')
        else:
            transformer = None

        learner = Learner(estimator=est, indexer=indexer,
                          preprocess='sc' if transformer else None,
                          scorer=mae if not proba else None,
                          name=kls, proba=proba)

        return learner, transformer

    def get_layer(self, kls, proba, preprocessing, *args, **kwargs):
        """Generate a layer instance.

        Parameters
        ----------
        kls : str
            class type

        proba : bool
            whether to set ``proba`` to ``True``

        preprocessing : bool
            layer with preprocessing cases
        """
        indexer, kwargs = self.load_indexer(kls, args, kwargs)

        learner_kwargs = {'proba': kwargs.pop('proba', proba),
                          'scorer': kwargs.pop('scorer', None)}

        layer = Layer(name='layer', dtype=np.float64, **kwargs)

        if preprocessing:
            ests = ESTIMATORS_PROBA if proba else ESTIMATORS
            prep = PREPROCESSING
        else:
            ests = ECM_PROBA if proba else ECM
            prep = []

        group = make_group(indexer, ests, prep,
                           learner_kwargs=learner_kwargs, name='group')
        layer.push(group)
        return layer

    def get_layer_estimator(self, kls, proba, preprocessing, *args, **kwargs):
        """Generate a layer estimator instance.

        Parameters
        ----------
        kls : str
            class type

        proba : bool
            whether to set ``proba`` to ``True``

        preprocessing : bool
            layer with preprocessing cases
        """
        indexer, kwargs = self.load_indexer(kls, args, kwargs)

        learner_kwargs = {'proba': kwargs.pop('proba', proba),
                          'scorer': kwargs.pop('scorer', None)}

        if preprocessing:
            ests = ESTIMATORS_PROBA if proba else ESTIMATORS
            prep = PREPROCESSING
        else:
            ests = ECM_PROBA if proba else ECM
            prep = []

        group = make_group(indexer, ests, prep,
                           learner_kwargs=learner_kwargs)
        layer = LayerEnsemble([group], dtype=np.float64, **kwargs)
        return layer

    def get_sequential(self, kls, proba, preprocessing, *args, **kwargs):
        """Generate a layer container instance.

        Parameters
        ----------
        kls : str
            class type

        proba : bool
            whether to set ``proba`` to ``True``

        preprocessing : bool
            layer with preprocessing cases
        """
        lyr = self.get_layer(kls, proba, preprocessing, *args, **kwargs)
        lyr.name += '-1'
        seq = Sequential()
        return seq.push(lyr)

    @staticmethod
    def load_indexer(kls, args, kwargs):
        """Load indexer and return remaining kwargs"""
        indexer = INDEXERS[kls]
        idx_kwargs = dict()
        for var in indexer.__init__.__code__.co_varnames:
            if var in kwargs:
                idx_kwargs[var] = kwargs.pop(var)
        indexer = indexer(*args, **idx_kwargs)
        return indexer, kwargs class LayerSpecificationError(TypeError, ValueError):

    """Error class for incorrectly specified layers.""" class LayerSpecificationWarning(UserWarning):

    """Warning class if layer has been specified in a dubious form.

    This warning is raised when the input does not look like expected, but
    is not fatal and a best guess of how to fix it will be made.
    """ 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 ParallelProcessingError(AttributeError, RuntimeError):

    """Error class for fatal errors related to :class:`ParallelProcessing`.

    Can be subclassed for more specific error classes.
    """ class ParallelProcessingWarning(UserWarning):

    """Warnings related to methods on :class:`ParallelProcessing`.

    Can be subclassed for more specific warning classes.
    """
import numpy as np
from mlens.externals.sklearn.base import clone
from mlens.utils.checks import check_ensemble_build, assert_valid_estimator, \
    assert_correct_format, check_initialized
from mlens.utils.dummy import OLS, Scale
from mlens.testing import EstimatorContainer
from mlens.utils.exceptions import LayerSpecificationError, \
    LayerSpecificationWarning, NotFittedError, ParallelProcessingError, \
    ParallelProcessingWarning
"""ML-ENSEMBLE

author: Sebastian Flennerhag
:copyirgh: 2017
:licence: MIT
"""



lg = EstimatorContainer()

LAYER = lg.get_layer('stack', False, True)
LAYER_CONTAINER = lg.get_sequential('stack', False, True)


class Tmp(object):

    """Temporary class for mimicking ParallelProcessing status."""

    def __init__(self, lyr, __initialized__, __fitted__):
        self.__initialized__ = __initialized__
        self.__fitted__ = __fitted__
        self.layers = lyr


class Lyr(object):

    """Temporary layer class for storing raise on exception."""

    def __init__(self, raise_on_exception):
        self.raise_on_exception = raise_on_exception


def test_check_ensemble_build_passes():
    """[Utils] check_ensemble_build : passes on default dummy LC."""

    FLAG = check_ensemble_build(LAYER_CONTAINER)