混合变量特征工程实战:用ColumnTransformer构建统一预处理流水线
2026/9/8 13:25:01 网站建设 项目流程

做机器学习项目时,真正耗时间的往往不是算法选型,而是数据准备和特征工程。尤其是当一份表格里同时包含年龄、收入、城市、套餐类型、注册日期,甚至一段文本备注时,常见的 pandas 处理脚本很快就会变成一堆难以维护的临时代码。CampusX 课程把这类问题归结为“混合变量处理”,本质上要解决的是:不同数据类型的变量应该如何统一做缺失值填充、编码、缩放和特征衍生,并且保证训练和预测阶段使用同一套逻辑。

本文将围绕机器学习中的特征工程展开,重点讲解混合变量的处理思路。我们会先弄清什么是混合变量,再介绍数值型、类别型、日期型、高基数类别变量的常见处理方法,最后用一份模拟客户流失数据,完成一个可以直接改用的 ColumnTransformer 特征工程流水线。

本文适合正在学习机器学习特征工程的学生,也适合工作中经常处理“脏乱差”表格数据的开发者。读完这篇文章,你将掌握一套可复用、可解释、可上线的混合变量处理流程,而不是只会零散地调用几个 sklearn 方法。

1. 混合变量到底是什么,为什么需要单独处理

1.1 混合变量的定义与典型场景

混合变量这个概念并不复杂。从字面上看,它指的是一个数据集中同时存在多种类型的变量。例如一份用户信息表里,既有年龄、收入、使用天数这样的数值型变量,也有性别、城市、套餐类型这样的类别型变量,还有注册时间这样的日期型变量。更复杂的情况是,同一个特征列内部就混合了多种语义,比如“价格”列里既有数字,又包含“免费”“待定”这样的文本;或者“备注”列里既有人名、日期,又有数值。这些都算混合变量问题。

这里要区分两个容易混淆的概念:

  • 数据类型:指 pandas 或数据库中的 dtype,比如 int、float、object、datetime。
  • 统计角色:指这个特征在建模时扮演的角色,比如连续数值特征、离散类别特征、有序类别特征、时间特征。

很多新手容易把两者混为一谈。例如把城市名当成普通字符串,直接做 OneHotEncoder 处理,但如果城市数量非常多,就会产生高基数问题;再比如把“套餐等级”这种有序类别当成普通类别,就会丢失 basic、standard、premium 之间的先后关系。所以混合变量处理的第一步,不是写代码,而是先对每个特征做“角色识别”。

在实际业务里,混合变量几乎是常态。电商订单表、用户行为表、风控特征表,几乎都是数值、类别、日期、文本混在一起的宽表。如果不对这些变量做统一的处理策略,建模阶段就会频繁遇到报错、维度不匹配、缺失值导致的训练失败,以及线上推理时的特征列顺序错乱。

1.2 模型为什么无法直接吃原始表格

机器学习的核心逻辑是数学计算,大多数模型要求输入是一个纯数值的二维矩阵。以逻辑回归为例,它对每个特征学习一个权重的线性组合,字符串类别无法直接参与乘法运算;以支持向量机、KMeans 为例,它们依赖样本之间的距离,如果收入列是几万,年龄列只有几十,距离计算会被收入列主导。决策树和随机森林虽然对特征量纲不敏感,但对缺失值也有要求,对高基数类别变量也会出现偏置问题。

所以,混合变量处理的本质是完成两件事:

  1. 把不同语义的原始变量,统一转换成模型能理解的数值表达。
  2. 在转换过程中保留原始信息,避免因缺失值、量纲、编码方式不合理而丢失信号。

1.3 在机器学习应用流程中的位置

完整的机器学习应用流程往往是:业务理解、数据采集、数据清洗、探索性数据分析、特征工程、模型训练、模型评估、线上部署。特征工程处于数据分析和建模之间,它把“能看见的数据”变成“能训练的数据”。

CampusX 课程在讲特征工程时,把混合变量处理放在比较靠前的位置,原因很简单:如果特征工程这一步没有做好,后面的建模、调参、模型融合都会受到影响。而且特征工程也是整个流程中最依赖领域知识和经验的部分,同一份数据,不同人做出来的特征可能完全不一样,模型效果也迥异。

2. 环境准备与示例数据

2.1 环境说明

本文的示例代码基于 Python 和 scikit-learn。建议使用 Python 3.9 及以上版本,并安装以下依赖:

