{"action":"created","comment":{"url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments/192008311","pull_request_review_id":124693504,"id":192008311,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5MjAwODMxMQ==","diff_hunk":"@@ -0,0 +1,845 @@\n+# Authors: Andreas Mueller <amueller@ais.uni-bonn.de>\n+#          Joris Van den Bossche <jorisvandenbossche@gmail.com>\n+# License: BSD 3 clause\n+\n+from __future__ import division\n+\n+import numbers\n+import warnings\n+\n+import numpy as np\n+from scipy import sparse\n+\n+from ..base import BaseEstimator, TransformerMixin\n+from ..externals import six\n+from ..utils import check_array\n+from ..utils import deprecated\n+from ..utils.fixes import _argmax\n+from ..utils.validation import check_is_fitted, FLOAT_DTYPES\n+from .label import LabelEncoder\n+\n+\n+range = six.moves.range\n+\n+\n+__all__ = [\n+    'OneHotEncoder',\n+    'OrdinalEncoder'\n+]\n+\n+\n+def _transform_selected(X, transform, selected=\"all\", copy=True):\n+    \"\"\"Apply a transform function to portion of selected features\n+\n+    Parameters\n+    ----------\n+    X : {array-like, sparse matrix}, shape [n_samples, n_features]\n+        Dense array or sparse matrix.\n+\n+    transform : callable\n+        A callable transform(X) -> X_transformed\n+\n+    copy : boolean, optional\n+        Copy X even if it could be avoided.\n+\n+    selected: \"all\" or array of indices or mask\n+        Specify which features to apply the transform to.\n+\n+    Returns\n+    -------\n+    X : array or sparse matrix, shape=(n_samples, n_features_new)\n+    \"\"\"\n+    X = check_array(X, accept_sparse='csc', copy=copy, dtype=FLOAT_DTYPES)\n+\n+    if isinstance(selected, six.string_types) and selected == \"all\":\n+        return transform(X)\n+\n+    if len(selected) == 0:\n+        return X\n+\n+    n_features = X.shape[1]\n+    ind = np.arange(n_features)\n+    sel = np.zeros(n_features, dtype=bool)\n+    sel[np.asarray(selected)] = True\n+    not_sel = np.logical_not(sel)\n+    n_selected = np.sum(sel)\n+\n+    if n_selected == 0:\n+        # No features selected.\n+        return X\n+    elif n_selected == n_features:\n+        # All features selected.\n+        return transform(X)\n+    else:\n+        X_sel = transform(X[:, ind[sel]])\n+        X_not_sel = X[:, ind[not_sel]]\n+\n+        if sparse.issparse(X_sel) or sparse.issparse(X_not_sel):\n+            return sparse.hstack((X_sel, X_not_sel))\n+        else:\n+            return np.hstack((X_sel, X_not_sel))\n+\n+\n+class _BaseEncoder(BaseEstimator, TransformerMixin):\n+    \"\"\"\n+    Base class for encoders that includes the code to categorize and\n+    transform the input features.\n+\n+    \"\"\"\n+\n+    def _fit(self, X, handle_unknown='error'):\n+\n+        X_temp = check_array(X, dtype=None)\n+        if not hasattr(X, 'dtype') and np.issubdtype(X_temp.dtype, np.str_):\n+            X = check_array(X, dtype=np.object)\n+        else:\n+            X = X_temp\n+\n+        n_samples, n_features = X.shape\n+\n+        if self.categories != 'auto':\n+            for cats in self.categories:\n+                if not np.all(np.sort(cats) == np.array(cats)):\n+                    raise ValueError(\"Unsorted categories are not yet \"\n+                                     \"supported\")\n+            if len(self.categories) != n_features:\n+                raise ValueError(\"Shape mismatch: if n_values is an array,\"\n+                                 \" it has to be of shape (n_features,).\")\n+\n+        self._label_encoders_ = [LabelEncoder() for _ in range(n_features)]\n+\n+        for i in range(n_features):\n+            le = self._label_encoders_[i]\n+            Xi = X[:, i]\n+            if self.categories == 'auto':\n+                le.fit(Xi)\n+            else:\n+                if handle_unknown == 'error':\n+                    valid_mask = np.in1d(Xi, self.categories[i])\n+                    if not np.all(valid_mask):\n+                        diff = np.unique(Xi[~valid_mask])\n+                        msg = (\"Found unknown categories {0} in column {1}\"\n+                               \" during fit\".format(diff, i))\n+                        raise ValueError(msg)\n+                le.classes_ = np.array(self.categories[i])\n+\n+        self.categories_ = [le.classes_ for le in self._label_encoders_]\n+\n+    def _transform(self, X, handle_unknown='error'):\n+\n+        X_temp = check_array(X, dtype=None)\n+        if not hasattr(X, 'dtype') and np.issubdtype(X_temp.dtype, np.str_):\n+            X = check_array(X, dtype=np.object)\n+        else:\n+            X = X_temp\n+\n+        _, n_features = X.shape\n+        X_int = np.zeros_like(X, dtype=np.int)\n+        X_mask = np.ones_like(X, dtype=np.bool)\n+\n+        for i in range(n_features):\n+            Xi = X[:, i]\n+            valid_mask = np.in1d(Xi, self.categories_[i])\n+\n+            if not np.all(valid_mask):\n+                if handle_unknown == 'error':\n+                    diff = np.unique(X[~valid_mask, i])\n+                    msg = (\"Found unknown categories {0} in column {1}\"\n+                           \" during transform\".format(diff, i))\n+                    raise ValueError(msg)\n+                else:\n+                    # Set the problematic rows to an acceptable value and\n+                    # continue `The rows are marked `X_mask` and will be\n+                    # removed later.\n+                    X_mask[:, i] = valid_mask\n+                    Xi = Xi.copy()\n+                    Xi[~valid_mask] = self.categories_[i][0]\n+            X_int[:, i] = self._label_encoders_[i].transform(Xi)\n+\n+        return X_int, X_mask\n+\n+\n+class OneHotEncoder(_BaseEncoder):\n+    \"\"\"Encode categorical integer features as a one-hot numeric array.\n+\n+    The input to this transformer should be an array-like of integers or\n+    strings, denoting the values taken on by categorical (discrete) features.\n+    The features are encoded using a one-hot (aka 'one-of-K' or 'dummy')\n+    encoding scheme. This creates a binary column for each category and\n+    returns a sparse matrix or dense array.\n+\n+    By default, the encoder derives the categories based on the unique values\n+    in each feature. Alternatively, you can also specify the `categories`\n+    manually.\n+    The OneHotEncoder previously assumed that the input features take on\n+    values in the range [0, max(values)). This behaviour is deprecated.\n+\n+    This encoding is needed for feeding categorical data to many scikit-learn\n+    estimators, notably linear models and SVMs with the standard kernels.\n+\n+    Note: a one-hot encoding of y labels should use a LabelBinarizer\n+    instead.\n+\n+    Read more in the :ref:`User Guide <preprocessing_categorical_features>`.\n+\n+    Parameters\n+    ----------\n+    categories : 'auto' or a list of lists/arrays of values.\n+        Categories (unique values) per feature:\n+\n+        - 'auto' : Determine categories automatically from the training data.\n+        - list : ``categories[i]`` holds the categories expected in the ith\n+          column. The passed categories must be sorted and should not mix\n+          strings and numeric values.\n+\n+        The used categories can be found in the ``categories_`` attribute.\n+\n+    sparse : boolean, default=True\n+        Will return sparse matrix if set True else will return an array.\n+\n+    dtype : number type, default=np.float\n+        Desired dtype of output.\n+\n+    handle_unknown : 'error' (default) or 'ignore'\n+        Whether to raise an error or ignore if an unknown categorical feature\n+        is present during transform (default is to raise). When this parameter\n+        is set to 'ignore' and an unknown category is encountered during\n+        transform, the resulting one-hot encoded columns for this feature\n+        will be all zeros. In the inverse transform, an unknown category\n+        will be denoted as None.\n+\n+    n_values : 'auto', int or array of ints\n+        Number of values per feature.\n+\n+        - 'auto' : determine value range from training data.\n+        - int : number of categorical values per feature.\n+                Each feature value should be in ``range(n_values)``\n+        - array : ``n_values[i]`` is the number of categorical values in\n+                  ``X[:, i]``. Each feature value should be\n+                  in ``range(n_values[i])``\n+\n+        .. deprecated:: 0.20\n+            The `n_values` keyword was deprecated in version 0.20 and will\n+            be removed in 0.22. Use `categories` instead.\n+\n+    categorical_features : \"all\" or array of indices or mask\n+        Specify what features are treated as categorical.\n+\n+        - 'all' (default): All features are treated as categorical.\n+        - array of indices: Array of categorical feature indices.\n+        - mask: Array of length n_features and with dtype=bool.\n+\n+        Non-categorical features are always stacked to the right of the matrix.\n+\n+        .. deprecated:: 0.20\n+            The `categorical_features` keyword was deprecated in version\n+            0.20 and will be removed in 0.22.\n+            You can use the ``ColumnTransformer`` instead.\n+\n+    Attributes\n+    ----------\n+    categories_ : list of arrays\n+        The categories of each feature determined during fitting\n+        (in order corresponding with output of ``transform``).\n+\n+    active_features_ : array\n+        Indices for active features, meaning values that actually occur\n+        in the training set. Only available when n_values is ``'auto'``.\n+\n+        .. deprecated:: 0.20\n+            The `active_features_` attribute was deprecated in version\n+            0.20 and will be removed in 0.22.\n+\n+    feature_indices_ : array of shape (n_features,)\n+        Indices to feature ranges.\n+        Feature ``i`` in the original data is mapped to features\n+        from ``feature_indices_[i]`` to ``feature_indices_[i+1]``\n+        (and then potentially masked by `active_features_` afterwards)\n+\n+        .. deprecated:: 0.20\n+            The `feature_indices_` attribute was deprecated in version\n+            0.20 and will be removed in 0.22.\n+\n+    n_values_ : array of shape (n_features,)\n+        Maximum number of values per feature.\n+\n+        .. deprecated:: 0.20\n+            The `n_values_` attribute was deprecated in version\n+            0.20 and will be removed in 0.22.\n+\n+    Examples\n+    --------\n+    Given a dataset with two features, we let the encoder find the unique\n+    values per feature and transform the data to a binary one-hot encoding.\n+\n+    >>> from sklearn.preprocessing import OneHotEncoder\n+    >>> enc = OneHotEncoder(handle_unknown='ignore')\n+    >>> X = [['Male', 1], ['Female', 3], ['Female', 2]]\n+    >>> enc.fit(X)\n+    ... # doctest: +ELLIPSIS\n+    OneHotEncoder(categorical_features='all', categories='auto',\n+           dtype=<... 'numpy.float64'>, handle_unknown='ignore',\n+           n_values=None, sparse=True)\n+\n+    >>> enc.categories_\n+    [array(['Female', 'Male'], dtype=object), array([1, 2, 3], dtype=object)]\n+    >>> enc.transform([['Female', 1], ['Male', 4]]).toarray()\n+    array([[1., 0., 1., 0., 0.],\n+           [0., 1., 0., 0., 0.]])\n+    >>> enc.inverse_transform([[0, 1, 1, 0, 0], [0, 0, 0, 1, 0]])\n+    array([['Male', 1],\n+           [None, 2]], dtype=object)\n+\n+    See also\n+    --------\n+    sklearn.preprocessing.OrdinalEncoder : performs an ordinal (integer)\n+      encoding of the categorical features.\n+    sklearn.feature_extraction.DictVectorizer : performs a one-hot encoding of\n+      dictionary items (also handles string-valued features).\n+    sklearn.feature_extraction.FeatureHasher : performs an approximate one-hot\n+      encoding of dictionary items or strings.\n+    sklearn.preprocessing.LabelBinarizer : binarizes labels in a one-vs-all\n+      fashion.\n+    sklearn.preprocessing.MultiLabelBinarizer : transforms between iterable of\n+      iterables and a multilabel format, e.g. a (samples x classes) binary\n+      matrix indicating the presence of a class label.\n+    \"\"\"\n+\n+    def __init__(self, n_values=None, categorical_features=None,\n+                 categories=None, sparse=True, dtype=np.float64,\n+                 handle_unknown='error'):\n+        self._categories = self.categories = categories\n+        self.sparse = sparse\n+        self.dtype = dtype\n+        self.handle_unknown = handle_unknown\n+        self._n_values = self.n_values = n_values\n+        self._categorical_features = categorical_features\n+        self.categorical_features = categorical_features\n+\n+    # Deprecated attributes\n+\n+    @property\n+    @deprecated(\"The 'active_features_' attribute was deprecated in version \"\n+                \"0.20 and will be removed 0.22.\")\n+    def active_features_(self):\n+        check_is_fitted(self, 'categories_')\n+        return self._active_features_\n+\n+    @property\n+    @deprecated(\"The 'feature_indices_' attribute was deprecated in version \"\n+                \"0.20 and will be removed 0.22.\")\n+    def feature_indices_(self):\n+        check_is_fitted(self, 'categories_')\n+        return self._feature_indices_\n+\n+    @property\n+    @deprecated(\"The 'n_values_' attribute was deprecated in version \"\n+                \"0.20 and will be removed 0.22.\")\n+    def n_values_(self):\n+        check_is_fitted(self, 'categories_')\n+        return self._n_values_\n+\n+    def _handle_deprecations(self, X):\n+\n+        # user manually set the categories -> never legacy mode\n+        if self._categories is not None:\n+            self._legacy_mode = False\n+\n+        # categories not set -> infer if we need legacy mode or not\n+        elif self._n_values is not None and self._n_values != 'auto':\n+            msg = (\n+                \"Passing 'n_values' is deprecated in version 0.20 and will be \"\n+                \"removed in 0.22. You can use the 'categories' keyword \"\n+                \"instead. 'n_values=n' corresponds to 'categories=[range(n)]'.\"\n+            )\n+            warnings.warn(msg, DeprecationWarning)\n+\n+            # we internally translate this to the correct categories\n+            # and don't use legacy mode\n+            X = check_array(X, dtype=np.int)\n+\n+            if isinstance(self.n_values, numbers.Integral):\n+                n_features = X.shape[1]\n+                self.categories = [\n+                    list(range(self.n_values)) for _ in range(n_features)]\n+                n_values = np.empty(n_features, dtype=np.int)\n+                n_values.fill(self.n_values)\n+            else:\n+                try:\n+                    n_values = np.asarray(self.n_values, dtype=int)\n+                    self.categories = [list(range(i)) for i in self.n_values]\n+                except (ValueError, TypeError):\n+                    raise TypeError(\n+                        \"Wrong type for parameter `n_values`. Expected 'auto',\"\n+                        \" int or array of ints, got %r\".format(type(X)))\n+\n+            self._n_values_ = n_values\n+            n_values = np.hstack([[0], n_values])\n+            indices = np.cumsum(n_values)\n+            self._feature_indices_ = indices\n+\n+            self._legacy_mode = False\n+\n+        else:  # n_values = 'auto'\n+            if self.handle_unknown == 'ignore':\n+                # no change in behaviour, no need to raise deprecation warning\n+                self._legacy_mode = False\n+                self.categories = 'auto'\n+                if self._n_values == 'auto':\n+                    # user manually specified this\n+                    msg = (\n+                        \"Passing 'n_values' is deprecated in version 0.20 and \"\n+                        \"will be removed in 0.22. n_values='auto' can be \"\n+                        \"replaced with categories='auto'.\"\n+                    )\n+                    warnings.warn(msg, DeprecationWarning)\n+            else:\n+\n+                # check if we have integer or categorical input\n+                try:\n+                    X = check_array(X, dtype=np.int)\n+                except ValueError:\n+                    self._legacy_mode = False\n+                    self.categories = 'auto'\n+                else:\n+                    msg = (\n+                        \"The handling of integer data will change in version \"\n+                        \"0.22. Currently, the categories are determined \"\n+                        \"based on the range [0, max(values)], while in the \"\n+                        \"future they will be determined based on the unique \"\n+                        \"values.\\nIf you want the future behaviour and \"\n+                        \"silence this warning, you can specify \"\n+                        \"\\\"categories='auto'\\\".\\n\"\n+                        \"In case you used a LabelEncoder before this \"\n+                        \"OneHotEncoder to convert the categories to integers, \"\n+                        \"then you can now use the OneHotEncoder directly.\"\n+                    )\n+                    warnings.warn(msg, FutureWarning)\n+                    self._legacy_mode = True\n+                    self.n_values = 'auto'\n+\n+        # if user specified categorical_features -> always use legacy mode\n+        if self._categorical_features is not None:\n+            if (isinstance(self._categorical_features, six.string_types)\n+                    and self._categorical_features == 'all'):\n+                warnings.warn(\n+                    \"The 'categorical_features' keyword is deprecated in \"\n+                    \"version 0.20 and will be removed in 0.22. The passed \"\n+                    \"value of 'all' is the default and can simply be removed.\",\n+                    DeprecationWarning)\n+            else:\n+                if self._categories is not None:\n+                    raise ValueError(\n+                        \"The 'categorical_features' keyword is deprecated, \"\n+                        \"and cannot be used together with specifying \"\n+                        \"'categories'.\")\n+                warnings.warn(\n+                    \"The 'categorical_features' keyword is deprecated in \"\n+                    \"version 0.20 and will be removed in 0.22. You can \"\n+                    \"use the ColumnTransformer instead.\", DeprecationWarning)\n+                self._legacy_mode = True\n+        else:\n+            self.categorical_features = 'all'\n+\n+    def fit(self, X, y=None):\n+        \"\"\"Fit OneHotEncoder to X.\n+\n+        Parameters\n+        ----------\n+        X : array-like, shape [n_samples, n_feature]\n+            The data to determine the categories of each feature.\n+\n+        Returns\n+        -------\n+        self\n+        \"\"\"\n+        if self.handle_unknown not in ('error', 'ignore'):\n+            msg = (\"handle_unknown should be either 'error' or 'ignore', \"\n+                   \"got {0}.\".format(self.handle_unknown))\n+            raise ValueError(msg)\n+\n+        self._handle_deprecations(X)\n+\n+        if self._legacy_mode:\n+            _transform_selected(X, self._legacy_fit_transform,\n+                                self.categorical_features,\n+                                copy=True)\n+            return self\n+        else:\n+            self._fit(X, handle_unknown=self.handle_unknown)\n+            return self\n+\n+    def _legacy_fit_transform(self, X):\n+        \"\"\"Assumes X contains only categorical features.\"\"\"\n+        dtype = getattr(X, 'dtype', None)\n+        X = check_array(X, dtype=np.int)\n+        if np.any(X < 0):\n+            raise ValueError(\"X needs to contain only non-negative integers.\")\n+        n_samples, n_features = X.shape\n+        if (isinstance(self.n_values, six.string_types) and\n+                self.n_values == 'auto'):\n+            n_values = np.max(X, axis=0) + 1\n+        elif isinstance(self.n_values, numbers.Integral):\n+            if (np.max(X, axis=0) >= self.n_values).any():\n+                raise ValueError(\"Feature out of bounds for n_values=%d\"\n+                                 % self.n_values)\n+            n_values = np.empty(n_features, dtype=np.int)\n+            n_values.fill(self.n_values)\n+        else:\n+            try:\n+                n_values = np.asarray(self.n_values, dtype=int)\n+            except (ValueError, TypeError):\n+                raise TypeError(\"Wrong type for parameter `n_values`. Expected\"\n+                                \" 'auto', int or array of ints, got %r\"\n+                                % type(X))\n+            if n_values.ndim < 1 or n_values.shape[0] != X.shape[1]:\n+                raise ValueError(\"Shape mismatch: if n_values is an array,\"\n+                                 \" it has to be of shape (n_features,).\")\n+\n+        self._n_values_ = n_values\n+        self.categories_ = [np.arange(n_val - 1, dtype=dtype)\n+                            for n_val in n_values]\n+        n_values = np.hstack([[0], n_values])\n+        indices = np.cumsum(n_values)\n+        self._feature_indices_ = indices\n+\n+        column_indices = (X + indices[:-1]).ravel()\n+        row_indices = np.repeat(np.arange(n_samples, dtype=np.int32),\n+                                n_features)\n+        data = np.ones(n_samples * n_features)\n+        out = sparse.coo_matrix((data, (row_indices, column_indices)),\n+                                shape=(n_samples, indices[-1]),\n+                                dtype=self.dtype).tocsr()\n+\n+        if (isinstance(self.n_values, six.string_types) and\n+                self.n_values == 'auto'):\n+            mask = np.array(out.sum(axis=0)).ravel() != 0\n+            active_features = np.where(mask)[0]\n+            out = out[:, active_features]\n+            self._active_features_ = active_features\n+\n+            self.categories_ = [\n+                np.unique(X[:, i]).astype(dtype) if dtype\n+                else np.unique(X[:, i]) for i in range(n_features)]\n+\n+        return out if self.sparse else out.toarray()\n+\n+    def fit_transform(self, X, y=None):\n+        \"\"\"Fit OneHotEncoder to X, then transform X.\n+\n+        Equivalent to self.fit(X).transform(X), but more convenient and more\n+        efficient. See fit for the parameters, transform for the return value.\n+\n+        Parameters\n+        ----------\n+        X : array-like, shape [n_samples, n_feature]\n+            Input array of type int.\n+        \"\"\"\n+        if self.handle_unknown not in ('error', 'ignore'):\n+            msg = (\"handle_unknown should be either 'error' or 'ignore', \"\n+                   \"got {0}.\".format(self.handle_unknown))\n+            raise ValueError(msg)\n+\n+        self._handle_deprecations(X)\n+\n+        if self._legacy_mode:\n+            return _transform_selected(X, self._legacy_fit_transform,\n+                                       self.categorical_features, copy=True)\n+        else:\n+            return self.fit(X).transform(X)\n+\n+    def _legacy_transform(self, X):\n+        \"\"\"Assumes X contains only categorical features.\"\"\"\n+        X = check_array(X, dtype=np.int)\n+        if np.any(X < 0):\n+            raise ValueError(\"X needs to contain only non-negative integers.\")\n+        n_samples, n_features = X.shape\n+\n+        indices = self._feature_indices_\n+        if n_features != indices.shape[0] - 1:\n+            raise ValueError(\"X has different shape than during fitting.\"\n+                             \" Expected %d, got %d.\"\n+                             % (indices.shape[0] - 1, n_features))\n+\n+        # We use only those categorical features of X that are known using fit.\n+        # i.e lesser than n_values_ using mask.\n+        # This means, if self.handle_unknown is \"ignore\", the row_indices and\n+        # col_indices corresponding to the unknown categorical feature are\n+        # ignored.\n+        mask = (X < self._n_values_).ravel()\n+        if np.any(~mask):\n+            if self.handle_unknown not in ['error', 'ignore']:\n+                raise ValueError(\"handle_unknown should be either error or \"\n+                                 \"unknown got %s\" % self.handle_unknown)\n+            if self.handle_unknown == 'error':\n+                raise ValueError(\"unknown categorical feature present %s \"\n+                                 \"during transform.\" % X.ravel()[~mask])\n+\n+        column_indices = (X + indices[:-1]).ravel()[mask]\n+        row_indices = np.repeat(np.arange(n_samples, dtype=np.int32),\n+                                n_features)[mask]\n+        data = np.ones(np.sum(mask))\n+        out = sparse.coo_matrix((data, (row_indices, column_indices)),\n+                                shape=(n_samples, indices[-1]),\n+                                dtype=self.dtype).tocsr()\n+        if (isinstance(self.n_values, six.string_types) and\n+                self.n_values == 'auto'):\n+            out = out[:, self._active_features_]\n+\n+        return out if self.sparse else out.toarray()\n+\n+    def _transform_new(self, X):\n+        \"\"\"New implementation assuming categorical input\"\"\"\n+        X_temp = check_array(X, dtype=None)\n+        if not hasattr(X, 'dtype') and np.issubdtype(X_temp.dtype, np.str_):\n+            X = check_array(X, dtype=np.object)\n+        else:\n+            X = X_temp\n+\n+        n_samples, n_features = X.shape\n+\n+        X_int, X_mask = self._transform(X, handle_unknown=self.handle_unknown)\n+\n+        mask = X_mask.ravel()\n+        n_values = [cats.shape[0] for cats in self.categories_]\n+        n_values = np.array([0] + n_values)\n+        feature_indices = np.cumsum(n_values)\n+\n+        indices = (X_int + feature_indices[:-1]).ravel()[mask]\n+        indptr = X_mask.sum(axis=1).cumsum()\n+        indptr = np.insert(indptr, 0, 0)\n+        data = np.ones(n_samples * n_features)[mask]\n+\n+        out = sparse.csr_matrix((data, indices, indptr),\n+                                shape=(n_samples, feature_indices[-1]),\n+                                dtype=self.dtype)\n+        if not self.sparse:\n+            return out.toarray()\n+        else:\n+            return out\n+\n+    def transform(self, X):\n+        \"\"\"Transform X using one-hot encoding.\n+\n+        Parameters\n+        ----------\n+        X : array-like, shape [n_samples, n_features]\n+            The data to encode.\n+\n+        Returns\n+        -------\n+        X_out : sparse matrix if sparse=True else a 2-d array\n+            Transformed input.\n+        \"\"\"\n+        if self._legacy_mode:\n+            return _transform_selected(X, self._legacy_transform,\n+                                       self.categorical_features,\n+                                       copy=True)\n+        else:\n+            return self._transform_new(X)\n+\n+    def inverse_transform(self, X):\n+        \"\"\"Convert back the data to the original representation.\n+\n+        In case unknown categories are encountered (all zero's in the\n+        one-hot encoding), ``None`` is used to represent this category.\n+\n+        Parameters\n+        ----------\n+        X : array-like or sparse matrix, shape [n_samples, n_encoded_features]\n+            The transformed data.\n+\n+        Returns\n+        -------\n+        X_tr : array-like, shape [n_samples, n_features]\n+            Inverse transformed array.\n+\n+        \"\"\"\n+        # if self._legacy_mode:\n+        #     raise ValueError(\"only supported for categorical features\")\n+\n+        check_is_fitted(self, 'categories_')\n+        X = check_array(X, accept_sparse='csr')\n+\n+        n_samples, _ = X.shape\n+        n_features = len(self.categories_)\n+        n_transformed_features = sum([len(cats) for cats in self.categories_])\n+\n+        # validate shape of passed X\n+        msg = (\"Shape of the passed X data is not correct. Expected {0} \"\n+               \"columns, got {1}.\")\n+        if X.shape[1] != n_transformed_features:\n+            raise ValueError(msg.format(n_transformed_features, X.shape[1]))\n+\n+        # create resulting array of appropriate dtype\n+        dt = np.find_common_type([cat.dtype for cat in self.categories_], [])\n+        X_tr = np.empty((n_samples, n_features), dtype=dt)\n+\n+        j = 0\n+        found_unknown = {}\n+\n+        for i in range(n_features):\n+            n_categories = len(self.categories_[i])\n+            sub = X[:, j:j + n_categories]\n+\n+            # for sparse X argmax returns 2D matrix, ensure 1D array\n+            labels = np.asarray(_argmax(sub, axis=1)).flatten()\n+            X_tr[:, i] = self.categories_[i][labels]\n+\n+            if self.handle_unknown == 'ignore':\n+                # ignored unknown categories: we have a row of all zero's\n+                unknown = np.asarray(sub.sum(axis=1) == 0).flatten()\n+                if unknown.any():\n+                    found_unknown[i] = unknown\n+\n+            j += n_categories\n+\n+        # if ignored are found: potentially need to upcast result to\n+        # insert None values\n+        if found_unknown:\n+            if X_tr.dtype != object:\n+                X_tr = X_tr.astype(object)\n+\n+            for idx, mask in found_unknown.items():\n+                X_tr[mask, idx] = None\n+\n+        return X_tr\n+\n+\n+class OrdinalEncoder(_BaseEncoder):\n+    \"\"\"Encode categorical features as an integer array.\n+\n+    The input to this transformer should be an array-like of integers or\n+    strings, denoting the values taken on by categorical (discrete) features.\n+    The features are converted to ordinal integers. This results in\n+    a single column of integers (0 to n_categories - 1) per feature.\n+\n+    Read more in the :ref:`User Guide <preprocessing_categorical_features>`.\n+\n+    Parameters\n+    ----------\n+    categories : 'auto' or a list of lists/arrays of values.\n+        Categories (unique values) per feature:\n+\n+        - 'auto' : Determine categories automatically from the training data.\n+        - list : ``categories[i]`` holds the categories expected in the ith\n+          column. The passed categories must be sorted and should not mix\n+          strings and numeric values.\n+\n+        The used categories can be found in the ``categories_`` attribute.\n+\n+    dtype : number type, default np.float64\n+        Desired dtype of output.\n+\n+    Attributes\n+    ----------\n+    categories_ : list of arrays\n+        The categories of each feature determined during fitting\n+        (in order corresponding with output of ``transform``).\n+\n+    Examples\n+    --------\n+    Given a dataset with two features, we let the encoder find the unique\n+    values per feature and transform the data to an ordinal encoding.\n+\n+    >>> from sklearn.preprocessing import OrdinalEncoder\n+    >>> enc = OrdinalEncoder()\n+    >>> X = [['Male', 1], ['Female', 3], ['Female', 2]]\n+    >>> enc.fit(X)\n+    ... # doctest: +ELLIPSIS\n+    OrdinalEncoder(categories='auto', dtype=<... 'numpy.float64'>)\n+    >>> enc.categories_\n+    [array(['Female', 'Male'], dtype=object), array([1, 2, 3], dtype=object)]\n+    >>> enc.transform([['Female', 3], ['Male', 1]])\n+    array([[0., 2.],\n+           [1., 0.]])\n+\n+    >>> enc.inverse_transform([[1, 0], [0, 1]])\n+    array([['Male', 1],\n+           ['Female', 2]], dtype=object)\n+\n+    See also\n+    --------\n+    sklearn.preprocessing.OneHotEncoder : performs a one-hot encoding of\n+      categorical features.\n+    sklearn.preprocessing.LabelEncoder : encodes target labels with values\n+      between 0 and n_classes-1.\n+    sklearn.feature_extraction.DictVectorizer : performs a one-hot encoding of","path":"sklearn/preprocessing/_encoders.py","position":767,"original_position":767,"commit_id":"cd14fb55a46ef1334fef0c8cc97878683308e4c9","original_commit_id":"cd14fb55a46ef1334fef0c8cc97878683308e4c9","user":{"login":"jnothman","id":78827,"node_id":"MDQ6VXNlcjc4ODI3","avatar_url":"https://avatars2.githubusercontent.com/u/78827?v=4","gravatar_id":"","url":"https://api.github.com/users/jnothman","html_url":"https://github.com/jnothman","followers_url":"https://api.github.com/users/jnothman/followers","following_url":"https://api.github.com/users/jnothman/following{/other_user}","gists_url":"https://api.github.com/users/jnothman/gists{/gist_id}","starred_url":"https://api.github.com/users/jnothman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jnothman/subscriptions","organizations_url":"https://api.github.com/users/jnothman/orgs","repos_url":"https://api.github.com/users/jnothman/repos","events_url":"https://api.github.com/users/jnothman/events{/privacy}","received_events_url":"https://api.github.com/users/jnothman/received_events","type":"User","site_admin":false},"body":"Not relevant here","created_at":"2018-05-31T07:19:46Z","updated_at":"2018-05-31T07:32:45Z","html_url":"https://github.com/scikit-learn/scikit-learn/pull/10523#discussion_r192008311","pull_request_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/10523","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments/192008311"},"html":{"href":"https://github.com/scikit-learn/scikit-learn/pull/10523#discussion_r192008311"},"pull_request":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/10523"}}},"pull_request":{"url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/10523","id":164598020,"node_id":"MDExOlB1bGxSZXF1ZXN0MTY0NTk4MDIw","html_url":"https://github.com/scikit-learn/scikit-learn/pull/10523","diff_url":"https://github.com/scikit-learn/scikit-learn/pull/10523.diff","patch_url":"https://github.com/scikit-learn/scikit-learn/pull/10523.patch","issue_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/10523","number":10523,"state":"open","locked":false,"title":"WIP: Refactor CategoricalEncoder into OneHotEncoder (with deprecated kwargs) and OrdinalEncoder","user":{"login":"jorisvandenbossche","id":1020496,"node_id":"MDQ6VXNlcjEwMjA0OTY=","avatar_url":"https://avatars2.githubusercontent.com/u/1020496?v=4","gravatar_id":"","url":"https://api.github.com/users/jorisvandenbossche","html_url":"https://github.com/jorisvandenbossche","followers_url":"https://api.github.com/users/jorisvandenbossche/followers","following_url":"https://api.github.com/users/jorisvandenbossche/following{/other_user}","gists_url":"https://api.github.com/users/jorisvandenbossche/gists{/gist_id}","starred_url":"https://api.github.com/users/jorisvandenbossche/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jorisvandenbossche/subscriptions","organizations_url":"https://api.github.com/users/jorisvandenbossche/orgs","repos_url":"https://api.github.com/users/jorisvandenbossche/repos","events_url":"https://api.github.com/users/jorisvandenbossche/events{/privacy}","received_events_url":"https://api.github.com/users/jorisvandenbossche/received_events","type":"User","site_admin":false},"body":"Possibly fixes https://github.com/scikit-learn/scikit-learn/issues/10521\r\n\r\nThis does split the CategoricalEncoder into two separate classes for onehot and ordinal encoding, and then integrates the onehot encoding into the existing OneHotEncoder.\r\n\r\nI also moved them to a separate file, so for reviewing actual changes might be better to not view the first commit.\r\n\r\nI also did not yet introduce deprecation warnings for the old kwargs / attributes (or computed the new attributes in the old setting), but I infer based on the passed data to fit whether it was accepted before (and in this case we should raise a deprecation warning) and if not directly use the new behaviour. This 'inferring' of the behaviour can be overwritten with a newly added a `encoded_input=True/False` keyword.\r\n\r\nThe main drawback for new users of the OneHotEncoder is the 'pollution' with the old keywords and attributes in the docstring, repr of the object and tab completion.","created_at":"2018-01-23T14:57:08Z","updated_at":"2018-05-31T07:32:46Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3d9c88f4a47c368e9e263e2b4980ec05cae0c16a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/10523/commits","review_comments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/10523/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/10523/comments","statuses_url":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/cd14fb55a46ef1334fef0c8cc97878683308e4c9","head":{"label":"jorisvandenbossche:categorical-refactor","ref":"categorical-refactor","sha":"cd14fb55a46ef1334fef0c8cc97878683308e4c9","user":{"login":"jorisvandenbossche","id":1020496,"node_id":"MDQ6VXNlcjEwMjA0OTY=","avatar_url":"https://avatars2.githubusercontent.com/u/1020496?v=4","gravatar_id":"","url":"https://api.github.com/users/jorisvandenbossche","html_url":"https://github.com/jorisvandenbossche","followers_url":"https://api.github.com/users/jorisvandenbossche/followers","following_url":"https://api.github.com/users/jorisvandenbossche/following{/other_user}","gists_url":"https://api.github.com/users/jorisvandenbossche/gists{/gist_id}","starred_url":"https://api.github.com/users/jorisvandenbossche/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jorisvandenbossche/subscriptions","organizations_url":"https://api.github.com/users/jorisvandenbossche/orgs","repos_url":"https://api.github.com/users/jorisvandenbossche/repos","events_url":"https://api.github.com/users/jorisvandenbossche/events{/privacy}","received_events_url":"https://api.github.com/users/jorisvandenbossche/received_events","type":"User","site_admin":false},"repo":{"id":89341156,"node_id":"MDEwOlJlcG9zaXRvcnk4OTM0MTE1Ng==","name":"scikit-learn","full_name":"jorisvandenbossche/scikit-learn","owner":{"login":"jorisvandenbossche","id":1020496,"node_id":"MDQ6VXNlcjEwMjA0OTY=","avatar_url":"https://avatars2.githubusercontent.com/u/1020496?v=4","gravatar_id":"","url":"https://api.github.com/users/jorisvandenbossche","html_url":"https://github.com/jorisvandenbossche","followers_url":"https://api.github.com/users/jorisvandenbossche/followers","following_url":"https://api.github.com/users/jorisvandenbossche/following{/other_user}","gists_url":"https://api.github.com/users/jorisvandenbossche/gists{/gist_id}","starred_url":"https://api.github.com/users/jorisvandenbossche/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jorisvandenbossche/subscriptions","organizations_url":"https://api.github.com/users/jorisvandenbossche/orgs","repos_url":"https://api.github.com/users/jorisvandenbossche/repos","events_url":"https://api.github.com/users/jorisvandenbossche/events{/privacy}","received_events_url":"https://api.github.com/users/jorisvandenbossche/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jorisvandenbossche/scikit-learn","description":"scikit-learn: machine learning in Python","fork":true,"url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn","forks_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/forks","keys_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/teams","hooks_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/hooks","issue_events_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/issues/events{/number}","events_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/events","assignees_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/assignees{/user}","branches_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/branches{/branch}","tags_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/tags","blobs_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/git/refs{/sha}","trees_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/statuses/{sha}","languages_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/languages","stargazers_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/stargazers","contributors_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/contributors","subscribers_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/subscribers","subscription_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/subscription","commits_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/commits{/sha}","git_commits_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/git/commits{/sha}","comments_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/comments{/number}","issue_comment_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/issues/comments{/number}","contents_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/contents/{+path}","compare_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/merges","archive_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/downloads","issues_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/issues{/number}","pulls_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/pulls{/number}","milestones_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/milestones{/number}","notifications_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/labels{/name}","releases_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/releases{/id}","deployments_url":"https://api.github.com/repos/jorisvandenbossche/scikit-learn/deployments","created_at":"2017-04-25T09:15:16Z","updated_at":"2017-04-25T09:15:27Z","pushed_at":"2018-05-30T09:04:06Z","git_url":"git://github.com/jorisvandenbossche/scikit-learn.git","ssh_url":"git@github.com:jorisvandenbossche/scikit-learn.git","clone_url":"https://github.com/jorisvandenbossche/scikit-learn.git","svn_url":"https://github.com/jorisvandenbossche/scikit-learn","homepage":"http://scikit-learn.org","size":89306,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"scikit-learn:master","ref":"master","sha":"1557cb8a1910bce6dc33cd5cd3a08c380bdec566","user":{"login":"scikit-learn","id":365630,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM2NTYzMA==","avatar_url":"https://avatars2.githubusercontent.com/u/365630?v=4","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,"node_id":"MDEwOlJlcG9zaXRvcnk4NDMyMjI=","name":"scikit-learn","full_name":"scikit-learn/scikit-learn","owner":{"login":"scikit-learn","id":365630,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM2NTYzMA==","avatar_url":"https://avatars2.githubusercontent.com/u/365630?v=4","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":"2018-05-31T07:22:52Z","pushed_at":"2018-05-31T04:18:45Z","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":90578,"stargazers_count":28449,"watchers_count":28449,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14130,"mirror_url":null,"archived":false,"open_issues_count":1595,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":14130,"open_issues":1595,"watchers":28449,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/10523"},"html":{"href":"https://github.com/scikit-learn/scikit-learn/pull/10523"},"issue":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/10523"},"comments":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/10523/comments"},"review_comments":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/10523/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/10523/commits"},"statuses":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/cd14fb55a46ef1334fef0c8cc97878683308e4c9"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"jnothman"}}