1
2.. _combining_estimators:
3
4==================================
5Pipelines and composite estimators
6==================================
7
8Transformers are usually combined with classifiers, regressors or other
9estimators to build a composite estimator.  The most common tool is a
10:ref:`Pipeline <pipeline>`. Pipeline is often used in combination with
11:ref:`FeatureUnion <feature_union>` which concatenates the output of
12transformers into a composite feature space.  :ref:`TransformedTargetRegressor
13<transformed_target_regressor>` deals with transforming the :term:`target`
14(i.e. log-transform :term:`y`). In contrast, Pipelines only transform the
15observed data (:term:`X`).
16
17.. _pipeline:
18
19Pipeline: chaining estimators
20=============================
21
22.. currentmodule:: sklearn.pipeline
23
24:class:`Pipeline` can be used to chain multiple estimators
25into one. This is useful as there is often a fixed sequence
26of steps in processing the data, for example feature selection, normalization
27and classification. :class:`Pipeline` serves multiple purposes here:
28
29Convenience and encapsulation
30    You only have to call :term:`fit` and :term:`predict` once on your
31    data to fit a whole sequence of estimators.
32Joint parameter selection
33    You can :ref:`grid search <grid_search>`
34    over parameters of all estimators in the pipeline at once.
35Safety
36    Pipelines help avoid leaking statistics from your test data into the
37    trained model in cross-validation, by ensuring that the same samples are
38    used to train the transformers and predictors.
39
40All estimators in a pipeline, except the last one, must be transformers
41(i.e. must have a :term:`transform` method).
42The last estimator may be any type (transformer, classifier, etc.).
43
44
45Usage
46-----
47
48Construction
49............
50
51The :class:`Pipeline` is built using a list of ``(key, value)`` pairs, where
52the ``key`` is a string containing the name you want to give this step and ``value``
53is an estimator object::
54
55    >>> from sklearn.pipeline import Pipeline
56    >>> from sklearn.svm import SVC
57    >>> from sklearn.decomposition import PCA
58    >>> estimators = [('reduce_dim', PCA()), ('clf', SVC())]
59    >>> pipe = Pipeline(estimators)
60    >>> pipe
61    Pipeline(steps=[('reduce_dim', PCA()), ('clf', SVC())])
62
63The utility function :func:`make_pipeline` is a shorthand
64for constructing pipelines;
65it takes a variable number of estimators and returns a pipeline,
66filling in the names automatically::
67
68    >>> from sklearn.pipeline import make_pipeline
69    >>> from sklearn.naive_bayes import MultinomialNB
70    >>> from sklearn.preprocessing import Binarizer
71    >>> make_pipeline(Binarizer(), MultinomialNB())
72    Pipeline(steps=[('binarizer', Binarizer()), ('multinomialnb', MultinomialNB())])
73
74Accessing steps
75...............
76
77The estimators of a pipeline are stored as a list in the ``steps`` attribute,
78but can be accessed by index or name by indexing (with ``[idx]``) the
79Pipeline::
80
81    >>> pipe.steps[0]
82    ('reduce_dim', PCA())
83    >>> pipe[0]
84    PCA()
85    >>> pipe['reduce_dim']
86    PCA()
87
88Pipeline's `named_steps` attribute allows accessing steps by name with tab
89completion in interactive environments::
90
91    >>> pipe.named_steps.reduce_dim is pipe['reduce_dim']
92    True
93
94A sub-pipeline can also be extracted using the slicing notation commonly used
95for Python Sequences such as lists or strings (although only a step of 1 is
96permitted). This is convenient for performing only some of the transformations
97(or their inverse):
98
99    >>> pipe[:1]
100    Pipeline(steps=[('reduce_dim', PCA())])
101    >>> pipe[-1:]
102    Pipeline(steps=[('clf', SVC())])
103
104
105.. _pipeline_nested_parameters:
106
107Nested parameters
108.................
109
110Parameters of the estimators in the pipeline can be accessed using the
111``<estimator>__<parameter>`` syntax::
112
113    >>> pipe.set_params(clf__C=10)
114    Pipeline(steps=[('reduce_dim', PCA()), ('clf', SVC(C=10))])
115
116This is particularly important for doing grid searches::
117
118    >>> from sklearn.model_selection import GridSearchCV
119    >>> param_grid = dict(reduce_dim__n_components=[2, 5, 10],
120    ...                   clf__C=[0.1, 10, 100])
121    >>> grid_search = GridSearchCV(pipe, param_grid=param_grid)
122
123Individual steps may also be replaced as parameters, and non-final steps may be
124ignored by setting them to ``'passthrough'``::
125
126    >>> from sklearn.linear_model import LogisticRegression
127    >>> param_grid = dict(reduce_dim=['passthrough', PCA(5), PCA(10)],
128    ...                   clf=[SVC(), LogisticRegression()],
129    ...                   clf__C=[0.1, 10, 100])
130    >>> grid_search = GridSearchCV(pipe, param_grid=param_grid)
131
132The estimators of the pipeline can be retrieved by index:
133
134    >>> pipe[0]
135    PCA()
136
137or by name::
138
139    >>> pipe['reduce_dim']
140    PCA()
141
142To enable model inspection, :class:`~sklearn.pipeline.Pipeline` has a
143``get_feature_names_out()`` method, just like all transformers. You can use
144pipeline slicing to get the feature names going into each step::
145
146    >>> from sklearn.datasets import load_iris
147    >>> from sklearn.feature_selection import SelectKBest
148    >>> iris = load_iris()
149    >>> pipe = Pipeline(steps=[
150    ...    ('select', SelectKBest(k=2)),
151    ...    ('clf', LogisticRegression())])
152    >>> pipe.fit(iris.data, iris.target)
153    Pipeline(steps=[('select', SelectKBest(...)), ('clf', LogisticRegression(...))])
154    >>> pipe[:-1].get_feature_names_out()
155    array(['x2', 'x3'], ...)
156
157You can also provide custom feature names for the input data using
158``get_feature_names_out``::
159
160    >>> pipe[:-1].get_feature_names_out(iris.feature_names)
161    array(['petal length (cm)', 'petal width (cm)'], ...)
162
163.. topic:: Examples:
164
165 * :ref:`sphx_glr_auto_examples_feature_selection_plot_feature_selection_pipeline.py`
166 * :ref:`sphx_glr_auto_examples_model_selection_grid_search_text_feature_extraction.py`
167 * :ref:`sphx_glr_auto_examples_compose_plot_digits_pipe.py`
168 * :ref:`sphx_glr_auto_examples_miscellaneous_plot_kernel_approximation.py`
169 * :ref:`sphx_glr_auto_examples_svm_plot_svm_anova.py`
170 * :ref:`sphx_glr_auto_examples_compose_plot_compare_reduction.py`
171 * :ref:`sphx_glr_auto_examples_miscellaneous_plot_pipeline_display.py`
172
173.. topic:: See Also:
174
175 * :ref:`composite_grid_search`
176
177
178Notes
179-----
180
181Calling ``fit`` on the pipeline is the same as calling ``fit`` on
182each estimator in turn, ``transform`` the input and pass it on to the next step.
183The pipeline has all the methods that the last estimator in the pipeline has,
184i.e. if the last estimator is a classifier, the :class:`Pipeline` can be used
185as a classifier. If the last estimator is a transformer, again, so is the
186pipeline.
187
188.. _pipeline_cache:
189
190Caching transformers: avoid repeated computation
191-------------------------------------------------
192
193.. currentmodule:: sklearn.pipeline
194
195Fitting transformers may be computationally expensive. With its
196``memory`` parameter set, :class:`Pipeline` will cache each transformer
197after calling ``fit``.
198This feature is used to avoid computing the fit transformers within a pipeline
199if the parameters and input data are identical. A typical example is the case of
200a grid search in which the transformers can be fitted only once and reused for
201each configuration.
202
203The parameter ``memory`` is needed in order to cache the transformers.
204``memory`` can be either a string containing the directory where to cache the
205transformers or a `joblib.Memory <https://pythonhosted.org/joblib/memory.html>`_
206object::
207
208    >>> from tempfile import mkdtemp
209    >>> from shutil import rmtree
210    >>> from sklearn.decomposition import PCA
211    >>> from sklearn.svm import SVC
212    >>> from sklearn.pipeline import Pipeline
213    >>> estimators = [('reduce_dim', PCA()), ('clf', SVC())]
214    >>> cachedir = mkdtemp()
215    >>> pipe = Pipeline(estimators, memory=cachedir)
216    >>> pipe
217    Pipeline(memory=...,
218             steps=[('reduce_dim', PCA()), ('clf', SVC())])
219    >>> # Clear the cache directory when you don't need it anymore
220    >>> rmtree(cachedir)
221
222.. warning:: **Side effect of caching transformers**
223
224   Using a :class:`Pipeline` without cache enabled, it is possible to
225   inspect the original instance such as::
226
227     >>> from sklearn.datasets import load_digits
228     >>> X_digits, y_digits = load_digits(return_X_y=True)
229     >>> pca1 = PCA()
230     >>> svm1 = SVC()
231     >>> pipe = Pipeline([('reduce_dim', pca1), ('clf', svm1)])
232     >>> pipe.fit(X_digits, y_digits)
233     Pipeline(steps=[('reduce_dim', PCA()), ('clf', SVC())])
234     >>> # The pca instance can be inspected directly
235     >>> print(pca1.components_)
236         [[-1.77484909e-19  ... 4.07058917e-18]]
237
238   Enabling caching triggers a clone of the transformers before fitting.
239   Therefore, the transformer instance given to the pipeline cannot be
240   inspected directly.
241   In following example, accessing the :class:`PCA` instance ``pca2``
242   will raise an ``AttributeError`` since ``pca2`` will be an unfitted
243   transformer.
244   Instead, use the attribute ``named_steps`` to inspect estimators within
245   the pipeline::
246
247     >>> cachedir = mkdtemp()
248     >>> pca2 = PCA()
249     >>> svm2 = SVC()
250     >>> cached_pipe = Pipeline([('reduce_dim', pca2), ('clf', svm2)],
251     ...                        memory=cachedir)
252     >>> cached_pipe.fit(X_digits, y_digits)
253     Pipeline(memory=...,
254             steps=[('reduce_dim', PCA()), ('clf', SVC())])
255     >>> print(cached_pipe.named_steps['reduce_dim'].components_)
256         [[-1.77484909e-19  ... 4.07058917e-18]]
257     >>> # Remove the cache directory
258     >>> rmtree(cachedir)
259
260.. topic:: Examples:
261
262 * :ref:`sphx_glr_auto_examples_compose_plot_compare_reduction.py`
263
264.. _transformed_target_regressor:
265
266Transforming target in regression
267=================================
268
269:class:`~sklearn.compose.TransformedTargetRegressor` transforms the
270targets ``y`` before fitting a regression model. The predictions are mapped
271back to the original space via an inverse transform. It takes as an argument
272the regressor that will be used for prediction, and the transformer that will
273be applied to the target variable::
274
275  >>> import numpy as np
276  >>> from sklearn.datasets import fetch_california_housing
277  >>> from sklearn.compose import TransformedTargetRegressor
278  >>> from sklearn.preprocessing import QuantileTransformer
279  >>> from sklearn.linear_model import LinearRegression
280  >>> from sklearn.model_selection import train_test_split
281  >>> X, y = fetch_california_housing(return_X_y=True)
282  >>> X, y = X[:2000, :], y[:2000]  # select a subset of data
283  >>> transformer = QuantileTransformer(output_distribution='normal')
284  >>> regressor = LinearRegression()
285  >>> regr = TransformedTargetRegressor(regressor=regressor,
286  ...                                   transformer=transformer)
287  >>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
288  >>> regr.fit(X_train, y_train)
289  TransformedTargetRegressor(...)
290  >>> print('R2 score: {0:.2f}'.format(regr.score(X_test, y_test)))
291  R2 score: 0.61
292  >>> raw_target_regr = LinearRegression().fit(X_train, y_train)
293  >>> print('R2 score: {0:.2f}'.format(raw_target_regr.score(X_test, y_test)))
294  R2 score: 0.59
295
296For simple transformations, instead of a Transformer object, a pair of
297functions can be passed, defining the transformation and its inverse mapping::
298
299  >>> def func(x):
300  ...     return np.log(x)
301  >>> def inverse_func(x):
302  ...     return np.exp(x)
303
304Subsequently, the object is created as::
305
306  >>> regr = TransformedTargetRegressor(regressor=regressor,
307  ...                                   func=func,
308  ...                                   inverse_func=inverse_func)
309  >>> regr.fit(X_train, y_train)
310  TransformedTargetRegressor(...)
311  >>> print('R2 score: {0:.2f}'.format(regr.score(X_test, y_test)))
312  R2 score: 0.51
313
314By default, the provided functions are checked at each fit to be the inverse of
315each other. However, it is possible to bypass this checking by setting
316``check_inverse`` to ``False``::
317
318  >>> def inverse_func(x):
319  ...     return x
320  >>> regr = TransformedTargetRegressor(regressor=regressor,
321  ...                                   func=func,
322  ...                                   inverse_func=inverse_func,
323  ...                                   check_inverse=False)
324  >>> regr.fit(X_train, y_train)
325  TransformedTargetRegressor(...)
326  >>> print('R2 score: {0:.2f}'.format(regr.score(X_test, y_test)))
327  R2 score: -1.57
328
329.. note::
330
331   The transformation can be triggered by setting either ``transformer`` or the
332   pair of functions ``func`` and ``inverse_func``. However, setting both
333   options will raise an error.
334
335.. topic:: Examples:
336
337 * :ref:`sphx_glr_auto_examples_compose_plot_transformed_target.py`
338
339
340.. _feature_union:
341
342FeatureUnion: composite feature spaces
343======================================
344
345.. currentmodule:: sklearn.pipeline
346
347:class:`FeatureUnion` combines several transformer objects into a new
348transformer that combines their output. A :class:`FeatureUnion` takes
349a list of transformer objects. During fitting, each of these
350is fit to the data independently. The transformers are applied in parallel,
351and the feature matrices they output are concatenated side-by-side into a
352larger matrix.
353
354When you want to apply different transformations to each field of the data,
355see the related class :class:`~sklearn.compose.ColumnTransformer`
356(see :ref:`user guide <column_transformer>`).
357
358:class:`FeatureUnion` serves the same purposes as :class:`Pipeline` -
359convenience and joint parameter estimation and validation.
360
361:class:`FeatureUnion` and :class:`Pipeline` can be combined to
362create complex models.
363
364(A :class:`FeatureUnion` has no way of checking whether two transformers
365might produce identical features. It only produces a union when the
366feature sets are disjoint, and making sure they are is the caller's
367responsibility.)
368
369
370Usage
371-----
372
373A :class:`FeatureUnion` is built using a list of ``(key, value)`` pairs,
374where the ``key`` is the name you want to give to a given transformation
375(an arbitrary string; it only serves as an identifier)
376and ``value`` is an estimator object::
377
378    >>> from sklearn.pipeline import FeatureUnion
379    >>> from sklearn.decomposition import PCA
380    >>> from sklearn.decomposition import KernelPCA
381    >>> estimators = [('linear_pca', PCA()), ('kernel_pca', KernelPCA())]
382    >>> combined = FeatureUnion(estimators)
383    >>> combined
384    FeatureUnion(transformer_list=[('linear_pca', PCA()),
385                                   ('kernel_pca', KernelPCA())])
386
387
388Like pipelines, feature unions have a shorthand constructor called
389:func:`make_union` that does not require explicit naming of the components.
390
391
392Like ``Pipeline``, individual steps may be replaced using ``set_params``,
393and ignored by setting to ``'drop'``::
394
395    >>> combined.set_params(kernel_pca='drop')
396    FeatureUnion(transformer_list=[('linear_pca', PCA()),
397                                   ('kernel_pca', 'drop')])
398
399.. topic:: Examples:
400
401 * :ref:`sphx_glr_auto_examples_compose_plot_feature_union.py`
402
403
404.. _column_transformer:
405
406ColumnTransformer for heterogeneous data
407========================================
408
409Many datasets contain features of different types, say text, floats, and dates,
410where each type of feature requires separate preprocessing or feature
411extraction steps.  Often it is easiest to preprocess data before applying
412scikit-learn methods, for example using `pandas <https://pandas.pydata.org/>`__.
413Processing your data before passing it to scikit-learn might be problematic for
414one of the following reasons:
415
4161. Incorporating statistics from test data into the preprocessors makes
417   cross-validation scores unreliable (known as *data leakage*),
418   for example in the case of scalers or imputing missing values.
4192. You may want to include the parameters of the preprocessors in a
420   :ref:`parameter search <grid_search>`.
421
422The :class:`~sklearn.compose.ColumnTransformer` helps performing different
423transformations for different columns of the data, within a
424:class:`~sklearn.pipeline.Pipeline` that is safe from data leakage and that can
425be parametrized. :class:`~sklearn.compose.ColumnTransformer` works on
426arrays, sparse matrices, and
427`pandas DataFrames <https://pandas.pydata.org/pandas-docs/stable/>`__.
428
429To each column, a different transformation can be applied, such as
430preprocessing or a specific feature extraction method::
431
432  >>> import pandas as pd
433  >>> X = pd.DataFrame(
434  ...     {'city': ['London', 'London', 'Paris', 'Sallisaw'],
435  ...      'title': ["His Last Bow", "How Watson Learned the Trick",
436  ...                "A Moveable Feast", "The Grapes of Wrath"],
437  ...      'expert_rating': [5, 3, 4, 5],
438  ...      'user_rating': [4, 5, 4, 3]})
439
440For this data, we might want to encode the ``'city'`` column as a categorical
441variable using :class:`~sklearn.preprocessing.OneHotEncoder` but apply a
442:class:`~sklearn.feature_extraction.text.CountVectorizer` to the ``'title'`` column.
443As we might use multiple feature extraction methods on the same column, we give
444each transformer a unique name, say ``'city_category'`` and ``'title_bow'``.
445By default, the remaining rating columns are ignored (``remainder='drop'``)::
446
447  >>> from sklearn.compose import ColumnTransformer
448  >>> from sklearn.feature_extraction.text import CountVectorizer
449  >>> from sklearn.preprocessing import OneHotEncoder
450  >>> column_trans = ColumnTransformer(
451  ...     [('categories', OneHotEncoder(dtype='int'), ['city']),
452  ...      ('title_bow', CountVectorizer(), 'title')],
453  ...     remainder='drop', verbose_feature_names_out=False)
454
455  >>> column_trans.fit(X)
456  ColumnTransformer(transformers=[('categories', OneHotEncoder(dtype='int'),
457                                   ['city']),
458                                  ('title_bow', CountVectorizer(), 'title')],
459                    verbose_feature_names_out=False)
460
461  >>> column_trans.get_feature_names_out()
462  array(['city_London', 'city_Paris', 'city_Sallisaw', 'bow', 'feast',
463  'grapes', 'his', 'how', 'last', 'learned', 'moveable', 'of', 'the',
464   'trick', 'watson', 'wrath'], ...)
465
466  >>> column_trans.transform(X).toarray()
467  array([[1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
468         [1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0],
469         [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
470         [0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1]]...)
471
472In the above example, the
473:class:`~sklearn.feature_extraction.text.CountVectorizer` expects a 1D array as
474input and therefore the columns were specified as a string (``'title'``).
475However, :class:`~sklearn.preprocessing.OneHotEncoder`
476as most of other transformers expects 2D data, therefore in that case you need
477to specify the column as a list of strings (``['city']``).
478
479Apart from a scalar or a single item list, the column selection can be specified
480as a list of multiple items, an integer array, a slice, a boolean mask, or
481with a :func:`~sklearn.compose.make_column_selector`. The
482:func:`~sklearn.compose.make_column_selector` is used to select columns based
483on data type or column name::
484
485  >>> from sklearn.preprocessing import StandardScaler
486  >>> from sklearn.compose import make_column_selector
487  >>> ct = ColumnTransformer([
488  ...       ('scale', StandardScaler(),
489  ...       make_column_selector(dtype_include=np.number)),
490  ...       ('onehot',
491  ...       OneHotEncoder(),
492  ...       make_column_selector(pattern='city', dtype_include=object))])
493  >>> ct.fit_transform(X)
494  array([[ 0.904...,  0.      ,  1. ,  0. ,  0. ],
495         [-1.507...,  1.414...,  1. ,  0. ,  0. ],
496         [-0.301...,  0.      ,  0. ,  1. ,  0. ],
497         [ 0.904..., -1.414...,  0. ,  0. ,  1. ]])
498
499Strings can reference columns if the input is a DataFrame, integers are always
500interpreted as the positional columns.
501
502We can keep the remaining rating columns by setting
503``remainder='passthrough'``. The values are appended to the end of the
504transformation::
505
506  >>> column_trans = ColumnTransformer(
507  ...     [('city_category', OneHotEncoder(dtype='int'),['city']),
508  ...      ('title_bow', CountVectorizer(), 'title')],
509  ...     remainder='passthrough')
510
511  >>> column_trans.fit_transform(X)
512  array([[1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 4],
513         [1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 3, 5],
514         [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 4, 4],
515         [0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 5, 3]]...)
516
517The ``remainder`` parameter can be set to an estimator to transform the
518remaining rating columns. The transformed values are appended to the end of
519the transformation::
520
521  >>> from sklearn.preprocessing import MinMaxScaler
522  >>> column_trans = ColumnTransformer(
523  ...     [('city_category', OneHotEncoder(), ['city']),
524  ...      ('title_bow', CountVectorizer(), 'title')],
525  ...     remainder=MinMaxScaler())
526
527  >>> column_trans.fit_transform(X)[:, -2:]
528  array([[1. , 0.5],
529         [0. , 1. ],
530         [0.5, 0.5],
531         [1. , 0. ]])
532
533.. _make_column_transformer:
534
535The :func:`~sklearn.compose.make_column_transformer` function is available
536to more easily create a :class:`~sklearn.compose.ColumnTransformer` object.
537Specifically, the names will be given automatically. The equivalent for the
538above example would be::
539
540  >>> from sklearn.compose import make_column_transformer
541  >>> column_trans = make_column_transformer(
542  ...     (OneHotEncoder(), ['city']),
543  ...     (CountVectorizer(), 'title'),
544  ...     remainder=MinMaxScaler())
545  >>> column_trans
546  ColumnTransformer(remainder=MinMaxScaler(),
547                    transformers=[('onehotencoder', OneHotEncoder(), ['city']),
548                                  ('countvectorizer', CountVectorizer(),
549                                   'title')])
550
551If :class:`~sklearn.compose.ColumnTransformer` is fitted with a dataframe
552and the dataframe only has string column names, then transforming a dataframe
553will use the column names to select the columns::
554
555
556  >>> ct = ColumnTransformer(
557  ...          [("scale", StandardScaler(), ["expert_rating"])]).fit(X)
558  >>> X_new = pd.DataFrame({"expert_rating": [5, 6, 1],
559  ...                       "ignored_new_col": [1.2, 0.3, -0.1]})
560  >>> ct.transform(X_new)
561  array([[ 0.9...],
562         [ 2.1...],
563         [-3.9...]])
564
565.. _visualizing_composite_estimators:
566
567Visualizing Composite Estimators
568================================
569
570Estimators can be displayed with a HTML representation when shown in a
571jupyter notebook. This can be useful to diagnose or visualize a Pipeline with
572many estimators. This visualization is activated by setting the
573`display` option in :func:`~sklearn.set_config`::
574
575  >>> from sklearn import set_config
576  >>> set_config(display='diagram')   # doctest: +SKIP
577  >>> # displays HTML representation in a jupyter context
578  >>> column_trans  # doctest: +SKIP
579
580An example of the HTML output can be seen in the
581**HTML representation of Pipeline** section of
582:ref:`sphx_glr_auto_examples_compose_plot_column_transformer_mixed_types.py`.
583As an alternative, the HTML can be written to a file using
584:func:`~sklearn.utils.estimator_html_repr`::
585
586   >>> from sklearn.utils import estimator_html_repr
587   >>> with open('my_estimator.html', 'w') as f:  # doctest: +SKIP
588   ...     f.write(estimator_html_repr(clf))
589
590.. topic:: Examples:
591
592 * :ref:`sphx_glr_auto_examples_compose_plot_column_transformer.py`
593 * :ref:`sphx_glr_auto_examples_compose_plot_column_transformer_mixed_types.py`
594