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