forked from skrub-data/skrub
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path0010_apply_to_cols.py
More file actions
161 lines (136 loc) · 5.51 KB
/
Copy path0010_apply_to_cols.py
File metadata and controls
161 lines (136 loc) · 5.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""
Hands-On with Column Selection and Transformers
===============================================
.. |ApplyToCols| replace:: :class:`~skrub.ApplyToCols`
.. |StringEncoder| replace:: :class:`~skrub.StringEncoder`
.. |SelectCols| replace:: :class:`~skrub.SelectCols`
.. |DropCols| replace:: :class:`~skrub.DropCols`
.. |TableVectorizer| replace:: :class:`~skrub.TableVectorizer`
.. |OrdinalEncoder| replace:: :class:`~sklearn.preprocessing.OrdinalEncoder`
.. |PCA| replace:: :class:`~sklearn.decomposition.PCA`
.. |Pipeline| replace:: :class:`~sklearn.pipeline.Pipeline`
.. |ColumnTransformer| replace:: :class:`~sklearn.compose.ColumnTransformer`
In this example, we show how to create flexible pipelines by selecting
and transforming dataframe columns using arbitrary logic with |ApplyToCols|.
"""
# %%
# We begin with loading a dataset with heterogeneous datatypes, and replacing Pandas's
# display with the TableReport display via :func:`skrub.patch_display`.
import pandas as pd
import skrub
from skrub.datasets import fetch_employee_salaries
skrub.patch_display()
file_path = fetch_employee_salaries().path
data = pd.read_csv(file_path)
X = data.drop(columns="current_annual_salary")
y = data["current_annual_salary"]
X
# %%
# Our goal is now to apply a |StringEncoder| to two columns of our
# choosing: ``division`` and ``employee_position_title``.
#
# We can achieve this using |ApplyToCols|, whose job is to apply a
# transformer to multiple columns in parallel, and let unmatched columns through
# without changes.
# This can be seen as a handy drop-in replacement of the
# |ColumnTransformer|.
#
# Since we selected two columns and set the number of components to ``30`` each,
# |ApplyToCols| will create ``2*30`` embedding columns in the dataframe
# ``Xt``, which we prefix with ``lsa_``.
from skrub import ApplyToCols, StringEncoder
apply_string_encoder = ApplyToCols(
StringEncoder(n_components=30),
cols=["division", "employee_position_title"],
rename_columns="lsa_{}",
)
Xt = apply_string_encoder.fit_transform(X)
Xt
# %%
# The |ApplyToCols| class can detect automatically whether the transformer is a
# ``SingleColumnTransformer`` (i.e., it can only be applied to one column at a time)
# or not, and apply it accordingly. The |StringEncoder| is a ``SingleColumnTransformer``
# and thus applied to each column independently.
# %%
# The |ApplyToCols| class can also be used with transformers that
# can be applied to multiple columns at once, such as the |PCA|.
# Here, we want to use PCA to reduce the number of dimensions of the new ``lsa_``
# columns.
#
# To select columns without hardcoding their names, we introduce
# :ref:`selectors<user_guide_selectors>`, which allow for flexible matching pattern
# and composable logic.
#
# The regex selector below will match all columns prefixed with ``"lsa"``, and pass them
# to |ApplyToCols| which will assemble these columns into a dataframe
# and finally pass it to the PCA
#
# Note that |ApplyToCols| will automatically detect that PCA is not a
# ``SingleColumnTransformer``
# and apply it to the whole sub-dataframe of columns chosen by the selector at once.
from sklearn.decomposition import PCA
from skrub import selectors as s
apply_pca = ApplyToCols(PCA(n_components=8), cols=s.regex("lsa"))
Xt = apply_pca.fit_transform(Xt)
Xt
# %%
# These two selectors are scikit-learn transformers and can be chained together within
# a |Pipeline|.
from sklearn.pipeline import make_pipeline
model = make_pipeline(
apply_string_encoder,
apply_pca,
).fit_transform(X)
# %%
# Note that selectors also come in handy in a pipeline to select or drop columns, using
# |SelectCols| and |DropCols|.
from sklearn.preprocessing import StandardScaler
from skrub import SelectCols
# Select only numerical columns
pipeline = make_pipeline(
SelectCols(cols=s.numeric()),
StandardScaler(),
).set_output(transform="pandas")
pipeline.fit_transform(Xt)
# %%
# Let's run through one more example to showcase the expressiveness of the selectors.
# Suppose we want to apply an |OrdinalEncoder| on
# categorical columns with low cardinality (e.g., fewer than ``40`` unique values).
#
# We define a column filter using skrub selectors with a lambda function. Note that
# the same effect can be obtained directly by using
# :func:`~skrub.selectors.cardinality_below`.
from sklearn.preprocessing import OrdinalEncoder
low_cardinality = s.filter(lambda col: col.nunique() < 40)
ApplyToCols(OrdinalEncoder(), cols=s.string() & low_cardinality).fit_transform(X)
# %%
# Notice how we composed the selector with :func:`~skrub.selectors.string()`
# using a logical operator. This resulting selector matches string
# columns with cardinality below ``40``.
#
# We can also define the opposite selector ``high_cardinality`` using the negation
# operator ``~`` and apply a |StringEncoder| to vectorize those
# columns.
from sklearn.ensemble import HistGradientBoostingRegressor
high_cardinality = ~low_cardinality
pipeline = make_pipeline(
ApplyToCols(
OrdinalEncoder(),
cols=s.string() & low_cardinality,
),
ApplyToCols(
StringEncoder(),
cols=s.string() & high_cardinality,
),
HistGradientBoostingRegressor(),
).fit(X, y)
pipeline
# %%
# Interestingly, the pipeline above is similar to the datatype dispatching performed by
# |TableVectorizer|, also used in :func:`~skrub.tabular_pipeline`.
#
# Click on the dropdown arrows next to the datatype to see the columns are mapped to
# the different transformers in |TableVectorizer|.
from skrub import tabular_pipeline
tabular_pipeline("regressor").fit(X, y)
# %%