资讯动态

Pandas实战复盘:从数据清洗到可视化的完整分析链路

发布时间:2026/10/17 14:27:31 来源:尧图企业网站定制
看到“Pandas第三次作业20200907”这个标题的时候我大概愣了两秒钟——2020年9月7号恰好是我带的一期数据分析入门训练营布置第三次Pandas作业的时间节点。第三次作业是一个很特殊的存在前两次还在练Series和DataFrame的基础操作到了第三次突然就进入了“拿到一份真实数据从清洗到聚合再到可视化”的完整链路。很多学员就是在这份作业里第一次体会到“会写Pandas语句”和“会用Pandas解决问题”之间的鸿沟。这篇文章我就以这份第三次作业为线索完整复盘一遍我当时设计这道题目的思路、学员最容易踩的坑、以及我自己重做一遍时的技术取舍。题目不是凭空编的核心是一份模拟的零售订单数据要求完成清洗、分组聚合、时间序列分析、多表关联和可视化输出。如果你正在学Pandas卡在“语法都会、做题就懵”的阶段或者准备自己找数据集练习这篇文章应该能帮你把Pandas的知识点串成一条能跑的流水线。1. 第三次作业在Pandas学习坐标系里的真实位置1.1 为什么前面的基础题和后面的综合题之间突然断层大部分Pandas教程的前两章都是这么安排的先讲Series和DataFrame的构造、索引、切片、布尔筛选再讲缺失值处理、apply函数、merge合并。学员跟着敲一遍感觉“都懂了”但一拿到新数据就发现完全无从下手。这不是学习态度的问题是知识组织方式的问题。前面的练习是“语法驱动的”每个题目都已经告诉你“请用groupby统计各类别的销售额”你只需要找到对应的API。而真实的任务是“需求驱动的”只给你一张表问“这个季度哪些门店的毛利率在下滑”你得自己决定先转日期格式、再清洗异常值、然后决定按周还是按月聚合、最后还可能要接一个可视化来看趋势。我设计第三次作业的初衷就是要在这个断层上架一座桥。整份作业包含六大块数据集探索、数据清洗、分组聚合、时间序列、多表连接、可视化输出。每一块都不是孤立的前一步的产物往往是后一步的输入。1.2 这道题的能力矩阵如果把这第三次作业的考查点拆开大概是下面这张能力清单能力维度具体考查点对应Pandas核心功能数据感知能从原始数据里发现问题字段info()、describe()、nunique()、isna()清洗能力处理乱码、前后缀空格、错误数据类型strip()、astype()、to_numeric()聚合分析多维度统计、汇总口径选择groupby、agg、pivot_table时间处理日期解析、重采样、变化率计算to_datetime、resample、shift数据整合多表合并、关联键确认merge、concat结果表达图表能辅助结论、不产生误导plot、matplotlib配置对照这张表你可以先给自己打个分。如果某项明显偏弱后面那项就是你的重点。比如很多人已经熟练掌握了groupby但一问到resample就发怵那这道题的时间序列部分就是为你准备的。2. 拿到数据先别急着groupby数据清洗才是这题的大头2.1 原始数据的真实面貌作业使用的模拟数据集是某连锁零售企业这里用“模拟项目X”代称2020年前三季度的订单流水包含约两万行记录。字段如下字段名类型含义初看时的问题order_id字符串订单编号部分含首尾空格order_date字符串下单日期存的是“2020/09/07 14:32”格式store_id字符串门店编号部分门店编码大小写混用region字符串大区少量缺失category字符串商品类目含前后空格sales浮点销售额偶尔出现负数和极端值cost浮点成本与sales不同步缺失customer_id字符串会员ID非会员为空如果直接读进来就开始groupby不夸张地说结果里至少会有以下问题category列的“数码”和“数码 ”会被算成两个类目order_date是字符串没法按月份重采样sales里有负数会把销售额拉低region缺了三百多行分组时会整行丢掉。2.2 清洗的标准动作与判断逻辑真实的清洗流程不是背出来的是“先体检、再下药”。第一步永远是调用df.info()和df.head()先把数据长什么样看清楚。第二步检查每个字段的类型和缺失情况第三步针对问题字段逐一下手。以这份作业为例清洗代码大致是这样一个标准化流程import pandas as pd import numpy as np df pd.read_csv(sales_data_raw.csv) print(df.info()) print(df.isna().sum()) print(f重复订单数: {df.duplicated(subsetorder_id).sum()}) # 清理前后空格和大小写不一致 df[category] df[category].str.strip() df[store_id] df[store_id].str.strip().str.upper() df[order_date] pd.to_datetime(df[order_date]) # 处理异常销售值销售额不应为负 df df[df[sales] 0] print(df[sales].describe())有几个判断逻辑值得说透。第一类别型字段的strip是必须的没有例外。任何从Excel人工录入迁移过来的数据字符串列里藏首尾空格几乎是必然事件。第二清洗时保留原始列还是覆盖原始列我通常建议保留一个原始副本比如复制一列df[sales_raw]留着核验等确认无误再删。第三缺失值不一定都要删除比如region缺失可以按已知门店映射补全但货真价实缺少的成本数据只能删除对应行因为你没法自己编一个成本出来。这里想提醒一个小细节处理负数销售额时题目特意埋了一个“退货订单”的概念。某些退款记录的sales本来就应该是负数直接一刀切删掉其实丢掉了有效信息。更合理的做法是先加一列flag标记是否为退货单再在后续统计时选择剔除或单独分析。我自己批改作业时会特别留意有没有学员发现这一步。2.3 别把类型转换想得太简单这题里还有一个很经典的隐形坑order_date列是“2020/09/07 14:32”这种字符串直接用pd.to_datetime是可以的但如果数据里混进“2020-9-7 14:32”或者“20200907”这种格式解析就会出现两种结果要么强行解析成NaT要么被错误解析成别的含义。批量数据里格式不规范太常见了所以更稳妥的做法是先用errorscoerce转一遍把NaT的行单独打印出来看看df[order_date] pd.to_datetime(df[order_date], errorscoerce) bad_dates df[df[order_date].isna()] print(bad_dates)如果坏数据不多可以用正则先把各种分隔符统一成“-”再解析。这样做的意义在于告诉你的后续步骤终表中的日期字段必须保证全部是datetime64类型否则后面所有resample操作都会失效。3. 分组聚合与透视表把“会写语句”变成“会做分析”3.1 groupby的三种写法你至少得熟练两种分组聚合是Pandas的看家本领但我批作业时发现一个很有意思的现象大约四成学员只会df.groupby(category).agg({sales: sum})这一种写法换成需要按两列分组、输出不同统计量时就开始蒙。实际工作中groupby至少有三种高频形态。第一种是单列分组加单函数也就是最常见的“求和”第二种是单列分组加多函数比如同时求销售额总和、订单数和平均客单价第三种是多列分组再组合自定义函数。第三种能极大提升工作效率。来看一段完整示例summary df.groupby([region, category]).agg( 总销售额(sales, sum), 总成本(cost, sum), 订单数(order_id, count), 平均客单价(sales, mean), 毛利额(sales, lambda x: x.sum() - df.loc[x.index, cost].sum()) ).reset_index()注意一下agg里我用了“新列名(列名, 函数)”的写法这是新版Pandas推荐的Named Aggregation比老式的{sales: sum}可读性高很多。lambda里通过x.index去关联同一行的cost这样能跨列计算这是很多教程里不会提到但实战经常用到的技巧。3.2 从聚合结果到透视表什么时候该用pivot_table有些人习惯把所有分组需求都写成groupby另一些人则不管三七二十一直接用pivot_table。这两种工具本质是同一件事的两种打开方式但观感差别很大。用pivot_table最大的价值是结果天然形成矩阵结构适合“一眼扫过去找规律”pivot pd.pivot_table( df, valuessales, indexregion, columnscategory, aggfuncsum, marginsTrue, margins_name合计 )这份作业里如果题目要求“按大区看各类目的销售贡献”用透视表会比groupby更合适。因为行是region、列是category交叉处的数值就是销售额marginsTrue还能自动加上行合计和列合计非常直观。实战建议是当你的输出要被别人“看”而不是被程序“取”时优先pivot_table当前序结果要接着做计算时优先groupby加reset_index。因为pivot_table天然带有层次化索引不reset_index的话后续join很容易踩“index对不上”的坑。3.3 多级分组后的unstack操作再往前一步。很多人的分组结果是以“多级索引”形式存在的比如groupby([region, category])之后索引有两层。这时候如果想画个堆叠柱状图通常需要unstack()把内层索引变成列。环节本身不复杂但顺序决定了成败如果希望在unstack后按销售额排序要在unstack之前先sort否则列顺序是乱的。region_cat df.groupby([region, category])[sales].sum().sort_values(ascendingFalse) pivot_table region_cat.unstack()踩过这个坑之后我每次都会在代码前加一句注释先排序再unstack。这种小顺序问题在交付时特别影响观感——列排序乱七八糟的图表数据再正确也会显得不专业。4. 时间序列与重采样把“20200907”这个日期真正用起来4.1 为什么日期列是字符串时很多高级分析都做不了第三次作业的标题里带了“20200907”这其实是我故意设计的线索——当学习者的Pandas训练进入第三周时间处理就必须正式提上日程。日期列如果一直是字符串你只能做到“按某一天筛选”这种低级操作。但一旦转成datetime64类型你就拥有了按周、按月、按季度聚合的能力也才能算出环比、同比、客流高峰时段。时间序列处理有一个极其关键但很多人忽视的前提先设置索引。resample操作只对索引生效或者显式指定on参数。我见过太多人对着一个日期列但不是索引的DataFrame调用df.resample(M)然后报错TypeError。正确做法是这样df[order_date] pd.to_datetime(df[order_date]) df.set_index(order_date, inplaceTrue) monthly_sales df[sales].resample(M).sum() weekly_sales df[sales].resample(W).sum() # 按月环比本期/上期 - 1 monthly_growth monthly_sales.pct_change()4.2 重采样里的常见陷阱重采样看着只有一行代码坑其实不少。首先是频率代码M是月末MS是月初两者差一天在月内数据上区别不大但做边界对齐时会导致索引标签不同后续merge时莫名其妙多出NA值。比较稳妥的办法是统一用MS月初或直接ME月末并始终保持同一口径。其次是处在时段的统计口径问题。比如“9月销售额”到底是9月1日到9月30日还是自然周周一至周日这两个口径的分析结论可能完全不同。作业里没有明确说明时我在参考答案里会写清楚用自然月“MS-M”口径并且上一列注释说明“口径说明自然月非工作日对齐”。一句话的标注在真实项目里能省去很多沟通成本。4.3 滑动窗口怎么选窗口大小时间序列题里我常常设置一个小问算销售额的7日滑动平均观察趋势是否比原始序列更平滑。滑动窗口的核心参数是window大小但window本身没有绝对正确答案。对零售数据来说7天对应自然周一个月按4周处理就是28天。如果业务周期明显是“周末高、工作日低”7日窗口能消除周内波动让你看到真正的中期趋势。monthly_sales daily_sales.resample(D).sum() monthly_sales[MA7] monthly_sales[sales].rolling(window7).mean()这个例子里我想特别说明一个常见操作误区有人把resample直接接rolling比如resample(W).sum().rolling(4).mean()得到的是“4周聚合值的均值”跟“以天为单位算7日均值”不是一回事。前者是压缩时间窗后用周数据再平滑后者是保留日粒度但弱化噪声。两条思路都对但对应的问题不一样——做周报时用前者做日趋势判断时用后者。作业里会明确要求做日粒度趋势平滑所以正确路径是resample(D)后rolling(7)。5. 多表关联与文档化作业之外最有用的两个习惯5.1 从三条数据表的拼接看merge的核心要点第三次作业不只给了一张订单表还附带了两张小表门店信息表store_id到门店名称、城市、开业日期的映射以及商品类目表category到类目标签、是否主力品类的映射。要完成“不同城市、不同品类”的分析就必须把三张表关联起来。merge的关键点在“连接键”上。写join之前先做两件事确认键在两个表里的名字是否一致确认键的类型是否一致。这两个检查能挡掉90%的关联错误。store_info pd.read_csv(store_info.csv) merged pd.merge(df, store_info, onstore_id, howleft) print(merged.shape, df.shape)这里howleft是为了保留订单表全部行。实际批改作业时最常见的错误是误用了howinner结果少了大量没有匹配到门店信息的订单行学员却浑然不知。正确姿势是merge完立刻核对行数如果行数和原表不一致说明数据质量有问题或连接键有重复值绝不能直接往下分析。5.2 concat的大坑索引对齐不只是小问题另一道关联题是让学员把7月、8月、9月三个月的数据分别读入再用pd.concat合并成季度数据。这道题考查的其实是concat在默认情况下的索引行为。q3 pd.concat([df_jul, df_aug, df_sep], ignore_indexTrue) print(q3.shape)如果不写ignore_indexTrueconcat后会保留各子表原来的索引导致结果里有大量重复的行号。这个问题在处理时候没什么技术难度但会在后续做布尔筛选时产生“索引错位”。我的习惯是合并之后马上reset_index(dropTrue)让表重新成为一张干净的、索引连续的DataFrame。5.3 用代码注释和说明文档保护三个小时后的自己有个现象很有意思学员做作业时两个小时后回头看自己的代码往往已经不记得某一步在干什么了。所以在第三次作业的参考答案里我一直强调注释习惯——不是每行都写而是每周写一段“这里为什么这么做”的注释。另一个更实用的习惯是每个输出csv都带上encodingutf-8-sig否则用Excel打开中文会乱码。这个细节opposite有些学员在提交作业时发现输出文件“中文全变问号”其实就是编码问题。统一用utf-8-sig能保平安。6. 最后一步用matplotlib把分析结果画出来6.1 Pandas内置绘图和matplotlib怎么配合第三次作业的压轴题是画出“各区域月度销售额趋势对比图”和“各类目销售额占比饼图”。不少同学一上来就import matplotlib.pyplot as plt然后手动写循环画线这当然可以。但更快的路径是直接调用DataFrame的plot方法因为Pandas已经帮我们封装好大多数场景monthly_region df.groupby([region, pd.Grouper(freqM)])[sales].sum().unstack(level0) monthly_region.plot(kindline, figsize(10, 6), markero, title各区域月度销售额趋势)这里的pd.Grouper(freqM)是和groupby联用的时间分组神器它能在分组聚合的同时完成按月重采样。pandas内置plot帮你自动生成了图例和坐标轴比手工一行行plot要利落得多。但如果你要精细控制每根线的颜色、线宽那还是老老实实plt.plot()。6.2 中文字体问题的标准解法只要用matplotlib画图中文大概率会显示成方框。这个问题Python升级了好几个版本都没有彻底解决原因也很简单——matplotlib的默认字体不包含中文。可靠的解法有两行代码plt.rcParams[font.sans-serif] [SimHei] # 或你系统中已有的中文字体 plt.rcParams[axes.unicode_minus] False第二行是为了正常显示负号很多人忘了它坐标轴上的“-1”会变成一个奇怪的竖杠。如果你用的是Mac系统把SimHei改成PingFang SC即可。字体设置在绘图代码的全局位置执行一次就够了。6.3 图表的自检先看图再说结论比画图技术更重要的是读图能力。批作业时我会看学员写下的结论是否与图表一致。一个经常翻车的场景是饼图算出的占比文字却写成了“数码类贡献了最大的销售额”可图上明明服饰类占比更高。这不是数据分析问题而是态度问题这种错误一票否决。画完图之后我强烈建议输出一张清单来做图表自检标题是否能说明变量和期间坐标轴是否标明了数值单位元还是万元图例是否完整且不与网格线重叠结论是否只针对图中可见范围没有做过度外推。这四点检查一遍图表基本就能直接放进汇报材料里了。7. 交作业前的自检清单与复盘心得7.1 这份作业最容易翻车的六个检查点每次我批完第三次作业都会汇总一次学员的高频错误。下面这六条基本覆盖了“看起来做了但没做对”的典型问题可以直接当提交前的自检清单用自检点错误现象修复方法行数是否保持一致合并后行数少于原表检查merge的how参数优先left吗日期是否全部为datetime64用resample报错to_datetime errorscoerce类目/门店是否统一大小写与空格分组后出现重复相近类目strip upper/lower汇总口径是否明确周报、月报结果对不上统一M/MS并写注释说明聚合后索引是否重置表格有冗余层级索引reset_index(dropTrue)中文图表是否用的是utf-8-sig编码Excel打开乱码to_csv(encodingutf-8-sig)7.2 我重做一遍之后的一些实在想法我后来在给下一届学员讲这道题时自己也完整重做了一遍。当时的感觉是这些知识点分开看都很简单但组合在一起之后整个流程对耐心和细致程度的考验远超对代码能力的考验。一个人能把这套流程顺畅跑下来其实已经具备解决那些零散日常数据问题的基本盘了。还有一点值得展开说一说的是对“答案”这件事本身的认知。十个人做同一份作业很可能做出十种结果——因为清洗时选择保留还是剔除异常值不同、聚合时选择“月”还是“自然月”不同、可视化时选择折线还是柱状图也不同。这种“没有唯一正确答案”的体验在课堂练习里很少出现但进入真实工作之后这恰恰才是常态。第三次作业设计得再巧妙最终训练的也不是某个函数的具体用法而是你面对一堆不听话的数据时如何层层递进地把它理顺、看懂并表达出来的整套思路。如果你正准备做一份类似的Pandas练习我会建议你不要一上来就追求“一步到位”的最优代码而是先把脏数据摸清楚、把口径想明白之后再谈性能优化和代码精简。我在复盘自己的学习路径时最深的体会是Pandas的上手速度跟你清理脏数据的耐心程度成正比而这份耐心的转折点往往就是某一次认真做下来的综合练习。希望这篇复盘能帮你少走一段弯路。

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

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

免费获取报价 →
↑