تبدیل توزیع ویژگیها
شکل ۲-۱۷ نشان میدهد ویژگی جمعیت پس از گرفتن لگاریتم تا چه اندازه بهتر میشود: توزیع حاصل بسیار به توزیع Gaussian (گوسی) یا زنگولهای نزدیک است.
شکل ۲-۱۷. تبدیل یک ویژگی برای نزدیکترکردن آن به توزیع Gaussian
راه دیگر برای مدیریت ویژگیهای دنبالهسنگین «Bucketizing (سطلبندی)» است. در این روش توزیع ویژگی به سطلهایی با اندازهٔ تقریباً برابر تقسیم میشود و هر مقدار ویژگی با اندیس سطلی که در آن قرار گرفته جایگزین میشود؛ شبیه کاری که هنگام ساخت ویژگی income_cat انجام دادیم، هرچند آنجا فقط برای نمونهگیری طبقهبندیشده از آن استفاده شد. برای مثال میتوان هر مقدار را با صدک آن جایگزین کرد. سطلبندی به سطلهای هماندازه توزیعی تقریباً یکنواخت میسازد، پس دیگر الزاماً نیازی به مقیاسبندی نیست؛ یا میتوانید اندیس سطل را بر تعداد سطلها تقسیم کنید تا مقدارها در بازهٔ صفر تا ۱ قرار گیرند.
اگر یک ویژگی توزیع چندوجهی داشته باشد - یعنی دو یا چند قلهٔ واضح که «Mode (مُد)» نامیده میشوند - مانند housing_median_age، سطلبندی باز هم میتواند سودمند باشد، اما این بار بهتر است شناسهٔ سطلها را بهعنوان دسته، نه مقدار عددی، در نظر بگیرید. بنابراین اندیس سطلها باید مثلاً با OneHotEncoder کدگذاری شود؛ به همین دلیل معمولاً نباید تعداد سطلها بیش از حد زیاد باشد. این راه به مدل رگرسیون اجازه میدهد برای بازههای مختلف یک ویژگی راحتتر قواعد متفاوتی یاد بگیرد. برای نمونه، شاید خانههای ساختهشده حدود ۳۵ سال پیش سبک خاصی داشته باشند که از مد افتاده و در نتیجه ارزانتر از چیزی باشند که فقط بر اساس سنشان انتظار میرود.
راه دیگر برای تبدیل توزیعهای چندوجهی این است که برای هر Mode - دستکم Modeهای اصلی - یک ویژگی تازه بسازید که شباهت مقدار ویژگی با آن Mode را نشان دهد. سنجهٔ شباهت معمولاً با «Radial Basis Function (تابع پایهٔ شعاعی یا RBF)» محاسبه میشود؛ یعنی هر تابعی که فقط به فاصلهٔ ورودی از یک نقطهٔ ثابت وابسته باشد. رایجترین RBF، «Gaussian RBF» است که با دورشدن ورودی از نقطهٔ ثابت خروجیاش بهصورت نمایی کاهش مییابد.
برای نمونه، شباهت Gaussian RBF میان سن خانه x و مقدار ۳۵ با معادلهٔ exp(−γ(x − 35)²) محاسبه میشود. فراپارامتر γ، گاما، مشخص میکند با دورشدن x از ۳۵ شباهت با چه سرعتی افت کند. با تابع rbf_kernel() در Scikit-Learn میتوانید ویژگی Gaussian RBF جدیدی بسازید که شباهت سن میانهٔ خانهها با ۳۵ را اندازه میگیرد:
from sklearn.metrics.pairwise import rbf_kernel
age_simil_35 = rbf_kernel(housing[["housing_median_age"]], [[35]], gamma=0.1)
شکل ۲-۱۸ این ویژگی جدید را در برابر سن میانهٔ مسکن با خط پیوسته نشان میدهد و همچنین نتیجهٔ استفاده از مقدار کوچکتر گاما را با خطچین نمایش میدهد. ویژگی شباهت سن در مقدار ۳۵ به اوج میرسد، درست نزدیک قلهٔ توزیع سن میانهٔ مسکن. اگر این گروه سنی خاص واقعاً با قیمتهای پایینتر همبستگی خوبی داشته باشد، احتمال زیادی وجود دارد که ویژگی جدید به مدل کمک کند.
شکل ۲-۱۸. ویژگی Gaussian RBF برای اندازهگیری شباهت سن میانهٔ مسکن به ۳۵ سال
تبدیل مقدار هدف
تا اینجا فقط ویژگیهای ورودی را بررسی کردیم، اما مقادیر هدف نیز ممکن است به تبدیل نیاز داشته باشند. برای مثال اگر توزیع هدف دنبالهٔ سنگین داشته باشد، میتوانید هدف را با لگاریتم آن جایگزین کنید. در این صورت مدل رگرسیون دیگر خود قیمت میانهٔ خانه را پیشبینی نمیکند، بلکه لگاریتم آن را پیشبینی میکند؛ بنابراین برای رسیدن به قیمت میانهٔ واقعی باید نمایی پیشبینی مدل را محاسبه کنید.
خوشبختانه بیشتر Transformerهای Scikit-Learn متد inverse_transform() دارند و محاسبهٔ معکوس تبدیل را آسان میکنند. مثال زیر برچسبها را با StandardScaler مقیاسبندی میکند، سپس یک مدل رگرسیون خطی ساده را روی برچسبهای مقیاسبندیشده آموزش میدهد، با دادهٔ تازه پیشبینی میکند و نتیجه را با inverse_transform() همان Scaler به مقیاس اصلی برمیگرداند. چون StandardScaler ورودی دوبعدی انتظار دارد، برچسبها از Pandas Series به DataFrame تبدیل میشوند. برای سادگی، مدل فقط با یک ویژگی ورودی خام، درآمد میانه، آموزش میبیند:
from sklearn.linear_model import LinearRegression
target_scaler = StandardScaler()
scaled_labels = target_scaler.fit_transform(housing_labels.to_frame())
model = LinearRegression()
model.fit(housing[["median_income"]], scaled_labels)
some_new_data = housing[["median_income"]].iloc[:5] # pretend this is new data
scaled_predictions = model.predict(some_new_data)
predictions = target_scaler.inverse_transform(scaled_predictions)
این روش درست کار میکند، اما گزینهٔ سادهتر استفاده از TransformedTargetRegressor است. کافی است آن را با مدل رگرسیون و Transformer برچسب بسازید و سپس با برچسبهای اصلی و بدون مقیاسبندی برازش دهید. این کلاس بهطور خودکار Transformer را برای مقیاسبندی برچسبها به کار میگیرد و مدل رگرسیون را روی برچسبهای تبدیلشده آموزش میدهد. هنگام پیشبینی نیز ابتدا predict() مدل و سپس inverse_transform() Scaler را اجرا میکند تا خروجی در مقیاس اصلی تولید شود:
from sklearn.compose import TransformedTargetRegressor
model = TransformedTargetRegressor(LinearRegression(),
transformer=StandardScaler())
model.fit(housing[["median_income"]], housing_labels)
predictions = model.predict(some_new_data)
Transformerهای سفارشی
Scikit-Learn Transformerهای کاربردی فراوانی دارد، اما برای بعضی کارها - مانند تبدیل اختصاصی، پاکسازی ویژه یا ترکیب چند ویژگی مشخص - لازم است Transformer خودتان را بنویسید.
برای تبدیلهایی که نیاز به آموزش ندارند، کافی است تابعی بنویسید که آرایهٔ NumPy را بگیرد و آرایهٔ تبدیلشده را برگرداند. همانطور که پیشتر گفته شد، برای ویژگیهای دنبالهسنگین مثبت معمولاً خوب است مقدارها با لگاریتمشان جایگزین شوند. Transformer لگاریتمی زیر را میسازیم و روی جمعیت اعمال میکنیم:
from sklearn.preprocessing import FunctionTransformer
log_transformer = FunctionTransformer(np.log, inverse_func=np.exp)
log_pop = log_transformer.transform(housing[["population"]])
آرگومان inverse_func اختیاری است و تابع تبدیل معکوس را مشخص میکند؛ مثلاً وقتی میخواهید Transformer را در TransformedTargetRegressor به کار ببرید. تابع تبدیل میتواند فراپارامترها را نیز بهصورت آرگومان اضافه دریافت کند. نمونهٔ زیر Transformerی میسازد که همان سنجهٔ شباهت Gaussian RBF قبلی را محاسبه میکند:
rbf_transformer = FunctionTransformer(rbf_kernel,
kw_args=dict(Y=[[35.]], gamma=0.1))
age_simil_35 = rbf_transformer.transform(housing[["housing_median_age"]])
برای Kernel از نوع RBF تابع معکوس وجود ندارد، زیرا برای هر فاصله از نقطهٔ ثابت معمولاً دو مقدار ممکن وجود دارد، مگر در فاصلهٔ صفر. همچنین rbf_kernel() ویژگیها را جداگانه پردازش نمیکند. اگر آرایهای با دو ویژگی به آن بدهید، برای سنجش شباهت فاصلهٔ دوبعدی اقلیدسی را حساب میکند. برای مثال میتوانید ویژگیای بسازید که شباهت جغرافیایی هر ناحیه با San Francisco را اندازه بگیرد:
sf_coords = 37.7749, -122.41
sf_transformer = FunctionTransformer(rbf_kernel,
kw_args=dict(Y=[sf_coords], gamma=0.1))
sf_simil = sf_transformer.transform(housing[["latitude", "longitude"]])
Transformerهای سفارشی برای ترکیب ویژگیها نیز مناسباند. نمونهٔ زیر با FunctionTransformer نسبت ویژگی ورودی ۰ به ویژگی ورودی ۱ را محاسبه میکند:
>>> ratio_transformer = FunctionTransformer(lambda X: X[:, [0]] / X[:, [1]])
>>> ratio_transformer.transform(np.array([[1., 2.], [3., 4.]]))
array([[0.5 ],
[0.75]])
FunctionTransformer بسیار راحت است، اما اگر Transformer باید قابل آموزش باشد، در fit() پارامترهایی یاد بگیرد و بعداً در transform() از آنها استفاده کند، باید یک کلاس سفارشی بنویسید. Scikit-Learn از Duck Typing استفاده میکند، بنابراین کلاس شما لازم نیست از کلاس پایهٔ خاصی ارث ببرد؛ فقط سه متد fit()، transform() و fit_transform() را نیاز دارد و fit() باید self را بازگرداند.
با افزودن TransformerMixin به کلاسهای پایه، fit_transform() را رایگان دریافت میکنید؛ پیادهسازی پیشفرض فقط fit() و بعد transform() را فراخوانی میکند. اگر BaseEstimator را نیز بهعنوان کلاس پایه اضافه کنید و در سازنده از *args یا **kwargs استفاده نکنید، دو متد get_params() و set_params() را هم خواهید داشت؛ این دو برای تنظیم خودکار فراپارامترها مفیدند.
کلاس زیر Transformer سفارشیای است که تا حدی مانند StandardScaler عمل میکند:
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.utils.validation import check_array, check_is_fitted
class StandardScalerClone(BaseEstimator, TransformerMixin):
def __init__(self, with_mean=True): # no *args or **kwargs!
self.with_mean = with_mean
def fit(self, X, y=None): # y is required even though we don't use it
X = check_array(X) # checks that X is an array with finite float values
self.mean_ = X.mean(axis=0)
self.scale_ = X.std(axis=0)
self.n_features_in_ = X.shape[1] # every estimator stores this in fit()
return self # always return self!
def transform(self, X):
check_is_fitted(self) # looks for learned attributes (with trailing _)
X = check_array(X)
assert self.n_features_in_ == X.shape[1]
if self.with_mean:
X = X - self.mean_
return X / self.scale_
چند نکته دربارهٔ این پیادهسازی:
- بستهٔ
sklearn.utils.validation چند تابع برای اعتبارسنجی ورودیها دارد. برای سادگی، کتاب در مثالهای بعدی چنین آزمونهایی را کنار میگذارد، اما کد عملیاتی باید آنها را داشته باشد.
- Pipelineهای Scikit-Learn نیاز دارند متد
fit() دو آرگومان X و y داشته باشد؛ به همین دلیل با وجود استفادهنکردن از y، آرگومان y=None لازم است.
- همهٔ Estimatorهای Scikit-Learn در
fit() مقدار n_features_in_ را تنظیم میکنند و بررسی میکنند دادهای که بعداً به transform() یا predict() میرسد همین تعداد ویژگی داشته باشد.
- متد
fit() باید همیشه self را برگرداند.
- این پیادهسازی صددرصد کامل نیست. همهٔ Estimatorها وقتی DataFrame میگیرند باید
feature_names_in_ را در fit() تنظیم کنند. همچنین همهٔ Transformerها باید متد get_feature_names_out() و در صورت برگشتپذیربودن تبدیل، متد inverse_transform() داشته باشند. تمرین آخر فصل جزئیات بیشتری دارد.
Transformer سفارشی مبتنی بر KMeans
یک Transformer سفارشی میتواند در پیادهسازی خودش از Estimatorهای دیگر استفاده کند. کد زیر نمونهای را نشان میدهد که در fit() از خوشهبند KMeans برای یافتن خوشههای اصلی دادهٔ آموزشی استفاده میکند و در transform() با rbf_kernel() شباهت هر نمونه به هر مرکز خوشه را میسنجد:
from sklearn.cluster import KMeans
class ClusterSimilarity(BaseEstimator, TransformerMixin):
def __init__(self, n_clusters=10, gamma=1.0, random_state=None):
self.n_clusters = n_clusters
self.gamma = gamma
self.random_state = random_state
def fit(self, X, y=None, sample_weight=None):
self.kmeans_ = KMeans(self.n_clusters, random_state=self.random_state)
self.kmeans_.fit(X, sample_weight=sample_weight)
return self # always return self!
def transform(self, X):
return rbf_kernel(X, self.kmeans_.cluster_centers_, gamma=self.gamma)
def get_feature_names_out(self, names=None):
return [f"Cluster {i} similarity" for i in range(self.n_clusters)]
همانطور که در فصل ۹ خواهید دید، k-means الگوریتمی برای یافتن خوشهها در داده است. تعداد خوشههای مورد جستوجو با فراپارامتر n_clusters کنترل میشود و پس از آموزش، مرکزها از ویژگی cluster_centers_ قابل دسترسیاند. متد fit() در KMeans آرگومان اختیاری sample_weight هم دارد که وزن نسبی نمونهها را مشخص میکند. k-means تصادفی است و برای پیداکردن خوشهها به تصادف تکیه میکند؛ بنابراین برای نتایج قابل تکرار باید random_state را تعیین کنید.
اکنون Transformer را بهکار میبریم:
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)
similarities = cluster_simil.fit_transform(housing[["latitude", "longitude"]],
sample_weight=housing_labels)
این کد یک ClusterSimilarity با ۱۰ خوشه میسازد. سپس fit_transform() را با عرض و طول جغرافیایی همهٔ ناحیههای مجموعهٔ آموزشی فراخوانی میکند و به هر ناحیه وزنی متناسب با قیمت میانهٔ خانه میدهد. Transformer از k-means برای یافتن خوشهها استفاده میکند و سپس شباهت Gaussian RBF میان هر ناحیه و هر ۱۰ مرکز خوشه را اندازه میگیرد. نتیجه ماتریسی با یک سطر برای هر ناحیه و یک ستون برای هر خوشه است. سه سطر نخست با گردکردن تا دو رقم اعشار:
>>> similarities[:3].round(2)
array([[0. , 0.14, 0. , 0. , 0. , 0.08, 0. , 0.99, 0. , 0.6 ],
[0.63, 0. , 0.99, 0. , 0. , 0. , 0.04, 0. , 0.11, 0. ],
[0. , 0.29, 0. , 0. , 0.01, 0.44, 0. , 0.7 , 0. , 0.3 ]])
شکل ۲-۱۹ ده مرکز خوشهٔ پیداشده با k-means را نشان میدهد. رنگ هر ناحیه بر اساس شباهت جغرافیایی آن با نزدیکترین مرکز خوشه است. بیشتر خوشهها در ناحیههای پرجمعیت و گران قرار دارند.
شکل ۲-۱۹. شباهت Gaussian RBF به نزدیکترین مرکز خوشه
Pipelineهای تبدیل
تعداد زیادی مرحلهٔ تبدیل داده وجود دارد که باید با ترتیب درست اجرا شوند. خوشبختانه Scikit-Learn کلاس رسمی Pipeline را برای مدیریت چنین دنبالههایی فراهم میکند. Pipeline کوچک زیر برای ویژگیهای عددی ابتدا مقادیر گمشده را جایگذاری و سپس ورودیها را استاندارد میکند:
from sklearn.pipeline import Pipeline
num_pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("standardize", StandardScaler()),
])
سازندهٔ Pipeline فهرستی از جفتهای نام/Estimator، یعنی Tupleهای دوتایی، میگیرد که مراحل را تعریف میکنند. نامها میتوانند دلخواه باشند، به شرط آنکه یکتا باشند و شامل دو زیرخط پشتسرهم __ نشوند. این نامها هنگام تنظیم فراپارامترها مفید خواهند بود. همهٔ Estimatorها بهجز آخرین مرحله باید Transformer باشند، یعنی fit_transform() داشته باشند. آخرین مرحله میتواند Transformer، Predictor یا هر Estimator دیگری باشد.
اگر نمیخواهید Transformerها را خودتان نامگذاری کنید، میتوانید از make_pipeline() استفاده کنید. این تابع Transformerها را بهصورت آرگومانهای ترتیبی میگیرد و نام هر مرحله را از نام کلاس آن Transformer، با حروف کوچک و بدون زیرخط، میسازد؛ مثلاً simpleimputer:
from sklearn.pipeline import make_pipeline
num_pipeline = make_pipeline(SimpleImputer(strategy="median"), StandardScaler())
اگر چند Transformer نام یکسان داشته باشند، به نام آنها اندیس افزوده میشود؛ مانند foo-1 و foo-2.
وقتی fit() Pipeline را فراخوانی میکنید، Pipeline بهترتیب روی همهٔ Transformerها fit_transform() را اجرا میکند و خروجی هر مرحله را به مرحلهٔ بعد میدهد تا به Estimator نهایی برسد؛ برای مرحلهٔ نهایی فقط fit() را فراخوانی میکند.
Pipeline همان متدهایی را در اختیار میگذارد که Estimator نهایی دارد. در این مثال مرحلهٔ آخر StandardScaler است که Transformer محسوب میشود؛ پس خود Pipeline نیز مانند Transformer رفتار میکند. اگر transform() آن را فراخوانی کنید، همهٔ تبدیلها بهترتیب روی داده اجرا میشوند. اگر مرحلهٔ آخر Predictor بود، Pipeline بهجای transform() متد predict() داشت؛ در آن صورت همهٔ تبدیلها اجرا میشدند و نتیجه به predict() Predictor میرسید.
اکنون fit_transform() Pipeline را اجرا و دو سطر نخست را با دو رقم اعشار بررسی کنید:
>>> housing_num_prepared = num_pipeline.fit_transform(housing_num)
>>> housing_num_prepared[:2].round(2)
array([[-1.42, 1.01, 1.86, 0.31, 1.37, 0.14, 1.39, -0.94],
[ 0.6 , -0.7 , 0.91, -0.31, -0.44, -0.69, -0.37, 1.17]])
برای بازیابی DataFrame خوانا میتوانید از get_feature_names_out() استفاده کنید:
df_housing_num_prepared = pd.DataFrame(
housing_num_prepared, columns=num_pipeline.get_feature_names_out(),
index=housing_num.index)
Pipeline از Indexing پشتیبانی میکند؛ مثلاً pipeline[1] Estimator دوم را برمیگرداند و pipeline[:-1] یک Pipeline شامل همهٔ مراحل بهجز مرحلهٔ آخر میدهد. همچنین میتوانید Estimatorها را از ویژگی steps، فهرست جفتهای نام/Estimator، یا از دیکشنری named_steps به دست آورید. برای نمونه num_pipeline["simpleimputer"] Estimator با نام simpleimputer را میدهد.
ColumnTransformer برای پردازش همهٔ ستونها
تا اینجا ستونهای دستهای و عددی جداگانه پردازش شدند. راحتتر است Transformer واحدی داشته باشیم که همهٔ ستونها را بگیرد و تبدیل مناسب را روی هر ستون اجرا کند. ColumnTransformer برای همین کار است. مثال زیر num_pipeline را روی ویژگیهای عددی و cat_pipeline را روی ویژگی دستهای اعمال میکند:
from sklearn.compose import ColumnTransformer
num_attribs = ["longitude", "latitude", "housing_median_age", "total_rooms",
"total_bedrooms", "population", "households", "median_income"]
cat_attribs = ["ocean_proximity"]
cat_pipeline = make_pipeline(
SimpleImputer(strategy="most_frequent"),
OneHotEncoder(handle_unknown="ignore"))
preprocessing = ColumnTransformer([
("num", num_pipeline, num_attribs),
("cat", cat_pipeline, cat_attribs),
])
ابتدا کلاس ColumnTransformer import میشود، سپس فهرست نام ستونهای عددی و دستهای تعریف میشود و Pipeline سادهای برای ویژگی دستهای ساخته میشود. سازندهٔ ColumnTransformer فهرستی از Tupleهای سهتایی میگیرد؛ هر Tuple شامل یک نام یکتا و بدون دو زیرخط، یک Transformer و فهرست نام یا اندیس ستونهایی است که Transformer باید روی آنها اعمال شود.
فهرستکردن همهٔ نام ستونها همیشه راحت نیست. Scikit-Learn تابع make_column_selector() را فراهم میکند که یک تابع Selector برای انتخاب خودکار ویژگیهای نوع خاص، مثلاً عددی یا دستهای، برمیگرداند. میتوانید این Selector را بهجای نام یا اندیس ستونها به ColumnTransformer بدهید. همچنین اگر نام Transformerها برایتان مهم نیست، make_column_transformer() مانند make_pipeline() نامها را خودکار انتخاب میکند. کد زیر همان ColumnTransformer قبلی را میسازد، اما Transformerها بهطور خودکار مثلاً pipeline-1 و pipeline-2 نامگذاری میشوند:
from sklearn.compose import make_column_selector, make_column_transformer
preprocessing = make_column_transformer(
(num_pipeline, make_column_selector(dtype_include=np.number)),
(cat_pipeline, make_column_selector(dtype_include=object)),
)
حالا ColumnTransformer را روی دادهٔ مسکن اعمال کنید:
housing_prepared = preprocessing.fit_transform(housing)
Pipeline پیشپردازش کل مجموعهٔ آموزشی را میگیرد، هر Transformer را روی ستونهای مناسب اجرا میکند و سپس ستونهای تبدیلشده را افقی کنار هم میگذارد. Transformerها نباید تعداد سطرها را تغییر دهند. خروجی باز هم آرایهٔ NumPy است، اما میتوانید نام ستونها را با preprocessing.get_feature_names_out() دریافت کنید و مانند قبل یک DataFrame بسازید.
Pipeline کامل پیشپردازش
پروژه خوب پیش میرود و تقریباً آمادهٔ آموزش مدلها هستید. اکنون میخواهید یک Pipeline واحد بسازید که همهٔ تبدیلهایی را که تا اینجا آزمایش کردید انجام دهد. خلاصهٔ کارهای Pipeline و دلیل هرکدام:
- مقادیر گمشدهٔ ویژگیهای عددی با میانه جایگزین میشوند، چون بیشتر الگوریتمهای یادگیری ماشین انتظار مقدار گمشده ندارند. در ویژگیهای دستهای، مقدار گمشده با پرتکرارترین دسته جایگزین میشود.
- ویژگی دستهای One-Hot Encoding میشود، زیرا بیشتر الگوریتمها ورودی عددی میپذیرند.
- چند ویژگی نسبتی افزوده میشود:
bedrooms_ratio، rooms_per_house و people_per_house. امید است این ویژگیها همبستگی بیشتری با قیمت میانهٔ خانه داشته باشند و به مدلها کمک کنند.
- چند ویژگی شباهت خوشهای اضافه میشود که احتمالاً برای مدل از خود عرض و طول جغرافیایی مفیدترند.
- ویژگیهای با دنبالهٔ بلند با لگاریتمشان جایگزین میشوند، چون بیشتر مدلها توزیعهای تقریباً یکنواخت یا Gaussian را ترجیح میدهند.
- همهٔ ویژگیهای عددی استاندارد میشوند، زیرا بیشتر الگوریتمها زمانی بهتر کار میکنند که همهٔ ویژگیها تقریباً مقیاس مشابهی داشته باشند.
کد ساخت این Pipeline باید اکنون آشنا باشد:
def column_ratio(X):
return X[:, [0]] / X[:, [1]]
def ratio_name(function_transformer, feature_names_in):
return ["ratio"] # feature names out
def ratio_pipeline():
return make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(column_ratio, feature_names_out=ratio_name),
StandardScaler())
log_pipeline = make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(np.log, feature_names_out="one-to-one"),
StandardScaler())
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)
default_num_pipeline = make_pipeline(SimpleImputer(strategy="median"),
StandardScaler())
preprocessing = ColumnTransformer([
("bedrooms", ratio_pipeline(), ["total_bedrooms", "total_rooms"]),
("rooms_per_house", ratio_pipeline(), ["total_rooms", "households"]),
("people_per_house", ratio_pipeline(), ["population", "households"]),
("log", log_pipeline, ["total_bedrooms", "total_rooms", "population",
"households", "median_income"]),
("geo", cluster_simil, ["latitude", "longitude"]),
("cat", cat_pipeline, make_column_selector(dtype_include=object)),
], remainder=default_num_pipeline) # one column remaining: housing_median_age