ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用Python+Pandas清洗电商订单数据:从杂乱表格到可视化分析

用Python+Pandas清洗电商订单数据:从杂乱表格到可视化分析 “ch4_2” 是我电脑里一个项目文件夹的名字也是我最近在整理的一套数据分析实操项目的第四章第二节。如果你打开这个目录会看到一堆 .py、.ipynb 和 .csv 文件但它们其实对应一个完整的小项目用 Python 对一份电商订单数据进行清洗、聚合分析并产出可视化图表和结论。这节内容解决的核心问题就是“拿到一份乱糟糟的表格怎么把它变成能讲故事、能支撑决策的数据结果”。如果你正在学 Pandas或者工作中经常要处理 Excel 导出的脏数据这篇记录应该能给你一些能直接抄作业的思路。这个项目本身没什么高深算法但每一步都是实际干活时躲不开的细节比如日期格式不统一、金额字段混着单位、同一个用户重复下单怎么算复购这些琐碎问题才是数据分析里真正耗时间的地方。ch4_2 这个编号对应我整理的一套实战课程的第四章第二节前几章分别是环境搭建、Pandas 基础和数据可视化入门而这一节就是把前面那些零散知识点串起来做一次完整的数据处理演练。下面我按实际的推进顺序把设计和踩坑过程都写出来。1. 项目方向与整体设计思路1.1 为什么叫 ch4_2它在一个什么结构里先解释一下这个编号的来历。我习惯把学习项目的代码按章节归档每一章对应一类主题每一节对应一个可独立运行的小案例。ch4_2 就是第四章的第二个案例第四章的主题是“数据清洗与聚合分析”ch4_1 做的是单表数据清洗ch4_2 则把清洗和分析串成一条完整的流水线。这样命名的好处是文件多了以后依然能快速定位知识点比如想找“分组聚合”相关代码直接进 ch4 目录翻就行不用靠记忆去猜哪个文件名对应什么内容。整个项目的工作目标也很简单拿到一份模拟的电商订单明细表里面有用户 ID、下单日期、商品类目、销售额、所在城市等字段但数据质量比较差。需要做三件事第一把数据清洗到可以直接分析的状态第二按时间、城市、类目等维度做聚合统计算出月度销售额、城市贡献、品类占比、复购率这些核心指标第三用图表把结果呈现出来形成一份简短的分析小结。这其实也对应了实际工作中最常见的需求业务方丢给你一张导出表问你“这个月哪个城市卖得最好”“复购用户占多少”你不能直接拿原始表去回答必须先理解字段、处理脏数据、再按业务口径计算。ch4_2 的价值就是把这条链路完完整整走一遍。1.2 技术方案选型为什么是 Python Pandas市面上能做数据清洗的工具很多Excel 可以、SQL 也可以但我最终选择 Python Pandas有几个很实际的理由。首先是字段处理的灵活性Excel 处理几万行数据时就容易卡顿而且在做“按用户 ID 去重后统计复购”这类逻辑时公式写起来非常绕。SQL 虽然擅长聚合但前提是数据已经在数据库里而实际工作中我们拿到的往往是 CSV 或 Excel 文件还得先导入调试成本不低。Pandas 正好卡在中间它的 DataFrame 结构对表格操作非常直观一行代码能完成筛选、去重、分组、合并它不要求数据必须放在数据库里本地文件直接读入就能开工。另外从学习和复用的角度看Pandas 的代码写一次就能长期使用下次拿到相同结构的数据只要改一下文件路径其他逻辑基本不用动这种可复用性很适合做个人分析工具箱。我选的 Python 版本是 3.10Pandas 版本 1.5.3Matplotlib 3.6.2Seaborn 0.12.2。这些版本搭配比较稳定不建议一上来就追最新版有些 API 变化会导致老代码报错。如果你是新手直接装 Anaconda里面的依赖基本是配好的能少踩很多坑。1.3 项目目录与处理流程设计在动手写代码前我先规划了一个清晰的目录结构避免所有代码堆在一个文件里。ch4_2 文件夹下分成了 data、scripts、output 三个子目录data 存放原始数据和清洗后的结果scripts 按步骤拆分了三个脚本output 放图表和分析小结。这样的好处是运行完一轮之后原始数据、中间产物、最终成果各归其位复盘时不会手忙脚乱。整个处理流程我分成了五个阶段数据加载与初步探查、数据清洗、指标计算、可视化输出、结论整理。这五个阶段不是随手拍的而是根据数据分析的基本逻辑来的——先要搞清楚“我手里有什么”再解决“数据能不能用来分析”然后是“能算什么”最后才是“怎么展示、怎么说清楚”。很多人一上来就画图结果画的都是脏数据做出来的结论自然站不住脚。流程上还有一个细节每个阶段我都会单独设一个检查点比如清洗完先 df.info() 和 df.describe() 看一眼再进下一步。数据工作最怕的就是闷头跑完一条流水线最后发现源头就错了所以宁可多花几次打印的时间也不要跳步。2. 数据加载与清洗实操2.1 原始数据结构与字段说明项目用的是一份模拟电商订单数据一共 8932 条记录13 个字段。字段包括订单编号、下单时间、用户 ID、用户城市、商品类目、商品名称、单价、数量、实付金额、支付方式、订单状态、收货地址、备注。这份数据的生成逻辑参考了真实电商平台的订单结构所以各种“意外”都非常典型。刚拿到数据时我先用 df.head() 和 df.info() 做初步探查发现了几个明显问题下单时间字段里既有“2024-03-15 13:22:45”这样的标准格式也有“2024/3/5”这种只有日期的写法实付金额里有 32 条记录的值是“-1”明显是异常占位用户城市字段存在“上海市”和“上海”两种写法备注字段全是空值基本没有分析价值还有 11 条订单出现了完全重复的记录。这些还不是全部问题更多细节是在清洗过程中逐渐暴露的。这类脏数据在实际工作中很常见因为它们来自不同业务系统、不同时期的录入规范差异。真正做项目的时候第一步不是急着清洗而是先建立“字段字典”把每个字段的含义、类型、取值范围、是否有空值记录下来。这一步虽然费一点时间但后面写聚合逻辑时会轻松很多因为你需要依赖这些信息去判断一个结果是不是合理。2.2 清洗规则与背后的判断依据我把清洗拆成四个动作去重、缺失处理、类型统一、异常值修正。每个动作都不是机械执行而是要回答“为什么这么做”。首先是去重。我用的判断标准是“订单编号 实付金额”两个字段同时相同才算重复因为正常情况下同一个订单号可能出现多次支付记录但金额不同代表的是不同事件。判断出来 11 条重复后直接保留第一条、删掉其余。这里要特别提醒不要用整行数据做去重因为备注、收货地址这类字段只要有细微差异整行去重就会失效。其次是缺失值处理。备注字段接近 100% 空值我直接选择删掉这一列因为即使补全也没有业务意义。下单时间字段有 7 条记录缺失但订单编号和用户 ID 都存在所以我根据同一用户其他订单的平均下单间隔做了一次估算填充。说实话这种填充方式在严谨的分析场景里有些冒险但对于教学项目来说它能展示“缺失值可以有多种处理策略”而不是只用 dropna 或者 fillna 这一个动作。第三是类型统一。我写了一个自定义函数把日期格式统一成“YYYY-MM-DD”同时把实付金额从字符串转成浮点数。这里有一个容易踩的坑直接用 pd.to_datetime 处理混合格式时Pandas 有可能把“2024/3/5”解析成 2024 年 3 月 5 日也可能因为格式歧义产生错误所以先规范化再转换会更稳。字符串转数字时我还顺手去掉了金额里的“”和空格因为原始数据里这两类符号都存在。最后是异常值修正。实付金额为 -1 的 32 条记录我核对了对应的单价和数量发现用“单价 × 数量 × 折扣”可以还原出正确金额于是做了修正而不是删除。这个思路很关键异常值不一定要删先尝试还原业务口径。城市字段统一成去掉“市”后缀的标准名称这样后面做分组时“上海”和“上海市”才能合并到一起。2.3 关键清洗代码示例与运行结果加载数据的代码很简单重点是 read_csv 里要指定 encoding 参数否则 Windows 环境下很容易出现中文乱码。我用的编码是“utf-8”如果你的源文件是 Excel 另存的 CSV可能需要改成“gbk”或“gb18030”这个在排查篇里会单独讲。import pandas as pd import numpy as np raw_df pd.read_csv(data/raw_orders.csv, encodingutf-8-sig) print(raw_df.shape) print(raw_df.info())去重和列删除是两步基础操作df raw_df.drop_duplicates(subset[订单编号, 实付金额], keepfirst) df df.drop(columns[备注])日期清洗函数我建议大家直接收藏因为混合日期格式这个问题太常见了。下面这个函数把常见的几种分隔符都做了处理def clean_date(s): if pd.isnull(s): return np.nan s str(s).strip() s s.replace(/, -).replace(年, -).replace(月, -).replace(日, ) # 有的记录只有日期没有时间统一补上 00:00:00 if len(s) 19: s s 00:00:00 return pd.to_datetime(s, errorscoerce)跑完清洗后我用一条 assertion 做校验确保核心字段没有空值assert df[下单时间].notnull().all() assert df[实付金额].notnull().all()清洗后的数据从 8932 条变成了 8914 条字段从 13 个变成 12 个整体量级变化不大但每一行数据的质量都可靠了。这个时候再往下做聚合我心里是有底的。实际项目中我还喜欢多打印一次清洗前后各字段的空值数量对比放在代码注释里方便后面回溯。3. 多维度聚合与核心指标计算3.1 分析问题定义与拆解清洗完成之后接下来要回答几个具体的业务问题整体销售趋势是怎样的哪个月卖得最好哪些城市贡献了绝大部分销售额不同商品类目的销量结构如何用户复购情况怎么样。这些问题不是随手列的而是我在设计项目时根据“时间、地域、品类、用户”四个经典分析维度拆出来的。任何一份电商数据基本都能从这四个角度找到突破口。每个问题都对应不同的聚合方式和业务口径。比如“月度销售额”口径是“按订单的下单时间月份分组对实付金额求和”“城市销售额”则是“按城市分组求和后排序”。“复购率”稍微复杂一些要先找出每个用户的下单次数再统计下单次数大于等于 2 的用户占比。这里有一个容易搞错的点复购率的分母应该是“有过成功下单行为的用户总数”而不是“订单总数”这两个口径算出来的数字差别非常大。我在代码里用注释把这些口径写在对应的聚合语句上方。这样做不仅是给自己留记录更重要的是让看代码的人知道“这个数字是怎么算出来的”。数据工作很多时候不是算法难而是口径不一致同一个指标两个人算出来不同基本都能追溯到口径定义上。3.2 groupby 聚合的实操细节Pandas 的 groupby 用起来很顺手但有一些细节会影响结果准确性。第一个细节是分组前要把用于分组的字段转换成合适的类型比如“月份”最好单独提取成一个新的字符串列而不是在 groupby 里对时间做复杂处理。我新增了df[月份] df[下单时间].dt.strftime(%Y-%m)这样后面按月份分组时就非常清爽。月度销售额的代码如下monthly_sales ( df.groupby(月份)[实付金额] .sum() .reset_index() .sort_values(月份) )注意这里用了 reset_index因为 groupby 之后月份会成为索引如果不转回列后续绘图时 x 轴数据会不好取。我早期就经常忘记这一步结果图出来坐标轴莫名其妙。排序也别忘了groupby 默认按索引排序但你最好显式 sort_values避免依赖默认行为。城市维度的聚合类似但我会在聚合后计算一下占比方便判断头部城市是否有绝对主导地位city_sales df.groupby(用户城市)[实付金额].sum().reset_index() city_sales city_sales.sort_values(实付金额, ascendingFalse) city_sales[占比] city_sales[实付金额] / city_sales[实付金额].sum()计算复购率时我先生成一个“每个用户的订单数”中间 DataFrame再在这个基础上做判断user_orders df.groupby(用户ID)[订单编号].count().reset_index() user_orders.columns [用户ID, 下单次数] total_users len(user_orders) repurchase_users len(user_orders[user_orders[下单次数] 2]) repurchase_rate repurchase_users / total_users这个逻辑并不复杂但却是最容易被写错的。如果你直接在原始订单上统计“下单次数大于 1 的订单数占比”就会得到完全不同的、没有业务意义的数字。这其实就是聚合层级的问题先搞清楚你要统计的主体是“用户”还是“订单”。3.3 指标计算中的口径选择与校验指标算完之后不能直接拿去画图要先做合理性校验。我用了几条简单的经验规则比如各月销售额加起来要等于清洗后数据的实付金额总和城市占比的合计要等于 100%用户总数加上所有用户订单数得到的平均客单价应该在合理范围内。这些校验本质上是把“总和”和“各部分之和”对账适用于任何聚合分析。这里我也特别想提一下“切比雪夫距离”式的数据敏感度你在预览数据时觉得某个月销售额特别高就要回去看那个月是不是有异常大额订单。我在做月度聚合时就发现 5 月销售额明显偏高查了明细后发现有两笔金额超过 5 万元的订单占比极大。这种极端值在现实业务中可能是大客户采购也可能是录入错误需要单独判断。我最后选择保留它们但在分析小结里单独标注了“该月销售额受大额订单影响较大”这个提示。对于复购率我也做了分城市的交叉观察发现一线城市的复购率明显高于新一线而低线城市更多是单次订单。这个观察虽然只是描述性的但它展示了一个思路单一的总体指标往往不够拆分到维度里才能看出结构差异。很多时候业务方问“复购率是多少”其实更想知道“哪里高、哪里低、为什么”。4. 可视化呈现与结论输出4.1 图表选型逻辑数据算完下一步是画图。图表不是越多越好而是要为每个问题选择最合适的表达方式。月度销售额本质是随时间变化的值我用折线图能同时看出趋势和波动城市销售额是不同类别之间的比较我用横向柱状图因为城市名一多纵向柱状图的标签会挤在一起品类结构占比用饼图但价格型占比我控制了品类数量超过 8 个就把小众品类合并成“其他”避免饼图碎成一大片。这里想多说一句很多人喜欢用饼图但饼图只适合展示“部分与整体”的关系而且分类不宜过多。像“各省份销售额都在总盘子里占多大比例”这种问题饼图合适但如果是“排名前十的城市分别卖了多少”柱状图明显更好读。选图之前先问自己一句话我想让读者一眼看到什么4.2 Matplotlib 绘图代码与细节调整用 Matplotlib 画图时有两个中文字体问题需要处理否则图里的中文全是方框。第一是设置字体我用的是 SimHei第二是确保坐标轴负号正常显示。这两行配置我写了一个统一的样式脚本放在 scripts 目录下每次画图前先运行它。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False月度销售额折线图的代码大概长这样fig, ax plt.subplots(figsize(10, 5)) ax.plot(monthly_sales[月份], monthly_sales[实付金额], markero, linewidth2) ax.set_title(月度销售额趋势) ax.set_xlabel(月份) ax.set_ylabel(销售额元) plt.xticks(rotation45) plt.tight_layout() plt.savefig(output/monthly_sales.png, dpi150) plt.show()这里有个小细节xticks 旋转 45 度是为了防止月份标签叠在一起。tight_layout 可以自动调整空白边距避免标题被截掉。savefig 时设置 dpi 不低于 150这样插入文档或 PPT 时图片不会模糊。城市销售额柱状图我用的是 seaborn 的 barplot因为它自带的配色和样式会好看一些不需要额外调很多参数import seaborn as sns plt.figure(figsize(10, 6)) sns.barplot(datacity_sales.head(10), x实付金额, y用户城市, paletteBlues_d) plt.title(销售额前十城市) plt.tight_layout() plt.savefig(output/top10_city_sales.png, dpi150)在画完所有图之后我还会统一打印一次每个统计结果的前几行把数字和图表放在一起做交叉确认。这样做看似多余其实非常有价值因为图是视觉判断数字是精确判断两个人看图得出的结论可能不同但看数字不会有歧义。4.3 分析小结的整理方式项目产出不只是一堆图和代码还需要一段能直接给人看的小结。我会在 output 目录里放一个 Markdown 文件里面包含三块内容数据概况、核心结论、附注与局限。数据概况记录清洗前后记录数、字段数、时间范围核心结论对应前面拆解的四个问题每一条都用“数据 结论”的句式附注与局限则标注极端值影响、估算填充的字段等。举个例子数据概况部分写“原始数据 8932 条清洗后 8914 条时间范围 2024-01 至 2024-06”核心结论部分写“5 月销售额最高达 68.3 万元但其中两笔订单金额占比超 15%上海、北京、广州合计贡献约 42% 的销售额食品类目销量占比最高达 34%整体复购率为 23.6%其中一线城市复购率约 31%四线城市约 15%”。这种写法后续做汇报时基本可以直接复用。我还养成了一个习惯把每次清洗和分析中的关键决策记录在一个“决策日志”里哪怕只是三五行字。比如“为什么缺失时间用均值填充”“为什么保留 5 月极端大额订单”这样过了半年再回看项目你还能想起来当时的业务背景。5. 排查记录与避坑指南5.1 高频问题速查表做这类项目时我遇到的报错和奇怪结果非常多这里整理一个速查表按出现频率排序现象可能原因解决方案读取 CSV 中文乱码文件编码与 read_csv 指定编码不一致改用 encodingutf-8-sig 或 gbk也可以先打开文件看编码to_datetime 解析错误或警告日期字符串混用“/”“年月日”等格式先统一格式再用 errorscoerce最后检查 NaT 行聚合结果索引混乱groupby 后没有 reset_index统一在聚合后 reset_index养成习惯图表中文显示为方框缺少中文字体配置设置 plt.rcParams[font.sans-serif] [SimHei]聚合后的数值比预期大很多存在重复记录或异常值未处理回溯到清洗步骤检查去重逻辑和异常值判断单列求和时提示类型错误有字符串混在数字列里用 pd.to_numeric(..., errorscoerce) 做强制转换这张表后面我会持续补充遇到新问题就往上加。你会发现很多报错其实是同一类问题反复出现比如日期格式和编码处理过一次之后就熟练了。5.2 实际踩过的几个坑第一个坑是 Windows 系统下文件路径的编码问题。有一次我在脚本里写df pd.read_csv(data/raw_orders.csv)本地跑没问题但换了一台电脑后文件路径带中文直接抛 UnicodeDecodeError。后来我统一用Path模块管理路径并养成了在 read_csv 里显式写 encoding 的习惯。第二个坑是 groupby 完发现销售额比原始总和少了近十万。排查了很久发现是清洗时把“实付金额”为 -1 的异常值直接过滤掉了而后来又做了一次去重把一些真实的大额订单也误伤了。这个问题给我的教训是每一步清洗都会影响后续结果每一步操作都要谨慎并且要保存“清洗前备份”和“清洗后结果”两份数据方便做对比。第三个坑和复购率有关。我第一次算复购率时用了df[df[下单次数] 1]的订单数去除总订单数结果算出复购率只有 8%后来想起口径问题改成“用户数”做分母才变成 23.6%。数字差了将近三倍但很多初级分析都会掉进这个坑。所以每算一个指标我都会在注释里写明分子是什么、分母是什么、代表什么业务含义。第四个坑是图表保存后不完整月度折线图保存出来 x 轴最后一个月份标签被切掉一半。这是因为保存时用的默认 bbox_inches 参数。解决方法是 savefig 时加上bbox_inchestight这个参数能自动调整画布边界保证保存出来的图片完整。5.3 内存与性能优化的小技巧数据量在近万条时Pandas 跑这些操作几乎零延迟但真实工作里数据量很容易到几百万行所以从一开始就要注意性能习惯。我常用的技巧有这么几个读取时只加载需要的列用usecols参数字符串列用category类型能大幅降低内存占用尽量用向量化操作而不是 for 循环遍历行聚合时先过滤再 groupby减少参与分组的数据量。比如原始数据有 13 列但我们的分析只用其中 6 列那我就会这样读取df pd.read_csv(data/raw_orders.csv, usecols[订单编号, 下单时间, 用户ID, 用户城市, 商品类目, 实付金额], encodingutf-8-sig)这类知识在 ch4_2 的正文里没有展开讲但实际项目里非常重要。等到数据量大了再想优化就晚了那时候每跑一步都要等很久非常折磨人。6. 项目复盘与可复用资产6.1 从 ch4_2 沉淀出来的东西这个项目做完我最大的收获不是代码本身而是把一套数据分析流程固化了下来。现在拿到任何一张表我几乎不用思考就按这个流程走先探查、再清洗、然后聚合、最后可视化。这个顺序的好处是每一个阶段都有明确产出不容易乱。更实用的是我把清洗和聚合的常用代码整理成了一个自定义模块里面封装了日期清洗、编码识别、去重、异常值修正这些函数。以后遇到新项目直接import data_clean_utils就能调用不用重写一遍。这个做法非常推荐尤其是同一类业务数据会反复收集时一次封装、长期受益。我还把这次分析形成的图表和结论做成了一个 PDF 文件方便放到个人作品集里。做数据相关岗位的朋友都知道作品集里放“完整的数据分析项目”比放零散的代码更有说服力因为它展示的是你从拿到脏数据到形成结论的完整能力。6.2 如何扩展这个项目如果你想在这个基础上继续深入有几个方向可以参考。第一个方向是增加预测利用前六个月的数据训练一个简单的回归或时间序列模型预测后三个月的销售额这能把项目从“描述性分析”升级成“预测性分析”。第二个方向是增加用户分层用 RFM 模型把用户分成高价值、流失风险、新客等群体这会引入更多业务分析思维。第三个方向是换一份真实数据比如一些开源平台上的电商数据集数据量更大、噪声更多能进一步检验你的清洗逻辑是否健壮。我自己的经验是做完一个项目后不要急着做下一个先花一天时间把这些扩展空间想一遍哪怕只做其中一个方向都比重复做十个同类型项目更有成长。最后再分享一个小技巧数据分析项目里的“过程文档”和最终结果一样重要。我会在 scripts 目录里留下一个 README记录每段脚本的运行顺序、数据路径、预期产出物。这样三个月后想重新跑一遍照着 README 点一遍就能复现不会因为忘了某一步而卡住。ch4_2 现在对我来说就是一个能随时复现、能随手扩展、也能拿去展示的完整样例。
返回列表