Repository: scikit-learn/scikit-learn
Pull request: [MRG+2] improved documentation of MissingIndicator
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at
the contribution guidelines: https://github.com/scikit-learn/scikit-learn/blob/master/CONTRIBUTING.md#pull-request-checklist
-->

#### Reference Issues/PRs
<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->
Fixes #12417 

#### What does this implement/fix? Explain your changes.
Personally, I found the documentation about the MissingIndicator a bit misleading and I tried to improve this a little bit. Also, I tried to contribute an example to the userguide, how to use this in classification pipelines

#### Any other comments?
Nope

Problem statement:
MissingIndicator Documentation & Interface
Today I discovered a huge mistake in my experimental pipeline, mostly due to me making too many and in particular the wrong assumptions. In order to prevent other people from making the same assumptions, I think the `MissingIndicator` documentation can be slightly improved. In particular, it was unclear to me that it transforms the input array into missing indicator variables, rather than adding these missing indicator variables. Some things that I would clarify:

* `Binary indicators for missing values.` should maybe be: `Transforms the input array into binary indicators for missing values`. Personally, I would also add: `Note that this component typically does not work well in a vanilla pipeline consisting of preprocessors and a classifier, but rather could be added using a column_transformer` (or something in that sense)
* `The placeholder for the missing values. All occurrences of missing_values will be imputed.` should maybe be: This seems incorrect. As per previous point, MissingIndicator is not an imputer, but rather something that recognized which entrees are missing and transforms these into the boolean array
* It is not clear that the MissingIndicator should never be added to a vanilla pipeline consisting of preprocessors and a classifier; at least not in a practical sense: after the missing indicator there will not be much left to learn for the classifier, except which features were missing. Rather, it could be added to a ColumnTransformer (as per following example)
* `[...] values in fit This is applicable [...]` -> missing dot.

Also, this would be an appropriate example to Impute and Indicate. I would propose to add this to `doc/modules/impute.rst`
```
import sklearn.datasets
import sklearn.model_selection
import sklearn.svm
import sklearn.pipeline
import sklearn.impute
import sklearn.compose


X, y = sklearn.datasets.fetch_openml(data_id=6, return_X_y=True)
transformer = sklearn.compose.ColumnTransformer(
    transformers=[
        ('vanilla_features', sklearn.impute.SimpleImputer(strategy='constant', fill_value=-1), list(range(X.shape[1]))),
        ('indicate_features', sklearn.impute.MissingIndicator(error_on_new=False), [])],
    remainder='passthrough')

clf = sklearn.pipeline.make_pipeline(transformer,
                                     sklearn.feature_selection.VarianceThreshold(),
                                     sklearn.svm.SVC())

scores = sklearn.model_selection.cross_val_score(clf, X, y, cv=5)

print(scores.mean())
```

If you agree, I could contribute with a PR.

Hints from issue discussion:
If you want to transform all features, FeatureUnion is more appropriate
than ColumnTransformer. But yes, pr welcome. I think an example in the
narrative docs would be appropriate more than in the example gallery

Yeah PR welcome, and I agree, should to to the narrative and some additions to the docstring are good, too.
There's also #12294, which will probably mean less people would use the ``MissingIndicator`` directly. Maybe you want to do that so your pipeline gets simplified.

Commit message: removes empty line

Patch to review:
diff --git a/doc/modules/impute.rst b/doc/modules/impute.rst
index 0fd119857177b..30a9ee2435320 100644
--- a/doc/modules/impute.rst
+++ b/doc/modules/impute.rst
@@ -120,3 +120,51 @@ whether or not they contain missing values::
          [False,  True, False, False]])
   >>> indicator.features_
   array([0, 1, 2, 3])
