news 2026/10/19 2:57:58

Pandas独热编码get_dummies列名规则与实战避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pandas独热编码get_dummies列名规则与实战避坑指南

如果你用过Pandas做数据分析,pd.get_dummies()几乎不可能绕开。网上很多教程一句话带过:“把类别变量变成0/1”。但我见过太多人真的只用这一句理解它,结果跑完一看DataFrame,发现多出来几十列,而且列名完全对不上预期,更不知道哪些是原来的列、哪些是新增列。这篇文章就围绕一个很具体的问题展开:pd.get_dummies()到底会生成哪些独热的新列,背后的列名规则是什么,有哪些参数会影响新列的数量和名字,以及在真实项目里怎么保证训练集和测试集不会因为独热编码而“列数对不上”。适合正在做特征工程、准备建模,或者被一堆自动生成的列名搞到头大的同学;哪怕你刚接触Pandas,直接照着代码跑一遍也能明白。

1. get_dummies到底在做什么:从一列到N列的拆分逻辑

1.1 它不是“把一列变成两列”,而是“每个类别单独占一列”

很多模型不认识“红”“绿”“蓝”这样的文本,只能吃数值。独热编码的核心思想是:把一个类别列里的每一个不同取值,拆成一个独立的二元列,样本属于哪个类别,对应列就置1,其他列置0。所以真正的问题不是“一列变几列”,而是“这一列里到底有多少个不同类别”。有多少个类别,就会生成多少个新列。

这里有个很容易忽略的点:pd.get_dummies()默认返回的是一个新的DataFrame,而不是在原表上原地修改。它会把被处理的文本列“消化”掉,转成一组新列,同时保留那些没有被处理的数值列。如果你直接把它赋值给原变量,原来的文本列就没了,只剩下拆分后的新列。

import pandas as pd df = pd.DataFrame({ "颜色": ["红", "绿", "蓝", "红"], "销量": [100, 120, 90, 110] }) df_dummies = pd.get_dummies(df) print(df_dummies.columns.tolist())

输出大致是:

['销量', '颜色_红', '颜色_绿', '颜色_蓝']

去掉销量这个数值列不看,颜色这一列变成了三个新列:颜色_红、颜色_绿、颜色_蓝。列名的基本规则在这里已经很明显了:默认前缀是原列名,分隔符是下划线,后缀是类别值本身。

1.2 多个分类列时,新列名按“原列名 + 类别值”各自独立生成

如果数据里同时有好几个类别列,比如城市、等级、渠道,get_dummies()会分别对每一列做独热,不会把不同列的值混在一起。每一列的新列名都遵循同样的拼装规则。

df2 = pd.DataFrame({ "城市": ["北京", "上海", "广州"], "等级": ["A", "B", "C"], "金额": [1, 2, 3] }) df2_dummies = pd.get_dummies(df2) print(df2_dummies.columns.tolist())

结果大致是:

['金额', '城市_北京', '城市_上海', '城市_广州', '等级_A', '等级_B', '等级_C']

所以,如果你看到一张表里多了城市_北京、等级_A这样的列,不要去类别值里找规律,规律全在列名拼接规则里:被处理列的名字 + 下划线 + 该列里出现的类别值。只要记住这条公式,绝大多数情况下你都能反推出一个新列到底是从哪一列来的、代表的是哪个类别。

1.3 新列的顺序和排序规则,不同数据版本会有差异

我实际用过不同版本的Pandas,get_dummies()生成新列的顺序并不可靠。大多数情况下,它会按类别在数据里第一次出现的先后顺序来排。但如果原列本身是category类型,它就会按categories属性里定义的顺序来生成。同一种数据,换成category类型后,输出列的顺序很可能是另一副面孔。

这个细节在小白阶段可能无所谓,但在做特征对齐的时候容易坑人。比如你要把一个独热编码后的结果喂给某个模型,而模型内部要求特征列顺序固定,那你就不能依赖“顺序恰好一致”。最稳的做法是生成之后立刻打印columns.tolist()检查,或者给所有新列做一次排序,让它可预测。

