资讯动态

特征工程中的混合变量:识别与处理实战指南

发布时间:2026/9/8 3:09:50 来源:尧图企业网站定制
混合变量Mixed Variables是特征工程里一个很常见、但容易被忽略的问题。很多人做数据清洗时只检查了缺失值、重复值、异常值却忘了检查某一列里是否同时混有数值和文本信息。等模型训练报错“could not convert string to float”时才回头去看数据发现某一列里既有数字又有“Not Available”“High”“保密”这类文本或者某个字符串列里同时编码了类别和数值信息。我之前在整理 CampusX 机器学习课程的笔记时也踩过这个坑。当时处理一份租金数据集Rent列看起来是数值型但str.contains(价格面议)查一下发现里面藏着文本BHK列看起来是分类但部分地区样本写成了“3 BHK”“2bhk”大小写和空格都不统一。这类数据直接建模会出问题但直接丢弃又损失信息量。这篇文章就把混合变量识别的思路、处理策略和完整 Python 示例整理出来方便以后处理类似特征时直接参考。与其问“混合变量怎么处理”不如先问“这列数据里到底混了几种信息”以及“拆解后哪种表示方式对模型最友好”。这篇文章会从概念、识别、处理到验证完整走一遍。1. 混合变量核心概念速览能力项说明项目背景机器学习特征工程环节源自 CampusX 课程中对特征处理方法的梳理核心问题数据列中同时包含数值信息和分类/文本信息直接建模会报错或产生无效特征常见形式数值列混入文本标记分类列混入数值字符串中同时编码数值和类别处理目标将混合变量拆解为代表数值信息、类别信息、缺失标记的干净特征主要方法正则表达式文本提取、类型转换、指示变量、频率编码、分箱、领域特征构造推荐工具Python、pandas、NumPy、re、scikit-learn适用平台Windows / macOS / Linux 均可普通笔记本即可运行是否支持批量任务支持通过 pandas 向量化操作即可处理整列数据是否支持 API不涉及属于离线数据处理流程可封装为特征工程函数或 sklearn Transformer表格里简化成一句话混合变量不是某种“错误数据”而是真实业务数据中常见的一种信息组织形式。处理它的核心思路是拆分、转换、补全而不是简单删除。2. 适用场景与使用边界混合变量处理属于特征工程中的通用技能几乎每个结构化数据分析项目都会碰到。最常见的场景有四个业务报表导出的数据。很多系统导出时会把数值和文本写在同一个单元格里比如“12个月”“5年经验”“价格面议”这类数据进入 DataFrame 后会自动变成 object 类型。开放数据集的真实数据。Kaggle、天池这类平台上很多真实数据集并不干净类别列里会有拼写变体、大小写变体、带单位或带备注的文本。网页爬取的数据。爬虫抓到的数据往往把 HTML 文本和数字混在一起需要正则表达式统一提取。数据库多源合并的数据。不同系统对同一字段的定义不一致有的存数值有的存文本合并后就会产生混合变量。但也有不适合处理、甚至不应该处理的情况目标列标签列如果本身就是混合形式应该先做标签统一和清洗而不是拆分后交给模型做多标签。有些文本信息本质上属于备注比如“客户ID123电话456”这种信息需要先做数据脱敏和授权评估不能直接作为特征无脑放入模型。如果数据量极小比如只有几十行特征工程带来的收益有限优先关注数据采集和标注质量问题。数据合规是一个不能跳过的问题。处理用户信息、客户数据、业务日志时涉及身份证号、手机号、地址、邮箱等个人敏感信息必须在脱敏后再进行特征工程涉及版权素材、商业数据、机密报表时需要确认是否有合法使用权。特征工程做的是“怎么让模型用上数据”但前提是“这些数据能不能用”。3. 特征工程环境准备与前置条件本文的代码只需要最基础的数据科学环境。如果你的电脑上已经有 Anaconda 或者 Python 3.8 以上的环境可以直接使用。建议先创建独立环境避免与日常开发环境冲突conda create -n feature-engineering python3.10 -y conda activate feature-engineering然后安装依赖库pip install pandas numpy scikit-learn如果网络下载比较慢可以使用国内镜像源。pip install pandas numpy scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple关于硬件这个主题和图像、视频模型完全不同CPU 就能跑不需要独立显卡也不需要大显存。几千行数据在普通笔记本上几分钟内就能完成全流程测试。需要说明的是混合变量处理不涉及模型训练阶段所以这里不讨论 CUDA、GPU 加速、显存占用等问题。如果你的数据量达到千万行级别再考虑用 Polars、Dask 或 Spark 替代 pandas。4. 第一步混合变量的识别方法处理混合变量之前要先有能力发现它们。很多情况下我们不会刻意去查某一列是不是混合变量结果到建模阶段才报错。4.1 创建一份包含混合变量的示例数据为了演示这里构造一份模拟的员工薪资与履历数据。数据中包含几种典型的混合变量形态import pandas as pd import numpy as np data { employee_id: [E001, E002, E003, E004, E005, E006], salary: [25 LPA, 18 LPA, Not Disclosed, 12 LPA, 30 LPA, 保密], experience: [5 years, Fresher, 8 years, 2 years, 12 years, 6 年], qualification: [B.Tech, MCA, 12, PG, B.Tech, 10], age: [29, 25, 35, 28, 40, 保密], city: [Delhi, Mumbai, Bangalore, Pune, Delhi, Chennai] } df pd.DataFrame(data) print(df) print(\n列的数据类型) print(df.dtypes)输出结果中salary、experience、qualification、age都是 object 类型但它们的“混合方式”各不相同salary是数值和单位混在一起部分行被替换成了“Not Disclosed”或“保密”。experience是数值和单位混在一起还存在中英文单位混用。qualification是分类列混入了数字“12”“10”很可能代表学历年限或年级。age是数值列混入了文本“保密”。如果直接把这个 DataFrame 丢给 sklearn 模型训练会直接报错ValueError: could not convert string to float。这就是混合变量带来的最直接问题。4.2 自动识别疑似混合变量对于较大的 DataFrame不可能每一列都手动看需要一个自动排查的思路。def find_mixed_columns(df, numeric_sample20): 初步标记可能存在混合变量的列。 思路对 object 列做非空值采样尝试转数值如果转失败且失败数量占比超过阈值 说明该列可能存在文本信息。 import re potential_mixed [] for col in df.columns: if df[col].dtype object: non_null df[col].dropna() if len(non_null) 0: continue sample non_null.sample(min(len(non_null), numeric_sample), random_state42) converted pd.to_numeric(sample, errorscoerce) fail_ratio converted.isna().mean() if fail_ratio 0.2: potential_mixed.append((col, round(fail_ratio, 2))) return potential_mixed print(find_mixed_columns(df))这段代码的逻辑是object 列如果大部分能直接转成数值则大概率是正常数值列如果有一部分无法转数值就说明里面可能存在文本或特殊标记。它只是一个初步的“医生分诊”工具真正的判断还是需要结合业务含义来确认。识别混合变量的另一个重要手段是查看唯一值for col in df.columns: print(f--- {col} ---) print(df[col].unique()[:20])用unique()查看每一列的非重复值能非常直观地看到是不是有“数字和文本混在一起”的现象。5. 第二步混合变量的处理策略识别出混合变量之后接下来的问题就是怎么处理。这里按“信息类型”分为五类策略实际项目往往需要组合使用。5.1 文本提取法用正则表达式从字符串中提取数值信息对于“25 LPA”“5 years”“6 年”这类数据核心思路是提取出里面的数值部分。import re def extract_number(text): 从字符串中提取数字保留浮点数。 如果没有数字返回 np.nan。 if pd.isna(text): return np.nan match re.search(r\d(\.\d)?, str(text)) if match: return float(match.group()) return np.nan df[salary_num] df[salary].apply(extract_number) df[experience_num] df[experience].apply(extract_number) print(df[[salary, salary_num, experience, experience_num]])对于salary来说“25 LPA”提取为 25.0“Not Disclosed”提取为 NaN。这样数值信息就变成了可以进入模型的数值特征。这里要注意一个细节如果数据里有“12000 INR”这种数值和单位语义更强的字段简单提取数字可能会导致量纲不一致。比如“12 LPA”提取为 12而“12000 per month”提取为 12000两者不在一个量纲上。此时需要结合单位做换算。def parse_salary(text): 处理常见薪资写法统一转为 LPA每年十万卢比。 注意这里的单位映射规则必须根据真实业务调整。 if pd.isna(text): return np.nan text str(text).lower() if not disclosed in text or 保密 in text: return np.nan match re.search(r(\d(\.\d)?), text) if not match: return np.nan value float(match.group(1)) if lpa in text or lakh in text: return value if k in text: return value / 100 if per month in text or /month in text: return value * 12 / 100000 return value df[salary_lpa] df[salary].apply(parse_salary) print(df[[salary, salary_lpa]])5.2 二元指示变量保留“是否存在信息”这个信号很多情况下salary为“Not Disclosed”或“保密”并不是缺失而是业务上的一种状态。如果直接提取为 NaN模型就无法区分“数值缺失”和“员工选择不披露”。处理方法是构造一个指示变量df[salary_not_disclosed] df[salary].apply( lambda x: 1 if (isinstance(x, str) and (not disclosed in x.lower() or 保密 in x)) else 0 ) print(df[[salary, salary_num, salary_not_disclosed]])这样一个包含“不披露”状态的薪资列就被拆成了两个特征salary_num数值信息缺失时用NaN或填充值。salary_not_disclosed是否不披露1 表示有明确文本标记。这个思路同样适用于其他场景比如“年龄保密”“价格面议”“电话未提供”等。指示变量的价值在于它把“文本标记”转换成模型能直接使用的 0/1 信息而不是把所有非数值信息都当作缺失值处理。这类特征往往能提升模型在特定业务场景下的表现。5.3 类型转换与分类编码处理分类列中的混杂信息qualification这列的典型问题是“B.Tech”“MCA” 等分类标签和 “12”“10” 这类数字混在一起。如果数字代表学历年限可以有两种处理方式方式一把数字映射成明确的分类标签。qualification_map { 10: Secondary, 12: Higher Secondary, B.Tech: Bachelor, MCA: Master, PG: Master } df[qualification_label] df[qualification].map(qualification_map) print(df[[qualification, qualification_label]])方式二直接对原始列做 One-Hot 编码把“12”“10”也视为合法类别。这种方法简单但会丢失学历层级顺序。5.4 顺序特征的映射编码有些混合变量虽然看起来是字符串但内部存在可比较的顺序比如“Fresher”“Junior”“Senior”“Lead”这类等级。类比到机器学习竞赛中常见的是“Graduate”“Post Graduate”“Doctorate”它们之间存在明显的次序关系。level_map { Fresher: 0, Junior: 1, Senior: 2, Lead: 3, Manager: 4 } df[experience_level] df[experience].map(level_map) print(df[[experience, experience_level]])如果原始数据是“5 years”“8 years”这种连续数值可以直接用提取出的experience_num如果数据里混有“Fresher”“Senior”这类等级标签就需要先把它们映射为有序数值再和其他数值型经验特征做整合。顺序映射的关键在于映射规则必须反映业务语义。比如“Fresher”为 0“5 years”提取为 5那“Junior”应该映射为 1 还是 2需要先和业务方确认不能拍脑袋。5.5 频率编码处理高基数分类变量有时候混合变量本身没有太多需要拆解的信息但类别数量很多比如“city”列有几十个城市直接 One-Hot 会产生大量稀疏列。这时候可以考虑频率编码。city_freq df[city].value_counts(normalizeTrue) df[city_freq] df[city].map(city_freq) print(df[[city, city_freq]])频率编码的思路是“出现频次越高的类别在数据中的普遍程度越高”用频次代替类别标签可以压缩维度同时保留类别分布信息。不过频率编码容易过拟合尤其在小数据集上。建议在训练集上计算频率再把映射关系应用到验证集和测试集避免数据泄漏。6. 第三步完整代码演示与效果验证前面每一步都是零散的这里把完整流程串起来从原始 DataFrame 到可建模的特征矩阵一步到位。6.1 完整特征工程代码import pandas as pd import numpy as np import re from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, classification_report # 1. 构造模拟数据 data { employee_id: [E001, E002, E003, E004, E005, E006], salary: [25 LPA, 18 LPA, Not Disclosed, 12 LPA, 30 LPA, 保密], experience: [5 years, Fresher, 8 years, 2 years, 12 years, 6 年], qualification: [B.Tech, MCA, 12, PG, B.Tech, 10], age: [29, 25, 35, 28, 40, 保密], city: [Delhi, Mumbai, Bangalore, Pune, Delhi, Chennai], promoted: [1, 0, 1, 0, 1, 0] } df pd.DataFrame(data) # 2. 从 salary 中提取数值和单位 def parse_salary(text): if pd.isna(text): return np.nan text str(text).lower() if not disclosed in text or 保密 in text: return np.nan match re.search(r(\d(\.\d)?), text) if not match: return np.nan value float(match.group(1)) if lpa in text or lakh in text: return value if k in text: return value / 100 if per month in text or /month in text: return value * 12 / 100000 return value df[salary_lpa] df[salary].apply(parse_salary) # 3. 构造“未披露”指示变量 df[salary_not_disclosed] df[salary].apply( lambda x: 1 if (isinstance(x, str) and (not disclosed in x.lower() or 保密 in x)) else 0 ) # 4. 从 experience 中提取数值 def extract_exp_num(text): if pd.isna(text): return np.nan text str(text).lower() if fresher in text: return 0 match re.search(r(\d(\.\d)?), text) if match: return float(match.group(1)) return np.nan df[experience_num] df[experience].apply(extract_exp_num) # 5. qualification 映射 qualification_map { 10: 0, 12: 1, B.Tech: 2, MCA: 3, PG: 3 } df[qualification_level] df[qualification].map(qualification_map) # 6. age 列的保密标记 df[age_not_disclosed] df[age].apply(lambda x: 1 if x 保密 else 0) df[age_num] pd.to_numeric(df[age], errorscoerce) # 7. city 频率编码 city_freq df[city].value_counts(normalizeTrue) df[city_freq] df[city].map(city_freq) # 8. 选择建模特征 features [ salary_lpa, salary_not_disclosed, experience_num, qualification_level, age_num, age_not_disclosed, city_freq ] model_df df[features].fillna({ salary_lpa: 0, experience_num: 0, age_num: 0 }) # 9. 建模验证 X model_df y df[promoted] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.4, random_state42, stratifyy ) model RandomForestClassifier(n_estimators50, random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test) print(Accuracy:, accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred))这个完整流程覆盖了前面讲的五种核心策略正则提取从salary、experience中提取数值。指示变量标记“未披露”和“保密”这类业务状态。业务映射把学历映射为等级。缺失补充用 0 填充提取失败的数值字段。频率编码处理城市这类高基数分类变量。6.2 效果验证思路在小规模模拟数据上模型准确率只是一个演示结果。更重要的验证方式是比较“处理前”和“处理后”的建模效果处理前直接对原始 DataFrame 建模会报错这是最直观的验证。处理后模型能正常训练并且可以输出分类报告。如果你有真实业务数据建议用同样的特征工程流程跑一遍然后用特征重要性和逻辑回归系数来验证每个新特征是否有效。比如salary_not_disclosed特征如果重要度很高说明“是否披露薪资”本身对业务结果有较强的区分能力。6.3 判断特征工程是否成功的标准特征工程做得好不好不是看代码跑通没有而是看以下几点模型输入中没有出现字符串报错。处理后的特征在业务上有明确含义能向业务方解释清楚。训练集和验证集的特征分布一致。特征数量控制在可解释范围内没有出现高维稀疏爆炸。新数据输入时特征工程函数可以无缝复用。如果这五条都满足特征工程这部分基本合格。7. 常见问题与排查方法混合变量处理过程中会遇到不少问题这里整理成表格方便排查。问题现象可能原因排查方式解决方案pd.to_numeric转换后全是 NaN文本格式太杂正则匹配范围不足打印该列所有唯一值检查正则覆盖情况增加正则分支或先做文本归一化提取出的数字量纲不一致数据中单位不统一月薪/年薪/万/千查看单位关键词分布先统一单位再做数值提取模型依然无法训练仍有字符串列未处理检查df.dtypes确认没有 object 类型对剩余 object 列再做编码或删除缺失值处理不当导致信息丢失把“未披露”直接当作缺失值处理统计“未披露”文本的数量构造二元指示变量再填充缺失值One-Hot 编码后维度爆炸分类列类别过多查看nunique()改用频率编码、目标编码或 Embedding训练集和测试集频率编码不一致在合并数据上计算频率或测试集有未知类别检查映射字典是否覆盖全部类别在训练集上计算频率未知类别填充为 0特征工程代码无法复用到新数据函数依赖了全局变量或单次数据的内容检查函数是否对输入数据有强依赖封装为函数输入 DataFrame输出增强后的 DataFrame正则提取把错误文本提取成数字数据中存在和业务无关的数字如 ID 号查看提取结果是否明显不合逻辑增加单位/关键词约束或结合前后文本判断中文和英文单位混用多语言数据来源统一转小写后再匹配构建中英文单位同义词表分类列中混入数字但无法判断含义缺少数据字典找数据提供方确认数字含义先作为独立类别处理后续再映射在这个表中最值得注意的问题是“文本格式太杂导致正则覆盖不全”。真实数据往往比模拟数据更脏可能同时存在“25LPA”“25 LPA”“Rs. 25 LPA”“25 LPA (Negotiable)”等多种写法。建议先做文本归一化统一大小写、去空格、替换常见缩写再写提取正则。8. 最佳实践与工程化建议特征工程在实际项目中不是一次性的它需要被反复使用在训练、验证、测试和线上推理流程中。以下经验来自常见的结构化数据项目实践可以直接参考。8.1 封装特征工程函数不要在每个 Notebook 里复制粘贴同一步处理逻辑把清洗流程封装成函数或 sklearn Pipeline。def clean_salary_feature(df, colsalary): df df.copy() df[salary_lpa] df[col].apply(parse_salary) df[salary_not_disclosed] df[col].apply( lambda x: 1 if (isinstance(x, str) and (not disclosed in x.lower() or 保密 in x)) else 0 ) return df # 训练数据和线上推理都可以复用 train_df clean_salary_feature(train_df) test_df clean_salary_feature(test_df)如果使用 scikit-learn可以把这些步骤封装成自定义 Transformer再和ColumnTransformer组合成完整 Pipeline。这样网格搜索、交叉验证、模型导出都会更规范。8.2 保留原始数据处理结果单独存储特征工程过程中原始数据绝对不能覆盖。建议把处理后的特征单独保存一份例如输出成新的 parquet、feather 或 CSV 文件。df.to_csv(processed_feature_data.csv, indexFalse)这样做的好处是如果后发现某一步特征构造有 bug可以回到原始数据重新处理而不必重新找数据。8.3 特征构建过程留下文档每一个新特征都应该记录三件事特征名称和数据类型。处理逻辑正则规则、映射规则、填充规则。创建日期和创建原因。例如特征名类型构建逻辑说明salary_lpafloat正则提取数字统一转 LPA提取自 salary 原始列salary_not_disclosedint(0/1)判断是否存在“Not Disclosed”保留信息缺失状态experience_numfloat正则提取数字Fresher 映射为 0提取自 experiencequalification_levelint字典映射学历等级学历有序化有文档的特征工程才具备可维护性尤其当项目交接或半年后再回来看时文档能省下大量时间。8.4 防止数据泄漏频率编码和顺序映射的规则只能基于训练集计算然后通过映射字典应用到验证集和测试集。如果先对全量数据做value_counts再切分训练集和测试集会造成评估指标虚高。city_freq_train train_df[city].value_counts(normalizeTrue) train_df[city_freq] train_df[city].map(city_freq_train) test_df[city_freq] test_df[city].map(city_freq_train).fillna(0)8.5 区分缺失值和业务状态混合变量处理中最容易犯的错误是把“Not Disclosed”“保密”“价格面议”这类文本和真正的缺失值混为一谈。它们在业务上的含义完全不同真正缺失可能是数据采集漏了而“保密/面议”是一个离散状态代表用户主动不披露或数据无法提供。建议先做指示变量再考虑填充策略。8.6 数据合规与隐私保护处理用户数据、客户信息、企业数据时要谨慎对待个人敏感信息。手机号、身份证号、详细地址、精确地理位置等字段不应该作为特征直接进入模型。如果必须使用必须经过脱敏、泛化或加密处理并确认数据来源合法、使用目的合规。特征工程不能成为绕过隐私保护的借口。9. 总结与下一步混合变量处理是机器学习特征工程中非常基础、但非常实用的一环。处理逻辑清晰五步可以走完先识别是否存在混合变量再判断列中混合了哪几种信息接着按信息类型拆分然后构造指示变量保留业务状态最后按需编码并验证模型效果。从我的经验看最容易踩的坑有两个。第一是把“Not Disclosed”“保密”这类业务状态直接当作缺失值填充导致信息丢失。第二是在没有检查唯一值的情况下直接训练模型等报错才发现有些列是 object 类型。这两类问题在真实数据集中非常常见处理成本也不高关键是要在特征工程阶段预留时间做数据排查。建议你拿到一份新数据集后先执行一次df.dtypes和df[col].unique()全列检查把混合变量标记出来再用这篇文章里的函数逐个处理。第一次先跑通流程后面再逐步完善单位映射和填充策略。把这套处理流程沉淀成自己的特征工程函数库以后做 Kaggle、天池或实际业务项目时都能直接复用。下一步可以继续深入的方向是目标编码和贝叶斯编码当类别列包含混合信息、而且类别数量和标签之间有明显关联时这两类编码会比 One-Hot 和频率编码更有效。不过在那之前先把混合变量识别与拆分练熟因为这是所有编码方法的基础。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价