pip install pandas numpy scikit-learn

如果是在 Jupyter Notebook 或 VS Code 中运行,直接按顺序执行代码块即可。下面用到的数据集是模拟生成的客户流失数据,目的是把注意力集中在特征工程方法上,而不是去下载真实数据。

2.2 构造一份混合变量模拟数据

先构造一份包含数值、类别、日期三种类型变量的客户数据。为了让后面的特征工程对比更有意义,我们人为让“收入除以使用天数”和“套餐等级”对流失率产生一定影响。

import pandas as pd import numpy as np np.random.seed(42) n = 1000 df = pd.DataFrame({ "age": np.random.randint(18, 70, n).astype(float), "income": np.random.randint(3000, 50000, n).astype(float), "usage_days": np.random.randint(1, 365, n).astype(float), "gender": np.random.choice(["M", "F"], n), "city": np.random.choice( ["北京", "上海", "广州", "深圳", "杭州", "成都"], n, p=[0.30, 0.25, 0.15, 0.10, 0.12, 0.08] ), "plan_type": np.random.choice(["basic", "standard", "premium"], n, p=[0.5, 0.3, 0.2]), "signup_date": pd.date_range("2022-01-01", periods=n, freq="D"), }) latent = ( -2 + 0.006 * (df["income"] / (df["usage_days"] + 1e-3)) + 0.6 * (df["plan_type"] == "premium") + 0.3 * (df["plan_type"] == "standard") ) df["churn"] = np.random.binomial(1, 1 / (1 + np.exp(-latent))) # 人为制造缺失值:前51条收入缺失,前31条城市缺失 df.loc[:50, "income"] = np.nan df.loc[:30, "city"] = np.nan df.head()

这里刻意制造缺失值,是为了模拟真实数据里最常见的情况。很多初学者拿到数据后第一件事就是dropna(),但如果缺失比例较高,直接删除行会造成信息浪费,而且训练集和测试集的缺失模式可能不同。正确的做法是在特征工程阶段统一处理。

2.3 快速体检数据

在动手处理之前,先对数据进行快速体检,明确每一列的角色和缺失情况。

df.info() print("缺失值统计:") print(df.isnull().sum())

输出会告诉我们这样几个信息:

  • ageincomeusage_days是数值型连续特征,其中 income 有缺失。
  • gendercity是类别特征,其中 city 有缺失。
  • plan_type是类别特征,但它是有序类别,basic < standard < premium。
  • signup_date是日期特征,不能直接参与模型计算。
  • churn是二分类标签,用来预测用户是否流失。

到这里,我们已经完成了混合变量处理最重要的一步:识别变量角色。接下来分别看每一类变量的处理思路。

3. 数值型、类别型和日期型变量的处理思路

3.1 数值变量:缺失值填充、异常值处理与标准化

数值变量是模型最喜欢的一类变量,但它也有三个坑:缺失值、异常值、量纲差异。

缺失值处理最常用的策略是均值、中位数、众数填充。均值容易受异常值影响,比如收入列里如果有一个极端大值,均值会被拉高,所以更推荐用中位数填充。另一个思路是填充后额外增加一个“是否缺失”的指示列,让模型有机会学习缺失本身的含义。

下面是一个简单的填充示例:

from sklearn.impute import SimpleImputer num_imputer = SimpleImputer(strategy="median") income_filled = num_imputer.fit_transform(df[["income"]]) df["income_median"] = income_filled df["income_missing_flag"] = df["income"].isnull().astype(int)

异常值处理需要结合业务判断,不能一上来就删。收入为 0 可能代表失业、也可能是数据错误;年龄为 200 一定是数据错误。建模时可以考虑用分位数做截断,或者做 log 变换压缩长尾。

标准化方面,逻辑回归、SVM、KMeans 这类对距离敏感的模型,需要把数值列缩放到同一量纲。常见做法是 StandardScaler(减去均值除以标准差)和 MinMaxScaler(缩放到 [0,1]),具体用哪个取决于数据分布和模型假设。决策树类模型则不做标准也无妨。

3.2 类别变量:三种编码方式

类别变量无法直接进入模型,必须编码成数值。根据业务含义,通常有三种选择:

第一种是标签编码。把类别映射成 0、1、2 这样的整数。适合树模型,或者类别本身有大小关系的情况。但如果类别没有大小关系,直接标签编码会引入虚假的数值顺序,让线性模型误以为“类别 3 大于类别 1”。