注意:绝大多数情况下,独热编码生成的列名里的“类别值”是原始字符串。如果原字符串带空格、带符号、是中文,这些内容都会原封不动出现在列名里。列名里出现空格和括号,后续访问列、存文件、接SQL都会很麻烦。

2. 新列是怎么被“改名”的:六个关键参数影响列名和列数量

2.1 prefix:默认前缀就是原列名,但你可以彻底改掉它

如果你什么都不传,prefix=None,那么前缀就是原列名。这是最常见的情况。但某些场景下,你不想让新列名跟原列名长得一样,比如想把多个不同数据源的表统一命名风格,或者列名太长想缩短,就可以用prefix参数。

prefix可以传字符串、字符串列表,也可以传字典。传字符串时,所有被处理的列都会共用这个前缀;传列表时,列表长度必须跟columns参数的长度对齐;传字典时,可以给每一列单独指定一个前缀,最直观。

pd.get_dummies(df, prefix={"颜色": "color"})

这样生成的新列名就变成color_红、color_绿、color_蓝。注意,前缀变了之后,你依然能从后缀看出原来的类别值,但列名和原列名的关系就没那么直白了。建议在代码里留好注释,免得一个月后自己都忘了color_xxx是从哪来的。

2.2 prefix_sep:分隔符看着不起眼,但影响后续解析

prefix_sep默认是下划线_。你可以改成点.、横杠-甚至空字符串。改成空字符串后,新列名会变成颜色红这样的形式。手写几个字符串可能没什么感觉,但放到几百列的宽表里,这种列名会让字段含义变得非常模糊。

我个人的建议是:不要用横杠,因为很多建模库、SQL引擎、特征存储系统对字段名里的减号很敏感,解析时会把它当成减法运算。点号虽然看着干净,但在有些工具里也有特殊含义。最稳妥的就是下划线。如果嫌列名太长,优先考虑缩短前缀,而不是换分隔符。

pd.get_dummies(df, prefix="c", prefix_sep="_")

生成的是c_红、c_绿、c_蓝,干净、好读、兼容性好。

2.3 columns:控制处理哪些列,这是第一道闸门

如果不指定columns,get_dummies()会把DataFrame里所有object类型和category类型的列都处理掉。这个问题在真实数据里非常致命。一个表里可能同时存在备注、用户ID、渠道名称、地址等一堆字符串列,你本来只想处理渠道名称,结果备注也被拆成了几百列,表直接宽到没法看。

所以,只要DataFrame不是只有一两个类别列,我都建议显式传columns。

df_dummies = pd.get_dummies(df, columns=["颜色", "等级"])

传了columns之后,只有这两列被拆分成新列,其他列保持原样。这一步是控制“新列集合”最有效的手段。尤其是当你的数据里有高基数字符串列时,漏掉这个参数几乎等于给自己埋雷。

2.4 dummy_na:缺失值到底怎么编码,是隐性信息

默认情况下,dummy_na=False。如果某个样本在类别列上是缺失值,它不会单独生成一个新列,而是所有类别列都填0。这在语义上等于“这个样本不属于任何类别”。

但缺失本身往往是有意义的,比如“颜色未知”和“颜色是红”是两种状态。这时候可以用dummy_na=True,Pandas会额外生成一列,名字一般是原列名_nan。注意,这个nan的外形会因为缺失值类型不同而略有差异:如果缺失值是np.nan,生成的列名后缀通常是小写nan;如果是字符串"nan",那它本来就是一个正常类别,不需要dummy_na单独处理。

pd.get_dummies(df, columns=["颜色"], dummy_na=True)

这样新列集合从三个变成四个:红、绿、蓝、nan。在建模时,这四列能完整表达原始信息,不会把缺失样本和普通类别样本混淆。

2.5 drop_first:从K列变成K-1列,减少的到底是哪一列

默认drop_first=False,一个类别列如果有K个不同值,就生成K列。但在线性回归、逻辑回归这类模型里,K列存在完全共线性问题:知道了前K-1列,第K列的值一定能推出来。模型会因此不稳定,甚至直接报奇异矩阵错误。

