实战:用Python分析某电商销售数据
先交代一下背景。这个项目源自我接到的一份模拟业务需求:某电商平台给了一份某年上半年的订单明细表,让我从数据中找出三个问题的答案——整体销售走势怎么样,哪些商品是真正的利润贡献主力,以及用户复购行为呈现什么规律。这类需求在电商公司里非常常见,也是Python数据分析入门最经典的实战场景之一。本文从拿到一份原始的CSV文件开始,走完数据清洗、字段加工、多维分析和可视化输出全流程。
我个人更愿意把这类项目称作“带着业务问题去做数据挖掘”,而不是“跑一遍pandas代码”。因为电商销售数据的分析看起来是个技术活,但实际上80%的时间花在理解数据、清洗数据、定义指标口径上,真正写绘图代码的时间反而不多。这篇文章会完整复现我当时从零到一的处理思路,会讲清楚每一步操作背后的业务考量,而不是简单地堆一段可运行的脚本。
无论你是刚学完Python基础语法、想找真实项目练手的新手,还是已经用Excel处理过销售数据、想转型用Python做自动化分析的运营同学,这篇文章都适用。建议你准备一个Jupyter Notebook环境,跟着文章把代码敲一遍,遇到问题再回头对照排查思路,收获会远大于直接复制粘贴。
1. 分析目标拆解与数据字典:动手写代码前必须想清楚的事
1.1 三个核心业务问题决定了分析方向
接到需求后,我做的第一件事并不是打开Python编辑器,而是把业务问题拆解成可量化的分析维度。这是很多新手最容易忽略的一步,上来就df.head()一顿操作,最后做出来的图表和业务决策完全脱节。
原始需求里的三个问题可以拆成这样:
| 业务问题 | 分析维度 | 核心指标 | 目标输出物 |
|---|---|---|---|
| 整体销售走势 | 时间维度 | 日/周/月销售额、订单量、客单价 | 折线趋势图、波动分析 |
| 商品贡献分布 | 商品维度 | 销售额TOP10、销量TOP10、利润率对比 | 柱状图、帕累托分析 |
| 用户复购规律 | 用户维度 | 复购率、回购周期、RFM分层 | 留存曲线、分层表格 |
这样拆完之后,整条代码路径就清晰了:加载数据、清洗字段、按时间汇总、按商品汇总、按用户汇总、绘图输出。你可以把这三个问题当作贯穿全文的一条主线,后面的每一段代码都在为回答它们服务。
1.2 字段类型和数据质量排查
拿到手的sales_data.csv包含约7万行订单记录,字段不算多,但脏数据该有的基本都有:缺失值、重复记录、日期格式不一致、金额字段混入负值、商品名称里的前后空格和全角括号。
我先把字段类型和缺失情况跑了一遍,这是每次分析项目的第一步。当时用的代码很简单:
python复制import pandas as pd
import numpy as np
df = pd.read_csv('sales_data.csv', encoding='gbk')
print(df.shape)
print(df.dtypes)
print(df.isnull().sum())
从字段类型和方法可以看出,这个项目主要依赖pandas做数据清洗与聚合分析,结合matplotlib和seaborn做可视化。数据量是7万行,完全在pandas的处理能力之内,不需要引入Spark或Dask之类的大数据框架,单机处理即可。
数据字段包括订单号、下单时间、商品ID、商品名称、类目、数量、单价、销售额、用户ID、支付方式等。我发现最严重的问题是“销售额”字段里出现了约600个负值,以及“用户ID”字段有约300条缺失记录。这些不处理干净,后面的所有统计都会失真。
提示:读取CSV文件时,如果文件是中文内容并且直接
read_csv报错,多半是编码问题。国内业务系统导出的文件,gbk和utf-8是两种最常见的情况,用文本编辑器打开看一眼就能判断。我试过用encoding='gbk'读成功后,后续所有字段里的中文才能正常显示。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据清洗全记录:编码问题、重复值、负金额和日期标准化
2.1 文件读取时最容易踩的编码坑
这个项目的原始文件是业务后台导出的,中文内容用了GBK编码。新手拿到这种文件,最容易复制网上的教程代码用read_csv直接读,然后报UnicodeDecodeError,第一反应往往是“代码写错了”或者“Python没装好”,实际上只是编码参数不对。
我在实际处理中是这样解决的:
python复制df = pd.read_csv('sales_data.csv', encoding='gbk')
如果你不确定文件具体是什么编码,可以用一个通用办法——用chardet库自动检测:
python复制import chardet
with open('sales_data.csv', 'rb') as f:
result = chardet.detect(f.read(10000))
print(result)
检测结果会告诉你文件是GB2312、utf-8还是其他编码。这种方法在别人发给你的文件来源不明时特别管用,比一个个试encoding参数要高效得多。
2.2 重复订单的判断与处理逻辑
这个数据里确实存在整行完全一样的重复记录。判断重复不能只看订单号,因为同一个订单号下可能有多行不同的商品明细,这是电商订单表的正常结构。所以必须用subset参数指定“订单号+商品ID+下单时间”三列组合判断,缺一不可。
我的处理方式:
python复制df.drop_duplicates(subset=['订单号', '商品ID', '下单时间'], inplace=True)
处理前7.1万行,处理后剩6.9万行,去掉了近2000行重复数据。为什么“下单时间”要参与判断?因为同一订单号下如果消费者买了同款商品两件,往往会出现两行数据,但下单时间相同且商品ID相同,这两行是合法明细,不该被去重。加上“下单时间”字段,就可以避免误删这类多行明细。
2.3 负金额字段:是优惠、退款还是异常值
“销售额”字段里出现负值,这是电商数据里最需要谨慎对待的问题。我排查后发现大部分负值对应的是“退款”或“订单取消”,但细看数据里没有独立的退款标记字段,只有销售明细。如果直接把这些负值当作正常销售额参与计算,“总销售额”会被明显拉低。
处理方案因业务口径而异。我当时采取了保守方案,先看负值占比:
python复制negative_mask = df['销售额'] < 0
print(df[negative_mask].head(20))
print(df[negative_mask]['销售额'].sum())
负值订单总金额大约占全量销售额的0.3%,占比不高。为简单起见,同时考虑到本次分析主要关心正向销售的走势和结构,我把负值行单独剔除在分析集之外,另存为一个退款明细表供后续专项分析:
python复制df_refund = df[negative_mask].copy()
df = df[~negative_mask].copy()
这样处理的好处是,分析主体不会因异常值产生偏差,退款数据也没有丢弃,后续如果要做净销售额口径,两个表直接合并计算即可。
2.4 日期字段的标准化:字符串转时间类型的正确姿势
原始数据的“下单时间”字段是字符串格式,形如2023/1/5 14:23和2023-01-05 14:23:00混在一起。如果不统一格式,按日期聚合时会出现同一天被拆成两个不同分组的尴尬情况。
我用pd.to_datetime统一转换:
python复制df['下单时间'] = pd.to_datetime(df['下单时间'])
然后提取日期、月份、小时等维度,方便后续按时间维度做不同粒度的聚合:
python复制df['下单日期'] = df['下单时间'].dt.date
df['下单月份'] = df['下单时间'].dt.to_period('M')
df['下单小时'] = df['下单时间'].dt.hour
日期统一后的一个直观验证就是:按月份聚合后,1到6月各月数据都连续存在,没有出现12月或异常月份混进来的情况。
2.5 用户ID缺失:直接丢弃还是填充
用户ID缺失300条,占总量的0.4%左右。缺失的订单基本上就没法关联用户,也就无法参与用户复购分析。对这类缺失,我的处理策略是:分析商品和时间维度时保留这些记录,分析用户维度时先过滤掉。
python复制df_user_analysis = df[df['用户ID'].notna()].copy()
为什么不直接dropna?因为不同分析任务对缺失值的容忍度不一样,一刀切删除可能会丢失商品维度上的有效销售信息。这也是实际业务处理中很常见的取舍逻辑:先保留,按需过滤。
3. 核心指标计算与多维度分析:从汇总表里读出业务含义
3.1 整体销售趋势分析:时间序列聚合技巧
清洗完数据,我做的第一张图是月度销售额和订单量趋势。为什么要先看整体趋势?因为这是最宏观的视图,能判断这半年销售走势是平稳增长、季节波动还是大起大落,后续所有细分的分析都要结合这个大背景来解读。
python复制monthly = df.groupby('下单月份').agg(
销售额=('销售额', 'sum'),
订单量=('订单号', 'nunique'),
销量=('数量', 'sum')
).reset_index()
monthly['客单价'] = monthly['销售额'] / monthly['订单量']
print(monthly)
聚合结果是1月到6月销售额呈现先降后升的趋势,2月因春节假期交易量明显下降,3-6月逐月回升,6月达到最高。客单价在2月反而上升,说明节假日期间虽然单量少,但单笔金额更大。
这里有个细节值得注意:订单量用的是nunique而不是count。原因是一张订单可能包含多行商品明细,count会把同一张订单的商品明细行数也数进去,导致订单量虚高。nunique按订单号去重后统计才是真实的订单数。这个错误非常隐蔽,新手极易忽略,但会直接影响你后续所有“按单量”指标的准确性。
3.2 商品贡献分析:销售额TOP10与帕累托法则验证
商品维度的分析,我选择了两个视角:销售额贡献和销量贡献。为什么不能只看销售额?因为高销售额商品可能是高单价低频次,高销量商品可能是低单价高频次,两种商品对于库存管理和营销策略的意义完全不同。
python复制top10_sales = df.groupby('商品名称').agg(
销售额=('销售额', 'sum'),
销量=('数量', 'sum'),
订单数=('订单号', 'nunique')
).sort_values('销售额', ascending=False).head(10)
TOP10商品的销售额占比大约为总销售额的42%左右,每个商品的订单数也拉开了明显差距。这说明头部商品效应非常显著,如果做促销活动,押注TOP10商品远比“撒胡椒面”式的全品类促销更高效。
接着我做了累计销售额占比曲线,验证帕累托法则(二八法则)——事实上这个数据集中,销售额前20%的商品贡献了大约65%的销售额,虽然没有完全做到“20%商品贡献80%销售”,但头部集中效应已经非常明显。
3.3 用户复购分析:定义指标口径是复购分析的关键
用户维度的分析,最重要的不是代码,而是指标口径的定义。“复购率”这个词在不同业务语境下含义可能完全不同——有的定义为“统计周期内购买两次及以上的用户数占总购买用户数的比例”,有的定义为“上期已购用户中本期再次购买的比例”。
我这里采用的分析口径是:统计半年内购买次数在2次及以上的用户数,除以总购买用户数。
python复制user_order_count = df_user_analysis.groupby('用户ID')['订单号'].nunique()
repurchase_users = user_order_count[user_order_count >= 2].count()
total_users = user_order_count.count()
repurchase_rate = repurchase_users / total_users
print(f'复购率: {repurchase_rate:.2%}')
计算结果是复购率约为19.25%。这个数字意味着约五分之一的用户在半年内产生了二次及以上购买。作为第一次分析,这个指标建立了用户忠诚度的基准线,后续如果优化了商品结构或服务质量,就可以拿这个基线数字做对比。
3.4 客单价分布与支付方式偏好
除了三个核心业务问题,我还额外分析了客单价分布和支付方式偏好。这两个维度属于“附加发现”,但往往能给业务带来意想不到的优化点。
客单价分布呈右偏形态,大部分订单金额集中在50到200元区间,少数大额订单把均值拉高。支付方式方面,移动支付占比超过七成,货到付款占比不足5%。
4. 可视化呈现:用图表讲清销售数据背后的业务趋势
4.1 颜色风格与整体布局设计
分析结果最终要给人看,可视化绝不是简单的“画个图就完事”。我采用了一套简洁统一的风格,字体用系统默认黑体,颜色用一组低饱和度的色系,整体偏商务分析报告风格。这样做的原因是,图表最终要放进给业务部门看的PPT里,太花哨的颜色会喧宾夺主。
设置绘图风格和中文显示的代码:
python复制import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
sns.set_style('whitegrid')
这里有两行代码是必写不可的,否则中文标签会变成方框。font.sans-serif指定中文字体,axes.unicode_minus解决负号显示为乱码的问题。很多新手在这卡了半天,其实就是缺这两行。
4.2 月度销售趋势图:展示整体走势的折线图
我绘制了月度销售额和订单量的双轴折线图。销售额用左侧纵轴,订单量用右侧纵轴。双轴图适合展示两组量纲不同的指标在同一时间轴上的变化趋势。
作图之后可以看到一个非常清晰的“V型”走势:2月跌入谷底,3月开始逐月拉升。这个规律如果用表格看不够直观,但一画成折线图,业务部门的人一眼就能读出“春节月低谷、6月大促爆发”的信息。这就是可视化的价值——把隐藏在数据中的业务规律用最直观的方式暴露出来。
4.3 商品TOP10柱状图与累计占比折线图
商品贡献的可视化我用了组合图:柱状图展示每个商品的销售额,折线展示累计销售额占比。
这种“柱线组合图”在商业分析里非常常见,它同时回答两个问题:谁卖得最好(柱子高度)、头部商品到底有多重要(折线斜率)。
python复制fig, ax1 = plt.subplots(figsize=(12, 5))
ax1.bar(top10_sales.index, top10_sales['销售额'], color='#5B9BD5')
ax1.set_ylabel('销售额(元)')
ax1.set_xticklabels(top10_sales.index, rotation=45, ha='right')
cumsum = top10_sales['销售额'].cumsum() / top10_sales['销售额'].sum() * 100
ax2 = ax1.twinx()
ax2.plot(range(len(cumsum)), cumsum, color='#ED7D31', marker='o')
ax2.set_ylabel('累计销售额占比(%)')
plt.title('销售额TOP10商品及累计占比')
plt.tight_layout()
plt.show()
4.4 复购用户画像:购买次数分布直方图
复购分析的可视化我画了用户购买次数的分布直方图。横轴是半年购买次数,纵轴是用户数。绝大多数用户的购买次数集中在1次到3次,超过5次的用户数量锐减。这说明目前用户的购买行为是低频、偶发性的,如何把1次用户转化为2次用户,是会员运营的核心命题。
4.5 可视化阶段踩过的字体和布局坑
这个项目里我踩了一个典型的坑:第一次出图时中文标签全部显示为方块,原因是操作系统默认字体没有包含中文字形。解决方案就是前面提到的rcParams设置。还有一个布局问题是plt.tight_layout()没加,导致横轴商品名称被截掉一半。
注意:如果你使用的是VS Code、PyCharm或Jupyter Notebook,
plt.show()之前加上plt.tight_layout(),能自动调整子图参数,避免标签重叠。这在标签比较长的柱状图里特别必要。
5. 项目全流程的环境配置建议:从安装Python到搭建虚拟环境
5.1 初学者最容易卡住的环节:Python安装和环境变量
关于Python本身的安装,网上的教程很多,但很多新手还是会在环境变量这里卡住。尤其是Windows系统,安装时如果没有勾选“Add Python to PATH”,后面在命令行里执行python就会提示“不是内部或外部命令”。我的建议是:安装时务必勾选Add Python 3.x to PATH,这一步能省掉无数麻烦。
安装完成后,在命令行里运行:
bash复制python --version
pip --version
两个命令都正常输出版本号,说明安装成功了。如果pip不可用,可以使用python -m pip install来替代。
5.2 用虚拟环境隔离项目依赖
这个项目的依赖是pandas、numpy、matplotlib、seaborn这几个库。我建议在项目目录下创建虚拟环境,避免污染全局Python环境:
bash复制python -m venv venv
# Windows激活
venv\Scripts\activate
# macOS/Linux激活
source venv/bin/activate
虚拟环境激活后,安装依赖:
bash复制pip install pandas numpy matplotlib seaborn
用虚拟环境的好处是:这个项目用的库版本固定后,不会因为其他项目升级库导致本项目的代码突然报错。我在实际中曾因为全局环境里pandas从1.x升到2.x,某个API行为变化导致脚本崩溃,虚拟环境有效规避了这种风险。
5.3 使用VS Code还是Jupyter Notebook
数据分析类项目,我更推荐VS Code里装好Jupyter扩展使用,或者直接用Jupyter Notebook。原因很简单:分析过程需要反复试错、逐步调整,逐单元格执行能让你随时看到中间结果,比一次性运行整个脚本的体验好得多。本项目的处理路径比较长,我也是分了几个阶段执行的,每个阶段结束后检查一下DataFrame的结构和内容,确认无误再进下一步。
如果是纯Python脚本,我仍然建议在代码里加打印语句或log,否则中间某个字段处理错了,最后的图表和数值会完全对不上,排查起来非常耗时。
6. 从这份电商数据中得到的业务洞察与复盘
6.1 销售额的头部集中效应
从销售额TOP10商品的数据来看,头部商品的集中度已经达到了“重点商品决定销售大盘”的程度。这个洞察对业务决策最直接的指导意义是:库存管理上,头部商品必须保证不缺货,因为缺货损失的销售额几乎无法通过长尾商品补回来;营销资源上,爆款商品的流量位和折扣资源应该优先保障。
6.2 复购率低是当前最大的增长机会点
19.25%的半年复购率,放在整个电商行业里属于中等偏低的水平。复购率每提升5个百分点,带来的销售额增量会比拉新同样比例用户大得多,因为老用户的客单价通常高于首单用户。
从数据上看,购买次数在2次和3次之间的用户量明显高于更高频次,说明“从1次到2次”是用户转化的核心门槛。针对这个门槛,可以设计“第二单立减”或“首单后7天内发放专属券”等触达策略。
6.3 2月销售低谷的本质原因拆解
2月销售额的大幅下滑,本质上源于春节期间的物流停运和消费需求推迟。3月之后逐月回升,既有季节因素,也有营销活动的功劳。如果明年要做销售目标,1月和2月合在一起看会更有参考价值,因为单独看1月正常、2月暴跌,会误判趋势。
7. 实操中的踩坑记录与效率提升技巧
7.1 编码问题不是小问题
项目结束后复盘,我最大的体感是:编码问题虽然是入门级问题,但它在真实项目中出现的频率远高于想象。除了读取的时候要考虑gbk和utf-8,写回CSV文件时也要注意编码统一。我导出分析结果时,用utf-8-sig而不是utf-8,因为Excel打开utf-8编码的CSV时中文会乱码,加-sig的BOM头就能被Excel正确识别。