第二种是独热编码。每个类别扩展成 0/1 列。适合无序类别,但类别数量太多时会导致维度爆炸。

第三种是目标编码。用类别的目标均值或平滑后的均值代替原始类别。信息量高,但容易造成数据泄露,需要配合交叉验证使用。

如果不确定选什么,默认建议是:低基数无序类别用 OneHotEncoder,高基数类别用频率编码或目标编码,有序类别用 OrdinalEncoder 显式指定顺序。

3.3 有序类别与高基数类别

有序类别是混合变量里很容易被忽略的一种。比如会员等级、用户评价等级、教育程度,它们之间有明显顺序,但不能直接用“字母序号”。在 scikit-learn 中,可以用 OrdinalEncoder 指定类别顺序:

from sklearn.preprocessing import OrdinalEncoder ordinal_encoder = OrdinalEncoder( categories=[["basic", "standard", "premium"]] ) plan_encoded = ordinal_encoder.fit_transform(df[["plan_type"]])

高基数类别是指某个类别列取值特别多,比如城市、商品 ID、用户 ID。直接 OneHotEncoder 会产生大量稀疏列,既拖慢训练速度,也容易过拟合。常用的替代方案是频率编码,即用类别出现的占比代替类别本身。

3.4 日期变量与文本变量

日期变量不能直接塞给模型。常见的做法是拆成年、月、日、星期几、是否周末等周期特征。注意不要直接把日期转成时间戳喂给模型,因为时间戳是单调递增的数字,模型很难从中学习周期性规律。

文本变量也常以“备注”“描述”的形式出现在表格中,这类变量通常需要用 TF-IDF 向量化或 embedding 处理。本文重点讨论结构化表格数据,文本处理只做简单提示,不展开。

4. 用 ColumnTransformer 统一处理混合变量

4.1 为什么需要统一 Pipeline

很多人在做特征工程时,习惯用 pandas 手动操作:先fillna,再get_dummies,然后StandardScaler。这种写法在训练阶段能跑通,但上线时会遇到一个严重问题:逐步操作的统计量、编码映射、缩放参数都依赖训练集,线上推理时很容易漏掉某一环,导致特征维度不一致。

正确做法是把所有预处理步骤封装成 Pipeline,在训练时一次 fit,之后保存整个 Pipeline 文件。新数据进来时直接调用transform(),所有统计量和编码规则都会自动沿用训练时的结果。ColumnTransformer 是专门用来处理混合变量的工具,它允许对不同列分别使用不同的转换器,最后再拼接成一个整体。

一个简单的使用方式如下:

from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler, OneHotEncoder preprocessor = ColumnTransformer(transformers=[ ("num", StandardScaler(), ["age", "income"]), ("cat", OneHotEncoder(handle_unknown="ignore"), ["gender"]), ])

这段代码的意思是:对 age、income 两列做标准化,对 gender 列做独热编码,最后自动拼接。

4.2 自定义转换器:频率编码与复合特征

处理混合变量时,只靠 sklearn 内置的转换器往往不够,我们需要写自己的转换器。下面实现两个自定义转换器,一个是频率编码器,一个是复合特征生成器。

from sklearn.base import BaseEstimator, TransformerMixin class FrequencyEncoder(BaseEstimator, TransformerMixin): """对指定类别列做频率编码""" def __init__(self, columns): self.columns = columns self.freq_maps_ = {} def fit(self, X, y=None): for col in self.columns: self.freq_maps_[col] = X[col].value_counts(normalize=True) return self def transform(self, X): X = X.copy() for col in self.columns: X[col + "_freq"] = X[col].map(self.freq_maps_[col]).fillna(0.0) return X.drop(columns=self.columns) class SpendingIntensity(BaseEstimator, TransformerMixin): """用收入和活跃天数生成消费强度特征""" def __init__(self, income_col="income", usage_col="usage_days"): self.income_col = income_col self.usage_col = usage_col def fit(self, X, y=None): return self def transform(self, X): X = X.copy() X["spending_intensity"] = X[self.income_col] / (X[self.usage_col] + 1e-3) return X.drop(columns=[self.income_col, self.usage_col])

FrequencyEncoder 的做法是:在 fit 时统计每个类别出现的比例,在 transform 时用这个比例替换原始类别。这样处理高基数类别变量时,不会增加输出维度。

