Pandas数据变形实战:宽表与长表的pivot/melt转换

发布时间:2026/10/13 22:52:15
Pandas数据变形实战:宽表与长表的pivot/melt转换 1. 变形Pandas里最被低估的一章我第一次认真对待数据变形这件事是在处理一张几百列的业务流水表时。当时我需要的只是把其中十几个字段从行变成列、再从列拆回行如果不会这些操作就只能复制粘贴到Excel里手动折腾既慢又容易出错。Joyful-Pandas第4章变形讲的正是这堆看似琐碎但实际高频到不行的数据形态转换操作。这一章的核心不是让你死记硬背pivot和melt的参数而是帮你建立一种数据形态翻译器的思维方式同一份数据有时候宽表好用有时候长表好用关键不在于哪种形态更标准而在于你要拿它做什么。看完这一章你会知道什么时候该把列拆成行、什么时候该把行聚成列、多层索引怎么和普通列互相转换也能顺手解决掉一大堆在真实业务里反复出现的表太宽了列要变成行这个表要转置之类的需求。在我看来凡是做数据分析、数据处理、数据开发的人哪怕是刚入门的小白这一章都值得认真过一遍。代码量不大但每个函数的使用场景差异非常明显理解不到位就很容易用错。2. 变形的本质宽表与长表的攻防转换2.1 为什么数据分析绕不开变形先想清楚一个问题数据表为什么需要变形因为在不同的分析阶段我们需要的表格形态根本不一样。宽表的特点是一行对应一个主体样本每一列对应这个主体的某个属性。比如说一张用户表一行就是一个用户列是姓名、城市、注册时间、消费金额。这种表的好处是直观扫一眼就能看出一个人是什么情况适合人工查看、特征工程、建模输入。但它的坏处也很明显一旦属性本身需要被当作分析维度比如不同月份的消费额分别占据多列宽表就非常不利于聚合、筛选和绘图。长表的特点是一行对应一个观测样本也就是说一个主体的多个属性或多个时间点的数值会被拆成多行。长表看起来又瘦又长但它是统计计算、绘图工具尤其是各种可视化库最喜欢的形态。因为画折线图、做分组聚合时机器喜欢所有值都堆在一列里再用另一列去标记这个值属于谁、属于哪个时间。所以分析流程里你往往需要在两种形态之间来回切换原始数据拿到了是宽表先用melt拉成长表去做清洗和聚合统计结果出来后再用pivot_table拉回宽表去汇报。这个过程就是变形。搞不定这个来回你就会被锁定在某一形态里所有操作都别扭。2.2 变形的三种底层操作如果把pandas里所有和变形相关的函数摊开看其实它们的本质可以归纳成三种操作第一种是行转列也就是把某个列里的分类值变成多个新列对应函数是pivot和pivot_table。比如有一列支付方式里面有微信、支付宝、银行卡三种取值你就可以把它转换成三列分别存放对应的金额。第二种是列转行也就是把多个列合并成一列同时新增一个列来记录原来的列名对应函数是melt。比如把一季度、二季度、三季度、四季度这四列合并成一列季度再把数值合并成一列销量。第三种是索引与列之间的搬运也就是把数据从行索引搬到列、或者从列搬到行索引对应函数是stack和unstack。这套操作搭配set_index使用能在长表和宽表之间完成各种自由变换而且可以处理多级索引的情况。理解到这一层pandas的变形就不再是一堆孤立函数了而是一个统一的搬数据逻辑每一份数据都存在两个位置要么躺在列上要么躺在索引上变形做的事情就是把这个位置换一换。后面所有的实操都会围绕这个逻辑展开。3. 核心工具拆解pivot、pivot_table、melt怎么用3.1 pivot简单场景下的行转列pivot的官方定义是通过指定的索引和列值重塑DataFrame。它需要你指定三个参数index表示新表的行索引来自哪个列columns表示新表的列名来自哪个列values表示新表的值填充自哪个列。举个例子假设你有一份订单数据import pandas as pd df pd.DataFrame({ 日期: [2024-01-01, 2024-01-01, 2024-01-02, 2024-01-02], 城市: [北京, 上海, 北京, 上海], 销售额: [100, 200, 150, 250] })如果你想把城市从列值变成列标题每天一行那写法就是pv df.pivot(index日期, columns城市, values销售额)这段代码会得到一张新表行索引是日期列是北京和上海单元格是对应的销售额。pivot本质上是把源数据里的城市这一列的分组值映射到结果的列名上。但pivot有个很大的限制如果源数据里同一个(索引, 列名)组合出现了多次它会直接抛错报错信息是Index contains duplicate entries。因为pivot只是重塑不负责聚合它没法决定重复位置该填哪个值。所以在业务数据不干净、有重复记录的情况下pivot很容易翻车。我的习惯是只要数据不是100%保证唯一就不用pivot直接走pivot_table更稳。3.2 pivot_table带聚合能力的透视表pivot_table相当于pivot的加强版它多了一个aggfunc参数用来指定当同一位置出现多条记录时怎么合并。默认是numpy.mean所以哪怕你不传aggfunc它也不会因为重复数据报错而是会算平均值。还是上面那个例子如果同一天同一个城市有两条订单记录df2 pd.DataFrame({ 日期: [2024-01-01, 2024-01-01, 2024-01-01], 城市: [北京, 北京, 上海], 销售额: [100, 150, 200] }) pt df2.pivot_table(index日期, columns城市, values销售额, aggfuncsum)结果里北京会变成250上海是200。这里我指定了aggfuncsum因为对销售额来说求和更常见如果是单价或者评分默认的mean反而更合理。pivot_table还有几个实用参数fill_value可以指定空值填充比如fill_value0会把NaN填成0很适合做报表展示marginsTrue会在结果里额外生成一行总计和一列总计方便看合计。需要注意的是margins生成的汇总行并不适合继续参与后续计算它更像是一个给人看的汇总和groupby里带observed参数的状态还不一样。3.3 melt把宽表熔成长表melt和pivot正好是相反方向的操作。它接收一个宽表然后把指定的多列压成两列一列是变量名一列是值。最关键的参数是id_vars也就是保留下来作为标识符的列。比如你有一张表列是城市、一月、二月、三月你想把后面三个月列合并成一列月份和一列销量那id_vars就填城市wide_df pd.DataFrame({ 城市: [北京, 上海], 一月: [300, 400], 二月: [350, 420], 三月: [380, 450] }) long_df wide_df.melt(id_vars城市, value_vars[一月, 二月, 三月], var_name月份, value_name销量)melt的结果是城市一列月份一列销量一列。这其实就是数据分析里经典的宽表转长表操作。如果你不传value_vars它默认会把id_vars之外的所有列都熔掉如果不传var_name和value_name生成的两列默认叫variable和value。正式写代码时我强烈建议把var_name和value_name显式写出来否则后续取列名的时候很容易懵。3.4 用groupby也能完成变形很多人不知道pivot_table的底层逻辑其实和groupby unstack非常接近。pivot_table指定index、columns、values和aggfunc本质上就是在按index和columns做分组聚合再把分组里的某一列展开成多个列。所以很多场景里你可以用groupby写出一模一样的变换df2.groupby([日期, 城市])[销售额].sum().unstack()这一行代码得到的结果和前面pivot_table(index日期, columns城市, values销售额, aggfuncsum)几乎一致。区别在于pivot_table直接返回DataFrame而groupby之后是Series需要你自己unstack一下。这个认识很有用因为它把变形和聚合打通了变形不是一次单纯的位置挪动它背后常常伴随了分组聚合的语义。能理解这一点你在处理复杂数据时就不会被API牵着走而是能根据需求反推用哪种组合。4. 实操过程从业务宽表到可分析长表再到汇总报表4.1 准备一份模拟的业务数据为了把几个工具串起来看我用一份完全虚构的销售数据来做实操演示。假设某个业务系统的订单表结构是一个订单有订单号、销售区域以及各季度的销售额字段。这份数据的特点是宽表形态季度作为列存在不适合直接做分组统计或者绘图。先构造模拟数据import pandas as pd import numpy as np df_sales pd.DataFrame({ 订单号: [A001, A002, A003, A004], 销售区域: [华东, 华北, 华南, 华北], Q1销售额: [1200, 1500, 1300, 1100], Q2销售额: [1350, 1420, 1600, 1250], Q3销售额: [1480, 1550, 1700, 1300], Q4销售额: [1600, 1680, 1750, 1420] })这份数据一眼能看懂但它有个典型问题季度是四个独立的列如果我想按季度求平均、按季度画趋势图这种宽表形态就很别扭。需要先把它熔成长表。4.2 第一步melt把多季度列压平把Q1销售额、Q2销售额、Q3销售额、Q4销售额合并成一列季度再合并成一列销售额同时保留订单号和销售区域作为标识df_long df_sales.melt( id_vars[订单号, 销售区域], value_vars[Q1销售额, Q2销售额, Q3销售额, Q4销售额], var_name季度, value_name销售额 )做完后df_long的行数从4行变成16行每行对应某个订单某个季度。这种长表形态拿来画折线图非常顺手直接用seaborn的lineplotx轴放季度y轴放销售额hue放销售区域一行代码就能出图。顺带提一个经验实际业务表里的季度列可能有中文名比如第一季度第二季度melt同样处理只是var_name的值会带上中文。绘图前如果觉得名字太长可以用replace把季度名重映射成Q1Q2这种短格式。4.3 第二步pivot_table把长表聚成透视宽表现在长表有了假设我需要出一张汇总表行是销售区域列是季度单元格是销售额合计。直接从df_long出发pt_summary df_long.pivot_table( index销售区域, columns季度, values销售额, aggfuncsum, marginsTrue, margins_name总计 )这里指定了聚合方式是sum同时加了margins所以结果里会多出每一行的合计列和每一列的合计行。如果不需要合计把marginsFalse去掉即可。我建议正式输出给业务方看之前加一个fill_value0避免某些区域在某季度没有订单时单元格是NaN显示成空值容易让人误会是数据缺失。pivot_table做完后的结果是一个带层级列索引的DataFrame列索引是MultiIndex最外层是季度。如果你只需要其中一个季度的数据可以用pt_summary[Q1销售额]直接取列也可以先把列索引降下来再操作。4.4 第三步stack / unstack自由切换接着上面的pt_summary我可以使用unstack把列索引转成行索引把这张透视宽表变回带多级索引的长表df_stacked pt_summary.stack()这一步会把季度从列索引搬到行索引结果是多级索引结构第一级是销售区域第二级是季度。如果你要的是DataFrame而不是Series记得加.reset_index()把索引落回普通列。反过来如果我从df_long出发想直接做一张区域x季度的透视表但又更习惯索引操作可以先用set_index把要作为行和列的字段放好再unstackdf_freq df_long.set_index([销售区域, 季度])[销售额] df_wide df_freq.unstack(level季度)这里unstack(level季度)能把季度从行索引搬到列索引。level参数是用来指定把哪一级索引搬到列上去的关键多级索引场景下非常有用。很多人一开始容易写错其实只要记住unstack是把行索引搬到列stack是把列索引搬到行方向别搞反就成功了一半。4.5 综合一张报表的多形态切换把上面几步连起来你就能应对绝大多数日常报表需求。完整流程是这样的原始宽表 → melt → 长表适合聚合、过滤、清洗、绘图长表 → pivot_table → 透视宽表适合展示、横向比较透视宽表 → stack → 长表适合二次分析或落库我处理过的一个实际需求是业务方给了一张每个城市多个指标的多列宽表我需要按月份做同比环比分析。直接拿宽表算环比非常麻烦因为环比要求上个月的数据和这个月的数据在同一个计算上下文里。我先用melt把月度列拉长再用pivot_table重建一张城市x月份的表整个过程不超过10行代码而且不用写任何循环。这样一套组合拳下来你会发现pandas的变形工具根本不需要死记硬背你只需要想清楚目标形态是什么然后从三个工具里选方向正确的那个就行。5. 常见报错与避坑速查手册5.1 pivot报 Index contains duplicate entries这是pivot最常见的翻车点根源在于源数据里同一个(行索引, 列索引)组合出现了多条记录。pivot不支持聚合它看到多个值不知道选哪个直接抛错。解决办法有几种如果数据结构允许重复直接换pivot_table并指定aggfunc如果重复记录里确实存在主键逻辑可以先drop_duplicates()再去重如果你想先看看哪些组合重复了可以用df.duplicated(subset[列A, 列B])排查。我在实际中基本默认用pivot_table除非确认数据在指定索引和列组合下唯一。pivot的报错虽然不算难解决但每次都要停下来处理挺打断思路的。5.2 melt之后的数据类型和列名问题melt本身很少报错但有个容易踩坑的地方如果被熔的几列数据类型不一致melt会把结果列自动变成object。比如一列是整数一列是字符串melt后值列就混成object了后续做sum或mean会失败或者结果不对。另一个坑是列名带中文或空格。用melt或pivot时如果列名是中文直接用变量名引用的代码没问题但如果从Excel读进来的列名带特殊字符比如销售额(万元)在传参时记得用字符串形式包好或者先df.columns [c.strip() for c in df.columns]清洗一遍。还有一点要注意melt不传value_vars时会把id_vars之外的所有列都熔掉。如果你源表里有些辅助列不想被熔一定记得显式列出来。我吃过一次亏某次没写value_vars结果把备注列也给熔进去了后面排查了半天才发现是多余列进入了melt。5.3 unstack和stack的索引错乱unstack/stack操作多级索引时最常出现的问题是层级顺序和预期不一致。比如level参数没有指定清楚结果里索引的顺序变来变去后续reset_index之后列名又多出几层。解决办法是在做set_index之前先理清楚哪些字段要留在索引里、哪些要在列上。用level参数时可以传入整数或者层级名称推荐传名称因为多级索引的层级顺序一变位置整数容易对不上。另一个建议是做完stack/unstack之后不要急着reset_index先用df.index.names看一眼层级名是否正常。如果层级名丢了后面取数会非常别扭。5.4 透视表结果里的NaNpivot_table生成的结果中如果某个组合没有数据默认是NaN。很多人以为数据出了问题其实只是不存在和缺失被设计成了同一个表示。处理方式前面提过加fill_value0或者用pt_summary.fillna(0, inplaceTrue)。但要注意fill_value0只影响结果中因为组合缺失而产生的空值对原本就是NaN的真实缺失值也会填充所以填0之前想好业务含义。如果你要保留缺失状态做进一步分析就别急着填0。5.5 性能优化的一点心得大数据量下做大范围变形时要留意性能。pivot_table和melt在千万行级别的数据上运行速度一般还行但如果做非常复杂的多重pivot内存占用会涨得很快。我的经验是能用groupby agg解决的先聚合把行数降下来再做pivot或者melt。比如先按城市和月份聚合出总量再pivot成城市x月份的宽表比直接拿几百万行明细做pivot快很多。另外pandas 2.x版本里某些操作的底层实现和1.x略有差异但常用的这几招pivot/melt/stack没有本质变化放心用。6. 变形的进阶用法与扩展建议6.1 列名本身就是信息用MultiIndex做批量变形当你的表里列名是类似(2024, Q1)这种结构时可以先把列索引转换为MultiIndex再利用stack按层级展开。这个技巧在从Excel读取多层表头的数据时特别有用。df_multi pd.DataFrame( [[1, 2, 3, 4], [5, 6, 7, 8]], columnspd.MultiIndex.from_product([[2023, 2024], [Q1, Q2]]) ) df_multi.stack(level1)这样能把年度和季度拆开从宽表头变成长表。类似的场景还包括每个月一列的数据列名格式是2024-01这种字符串你可以先pd.to_datetime(df.columns, format%Y-%m)解析后再做变形。6.2 把变形当成数据清洗的前置步骤很多人习惯拿到数据先做清洗再做变形但实际操作里变形本身也可以是清洗的手段。比如一列里混了多个指标值时先用melt拆成长表再按指标名分别处理再比如某些列在宽表里无法做类型转换但拆成长表后因为变成了同一列类型处理就变得统一了。这种先变形再清洗的思路在杂乱的非标准报表里非常管用。6.3 自动化报表里的变形模板如果你经常跑同类型的报表可以把变形步骤封装成一个小函数。我个人的做法是参数化id_vars和value_vars这样每次只需要传入列名配置就能复用一个melt pivot_table流程。数据字段一变只需改配置不用改逻辑。def reshape_report(df, id_cols, value_cols, var_name变量, value_name数值): long_df df.melt(id_varsid_cols, value_varsvalue_cols, var_namevar_name, value_namevalue_name) return long_df这种封装带来的价值是报表需求再多核心代码只有一套维护成本大幅降低。6.4 多表合并时的变形配合最后说一个很常见的实战经验在合并多张表之前先统一它们的形态。比如你手里有A表的宽表版本和B表的长表版本如果不先统一形态就合并很容易因为索引层级不一致导致数据错乱。我通常的做法是先把所有表都melt成统一的长表格式包含主体标识、变量名、值这三列再用concat合并。这样既规范又不需要为每张表单独写合并逻辑。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询