class BaseEstimator(ParamMixin, _BaseEstimator, BaseParallel):

    """Base Parallel Estimator class

    Modified Scikit-learn class to handle backend params that we want to
    protect from changes.
    """

    def __init__(self, *args, **kwargs):
        super(BaseEstimator, self).__init__(*args, **kwargs)
        self.__static__ = list()
        self._static_fit_params = dict()

    def get_params(self, deep=True):
        out = super(BaseEstimator, self).get_params(deep=deep)
        for name in BaseBackend.__init__.__code__.co_varnames:
            if name not in ['self']:
                out[name] = getattr(self, name)
        return out

    @property
    @abstractmethod
    def __fitted__(self):
        """Fit status"""
        return self._check_static_params() class Learner(ProbaMixin, BaseNode):

    """Learner

    Wrapper for base learners.

    Parameters
    __________
    estimator : obj
        estimator to construct learner from

    preprocess : str, obj
        preprocess transformer. Pass either the string
        cache reference or the transformer instance. If the latter,
        the :attr:`preprocess` will refer to the transformer name.

    name : str
        name of learner. If ``preprocess`` is not ``None``,
        the name will be prepended to ``preprocess__name``.

    attr : str (default='predict')
        predict attribute, typically one of 'predict' and 'predict_proba'

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

    output_columns : dict, optional
        mapping of prediction feature columns from learner to columns in
        output array. Normally, this map is ``{0: x}``, but if the ``indexer``
        creates partitions, each partition needs to be mapped:
        ``{0: x, 1: x + 1}``. Note that if ``output_columns`` are not given at
        initialization, the ``set_output_columns`` method must be called before
        running estimations.

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

    **kwargs : bool (default=True)
        Optional ParallelProcessing arguments. See :class:`BaseParallel`.
    """

    __subtype__ = SubLearner

    def __init__(self, estimator, indexer=None, name=None, preprocess=None,
                 attr=None, scorer=None, proba=False, **kwargs):
        super(Learner, self).__init__(
            name=format_name(name, 'learner', GLOBAL_LEARNER_NAMES),
            estimator=estimator, indexer=indexer, **kwargs)

        self._classes = None
        self.proba = proba
        self._scorer = scorer
        self.preprocess = preprocess
        self.n_pred = self._partitions
        self.attr = attr if attr else self._predict_attr

        # Protect preprocess against later changes
        self.__static__.append('preprocess')

    @property
    def scorer(self):
        """Copy of scorer"""
        return self._scorer

    @scorer.setter
    def scorer(self, scorer):
        """Copy of scorer"""
        self._scorer = scorer class Transformer(BaseNode):

    """Preprocessing handler.

    Wrapper for transformation pipeline.

    Parameters
    __________
    indexer : obj, None
        indexer to use for generating fits.
        Set to ``None`` to fit only on all data.

    estimator : obj
        transformation pipeline to construct learner from

    name : str
        name of learner. If ``preprocess`` is not ``None``,
        the name will be prepended to ``preprocess__name``.

    output_columns : dict, optional
        If transformer is to be used to output data, need to
        set ``output_columns``. Normally, this map is
        ``{0: x}``, but if the ``indexer``
        creates partitions, each partition needs to be mapped:
        ``{0: x, 1: x + 1}``.

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

    raise_on_exception : bool (default=True)
        whether to warn on non-fatal exceptions or raise an error.
    """

    __subtype__ = SubTransformer

    def __init__(self, estimator, indexer=None, name=None, **kwargs):
        assert_valid_pipeline(estimator)
        name = format_name(name, 'transformer', GLOBAL_TRANSFORMER_NAMES)
        super(Transformer, self).__init__(
            name=name, estimator=estimator, indexer=indexer, **kwargs)
        self.__no_output__ = True

    def _get_multiplier(self, X, y=None, alt=None):
        """Number of cols produced in prediction"""
        return X.shape[1]

    def _gen_pred(self, job, X, P, generator):
        if self.__no_output__:
            def gen():
                for o in generator:
                    yield Cache(o, self._path, self.verbose)

            return gen()
        else:
            return super(Transformer, self)._gen_pred(job, X, P, generator) def mold_objects(learners, transformers):
    """Utility for enforcing compatible setup"""
    # TODO: remove
    out = []
    for objects in [learners, transformers]:
        if objects:
            if not isinstance(objects, list):
                objects = [objects]
        out.append(objects)
    return out def transform(tr, x, y):
    """Try transforming with X and y. Else, transform with only X."""
    try:
        x = tr.transform(x)
    except TypeError:
        x, y = tr.transform(x, y)

    return x, y def check_instances(estimators=None, preprocessing=None, namespace=None):
    """Ensure estimators and preprocessing pipelines are correctly formatted

    Utility for formating estimator iterable and preprocessing iterable into
    formats accepted by a the :class:`Layer`.

    Parameters
    ----------
    estimators : iterable, optional
        estimator instances.

    preprocessing : iterable, optional
        preprocessing pipeline instances.
    """
    if not namespace:
        namespace = list()
    assert_correct_format(estimators, preprocessing)
    preprocessing = _check_instances(preprocessing)
    if estimators is not None and not isinstance(estimators, (dict, list)):
        estimators = [estimators]

    if preprocessing:
        if isinstance(preprocessing, list):
            # Preprocessing but there's not case: force create
            preprocessing = {'pr': preprocessing}
            estimators = {'pr': estimators} if estimators else dict()
        preprocessing = [(n, l) for n, l in sorted(preprocessing.items())]
        namespace += [n for n, l in preprocessing]

    if estimators:
        estimators = _check_instances(estimators, namespace=namespace)
        estimators = _flatten(estimators)

    out_prep, out_est, cases = list(), list(), list()
    if preprocessing:
        for preprocess_name, tr in sorted(preprocessing):
            if tr:
                out_prep.append((preprocess_name,
                                 [(n, clone(t)) for n, t in tr]))
                cases.append(preprocess_name)
    if estimators:
        for preprocess_name, learner_name, est in estimators:
            pr_name = preprocess_name if preprocess_name in cases else None
            out_est.append((pr_name, learner_name, clone(est)))

    return out_prep, out_est def format_name(name, cls, global_names):
    """Utility for assigning a unique name"""
    # TODO: should pop names when instance is destroyed and check if name taken
    if not name:
        name = '%s-%i' % (cls, len(global_names))
        global_names.append(name)
    return name def _check_instances(instances, namespace=None):
    """Helper to ensure all instances are named.

    Check if ``instances`` is formatted as expected, and if not convert
    formatting or throw traceback error if impossible to anticipate formatting.

    Parameters
    ----------
    instances : iterable
        instance iterable to test.

    namespace: list, optional
        list of assigned instance names.

    Returns
    -------
    formatted : list or dict
        formatted ``instances`` object. Will be formatted as a dict if
        preprocessing cases are detected, otherwise as a list. The dict will
        contain lists identical to those in the single preprocessing case.
        Each list is of the form ``[('name', instance)]`` and no names overlap.

    Raises
    ------
    LayerSpecificationError :
        Raises error if formatting fails, which is most likely due to wrong
        ordering of tuple entries, or wrong argument in the wrong position.

    See Also
    --------
        :class:`mlens.ensemble.base.Layer`
    """
    if not namespace:
        namespace = list()

    is_iterable = isinstance(instances, (list, tuple, dict))
    if not is_iterable:
        instances = [instances]
    if not instances or None in instances:
        return None

    if _assert_format(instances, namespace):
        out = instances
    else:
        out = _format_instances(
            instances, namespace=namespace)
    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 class BaseEstimator(object):
    """Base class for all estimators in scikit-learn
    Notes
    -----
    All estimators should specify all the parameters that can be set
    at the class level in their ``__init__`` as explicit keyword
    arguments (no ``*args`` or ``**kwargs``).
    """

    @classmethod
    def _get_param_names(cls):
        """Get parameter names for the estimator"""
        # fetch the constructor or the original constructor before
        # deprecation wrapping if any
        init = getattr(cls.__init__, 'deprecated_original', cls.__init__)
        if init is object.__init__:
            # No explicit constructor to introspect
            return []

        # introspect the constructor arguments to find the model parameters
        # to represent
        init_signature = signature(init)
        # Consider the constructor parameters excluding 'self'
        parameters = [p for p in init_signature.parameters.values()
                      if p.name != 'self' and p.kind != p.VAR_KEYWORD]
        for p in parameters:
            if p.kind == p.VAR_POSITIONAL:
                raise RuntimeError("scikit-learn estimators should always "
                                   "specify their parameters in the signature"
                                   " of their __init__ (no varargs)."
                                   " %s with constructor %s doesn't "
                                   " follow this convention."
                                   % (cls, init_signature))
        # Extract and sort argument names excluding 'self'
        return sorted([p.name for p in parameters])

    def get_params(self, deep=True):
        """Get parameters for this estimator.
        Parameters
        ----------
        deep : boolean, optional
            If True, will return the parameters for this estimator and
            contained subobjects that are estimators.
        Returns
        -------
        params : mapping of string to any
            Parameter names mapped to their values.
        """
        out = dict()
        for key in self._get_param_names():
            value = getattr(self, key, None)
            if deep and hasattr(value, 'get_params'):
                deep_items = value.get_params().items()
                out.update((key + '__' + k, val) for k, val in deep_items)
            out[key] = value
        return out

    def set_params(self, **params):
        """Set the parameters of this estimator.
        The method works on simple estimators as well as on nested objects
        (such as pipelines). The latter have parameters of the form
        ``<component>__<parameter>`` so that it's possible to update each
        component of a nested object.
        Returns
        -------
        self
        """
        if not params:
            # Simple optimization to gain speed (inspect is slow)
            return self
        valid_params = self.get_params(deep=True)

        nested_params = defaultdict(dict)  # grouped by prefix
        for key, value in params.items():
            key, delim, sub_key = key.partition('__')
            if key not in valid_params:
                raise ValueError('Invalid parameter %s for estimator %s. '
                                 'Check the list of available parameters '
                                 'with `estimator.get_params().keys()`.' %
                                 (key, self))

            if delim:
                nested_params[key][sub_key] = value
            else:
                setattr(self, key, value)
                valid_params[key] = value

        for key, sub_params in nested_params.items():
            valid_params[key].set_params(**sub_params)

        return self

    def __repr__(self):
        class_name = self.__class__.__name__
        return '%s(%s)' % (class_name, _pprint(self.get_params(deep=False),
                                               offset=len(class_name),),)

    def __getstate__(self):
        try:
            state = super(BaseEstimator, self).__getstate__()
        except AttributeError:
            state = self.__dict__.copy()

        if type(self).__module__.startswith('sklearn.'):
            return dict(state.items(), _sklearn_version=__version__)
        else:
            return state

    def __setstate__(self, state):
        if type(self).__module__.startswith('sklearn.'):
            pickle_version = state.pop("_sklearn_version", "pre-0.18")
            if pickle_version != __version__:
                warnings.warn(
                    "Trying to unpickle estimator {0} from version {1} when "
                    "using version {2}. This might lead to breaking code or "
                    "invalid results. Use at your own risk.".format(
                        self.__class__.__name__, pickle_version, __version__),
                    UserWarning)
        try:
            super(BaseEstimator, self).__setstate__(state)
        except AttributeError:
            self.__dict__.update(state)