+
+When using it in a pipeline, be sure to use the :class:`FeatureUnion` to add
+the indicator features to the regular features. First we obtain the `annael`
+dataset from OpenML, which has many missing values.
+
+  >>> import sklearn.datasets
+  >>> import sklearn.impute
+  >>> import sklearn.model_selection
+  >>> import sklearn.pipeline
+  >>> import sklearn.tree
+  >>> X, y = sklearn.datasets.fetch_openml('anneal', 1, return_X_y=True)
+  >>> X_train, X_test, y_train, _ = sklearn.model_selection.train_test_split(
+  >>>     X, y, test_size=100, random_state=0)
+
+Now we create a :class:`FeatureUnion`. All features will be imputed using
+:class:`SimpleImputer`, in order to enable classifiers to work with this data.
+Additionally, it adds the the indicator variables from
+:class:`MissingIndicator`.
+
+  >>> transformer = sklearn.pipeline.FeatureUnion(
+  >>>     transformer_list=[
+  >>>         ('vanilla_features',
+  >>>          sklearn.impute.SimpleImputer(strategy='constant',
+  >>>                                       fill_value=-1)),
+  >>>         ('indicate_features',
+  >>>          sklearn.impute.MissingIndicator(features='all'))])
+  >>> clf = sklearn.pipeline.make_pipeline(transformer,
+  >>>                                      sklearn.tree.DecisionTreeClassifier())
+
+
+Note that the anneal dataset has 38 columns. By applying the `features='all'`
+function, we ensure that all columns obtain a indicator column, also the ones
+that did not have any missing values.
+
+  >>> transformer.fit(X_train, y_train)
+  >>> results = transformer.transform(X_test)
+  >>> results.shape
+  (100, 76)
+
+Of course, we can not use the transformer to make any predictions. We should
+wrap this in a :class:`Pipeline` with a classifier (e.g., a
+:class:`DecisionTreeClassifier`) to be able to make predictions.
+
+  >>> clf.fit(X_train, y_train)
+  >>> results = clf.predict(X_test)
+  >>> results.shape
+  (100,)
+
diff --git a/sklearn/impute.py b/sklearn/impute.py
index e98c425d1b34f..f8432c2a142df 100644
--- a/sklearn/impute.py
+++ b/sklearn/impute.py
@@ -410,13 +410,18 @@ def transform(self, X):
 
 
 class MissingIndicator(BaseEstimator, TransformerMixin):
-    """Binary indicators for missing values.
+    """Binary indicators for missing values. Note that this component typically
+    should not not be used in a vanilla pipeline consisting of transformers and
+    a classifier, but rather could be added using a FeatureUnion.
+
+    Read more in the :ref:`User Guide <impute>`.
 
     Parameters
     ----------
     missing_values : number, string, np.nan (default) or None
         The placeholder for the missing values. All occurrences of
-        `missing_values` will be imputed.
+        `missing_values` will be indicated (True in the output array), the
+        other values will be marked as False.
 
     features : str, optional
         Whether the imputer mask should represent all or a subset of
@@ -437,7 +442,7 @@ class MissingIndicator(BaseEstimator, TransformerMixin):
     error_on_new : boolean, optional
         If True (default), transform will raise an error when there are
         features with missing values in transform that have no missing values
-        in fit This is applicable only when ``features="missing-only"``.
+        in fit. This is applicable only when ``features="missing-only"``.
 
     Attributes
     ----------


Code review comments:
diff_hunk: @@ -410,13 +410,18 @@ def transform(self, X):
 
 
 class MissingIndicator(BaseEstimator, TransformerMixin):
-    """Binary indicators for missing values.
+    """Binary indicators for missing values. Note that this component typically
path: sklearn/impute.py
text: @user1:
https://www.python.org/dev/peps/pep-0257/

single line description, than blank line, than longer description

diff_hunk: @@ -120,3 +120,51 @@ whether or not they contain missing values::
          [False,  True, False, False]])
   >>> indicator.features_
   array([0, 1, 2, 3])
+
+When using it in a pipeline, be sure to use the :class:`FeatureUnion` to add
+the indicator features to the regular features. First we obtain the `annael`
+dataset from OpenML, which has many missing values.
+
+  >>> import sklearn.datasets
+  >>> import sklearn.impute
+  >>> import sklearn.model_selection
+  >>> import sklearn.pipeline
+  >>> import sklearn.tree
+  >>> X, y = sklearn.datasets.fetch_openml('anneal', 1, return_X_y=True)
+  >>> X_train, X_test, y_train, _ = sklearn.model_selection.train_test_split(
+  >>>     X, y, test_size=100, random_state=0)
+
+Now we create a :class:`FeatureUnion`. All features will be imputed using
+:class:`SimpleImputer`, in order to enable classifiers to work with this data.
+Additionally, it adds the the indicator variables from
+:class:`MissingIndicator`.
+
+  >>> transformer = sklearn.pipeline.FeatureUnion(
+  >>>     transformer_list=[
+  >>>         ('vanilla_features',
+  >>>          sklearn.impute.SimpleImputer(strategy='constant',
path: doc/modules/impute.rst
text: @user1:
Is there a reason you're not using mean?