drop_first=True会把第一个出现的类别作为基准,只生成K-1列。比如颜色列有红、绿、蓝,那么会生成颜色_绿、颜色_蓝两列;样本颜色为红时,这两列都是0,相当于默认基准。

pd.get_dummies(df["颜色"], drop_first=True)

这个参数直接影响新列数量。树模型不太在意共线性,所以要不要用drop_first完全看你后续接什么模型。如果你不确定,稳妥做法是默认False,让信息完整保留。

2.6 dtype:生成的新列到底是什么类型,直接影响内存

get_dummies()生成的新列默认类型在不同Pandas版本里不太一样,早期版本常见uint8,后来版本也可能默认返回bool。与其依赖默认值,不如显式指定dtype="uint8",让结果可控。

这不仅仅是类型洁癖问题。百万行数据、几十个类别列的场景下,uint8每个元素只占1字节,而int64每个元素占8字节,内存差距接近8倍。如果你用的是老版本,或者团队代码里混用了不同Pandas版本,显式传dtype可以避免一上线就内存溢出的尴尬。

pd.get_dummies(df, columns=["颜色"], dtype="uint8")

另外,老版本里的sparse参数现在基本被废弃了,不建议继续用。想要稀疏矩阵,直接考虑用下面的OneHotEncoder,生态支持更完整。

3. 真正会翻车的三个场景:列数对不上、列名冲突、漏掉类别

3.1 训练集和测试集分开做独热,模型必炸

这是我在真实项目里见过最多、也最典型的错误。很多人会把训练集和测试集分开放好,然后各自调用一次get_dummies()。表面上看没什么问题,但独热编码生成的列集合取决于“当前数据里实际出现了哪些类别”。

如果某个类别只在训练集里出现,测试集里没有,那么测试集的列数就比训练集少一列;反过来说,如果测试集里冒出一个训练集从未见过的新类别,测试集的列数又会比训练集多一列。模型训练时用的特征矩阵是K列,预测时测试集却是K+1列,框架直接报特征数量不一致。

解决办法有两个。第一个是先拼接再拆分:

df_all = pd.concat([df_train, df_test], axis=0) df_all_dummies = pd.get_dummies(df_all, columns=["颜色"]) df_train_dummies = df_all_dummies.iloc[:len(df_train)] df_test_dummies = df_all_dummies.iloc[len(df_train):]

第二个是先对训练集做独热,保存训练集的列名,再用reindex对齐测试集:

train_columns = df_train_dummies.columns df_test_dummies = pd.get_dummies(df_test, columns=["颜色"]) df_test_dummies = df_test_dummies.reindex(columns=train_columns, fill_value=0)

第二种方式更常用,因为测试集可能不是一次性读进来的,真实场景里还要处理线上预测。这一步的关键在于:永远以训练集的列集合为准,不要用测试集反过来对齐训练集。

3.2 类别值里的特殊字符会把列名搞乱

独热编码的新列名等于“前缀 + 分隔符 + 原始类别值”。一旦原始类别值里带空格、斜杠、括号、中文标点,新列名就会变得非常难用。

比如城市列里有New York,生成的列名是城市_New York。这个列名用df["城市_New York"]访问没问题,但你要是存成CSV再读回来,或者接SQL、接某些特征平台,空格就可能成为安全隐患。中文列名虽然Pandas支持,但在一些建模框架里会乱码或无法解析。

我踩过几次坑之后,养成了一个习惯:在get_dummies()之前先对类别值做一次清洗,把空格统一替换成下划线,大小写统一,再去掉特殊符号。这一步不改变类别语义,但能让生成的列名干净很多。

df["城市"] = df["城市"].str.strip().str.replace(" ", "_").str.lower()

当然,如果你必须保留原始类别值用于业务解释,那就不要清洗,而是在独热之后对列名再统一做一次rename。无论哪种方式,都要有意识地处理,而不是让列名“随机生成”。

3.3 数字型分类变量默认不生成新列,很多人都会懵