from .base import BaseEstimator
from .learner import Learner, Transformer
from ._base_functions import mold_objects, transform
from ..utils import format_name, check_instances
from ..utils.formatting import _check_instances
from ..externals.sklearn.base import clone, BaseEstimator as _BaseEstimator
    def _run(self, fit, process, X, y=None):
        """Run job on pipeline."""
        out = self._check_empty(process, X, y)
        if out is not False:
            return out

        if fit:
            self._pipeline = [(tr_name, clone(tr))
                              for tr_name, tr in self.pipeline]

        for tr_name, tr in self._pipeline:
            if fit:
                tr.fit(X, y)

            if len(self._pipeline) > 1 or process:
                X, y = transform(tr, X, y)

        if process:
            if self.return_y:
                return X, y
            return X
        return self

    def _check_empty(self, process, X, y=None):
        """Check if empty pipeline and return vacuously"""
        # TODO: remove ability to set None pipelines: need to modify Evalutor

        if self.pipeline:
            return False
        if not process:
            return self
        if self.return_y:
            return X, y
        return X

    def fit(self, X, y=None):
        """Fit pipeline.

        Note that the :class:`Pipeline` accepts both X and y arguments, and
        can return both X and y, depending on the transformers. The
        pipeline itself does no checks on the input.

        Parameters
        ----------
        X : array-like of shape [n_samples, n_features]
            Input data

        y : array-like of shape [n_samples, ]
            Targets

        Returns
        -------
        self : instance
            Fitted pipeline
        """
        return self._run(True, False, X, y)

    def transform(self, X, y=None):
        """Transform pipeline.

        Note that the :class:`Pipeline` accepts both X and y arguments, and
        can return both X and y, depending on the transformers.
        Pipeline itself does not checks the input.

        Parameters
        ----------
        X : array-like of shape [n_samples, n_features]
            Input data

        y : array-like of shape [n_samples, ]
            Targets

        Returns
        -------
        X_processed : array-like of shape [n_samples, n_preprocessed_features]
            Preprocessed input data

        y : array-like of shape [n_samples, ], optional
            Original or preprocessed targets, depending on the transformers.
        """
        return self._run(False, True, X, y)

    def fit_transform(self, X, y=None):
        """Fit and transform pipeline.

        Note that the :class:`Pipeline` accepts both X and y arguments, and
        can return both X and y, depending on the transformers. The
        pipeline itself does no checks on the input.

        Parameters
        ----------
        X : array-like of shape [n_samples, n_features]
            Input data

        y : array-like of shape [n_samples, ]
            Targets

        Returns
        -------
        X_processed : array-like of shape [n_samples, n_preprocessed_features]
            Preprocessed input data

        y : array-like of shape [n_samples, ], optional
            Preprocessed targets
        """
        return self._run(True, True, X, y)

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

        if self.pipeline:
            for tr_name, tr in self.pipeline:
                for k, v in tr.get_params(deep=True).items():
                    out['%s__%s' % (tr_name, k)] = v
                    out[tr_name] = tr
        return out



class Group(BaseEstimator):