周一上午,运营同事在群里丢了一份Excel:"这是我们过去一年的订单明细,你帮我看看到底哪儿涨哪儿跌。"这类需求在电商公司实在太常见了,看起来只是"算个数",但真正用Python数据分析把这件事做扎实,要经历数据清洗、口径确认、指标拆解、可视化输出好几个阶段。我当时把一份六万多行的订单流水,从乱糟糟的原始表整理成管理层能直接看结论的报表,也顺手沉淀了一套可以反复复用的分析流程。这篇文章就完整记录整个项目的思路、代码和踩过的坑,适合刚接触Python数据分析、想拿真实项目练手的朋友。
这次分析使用的是一份电商平台订单流水表,覆盖2023年7月到2024年6月,字段包含订单编号、下单时间、支付时间、商品信息、销售额、成本、数量、用户ID、收货省份/城市、支付方式等。拿到需求后我没有马上写groupby,而是先和运营确认了三个问题:销售额按支付口径还是下单口径?退款怎么算?订单量是按订单号去重还是按商品行计数?这三个问题直接决定了后面所有指标对不对。事实证明,这一步省掉了后面大量的返工。
1. 为什么这份订单流水不能直接用来算GMV
很多人拿到数据的第一步就是df['sales_amount'].sum(),觉得总销售额出来了就完事。这个动作在干净的教学数据集上没问题,但在真实业务数据上几乎一定会出错,因为原始订单流水里藏着大量需要判断的业务语义。
1.1 先看清手上有哪些字段
我习惯先把数据字典列出来,字段含义不清楚就直接分析,后面一定会被业务方问住。这次拿到的表结构大概是这样:
| 字段名 | 含义 | 常见问题 |
|---|---|---|
| order_id | 订单编号 | 一个订单买多个商品时会重复出现 |
| order_time | 下单时间 | 有2024/1/5和2024-01-05两种格式 |
| pay_time | 支付时间 | 大量空值,说明用户下单后没付款 |
| product_id | 商品ID | 同名商品可能有多个规格变体 |
| product_name | 商品名称 | 命名不规范,有空格和特殊符号 |
| cat1 / cat2 | 一级/二级类目 | 偶有跨类目错放 |
| sales_amount | 订单行实付金额 | 负值表示退款,不是脏数据 |
| cost_amount | 成本金额 | 可能为空 |
| quantity | 购买数量 | 正常应该是正整数 |
| user_id | 用户ID | 有匿名用户,无法归因 |
| province / city | 省份 / 城市 | 部分带空格或"省/市"后缀 |
| pay_method | 支付方式 | 有缺失 |
先跑几行代码看数据形状、字段类型和缺失情况,比直接算指标重要得多:
python复制import pandas as pd
df = pd.read_excel('order_data.xlsx')
print(df.shape)
print(df.dtypes)
print(df.isna().sum())
print(df.head())
这一步能快速暴露问题。比如order_time是object不是datetime,sales_amount被读成了float还好,但城市列如果混入数字就会被读成异常类型。
1.2 藏在原始流水里的几个"地雷"
第一颗雷是日期格式混用。后台导出的时候不同时期用了不同格式,有的单元格是2024/1/5,有的是2024-01-05 10:23:11,还有极少数是20240105这种纯数字。如果直接按字符串处理,排序和按月聚合全是错的。
第二颗雷是销售额里出现了负值。这不是系统bug,而是退款单被记成了负金额。正常销售额和退款必须分开统计,否则加总起来会互相抵消,GMV严重低估。
第三颗雷是同一订单号重复出现。一种情况是用户一个订单买了好几个商品,每个商品占一行,这是正常的;另一种情况是后台报表重跑导致整行完全重复,这是要删掉的。不能简单用drop_duplicates('order_id')处理,否则会把多商品订单的明细全删掉。
第四颗雷是省份城市字段不够干净,比如"广东 省""浙江 省"这种中间带空格的情况,还有"广东""广东省""广东深圳"三种写法同时存在。如果不处理,按省份聚合时会被拆成好几个桶,分析结果完全没法看。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 清洗订单数据的四板斧:列名、日期、金额、重复
数据清洗没有想象中那么神秘,核心就四件事:让列名能写进代码、让日期能被Pandas识别、让金额正负有意义、让重复行和拆分订单各归各位。做完这四步,后面所有分析才站得住脚。
2.1 列名规范化
原始Excel导出的列名经常带着空格、换行和中文括号,比如"商品名称 "、"销售额(元)"这类。中文括号在pandas里访问会出问题,列名中的空格也会导致代码可读性很差。我统一做了一次清洗:
python复制def clean_col(name):
name = str(name).strip()
name = name.replace(' ', '_')
name = name.replace('(', '(').replace(')', ')')
return name
df.columns = [clean_col(c) for c in df.columns]
df.rename(columns={
'商品名称': 'product_name',
'销售额(元)': 'sales_amount',
'成本(元)': 'cost_amount'
}, inplace=True)
中文列名本身不影响pandas运行,但字段一旦多了,每次都要打中文很不方便。我建议统一转成英文小写加下划线,团队协作时不会出现"你用的是下单时间,我写的是order_time"这种混乱。
2.2 日期时间清洗
日期清洗的标准化做法是交给pd.to_datetime,让Pandas自动识别常见格式:
python复制df['order_time'] = pd.to_datetime(df['order_time'], errors='coerce')
df['pay_time'] = pd.to_datetime(df['pay_time'], errors='coerce')
注意一定要加errors='coerce',否则遇到无法解析的值会直接抛异常,整个脚本中断。加了coerce之后,非法值会变成NaT,再用isna()统计出来,看看到底有多少行有问题。
这次清洗完发现有大约2%的order_time是NaT