get_dummies()的默认行为是只处理object、string、category类型的列。如果某一列是整数编码的地区码,比如1、2、3,就算它本质上是分类变量,Pandas也不会理它。为什么?因为Pandas默认把数值列当作连续变量,独热一个连续变量没有意义。

解决办法是先把这一列转成category类型,再交给get_dummies()。

df["地区码"] = df["地区码"].astype("category") pd.get_dummies(df, columns=["地区码"])

这样生成的新列是地区码_1、地区码_2、地区码_3。这个细节很重要,因为很多从数据库里导出的数据,分类变量经常被存成整数。如果没转类型就调用函数,你可能以为自己在做独热编码,实际上这一列压根没被处理,最后模型结果差得莫名其妙。

3.4 布尔列和category类型的“未出现类别”会带来隐性差异

如果某一列本来就是布尔值True/False,get_dummies()也会生成两个新列,列名是列名_True、列名_False。这本身没问题,但要注意列名里的True/False只是字符串,别在代码里把它们和Python布尔值搞混。

另一个更容易被忽略的是category类型的“未出现类别”。如果某一列的categories里定义了A、B、C,但当前数据里只出现了A、B,get_dummies()依然会生成A、B、C三列,因为C已经在分类定义里。而普通object类型只会按实际出现的值生成两列。这两种行为没有绝对的对错,但会影响你“新列数量”的预期。想要稳定可控,建议对分类列统一使用category类型,并主动检查cat.categories。

4. 项目实战中的排查技巧,以及和OneHotEncoder怎么选

4.1 每次生成之后,先做这三件事

很多新手生成完独热列就急着建模,结果列名、类型、缺失值哪里出问题都不知道。我的习惯是生成之后固定做三件事。

第一,看新列名。不能只看一眼,要用一个列表把新增列全部列出来:

original_cols = set(df.columns) new_cols = [c for c in df_dummies.columns if c not in original_cols] print(new_cols)

注意,被处理的原始列在df_dummies里已经不在了,所以这个集合差能看到所有新列。如果新列列表里混进了颜色这种看起来像原列名的东西,多半是你忘了传columns,导致某些列被重复处理。

第二,看类型。检查是不是目标类型:

print(df_dummies.dtypes)

如果新列是uint8或者bool,内存基本可控;如果全是int64,建议回头把dtype参数加上。

第三,看缺失。独热编码后的结果里理论上不应该有NaN:

print(df_dummies.isna().sum().sum())

如果这个数大于0,说明数据在独热之前就已经存在缺失,并且dummy_na=False。要不要处理缺失,取决于业务判断,但你不能连它存在都不知道。

4.2 get_dummies和OneHotEncoder,到底用哪个

很多人会在pd.get_dummies()和sklearn.preprocessing.OneHotEncoder之间纠结。我的判断标准看使用场景。

维度pd.get_dummiesOneHotEncoder
返回结果DataFrame,和Pandas无缝衔接ndarray或稀疏矩阵,需要自己拼回DataFrame
训练/测试对齐需要手动reindex,容易出错先fit训练集,再用同一个encoder transform测试集,天然对齐
未知类别处理会直接多生成新列handle_unknown='ignore'可以忽略未知类别
内存表现默认稠密矩阵,高基数时容易爆支持sparse_output=True,内存友好很多
Pipeline集成不适直接进sklearn管道可以作为管道里的一个转换器

简单探索、快速验证、写一次性脚本,用get_dummies()很顺手。正式建模、有严格的训练测试流程、需要做交叉验证,我更推荐用OneHotEncoder,因为它能保证训练集和测试集使用同一套类别字典,不会在特征对齐上出幺蛾子。

4.3 高基数列失控之前,先处理类别数量

如果一个类别列有几千个不同取值,直接独热会生成几千列。这个操作在百万行数据上基本就是灾难,光是存储和计算开销就能把内存吃满。

我的做法是先把基数降下来。最常用的是频数编码:保留出现次数最多的Top N个类别,其余归为“其他”。这样既保留了主要信息,又把列数控制在一个合理的范围内。