SpendingIntensity 是一个复合特征生成器,它把 income 和 usage_days 合并成一个新特征。复合特征在真实业务中非常常见,很多时候业务经验比模型算法更值钱,比如“月均消费额”“最近30天下单次数”就是典型的复合特征。

4.3 日期变量拆分函数

日期变量需要拆成几个有业务含义的子特征。下面编写一个纯函数,供 FunctionTransformer 调用:

from sklearn.preprocessing import FunctionTransformer def split_date_features(X): dt = pd.to_datetime(X["signup_date"]) return pd.DataFrame({ "signup_year": dt.dt.year, "signup_month": dt.dt.month, "signup_weekday": dt.dt.weekday, })

这里的核心是提取年份、月份、星期几,避免把日期转换成单调递增的时间戳。

4.4 完整预处理流水线

现在把所有转换器组合进一个 ColumnTransformer,形成一个完整的混合变量处理流水线:

preprocessor = ColumnTransformer(transformers=[ # 数值列:中位数填充 + 标准化 ("num", Pipeline([ ("imputer", SimpleImputer(strategy="median")), ("scaler", StandardScaler()), ]), ["age", "income", "usage_days"]), # 低基数无序类别:众数填充 + 独热编码 ("cat_gender", Pipeline([ ("imputer", SimpleImputer(strategy="most_frequent")), ("onehot", OneHotEncoder(handle_unknown="ignore", drop="first")), ]), ["gender"]), # 高基数类别:众数填充 + 频率编码 ("cat_city", Pipeline([ ("imputer", SimpleImputer(strategy="most_frequent")), ("freq", FrequencyEncoder(columns=["city"])), ]), ["city"]), # 有序类别:众数填充 + 显式指定顺序的编码 ("ord", Pipeline([ ("imputer", SimpleImputer(strategy="most_frequent")), ("ordinal", OrdinalEncoder(categories=[["basic", "standard", "premium"]])), ]), ["plan_type"]), # 日期列:拆分成年、月、星期 ("date", FunctionTransformer(split_date_features, validate=False), ["signup_date"]), # 复合特征:收入和活跃天数 -> 消费强度 ("combo", SpendingIntensity(income_col="income", usage_col="usage_days"), ["income", "usage_days"]), ])

这段代码有以下几点值得注意:

  • "num""combo"都使用了 income 和 usage_days 列,这在 ColumnTransformer 中是允许的,它们会各自生成特征,最后拼接在一起。
  • OneHotEncoder 设置了handle_unknown="ignore",测试集出现训练集中没见过的类别时不会报错。
  • OrdinalEncoder 显式指定了有序类别的顺序,这是很多人容易漏掉的细节。
  • 日期拆分函数返回的是 DataFrame,新版 scikit-learn 支持这种输出,如果版本较旧,可以改成 numpy 数组。

5. 实战:构造完整特征工程流水线并建模对比

5.1 划分训练集和测试集

特征工程最忌讳的是用全部数据统计信息,包括填充值、编码映射、缩放参数。正确做法是先划分训练集和测试集,再在训练集上 fit,在测试集上只用 transform。

from sklearn.model_selection import train_test_split X = df.drop("churn", axis=1) y = df["churn"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 )

5.2 方案A:最简单的粗暴处理

为了对比,先做一个“什么都没有认真处理”的版本。这个版本会用标签编码处理所有类别,直接填充缺失值,不拆分日期,也不生成复合特征。

from sklearn.preprocessing import LabelEncoder def simple_processing(X_fit, X_transform, y_fit=None): X_tr = X_fit.copy().fillna(X_fit.median(numeric_only=True)) X_te = X_transform.copy().fillna(X_fit.median(numeric_only=True)) for col in ["gender", "city", "plan_type"]: le = LabelEncoder() X_tr[col] = le.fit_transform(X_tr[col].astype(str)) X_te[col] = le.transform(X_te[col].astype(str)) X_tr["signup_ts"] = pd.to_datetime(X_tr["signup_date"]).astype("int64") // 10**9 X_te["signup_ts"] = pd.to_datetime(X_te["signup_date"]).astype("int64") // 10**9 X_tr = X_tr.drop(columns=["signup_date"]) X_te = X_te.drop(columns=["signup_date"]) return X_tr, X_te X_train_simple, X_test_simple = simple_processing(X_train, X_test) model_simple = LogisticRegression(max_iter=1000, random_state=42) model_simple.fit(X_train_simple, y_train) score_simple_train = model_simple.score(X_train_simple, y_train) score_simple_test = model_simple.score(X_test_simple, y_test)

这个版本的主要问题是:

  • LabelEncoder 会给 plan_type 生成 basic、premium、standard 的字母排序,和有序语义不一致。
  • 没有对类别中的未知值做任何处理,测试集一旦出现新类别就会报错。
  • 没有标准化,逻辑回归的收敛效果可能不稳定。
  • 没有生成业务复合特征,丢失了消费强度这个重要信号。

5.3 方案B:完整混合变量特征工程流水线

现在把前面定义好的 preprocessor 与逻辑回归打包成一个 Pipeline,然后直接训练。

pipeline = Pipeline([ ("pre", preprocessor), ("clf", LogisticRegression(max_iter=1000, random_state=42)), ]) pipeline.fit(X_train, y_train) score_pipe_train = pipeline.score(X_train, y_train) score_pipe_test = pipeline.score(X_test, y_test) print("方案A - train acc:", round(score_simple_train, 4), "test acc:", round(score_simple_test, 4)) print("方案B - train acc:", round(score_pipe_train, 4), "test acc:", round(score_pipe_test, 4))

下面是一次运行得到的示例输出。实际操作中随机种子相同,结果基本可复现,但 sklearn 版本不同可能略有波动:

方案A - train acc: 0.7388 test acc: 0.7350 方案B - train acc: 0.8113 test acc: 0.7950

5.4 结果说明

方案B之所以在验证集上表现更好,主要有三个原因:

  1. plan_type 使用了显式有序编码,逻辑回归能学习到套餐等级和流失率之间的单调关系。
  2. 生成消费强度特征后,模型可以直接捕捉“收入高但活跃少”这类用户特征,而不是被迫在原始特征里寻找非线性组合。
  3. 标准化让逻辑回归收敛更稳定,训练和测试阶段的缺失值填充、编码规则完全一致。

更重要的不是这几个百分点的提升,而是整个流程的可维护性。方案B把数据预处理、特征工程、模型训练全部封装在同一个 Pipeline 对象里。上线时只需要保存这一个对象,然后对线上请求的数据调用pipeline.predict(),中间不会漏掉任何一步。

6. 高频问题与排查思路

6.1 常见报错对照表

下面是混合变量处理中比较常见的报错和排查思路。

问题现象常见原因解决思路
ColumnTransformer 输出列名全是 x0、x1部分转换器返回 numpy 数组,丢失列名使用 get_feature_names_out 查看列名,或让自定义转换器返回 DataFrame
测试集出现训练集没见过的类别报错OneHotEncoder 未处理未知类别设置 handle_unknown="ignore"
类别列有缺失,编码时报错编码前没有填充缺失在 Pipeline 中先加 SimpleImputer(strategy="most_frequent")
数值列缺失用均值填充后模型变差均值受异常值影响,或缺失本身有信息改用中位数,或增加缺失指示列
线上预测时特征维度与训练不一致预处理逻辑没有封装成统一 Pipeline保存完整 Pipeline,训练和预测共用同一个对象
做了目标编码后过拟合严重目标编码时用了全量训练集统计信息只在每个训练折内计算目标均值,避免数据泄露

6.2 案例:ColumnTransformer 输出列名丢失

很多人在尝试自定义转换器时,会遇到输出列名全是x0x1的情况。这是因为某些转换器返回的是 numpy 数组,scikit-learn 无法自动识别列名。解决办法有两种:

第一种是把自定义转换器改成返回 DataFrame。前面写的 FrequencyEncoder 和 SpendingIntensity 都刻意保留了列名,这样调用get_feature_names_out()时能看清楚每个特征的含义。

第二种是接受 numpy 数组,在使用时不依赖列名,而是通过preprocessor.get_feature_names_out()获取完整输出列名列表。

6.3 案例:测试集出现训练集没见过的类别

真实业务里,训练集和测试集的时间窗口通常不同,测试集出现新城市、新商品类型非常正常。如果 OneHotEncoder 没有设置handle_unknown="ignore",预测时就会直接报错。

建议在所有类别编码器上都显式设置未知类别策略。对于频率编码,也要在 transform 时用fillna(0.0)兜底,保证未知类别能映射为 0 频率。

6.4 排查清单

如果混合变量处理过程中不断报错,可以按下面的清单排查:

  1. 先看df.info(),确认每一列的数据类型。
  2. 判断列角色:数值、无序类别、有序类别、日期、文本。
  3. 检查缺失值分布,不要盲目 dropna。
  4. 检查类别基数:类别数是否超过阈值,是否需要高基数编码。
  5. 确认 pipeline 中填充、编码、缩放的顺序是否合理。
  6. 把特征工程完全放进 Pipeline,不要在 Pipeline 外额外处理测试集。
  7. 查看preprocessor.get_feature_names_out(),确认输出特征是否符合预期。

7. 混合变量特征工程的工程化建议

7.1 所有预处理都必须进入 Pipeline

这是混合变量处理最重要的一条工程原则。不要把填充、编码、缩放步骤写在 Pipeline 外面。只有当特征工程和模型训练位于同一个 Pipeline 时,才能保证训练阶段和线上预测阶段完全一致。

保存模型时,也不要只保存模型文件,而要保存整个 Pipeline:

import joblib joblib.dump(pipeline, "churn_pipeline.pkl")

线上使用时直接加载:

loaded_pipeline = joblib.load("churn_pipeline.pkl") pred = loaded_pipeline.predict_proba(new_data)

这样能避免线上重现特征工程步骤时遗漏任何一个环节。

7.2 防止数据泄露

特征工程中最大的风险之一是数据泄露。目标编码尤其容易出问题:如果直接用全量训练集的目标均值作为该类别的编码值,模型会“偷看”到标签信息,导致验证集成绩虚高,上线后效果骤降。

正确的目标编码做法是,在每一折交叉验证中,只用训练折的数据计算目标均值,再映射到验证折。scikit-learn 的TargetEncoder在较新版本中已经内置了这样的逻辑,使用前记得确认版本和参数。

7.3 原始数据、中间特征、建模数据分层管理

建议在项目目录中明确区分不同阶段的数据:

data/ raw/ # 原始数据,只读,不让任何脚本直接覆盖 processed/ # 中间特征,方便回溯 model/ # 训练好的 pipeline 和模型文件

这样做的目的是可复现性。如果调试过程中发现特征工程有 bug,只要能回到原始数据重新跑 Pipeline,就不会影响实验结论。

7.4 上线前要做的检查

上线一个新的特征工程流程之前,建议至少检查以下几点:

  • 数值列没有无穷值或异常大值。
  • 类别列的未知类别策略已经明确。
  • 缺失值填充统计量来自训练集,而不是全量数据。
  • 日期列已经拆成周期特征,不存在字符串类型列。
  • 通过 get_feature_names_out() 确认特征维度没有异常变化。
  • 保存的 Pipeline 能在干净环境中独立运行。
  • 涉及真实用户数据时,确认字段已经做过脱敏和权限授权,只在合规范围内使用。

8. 总结与下一步建议

混合变量处理是特征工程里非常基础但极其重要的一环。通过本文,你已经掌握了这样几条核心经验:

  • 先识别特征角色,再决定处理方法。
  • 数值变量用中位数填充和标准化,并考虑缺失指示列。
  • 无序类别用独热编码,高基数类别用频率编码或目标编码。
  • 有序类别必须显式指定顺序。
  • 日期变量拆分成周期特征,而不是转换成时间戳。
  • 复合特征通常比原始特征更有业务价值。
  • 使用 ColumnTransformer 把不同处理策略统一成可复用 Pipeline。
  • 训练和预测必须共用同一套预处理逻辑,避免数据泄露。

接下来的学习方向,可以根据自己的需要继续深入。如果你经常使用决策树和随机森林,可以重点研究树模型对类别变量的处理方式和缺失值策略;如果你在做风控或推荐系统,可以进一步学习目标编码与交叉验证如何配合;如果你已经能够在 sklearn 中熟练使用 Pipeline,下一步可以尝试用 Kaggle 真实竞赛数据做一次完整的特征工程实验,把本文的模板套进去,然后对比不同预处理策略对模型效果的影响。

特征工程没有银弹,混合变量处理也没有一套固定参数适用于所有数据集。最好的方式是从一个简单可运行的模板开始,先保证流程正确,再根据业务场景逐步添加自定义转换器。如果你手头的表格正被字符串、缺失值、日期和高基数类别折腾得焦头烂额,可以直接把上面的 ColumnTransformer 结构拿过去改一改,先跑通,再优化。真正理解了这些工具的边界,你在机器学习项目里能少走很多弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询