{"action":"created","comment":{"url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments/72781883","id":72781883,"diff_hunk":"@@ -0,0 +1,804 @@\n+\"\"\"\n+This module contains Gradient Boosting Classifier and Regressor with\n+Cross Validation.\n+\"\"\"\n+\n+import warnings\n+import numpy as np\n+\n+from ..base import BaseEstimator\n+from ..metrics.scorer import check_scoring\n+from ..utils.validation import check_X_y\n+\n+from .gradient_boosting import GradientBoostingClassifier\n+from .gradient_boosting import GradientBoostingRegressor\n+from ..tree._tree import DTYPE\n+\n+from ..model_selection import check_cv\n+from ..model_selection import ParameterGrid\n+from ..model_selection import ParameterSampler\n+from ..model_selection._search import _CVScoreTuple\n+\n+from ..externals.joblib import Parallel, delayed\n+\n+\n+class GradientBoostingClassifierCV(BaseEstimator):\n+    \"\"\" Gradient Boosting Classification with Cross Validation\n+\n+    This class keeps adding estimators to the underlying GB estimator till\n+    adding a new estimators does not improve the score by much. The goal of\n+    this class is to determine the best choice of `n_estimators` for each set\n+    of parameters. In most cases, this will be much faster than using\n+    :class:`sklearn.model_selection.GridSearchCV` due to early stopping.\n+    All parameters which are not parsed by this class are passed directly to\n+    the underlying Gradient Boosting estimator. If one or more parameters is a\n+    list, the Gradient Boosting model is evaluated for all elements of the\n+    list.\n+\n+    Parameters\n+    ----------\n+    n_stop_rounds : int, optional, default=10\n+        If the score on the test set rounded off to `score_precision` decimal\n+        places does not change for `n_stop_rounds` iterations, the gradient\n+        boosting is halted.\n+\n+    score_precision : int, optional, default=2\n+        The number of decimal places to round the score off by before\n+        comparing it with the scores of previous iterations.\n+\n+    max_iter : int, optinal, default=10000\n+        The maximum number of estimators that will be added to the Gradient\n+        Boosting class. If the model converges, the number of estimators will\n+        be lower than this.\n+\n+    cv : int, cross-validation generator or an iterable, optional\n+        Determines the cross-validation splitting strategy.\n+        Possible inputs for cv are:\n+          - None, to use the default 3-fold cross validation,\n+          - integer, to specify the number of folds in a `(Stratified)KFold`,\n+          - An object to be used as a cross-validation generator.\n+          - An iterable yielding train, validation splits.\n+\n+        For integer/None inputs, ``StratifiedKFold`` is used for classification\n+        tasks, when ``y`` is binary or multiclass.\n+\n+        See the :mod:`sklearn.model_selection` module for the list of\n+        cross-validation strategies that can be used here.\n+\n+        Also refer :ref:`cross-validation documentation <cross_validation>`\n+\n+    scoring : string, callable or None, default=None\n+        A string (see model evaluation documentation) or\n+        a scorer callable object / function with signature\n+        ``scorer(estimator, X, y)``.\n+        If ``None``, the ``score`` method of the estimator is used.\n+\n+    refit : boolean, default=True\n+        If set, the data will be fit on a new Gradient Boosting model with\n+        the parameters that gave the best mean score. If not set, it is not\n+        possbile to make predections later with this class.\n+\n+    n_jobs : int, default=1\n+        Number of jobs to run in parallel.\n+\n+    verbose : integer\n+        Controls the verbosity: the higher, the more messages.\n+\n+    loss : string, list of strings, optional (default='deviance')\n+        loss function to be optimized. 'deviance' refers to\n+        deviance (= logistic regression) for classification\n+        with probabilistic outputs. For loss 'exponential' gradient\n+        boosting recovers the AdaBoost algorithm. If a list is given, all\n+        loss functions are evaluated to choose the best one.\n+\n+    learning_rate : float, list of floats, optional (default=0.1)\n+        learning rate shrinks the contribution of each tree by `learning_rate`.\n+        There is a trade-off between learning_rate and n_estimators. If a list\n+        is given, all learning rates will be evaluated.\n+\n+    max_depth : integer, list of integers, optional (default=3)\n+        maximum depth of the individual regression estimators. The maximum\n+        depth limits the number of nodes in the tree. Tune this parameter\n+        for best performance; the best value depends on the interaction\n+        of the input variables.\n+        Ignored if ``max_leaf_nodes`` is not None.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    min_samples_split : int, float, list of int or float, optional (default=2)\n+        The minimum number of samples required to split an internal node:\n+        - If int, then consider `min_samples_split` as the minimum number.\n+        - If float, then `min_samples_split` is a percentage and\n+          `ceil(min_samples_split * n_samples)` are the minimum\n+          number of samples for each split.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    min_samples_leaf : int, float, list of int or floats, optional (default=1)\n+        The minimum number of samples required to be at a leaf node:\n+        - If int, then consider `min_samples_leaf` as the minimum number.\n+        - If float, then `min_samples_leaf` is a percentage and\n+          `ceil(min_samples_leaf * n_samples)` are the minimum\n+          number of samples for each node.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    min_weight_fraction_leaf : float, list of floats, optional (default=0.)\n+        The minimum weighted fraction of the input samples required to be at a\n+        leaf node. If a list is given, each item in the list will be evaluated.\n+\n+    subsample : float, list of floats, optional (default=1.0)\n+        The fraction of samples to be used for fitting the individual base\n+        learners. If smaller than 1.0 this results in Stochastic Gradient\n+        Boosting. `subsample` interacts with the parameter `n_estimators`.\n+        Choosing `subsample < 1.0` leads to a reduction of variance\n+        and an increase in bias. If a list is given, each item in the list\n+        will be evaluated.\n+\n+    max_features : int, float, string or None, list, optional (default=None)\n+        The number of features to consider when looking for the best split:\n+        - If int, then consider `max_features` features at each split.\n+        - If float, then `max_features` is a percentage and\n+          `int(max_features * n_features)` features are considered at each\n+          split.\n+        - If \"auto\", then `max_features=sqrt(n_features)`.\n+        - If \"sqrt\", then `max_features=sqrt(n_features)`.\n+        - If \"log2\", then `max_features=log2(n_features)`.\n+        - If None, then `max_features=n_features`.\n+        Choosing `max_features < n_features` leads to a reduction of variance\n+        and an increase in bias.\n+        Note: the search for a split does not stop until at least one\n+        valid partition of the node samples is found, even if it requires to\n+        effectively inspect more than ``max_features`` features. If a list is\n+        given, each item in the list will be evaluated.\n+\n+    max_leaf_nodes : int, list of int, or None, optional (default=None)\n+        Grow trees with ``max_leaf_nodes`` in best-first fashion.\n+        Best nodes are defined as relative reduction in impurity.\n+        If None then unlimited number of leaf nodes.\n+        If not None then ``max_depth`` will be ignored.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    init : BaseEstimator, None, list, optional (default=None)\n+        An estimator object that is used to compute the initial\n+        predictions. ``init`` has to provide ``fit`` and ``predict``.\n+        If None it uses ``loss.init_estimator``.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    random_state : int, RandomState instance or None,\n+                   list of int or RandomState, optional (default=None)\n+        If int, random_state is the seed used by the random number generator;\n+        If RandomState instance, random_state is the random number generator;\n+        If None, the random number generator is the RandomState instance used\n+        by `np.random`.\n+\n+    presort : bool or 'auto', list of string or bool optional (default='auto')\n+        Whether to presort the data to speed up the finding of best splits in\n+        fitting. Auto mode by default will use presorting on dense data and\n+        default to normal sorting on sparse data. Setting presort to true on\n+        sparse data will raise an error. If a list is given, each item in the\n+        list will be evaluated.\n+\n+    Attributes\n+    ----------\n+    grid_scores_ : list of named tuples\n+        Contains scores for all parameter combinations in param_grid.\n+        Each entry corresponds to one parameter setting.\n+        Each named tuple has the attributes:\n+\n+            * ``parameters``, a dict of parameter settings\n+            * ``mean_validation_score``, the mean score over the\n+              cross-validation folds\n+            * ``cv_validation_scores``, the list of scores for each fold\n+\n+    best_score_tuple_ : named tuple\n+        The attributes of the model which gave the best score.\n+\n+        * ``parameters``, a dict of parameter settings, which includes the\n+          `n_estimators` parameter\n+        * ``mean_validation_score``, the mean score over the\n+          cross-validation folds\n+        * ``cv_validation_scores``, the list of scores for each fold\n+\n+    best_params_ : dict\n+        The parameters which gave the best score, along with the `n_estimators`\n+        parameter.\n+\n+    best_estimator_ : GradientBoostingClassifier\n+        If `refit` was `True`, the model with the best score fit to the entire\n+        dataset.\n+\n+    \"\"\"\n+\n+    _estimator_class = GradientBoostingClassifier\n+\n+    def __init__(self, n_stop_rounds=10, score_precision=2,\n+                 max_iterations=10000, cv=3, n_iter_combo=None, scoring=None,\n+                 refit=True, n_jobs=1, pre_dispatch='2*n_jobs', verbose=0,\n+                 loss='deviance', learning_rate=0.1, subsample=1.0,\n+                 min_samples_split=2, min_samples_leaf=1,\n+                 min_weight_fraction_leaf=0., max_depth=3, init=None,\n+                 random_state=None, max_features=None, max_leaf_nodes=None,\n+                 presort='auto'):\n+\n+        self.n_stop_rounds = n_stop_rounds\n+        self.score_precision = score_precision\n+        self.max_iterations = max_iterations\n+        self.cv = cv\n+        self.n_iter_combo = n_iter_combo\n+        self.scoring = scoring\n+        self.refit = refit\n+        self.n_jobs = n_jobs\n+        self.pre_dispatch = pre_dispatch\n+        self.verbose = verbose\n+\n+        self.loss = loss\n+        self.learning_rate = learning_rate\n+        self.subsample = subsample\n+        self.min_samples_split = min_samples_split\n+        self.min_samples_leaf = min_samples_leaf\n+        self.min_weight_fraction_leaf = min_weight_fraction_leaf\n+        self.max_depth = max_depth\n+        self.init = init\n+        self.random_state = random_state\n+        self.max_features = max_features\n+        self.max_leaf_nodes = max_leaf_nodes\n+        self.presort = presort\n+\n+    def fit(self, X, y):\n+        \"\"\"Run fit with all sets of parameters till convergence.\n+\n+        X : array-like, shape = [n_samples, n_features]\n+            Training vectors, where n_samples is the number of samples\n+            and n_features is the number of features.\n+        y : array-like, shape = [n_samples]\n+            Target values (integers in classification, real numbers in\n+            regression)\n+            For classification, labels must correspond to classes.\n+        \"\"\"\n+\n+        params = self._get_params()\n+        X, y = check_X_y(X, y, dtype=DTYPE)\n+\n+        cv = check_cv(self.cv, y)\n+        if self.n_iter_combo is None:\n+            param_iter = ParameterGrid(params)\n+        else:\n+            param_iter = ParameterSampler(params, self.n_iter_combo,\n+                                          random_state=self.random_state)\n+\n+        parallel = Parallel(n_jobs=self.n_jobs, verbose=self.verbose,\n+                            backend='threading')\n+\n+        out = parallel(delayed(_fit_single_param)\n+                       (self._estimator_class, X, y, train, validation, params,\n+                        self.n_stop_rounds, self.max_iterations, self.scoring,\n+                        self.score_precision)\n+                       for train, validation in cv.split(X)\n+                       for params in param_iter)\n+\n+        n_splits = int(len(out)/len(param_iter))\n+\n+        grid_scores = []\n+        i = 0\n+        best_mean = -np.inf\n+        for params in param_iter:\n+\n+            score_list = []\n+            n_est_list = []\n+            for idx in range(n_splits):\n+                score, n_estimators = out[i]\n+                n_est_list.append(n_estimators)\n+                score_list.append(score)\n+                i += 1\n+\n+            scores = np.array(score_list)\n+            mean = np.mean(scores)\n+            params['n_estimators'] = int(np.mean(n_est_list))\n+            score_tuple = _CVScoreTuple(params, mean, scores)\n+            grid_scores.append(_CVScoreTuple(params, mean, scores))\n+\n+            if mean > best_mean:\n+                best_tuple = score_tuple\n+                best_params = params\n+\n+        self.grid_scores_ = grid_scores\n+        self.best_score_tuple_ = best_tuple\n+        self.best_params_ = best_params\n+\n+        if self.refit:\n+            self.best_params_['random_state'] = self.random_state\n+            gb = self._estimator_class(**self.best_params_)\n+            gb.fit(X, y)\n+            self.best_estimator_ = gb\n+\n+        return self\n+\n+    def predict(self, X):\n+        \"\"\"Call `predict` on the best estimator.\n+\n+        Parameters\n+        ----------\n+        X : array-like of shape = [n_samples, n_features]\n+            The input samples.\n+        Returns\n+        -------\n+        y: array of shape = [\"n_samples]\n+            The predicted values.\n+        \"\"\"\n+        return self.best_estimator_.predict(X)\n+\n+    def predict_proba(self, X):\n+        \"\"\"Call `predict_proba` on the best estimator.\n+\n+        Parameters\n+        ----------\n+        X : array-like of shape = [n_samples, n_features]\n+            The input samples.\n+\n+        Raises\n+        ------\n+        AttributeError\n+            If the ``loss`` does not support probabilities.\n+\n+        Returns\n+        -------\n+        p : array of shape = [n_samples]\n+            The class probabilities of the input samples. The order of the\n+            classes corresponds to that in the attribute `classes_`.\n+        \"\"\"\n+        return self.best_estimator_.predict_proba(X)\n+\n+    def predict_log_proba(self, X):\n+        \"\"\"Call `predict_log_proba` on the best estimator.\n+\n+        Parameters\n+        ----------\n+        X : array-like of shape = [n_samples, n_features]\n+            The input samples.\n+        Raises\n+        ------\n+\n+        AttributeError\n+            If the ``loss`` does not support probabilities.\n+\n+        Returns\n+        -------\n+        p : array of shape = [n_samples]\n+            The class probabilities of the input samples. The order of the\n+            classes corresponds to that in the attribute `classes_`.\n+        \"\"\"\n+        return self.best_estimator_.predict_log_proba(X)\n+\n+    def _get_params(self):\n+        \"Return parametrs to iterate over for underlying gradient boosting\"\n+\n+        params = {\n+            'loss': self.loss,\n+            'learning_rate': self.learning_rate,\n+            'subsample': self.subsample,\n+            'min_samples_split': self.min_samples_split,\n+            'min_samples_leaf': self.min_samples_leaf,\n+            'min_weight_fraction_leaf': self.min_weight_fraction_leaf,\n+            'max_depth': self.max_depth,\n+            'init': self.init,\n+            'max_features': self.max_features,\n+            'max_leaf_nodes': self.max_leaf_nodes,\n+            'presort': self.presort,\n+            'random_state': self.random_state,\n+        }\n+\n+        # Pre processing to ensure every parameter is a list\n+        # which `ParameterGrid` can iterate over\n+        for key, value in params.items():\n+            params[key] = np.atleast_1d(value)\n+\n+        return params\n+\n+\n+class GradientBoostingRegressorCV(BaseEstimator):\n+    \"\"\" Gradient Boosting Regression with Cross Validation\n+\n+    This class keeps adding estimators to the underlying GB estimator till\n+    adding a new estimators does not improve the score by much. The goal of\n+    this class is to determine the best choice of `n_estimators` for each set\n+    of parameters. In most cases, this will be much faster than using\n+    :class:`sklearn.model_selection.GridSearchCV` due to early stopping.\n+    All parameters which are not parsed by this class are passed directly to\n+    the underlying Gradient Boosting estimator. If one or more parameters is a\n+    list, the Gradient Boosting model is evaluated for all elements of the\n+    list.\n+\n+    Parameters\n+    ----------\n+    n_stop_rounds : int, optional, default=10\n+        If the score on the test set rounded off to `score_precision` decimal\n+        places does not change for `n_stop_rounds` iterations, the gradient\n+        boosting is halted.\n+\n+    score_precision : int, optional, default=2\n+        The number of decimal places to round the score off by before\n+        comparing it with the scores of previous iterations.\n+\n+    max_iter : int, optinal, default=10000\n+        The maximum number of estimators that will be added to the Gradient\n+        Boosting class. If the model converges, the number of estimators will\n+        be lower than this.\n+\n+    cv : int, cross-validation generator or an iterable, optional\n+        Determines the cross-validation splitting strategy.\n+        Possible inputs for cv are:\n+          - None, to use the default 3-fold cross validation,\n+          - integer, to specify the number of folds in a `(Stratified)KFold`,\n+          - An object to be used as a cross-validation generator.\n+          - An iterable yielding train, validation splits.\n+\n+        For integer/None inputs, ``StratifiedKFold`` is used for classification\n+        tasks, when ``y`` is binary or multiclass.\n+\n+        See the :mod:`sklearn.model_selection` module for the list of\n+        cross-validation strategies that can be used here.\n+\n+        Also refer :ref:`cross-validation documentation <cross_validation>`\n+\n+    scoring : string, callable or None, default=None\n+        A string (see model evaluation documentation) or\n+        a scorer callable object / function with signature\n+        ``scorer(estimator, X, y)``.\n+        If ``None``, the ``score`` method of the estimator is used.\n+\n+    refit : boolean, default=True\n+        If set, the data will be fit on a new Gradient Boosting model with\n+        the parameters that gave the best mean score. If not set, it is not\n+        possbile to make predections later with this class.\n+\n+    n_jobs : int, default=1\n+        Number of jobs to run in parallel.\n+\n+    verbose : integer\n+        Controls the verbosity: the higher, the more messages.\n+\n+    loss : {'ls', 'lad', 'huber', 'quantile'}, optional (default='ls')\n+        loss function to be optimized. 'ls' refers to least squares\n+        regression. 'lad' (least absolute deviation) is a highly robust\n+        loss function solely based on order information of the input\n+        variables. 'huber' is a combination of the two. 'quantile'\n+        allows quantile regression (use `alpha` to specify the quantile).\n+\n+    learning_rate : float, list of floats, optional (default=0.1)\n+        learning rate shrinks the contribution of each tree by `learning_rate`.\n+        There is a trade-off between learning_rate and n_estimators. If a list\n+        is given, all learning rates will be evaluated.\n+\n+    max_depth : integer, list of integers, optional (default=3)\n+        maximum depth of the individual regression estimators. The maximum\n+        depth limits the number of nodes in the tree. Tune this parameter\n+        for best performance; the best value depends on the interaction\n+        of the input variables.\n+        Ignored if ``max_leaf_nodes`` is not None.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    min_samples_split : int, float, list of int or float, optional (default=2)\n+        The minimum number of samples required to split an internal node:\n+        - If int, then consider `min_samples_split` as the minimum number.\n+        - If float, then `min_samples_split` is a percentage and\n+          `ceil(min_samples_split * n_samples)` are the minimum\n+          number of samples for each split.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    min_samples_leaf : int, float, list of int or floats, optional (default=1)\n+        The minimum number of samples required to be at a leaf node:\n+        - If int, then consider `min_samples_leaf` as the minimum number.\n+        - If float, then `min_samples_leaf` is a percentage and\n+          `ceil(min_samples_leaf * n_samples)` are the minimum\n+          number of samples for each node.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    min_weight_fraction_leaf : float, list of floats, optional (default=0.)\n+        The minimum weighted fraction of the input samples required to be at a\n+        leaf node. If a list is given, each item in the list will be evaluated.\n+\n+    subsample : float, list of floats, optional (default=1.0)\n+        The fraction of samples to be used for fitting the individual base\n+        learners. If smaller than 1.0 this results in Stochastic Gradient\n+        Boosting. `subsample` interacts with the parameter `n_estimators`.\n+        Choosing `subsample < 1.0` leads to a reduction of variance\n+        and an increase in bias. If a list is given, each item in the list\n+        will be evaluated.\n+\n+    max_features : int, float, string or None, list, optional (default=None)\n+        The number of features to consider when looking for the best split:\n+        - If int, then consider `max_features` features at each split.\n+        - If float, then `max_features` is a percentage and\n+          `int(max_features * n_features)` features are considered at each\n+          split.\n+        - If \"auto\", then `max_features=sqrt(n_features)`.\n+        - If \"sqrt\", then `max_features=sqrt(n_features)`.\n+        - If \"log2\", then `max_features=log2(n_features)`.\n+        - If None, then `max_features=n_features`.\n+        Choosing `max_features < n_features` leads to a reduction of variance\n+        and an increase in bias.\n+        Note: the search for a split does not stop until at least one\n+        valid partition of the node samples is found, even if it requires to\n+        effectively inspect more than ``max_features`` features. If a list is\n+        given, each item in the list will be evaluated.\n+\n+    max_leaf_nodes : int, list of int, or None, optional (default=None)\n+        Grow trees with ``max_leaf_nodes`` in best-first fashion.\n+        Best nodes are defined as relative reduction in impurity.\n+        If None then unlimited number of leaf nodes.\n+        If not None then ``max_depth`` will be ignored.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    init : BaseEstimator, None, list, optional (default=None)\n+        An estimator object that is used to compute the initial\n+        predictions. ``init`` has to provide ``fit`` and ``predict``.\n+        If None it uses ``loss.init_estimator``.\n+        If a list is given, each item in the list will be evaluated.\n+\n+    random_state : int, RandomState instance or None,\n+                   list of int or RandomState, optional (default=None)\n+        If int, random_state is the seed used by the random number generator;\n+        If RandomState instance, random_state is the random number generator;\n+        If None, the random number generator is the RandomState instance used\n+        by `np.random`.\n+\n+    presort : bool or 'auto', list of string or bool optional (default='auto')\n+        Whether to presort the data to speed up the finding of best splits in\n+        fitting. Auto mode by default will use presorting on dense data and\n+        default to normal sorting on sparse data. Setting presort to true on\n+        sparse data will raise an error. If a list is given, each item in the\n+        list will be evaluated.\n+\n+    Attributes\n+    ----------\n+    grid_scores_ : list of named tuples\n+        Contains scores for all parameter combinations in param_grid.\n+        Each entry corresponds to one parameter setting.\n+        Each named tuple has the attributes:\n+\n+            * ``parameters``, a dict of parameter settings\n+            * ``mean_validation_score``, the mean score over the\n+              cross-validation folds\n+            * ``cv_validation_scores``, the list of scores for each fold\n+\n+    best_score_tuple_ : named tuple\n+        The attributes of the model which gave the best score.\n+\n+        * ``parameters``, a dict of parameter settings, which includes the\n+          `n_estimators` parameter\n+        * ``mean_validation_score``, the mean score over the\n+          cross-validation folds\n+        * ``cv_validation_scores``, the list of scores for each fold\n+\n+    best_params_ : dict\n+        The parameters which gave the best score, along with the `n_estimators`\n+        parameter.\n+\n+    best_estimator_ : GradientBoostingClassifier\n+        If `refit` was `True`, the model with the best score fit to the entire\n+        dataset.\n+\n+    \"\"\"\n+\n+    _estimator_class = GradientBoostingRegressor\n+\n+    def __init__(self, n_stop_rounds=10, score_precision=2,\n+                 max_iterations=10000, cv=3, n_iter_combo=None, scoring=None,\n+                 refit=True, n_jobs=1, pre_dispatch='2*n_jobs', verbose=0,\n+                 loss='ls', learning_rate=0.1, subsample=1.0,\n+                 min_samples_split=2, min_samples_leaf=1,\n+                 min_weight_fraction_leaf=0., max_depth=3, init=None,\n+                 random_state=None, max_features=None, max_leaf_nodes=None,\n+                 presort='auto'):\n+\n+        self.n_stop_rounds = n_stop_rounds\n+        self.score_precision = score_precision\n+        self.max_iterations = max_iterations\n+        self.cv = cv\n+        self.n_iter_combo = n_iter_combo\n+        self.scoring = scoring\n+        self.refit = refit\n+        self.n_jobs = n_jobs\n+        self.pre_dispatch = pre_dispatch\n+        self.verbose = verbose\n+\n+        self.loss = loss\n+        self.learning_rate = learning_rate\n+        self.subsample = subsample\n+        self.min_samples_split = min_samples_split\n+        self.min_samples_leaf = min_samples_leaf\n+        self.min_weight_fraction_leaf = min_weight_fraction_leaf\n+        self.max_depth = max_depth\n+        self.init = init\n+        self.random_state = random_state\n+        self.max_features = max_features\n+        self.max_leaf_nodes = max_leaf_nodes\n+        self.presort = presort\n+\n+    def fit(self, X, y):\n+        \"\"\"Run fit with all sets of parameters till convergence.\n+\n+        X : array-like, shape = [n_samples, n_features]\n+            Training vectors, where n_samples is the number of samples\n+            and n_features is the number of features.\n+        y : array-like, shape = [n_samples]\n+            Target values (integers in classification, real numbers in\n+            regression)\n+            For classification, labels must correspond to classes.\n+        \"\"\"\n+\n+        params = self._get_params()\n+        X, y = check_X_y(X, y, dtype=DTYPE)\n+\n+        cv = check_cv(self.cv, y)\n+        if self.n_iter_combo is None:\n+            param_iter = ParameterGrid(params)\n+        else:\n+            param_iter = ParameterSampler(params, self.n_iter_combo,\n+                                          random_state=self.random_state)\n+\n+        parallel = Parallel(n_jobs=self.n_jobs, verbose=self.verbose,\n+                            backend='threading')\n+\n+        out = parallel(delayed(_fit_single_param)\n+                       (self._estimator_class, X, y, train, validation, params,\n+                        self.n_stop_rounds, self.max_iterations, self.scoring,\n+                        self.score_precision)\n+                       for train, validation in cv.split(X)\n+                       for params in param_iter)\n+\n+        n_splits = int(len(out)/len(param_iter))\n+\n+        grid_scores = []\n+        i = 0\n+        best_mean = -np.inf\n+        for params in param_iter:\n+\n+            score_list = []\n+            n_est_list = []\n+            for idx in range(n_splits):\n+                score, n_estimators = out[i]\n+                n_est_list.append(n_estimators)\n+                score_list.append(score)\n+                i += 1\n+\n+            scores = np.array(score_list)\n+            mean = np.mean(scores)\n+            params['n_estimators'] = int(np.mean(n_est_list))\n+            score_tuple = _CVScoreTuple(params, mean, scores)\n+            grid_scores.append(_CVScoreTuple(params, mean, scores))\n+\n+            if mean > best_mean:\n+                best_tuple = score_tuple\n+                best_params = params\n+\n+        self.grid_scores_ = grid_scores\n+        self.best_score_tuple_ = best_tuple\n+        self.best_params_ = best_params\n+\n+        if self.refit:\n+            self.best_params_['random_state'] = self.random_state\n+            gb = self._estimator_class(**self.best_params_)\n+            gb.fit(X, y)\n+            self.best_estimator_ = gb\n+\n+        return self\n+\n+    def predict(self, X):\n+        \"\"\"Call `predict` on the best estimator.\n+\n+        Parameters\n+        ----------\n+        X : array-like of shape = [n_samples, n_features]\n+            The input samples.\n+        Returns\n+        -------\n+        y: array of shape = [\"n_samples]\n+            The predicted values.\n+        \"\"\"\n+        return self.best_estimator_.predict(X)\n+\n+    def _get_params(self):\n+        \"Return parametrs to iterate over for underlying gradient boosting\"\n+\n+        params = {\n+            'loss': self.loss,\n+            'learning_rate': self.learning_rate,\n+            'subsample': self.subsample,\n+            'min_samples_split': self.min_samples_split,\n+            'min_samples_leaf': self.min_samples_leaf,\n+            'min_weight_fraction_leaf': self.min_weight_fraction_leaf,\n+            'max_depth': self.max_depth,\n+            'init': self.init,\n+            'max_features': self.max_features,\n+            'max_leaf_nodes': self.max_leaf_nodes,\n+            'presort': self.presort,\n+            'random_state': self.random_state,\n+        }\n+\n+        # Pre processing to ensure every parameter is a list\n+        # which `ParameterGrid` can iterate over\n+        for key, value in params.items():\n+            params[key] = np.atleast_1d(value)\n+\n+        return params\n+\n+\n+def _fit_single_param(estimator, X, y, train, validation, params, stop_rounds,\n+                      max_iter, scoring, score_precision):\n+    \"\"\" Fit a single estimator till stopping criteria is reached.\n+\n+    Parameters\n+    ---------\n+    estimator : GradientBoostingRegressor or GradientBoostingClassifier\n+        The class of the model to instantiate.\n+\n+    X : array-like, shape = [n_samples, n_features]\n+        Training vectors, where n_samples is the number of samples\n+        and n_features is the number of features.\n+\n+    y : array-like, shape = [n_samples]\n+        Target values (integers in classification, real numbers in\n+        regression)\n+        For classification, labels must correspond to classes.\n+\n+    train : array-like\n+        The indices of the training samples.\n+\n+    validation : array-like\n+        The indices of the validation samples.\n+\n+    params : dict\n+        The parameters to instantiate the Gradient Boosting model with\n+\n+    stop_rounds : int\n+        The number of iterations for which the score should remain constant\n+        over to stop training.\n+\n+    max_iter : int\n+        The maximum number of estimators to fit during gradient boosting.\n+\n+    scoring : string, callable or None, default=None\n+        A string (see model evaluation documentation) or\n+        a scorer callable object / function with signature\n+        ``scorer(estimator, X, y)``.\n+        If ``None``, the ``score`` method of the estimator is used.\n+\n+    score_precision : int\n+        The number of decimal places the score during each iterations is\n+        rounded off to before comparing with the previous ones.\n+\n+    Returns\n+    -------\n+    score: float\n+        The score when the model converged.\n+\n+    n_iterations:\n+        The number of iterations it took to converge. This is the same as\n+        parameter `n_estimators` for the gradient boosting model\n+    \"\"\"\n+\n+    params['warm_start'] = True\n+    gb = estimator(**params)\n+    scorer = check_scoring(estimator, scoring=scoring)\n+    scores = np.empty((stop_rounds,))\n+    scores.fill(-np.inf)\n+\n+    X_train = X[train]\n+    y_train = y[train]\n+    X_validation = X[validation]\n+    y_validation = y[validation]\n+\n+    for i in range(1, max_iter):\n+        gb.n_estimators = i\n+        gb.fit(X_train, y_train)\n+\n+        scores = np.roll(scores, shift=-1)\n+        scores[-1] = scorer(gb, X_validation, y_validation)\n+        rounded_scores = np.round(scores, score_precision)\n+\n+        if np.all(rounded_scores[-1] <= rounded_scores):","path":"sklearn/ensemble/gradient_boosting_cv.py","position":798,"original_position":798,"commit_id":"5c771f80d3489b9c0ce977a0e6a1d1295e1fe90c","original_commit_id":"5c771f80d3489b9c0ce977a0e6a1d1295e1fe90c","user":{"login":"raghavrv","id":9487348,"avatar_url":"https://avatars.githubusercontent.com/u/9487348?v=3","gravatar_id":"","url":"https://api.github.com/users/raghavrv","html_url":"https://github.com/raghavrv","followers_url":"https://api.github.com/users/raghavrv/followers","following_url":"https://api.github.com/users/raghavrv/following{/other_user}","gists_url":"https://api.github.com/users/raghavrv/gists{/gist_id}","starred_url":"https://api.github.com/users/raghavrv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raghavrv/subscriptions","organizations_url":"https://api.github.com/users/raghavrv/orgs","repos_url":"https://api.github.com/users/raghavrv/repos","events_url":"https://api.github.com/users/raghavrv/events{/privacy}","received_events_url":"https://api.github.com/users/raghavrv/received_events","type":"User","site_admin":false},"body":"But that assumes that all the scores are strictly decreasing or same right? Would there be no case where one score drops spuriously?\r\n\r\nSay\r\n\r\n```\r\n  0     1     2   3     4     5     6     7     8     9    10\r\n0.91, 0.8, 0.9, 0.89, 0.88, 0.87, 0.86, 0.85, 0.84, 0.83, 0.82\r\n```\r\n\r\nIn this case we don't want to stop at 10 because 10 is not better than 1 correct?","created_at":"2016-07-29T12:13:18Z","updated_at":"2016-07-29T12:13:18Z","html_url":"https://github.com/scikit-learn/scikit-learn/pull/7071#discussion_r72781883","pull_request_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071","_links":{"self":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments/72781883"},"html":{"href":"https://github.com/scikit-learn/scikit-learn/pull/7071#discussion_r72781883"},"pull_request":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071"}}},"pull_request":{"url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071","id":78600911,"html_url":"https://github.com/scikit-learn/scikit-learn/pull/7071","diff_url":"https://github.com/scikit-learn/scikit-learn/pull/7071.diff","patch_url":"https://github.com/scikit-learn/scikit-learn/pull/7071.patch","issue_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/7071","number":7071,"state":"open","locked":false,"title":"[WIP] Gradient Boosting Classifier/Regressor CV","user":{"login":"raghavrv","id":9487348,"avatar_url":"https://avatars.githubusercontent.com/u/9487348?v=3","gravatar_id":"","url":"https://api.github.com/users/raghavrv","html_url":"https://github.com/raghavrv","followers_url":"https://api.github.com/users/raghavrv/followers","following_url":"https://api.github.com/users/raghavrv/following{/other_user}","gists_url":"https://api.github.com/users/raghavrv/gists{/gist_id}","starred_url":"https://api.github.com/users/raghavrv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raghavrv/subscriptions","organizations_url":"https://api.github.com/users/raghavrv/orgs","repos_url":"https://api.github.com/users/raghavrv/repos","events_url":"https://api.github.com/users/raghavrv/events{/privacy}","received_events_url":"https://api.github.com/users/raghavrv/received_events","type":"User","site_admin":false},"body":"Finishing up @vighneshbirodkar's #5689 (Also refer #1036)\r\n\r\nYet to do - \r\n\r\n- [x] GBRCV\r\n- [x] Test GBRCV\r\n- [ ] Memory profile `'threading'` backend with multiprocessing backend.\r\n- [ ] More tests and examples?\r\n\r\n@amueller @agramfort @MechCoder @vighneshbirodkar @ogrisel @glouppe ","created_at":"2016-07-24T15:22:09Z","updated_at":"2016-07-29T12:13:18Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c59542f2d9f80539f94845321b728e09cb44b7d1","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071/commits","review_comments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071/comments","review_comment_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments{/number}","comments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/7071/comments","statuses_url":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/5c771f80d3489b9c0ce977a0e6a1d1295e1fe90c","head":{"label":"raghavrv:gbcv","ref":"gbcv","sha":"5c771f80d3489b9c0ce977a0e6a1d1295e1fe90c","user":{"login":"raghavrv","id":9487348,"avatar_url":"https://avatars.githubusercontent.com/u/9487348?v=3","gravatar_id":"","url":"https://api.github.com/users/raghavrv","html_url":"https://github.com/raghavrv","followers_url":"https://api.github.com/users/raghavrv/followers","following_url":"https://api.github.com/users/raghavrv/following{/other_user}","gists_url":"https://api.github.com/users/raghavrv/gists{/gist_id}","starred_url":"https://api.github.com/users/raghavrv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raghavrv/subscriptions","organizations_url":"https://api.github.com/users/raghavrv/orgs","repos_url":"https://api.github.com/users/raghavrv/repos","events_url":"https://api.github.com/users/raghavrv/events{/privacy}","received_events_url":"https://api.github.com/users/raghavrv/received_events","type":"User","site_admin":false},"repo":{"id":26038110,"name":"scikit-learn","full_name":"raghavrv/scikit-learn","owner":{"login":"raghavrv","id":9487348,"avatar_url":"https://avatars.githubusercontent.com/u/9487348?v=3","gravatar_id":"","url":"https://api.github.com/users/raghavrv","html_url":"https://github.com/raghavrv","followers_url":"https://api.github.com/users/raghavrv/followers","following_url":"https://api.github.com/users/raghavrv/following{/other_user}","gists_url":"https://api.github.com/users/raghavrv/gists{/gist_id}","starred_url":"https://api.github.com/users/raghavrv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raghavrv/subscriptions","organizations_url":"https://api.github.com/users/raghavrv/orgs","repos_url":"https://api.github.com/users/raghavrv/repos","events_url":"https://api.github.com/users/raghavrv/events{/privacy}","received_events_url":"https://api.github.com/users/raghavrv/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/raghavrv/scikit-learn","description":"scikit-learn: machine learning in Python","fork":true,"url":"https://api.github.com/repos/raghavrv/scikit-learn","forks_url":"https://api.github.com/repos/raghavrv/scikit-learn/forks","keys_url":"https://api.github.com/repos/raghavrv/scikit-learn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/raghavrv/scikit-learn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/raghavrv/scikit-learn/teams","hooks_url":"https://api.github.com/repos/raghavrv/scikit-learn/hooks","issue_events_url":"https://api.github.com/repos/raghavrv/scikit-learn/issues/events{/number}","events_url":"https://api.github.com/repos/raghavrv/scikit-learn/events","assignees_url":"https://api.github.com/repos/raghavrv/scikit-learn/assignees{/user}","branches_url":"https://api.github.com/repos/raghavrv/scikit-learn/branches{/branch}","tags_url":"https://api.github.com/repos/raghavrv/scikit-learn/tags","blobs_url":"https://api.github.com/repos/raghavrv/scikit-learn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/raghavrv/scikit-learn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/raghavrv/scikit-learn/git/refs{/sha}","trees_url":"https://api.github.com/repos/raghavrv/scikit-learn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/raghavrv/scikit-learn/statuses/{sha}","languages_url":"https://api.github.com/repos/raghavrv/scikit-learn/languages","stargazers_url":"https://api.github.com/repos/raghavrv/scikit-learn/stargazers","contributors_url":"https://api.github.com/repos/raghavrv/scikit-learn/contributors","subscribers_url":"https://api.github.com/repos/raghavrv/scikit-learn/subscribers","subscription_url":"https://api.github.com/repos/raghavrv/scikit-learn/subscription","commits_url":"https://api.github.com/repos/raghavrv/scikit-learn/commits{/sha}","git_commits_url":"https://api.github.com/repos/raghavrv/scikit-learn/git/commits{/sha}","comments_url":"https://api.github.com/repos/raghavrv/scikit-learn/comments{/number}","issue_comment_url":"https://api.github.com/repos/raghavrv/scikit-learn/issues/comments{/number}","contents_url":"https://api.github.com/repos/raghavrv/scikit-learn/contents/{+path}","compare_url":"https://api.github.com/repos/raghavrv/scikit-learn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/raghavrv/scikit-learn/merges","archive_url":"https://api.github.com/repos/raghavrv/scikit-learn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/raghavrv/scikit-learn/downloads","issues_url":"https://api.github.com/repos/raghavrv/scikit-learn/issues{/number}","pulls_url":"https://api.github.com/repos/raghavrv/scikit-learn/pulls{/number}","milestones_url":"https://api.github.com/repos/raghavrv/scikit-learn/milestones{/number}","notifications_url":"https://api.github.com/repos/raghavrv/scikit-learn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/raghavrv/scikit-learn/labels{/name}","releases_url":"https://api.github.com/repos/raghavrv/scikit-learn/releases{/id}","deployments_url":"https://api.github.com/repos/raghavrv/scikit-learn/deployments","created_at":"2014-11-01T00:56:31Z","updated_at":"2016-04-27T18:29:41Z","pushed_at":"2016-07-28T14:24:31Z","git_url":"git://github.com/raghavrv/scikit-learn.git","ssh_url":"git@github.com:raghavrv/scikit-learn.git","clone_url":"https://github.com/raghavrv/scikit-learn.git","svn_url":"https://github.com/raghavrv/scikit-learn","homepage":"http://scikit-learn.org","size":91543,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"scikit-learn:master","ref":"master","sha":"2ace75b860057f8790773ded4afa332a78bba4e1","user":{"login":"scikit-learn","id":365630,"avatar_url":"https://avatars.githubusercontent.com/u/365630?v=3","gravatar_id":"","url":"https://api.github.com/users/scikit-learn","html_url":"https://github.com/scikit-learn","followers_url":"https://api.github.com/users/scikit-learn/followers","following_url":"https://api.github.com/users/scikit-learn/following{/other_user}","gists_url":"https://api.github.com/users/scikit-learn/gists{/gist_id}","starred_url":"https://api.github.com/users/scikit-learn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scikit-learn/subscriptions","organizations_url":"https://api.github.com/users/scikit-learn/orgs","repos_url":"https://api.github.com/users/scikit-learn/repos","events_url":"https://api.github.com/users/scikit-learn/events{/privacy}","received_events_url":"https://api.github.com/users/scikit-learn/received_events","type":"Organization","site_admin":false},"repo":{"id":843222,"name":"scikit-learn","full_name":"scikit-learn/scikit-learn","owner":{"login":"scikit-learn","id":365630,"avatar_url":"https://avatars.githubusercontent.com/u/365630?v=3","gravatar_id":"","url":"https://api.github.com/users/scikit-learn","html_url":"https://github.com/scikit-learn","followers_url":"https://api.github.com/users/scikit-learn/followers","following_url":"https://api.github.com/users/scikit-learn/following{/other_user}","gists_url":"https://api.github.com/users/scikit-learn/gists{/gist_id}","starred_url":"https://api.github.com/users/scikit-learn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scikit-learn/subscriptions","organizations_url":"https://api.github.com/users/scikit-learn/orgs","repos_url":"https://api.github.com/users/scikit-learn/repos","events_url":"https://api.github.com/users/scikit-learn/events{/privacy}","received_events_url":"https://api.github.com/users/scikit-learn/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/scikit-learn/scikit-learn","description":"scikit-learn: machine learning in Python","fork":false,"url":"https://api.github.com/repos/scikit-learn/scikit-learn","forks_url":"https://api.github.com/repos/scikit-learn/scikit-learn/forks","keys_url":"https://api.github.com/repos/scikit-learn/scikit-learn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/scikit-learn/scikit-learn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/scikit-learn/scikit-learn/teams","hooks_url":"https://api.github.com/repos/scikit-learn/scikit-learn/hooks","issue_events_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/events{/number}","events_url":"https://api.github.com/repos/scikit-learn/scikit-learn/events","assignees_url":"https://api.github.com/repos/scikit-learn/scikit-learn/assignees{/user}","branches_url":"https://api.github.com/repos/scikit-learn/scikit-learn/branches{/branch}","tags_url":"https://api.github.com/repos/scikit-learn/scikit-learn/tags","blobs_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/refs{/sha}","trees_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/{sha}","languages_url":"https://api.github.com/repos/scikit-learn/scikit-learn/languages","stargazers_url":"https://api.github.com/repos/scikit-learn/scikit-learn/stargazers","contributors_url":"https://api.github.com/repos/scikit-learn/scikit-learn/contributors","subscribers_url":"https://api.github.com/repos/scikit-learn/scikit-learn/subscribers","subscription_url":"https://api.github.com/repos/scikit-learn/scikit-learn/subscription","commits_url":"https://api.github.com/repos/scikit-learn/scikit-learn/commits{/sha}","git_commits_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/commits{/sha}","comments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/comments{/number}","issue_comment_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/comments{/number}","contents_url":"https://api.github.com/repos/scikit-learn/scikit-learn/contents/{+path}","compare_url":"https://api.github.com/repos/scikit-learn/scikit-learn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/scikit-learn/scikit-learn/merges","archive_url":"https://api.github.com/repos/scikit-learn/scikit-learn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/scikit-learn/scikit-learn/downloads","issues_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues{/number}","pulls_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls{/number}","milestones_url":"https://api.github.com/repos/scikit-learn/scikit-learn/milestones{/number}","notifications_url":"https://api.github.com/repos/scikit-learn/scikit-learn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/scikit-learn/scikit-learn/labels{/name}","releases_url":"https://api.github.com/repos/scikit-learn/scikit-learn/releases{/id}","deployments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/deployments","created_at":"2010-08-17T09:43:38Z","updated_at":"2016-07-29T10:37:20Z","pushed_at":"2016-07-29T10:27:51Z","git_url":"git://github.com/scikit-learn/scikit-learn.git","ssh_url":"git@github.com:scikit-learn/scikit-learn.git","clone_url":"https://github.com/scikit-learn/scikit-learn.git","svn_url":"https://github.com/scikit-learn/scikit-learn","homepage":"http://scikit-learn.org","size":122221,"stargazers_count":12604,"watchers_count":12604,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":7296,"mirror_url":null,"open_issues_count":1243,"forks":7296,"open_issues":1243,"watchers":12604,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071"},"html":{"href":"https://github.com/scikit-learn/scikit-learn/pull/7071"},"issue":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/7071"},"comments":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/7071/comments"},"review_comments":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071/comments"},"review_comment":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/7071/commits"},"statuses":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/5c771f80d3489b9c0ce977a0e6a1d1295e1fe90c"}}}}
{"actor":{"display_login":"raghavrv"}}