top_cities = df["城市"].value_counts().head(20).index df["城市_压缩"] = df["城市"].where(df["城市"].isin(top_cities), "其他")

然后再对压缩后的列做独热。这一步在真实数据里非常实用。不要看到列数爆炸才想起来优化,而是在对表结构还不熟悉的时候,就要提前看每个列的nunique。

print(df.select_dtypes("object").nunique().sort_values())

在动手独热之前多花一分钟跑这个,能帮你判断哪些列适合独热,哪些列需要先做降基处理。

4.4 我的个人排坑流程,最后分享一个小技巧

我处理这种特征工程任务时,习惯先把所有object列和category列的nunique打印出来,只挑基数低于某个阈值的列做独热,剩下的列单独走频数编码或嵌入编码。列名统一采用小写加下划线,生成后用reindex(columns=train_columns, fill_value=0)对齐测试集,最后再用memory_usage检查一次内存占用。这套流程虽然多几步,但能省掉后面调试特征对齐和内存溢出的大量时间。

最后说一个很多文档不会写的小技巧:如果你只是想知道某一列经get_dummies()后会生成哪些新列,不必真的跑一整个模型,直接看df[col].unique()然后按前缀_类别值手工拼出来就八九不离十。但在真实项目里,类别值可能带着空格和特殊符号,拼接结果不一定完全符合预期,所以最终还是要以实际生成后的columns.tolist()为准。毕竟,独热编码的规律可以背,数据里的脏值却永远需要亲眼确认。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 2:57:57

DFTtoolbox:用MATLAB让第一性原理计算前后处理全自动化

简介:DFTtoolbox是一个面向凝聚态物理与材料科学工作者的开源Python模块,旨在帮助使用Quantum ESPRESSO、Abinit、Elk等密度泛函理论(DFT)代码的研究者快速构建输入文件,并对能带、态密度等计算结果进行即时分析与可视…

作者头像 李华
网站建设 2026/10/19 2:57:28

优雅卡片滑块轮播图:原生JavaScript实现布局、拖拽与无限循环

最近被一个作品展示类的页面逼了一把:产品经理提了个硬需求——不要传统那种一张大图左右横切的轮播图,要有“卡片感”,中间主卡完整展示,旁边的卡片缩小、半透明、错位露出,手指划过的时候一层一层跟上去,…

作者头像 李华
网站建设 2026/10/19 2:56:51

dlib人脸识别实战:从关键点检测到128维特征编码与微笑识别

1. 为什么我反复对比之后还是选了dlib:人脸识别工具选型心得1.1 从一次翻车开始:OpenCV Haar检测和人脸识别框架的短板先交代一个背景。之前我负责一个现场考勤类的小系统,设备是一台普通的办公电脑,没有独立显卡,摄像…

作者头像 李华
网站建设 2026/10/19 2:56:23

AI辅助论文写作实用指南:四款工具分环节协作与避坑经验

前两年我在某高校帮A同学改论文,初稿交上来,整页的"随着社会的发展"。我问他这是不是AI写的,他点头,还觉得写得挺顺。从那次之后我就意识到,AI写论文这事本身不是问题,问题在于大多数人把它用成了…

作者头像 李华
网站建设 2026/10/19 2:56:23

深度学习焊接缺陷检测:从数据标注到U-Net落地全流程

1. 为什么必须用深度学习方法做焊接缺陷检测——传统算法的边界在这里焊接缺陷检测系统,听起来像是一个很传统的工业质检话题,但真正用Python把深度学习方案完整落地时,你会发现这里面有大量容易被忽略的细节。我最近做一个模拟项目X&#xf…

作者头像 李华
网站建设 2026/10/19 2:56:10

fcgi-2.4.0源码解析:C语言如何成就Nginx高性能FastCGI服务

简介:fcgi-2.4.0 是 FastCGI 协议的一个特定版本,面向需要在 nginx 中接入动态脚本处理能力的开发者和运维人员。这套压缩包共收录 127 个文件,整体仅 1.03 MB,文件类型涵盖 C 语言头文件与源码(.c/.h)、co…

作者头像 李华