我这学期在学数据分析,课程进度已经推到第七次作业了。说实话,前六次作业基本都是单个知识点的练习,要么是Excel透视表操作,要么是Matplotlib画个折线图,任务量也就在半天左右。但第七次作业突然就不一样了,要求用Python完成一份完整的销售数据可视化分析报告,数据给的是某连锁超市一年多的真实订单流水,还要求输出可交互的图表和结论。我当时看到题目的第一反应是:这门课终于要来真格的了。
这篇内容我想把整个完成过程做一个复盘,包括我怎么拆解需求、怎么清洗数据、怎么选图表、踩了哪些坑,以及最后报告里写了哪些分析结论。如果你也在学数据分析、正要完成类似的课程作业,或者工作中第一次接手“从数据到报告”这种活,这篇文章应该能帮你少走不少弯路。我会把关键代码和操作逻辑都贴出来,你可以直接参考,对照自己的数据改一改就能用。
1. 作业需求拆解与整体思路
1.1 先把题目读懂,再动手写代码
拿到作业的第一件事,不是打开Jupyter Notebook就开始导入数据,而是先把题目要求拆成几个可以执行的模块。这次作业的需求文字并不算多,核心要求有以下几条:
- 使用Python对给定的销售数据进行清洗与预处理;
- 从时间、地区、品类、门店等多个维度进行统计分析;
- 至少输出5种不同类型的可视化图表,并配上分析结论;
- 整体输出为一份完整的分析报告,格式不限。
单看这几条好像不难,但真开工之后会发现,这些要求里藏着很多模糊地带。“清洗与预处理”到底要处理哪些问题?“多个维度”是哪些维度?“分析结论”写到什么深度才算合格?我的做法是先列一个任务清单,把大作业拆成五个阶段:数据检查、数据清洗、维度分析、可视化实现、报告汇总。每一阶段都有明确的产出,做完一个再进下一个。
1.2 为什么把分析流程拆成五步
拆阶段这个习惯,是我前几次作业里吃过大亏之后养成的。第一次做大作业时,我拿到数据就急着画图,结果图是画出来了,但数据里全是重复项和缺失值,画出来的趋势完全是歪的,被老师批了一顿“垃圾进垃圾出”。从那以后,我每次做分析类项目都强制自己先做数据检查,再做清洗,最后才进入分析和可视化环节。
这次作业的数据量是两万多条订单记录,不算大,但脏数据问题一点不少。拆成五步之后,我大概规划了一下时间占比:数据检查和清洗占40%,分析和可视化占40%,报告汇总占20%。很多人做数据分析会把大部分时间放在画图上,但真实业务里,清洗才是真正拉差距的环节。这一步做得扎实,后面的可视化才能出彩;做得敷衍,图表再好看也只是把错误数据重新装饰了一遍而已。
1.3 技术选型:为什么用Pandas加Pyecharts
技术选型上,我核心用了三件套:Pandas做数据处理,Pyecharts做可视化,Jupyter Notebook做编码和报告整理。
Pandas在表格数据处理上基本是Python生态里没有对手的存在,groupby、merge、pivot_table这些操作,写起来快,逻辑也直白。Pyecharts的优势是能生成HTML格式的可交互图表,鼠标悬停能看到详细数值,比Matplotlib那种静态图在展示效果上高一档,交作业的时候更容易做出“成品感”。
至于为什么不用Matplotlib或者Seaborn,也不是不能用,只是这门课之前已经用过好几次了,这次作业我确实想换个风格。Matplotlib在学术论文风格图表上依然很强,但要说让非技术背景的人看着也舒服、能上手交互,Pyecharts明显更合适。你要是习惯Matplotlib也没问题,重点还是分析逻辑和图表表达是否到位,而不是纠结于工具本身。
数据分析项目里,工具永远只是手段。真正值钱的是你怎么理解数据、怎么发现问题、怎么把结论讲清楚。技术选型只要顺手、能实现需求就够了,不必追求复杂。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据清洗:这次作业真正拉开差距的地方
2.1 拿到数据的第一件事:先看基本信息
我用Pandas读入数据后,第一步是执行df.info()和df.head(),把数据长什么样摸清楚。这一步真的非常重要,它能告诉你有哪些列、每列有几条非空值、各列的类型是什么,比你瞎猜数据质量靠谱得多。
我当时看到的情况是这样:
- 数据总共21450行,14列;
- 其中“客户姓名”有327个缺失值,“联系电话”有1500多个缺失值;
- “订单日期”是字符串格式,没有转成日期类型;
- “销售额”和“利润”列存在部分负值,需要进一步判断是否合理;
- 有1068条完全重复的订单号记录。
只看这些信息,就知道这数据一定不能直接拿来画图。如果你跳过了这一步,后面做的所有分析都会带着这些隐患。
2.2 脏数据都有哪些“脏法”
结合这次作业的数据,我把脏数据问题整理成几种常见类型,以后你自己做数据分析时也可以按这个清单自查:
缺失值问题。 客户信息和联系方式缺失比较多,好在分析维度主要集中在时间、地区、品类和门店上,客户维度的缺失对整体结果影响不大。我处理的方式是先统计缺失比例,再决定是删除还是填充。联系方式这类缺失占比高且分析用不上的列,直接不纳入分析范围;客户姓名这类缺失,我暂时用“未知客户”填充。
重复值问题。 重复的订单号有1068条,说明同一个订单被录入了多次。这种数据如果不处理,统计销售额的时候等于给同一个订单算了两次甚至三次,结果自然就虚高了。对这类重复记录,我按订单号去重,保留第一条。
格式不一致问题。 这是最容易被忽略但也最常见的坑。订单日期在源表里是“2023/1/15”和“2023-01-15”两种格式混着存的,如果直接按字符串排序,就会出现1月排在10月后面的情况。处理方式是统一转成datetime类型,只有转成真正的日期格式,时间序列分析才能做对。
异常值问题。 销售额和利润出现负值,这就要靠业务常识来判断了。销售额为负,大概率是退款订单;利润为负,可能是打折促销或者高成本订单。退款订单可以从销售分析中剔除,避免干扰整体趋势;利润为负的记录要保留,因为亏损本身就是一个值得分析的现象。
2.3 清洗代码怎么写的
数据清洗我用了一个比较标准化的流程,每一步都有明确的动作和目的。你参考的时候可以按同样的思路来:
python复制import pandas as pd
# 1. 读入数据
df = pd.read_excel('supermarket_sales.xlsx')
# 2. 查看基本信息
print(df.info())
print(df.head())
# 3. 去重:按订单号去重,保留第一条
df = df.drop_duplicates(subset=['订单号'], keep='first')
# 4. 日期格式统一
df['订单日期'] = pd.to_datetime(df['订单日期'], errors='coerce')
# 5. 删除日期解析失败的行(异常数据直接扔掉)
df = df.dropna(subset=['订单日期'])
# 6. 处理缺失值:分析用不到的列不处理,必要的列做填充
df['客户姓名'] = df['客户姓名'].fillna('未知客户')
df['联系电话'] = df['联系电话'].fillna('')
# 7. 剔除销售额为负的退款记录
df = df[df['销售额'] > 0]
# 8. 添加月份、季度等用于时间维度分析的辅助列
df['月份'] = df['订单日期'].dt.to_period('M')
df['季度'] = df['订单日期'].dt.quarter
df['年份'] = df['订单日期'].dt.year
# 9. 清洗后的数据保存,后续分析从这里读取
df.to_csv('supermarket_sales_clean.csv', index=False, encoding='utf-8-sig')
每一步都对应一个明确的检查动作。清洗完再跑一次df.info(),你会发现空值、重复值、格式问题全部消失了,这时候的数据才真正可以用来做分析。整个清洗过程花了两三个小时,比画图的时间还长,但做完之后我心里很踏实,因为后续所有的结论都建立在一个干净的数据集上。
注意:
encoding='utf-8-sig'这个参数很重要,不然用Excel打开清洗后的CSV文件,中文列名会乱码。我在这上面栽过一次,后来就记住了。
3. 多维度分析与可视化实现
3.1 时间维度的月度趋势分析
清洗完数据,我做的第一个分析维度是时间。对这组销售数据来说,时间是观察整体业务最核心的坐标轴。我按月份对销售额和利润做了分组聚合,生成了每个月的汇总数据。
python复制monthly_sales = df.groupby('月份').agg(
总销售额=('销售额', 'sum'),
总利润=('利润', 'sum'),
订单量=('订单号', 'count')
).reset_index()
对月度趋势我进行了三项具体分析。
第一个是全年销售额的走势。可以看出销售高峰集中在下半年的9月和11月,9月应该是开学季备货带来的增长,11月明显是大促活动带动的订单量爆发。这两个时间点背后都有对应的业务事件,不是凭空涨上来的。
第二个是月均销售额和峰值月份的差距。峰值月份销售额是平均水平的1.8倍左右,这说明这家超市的销售节奏有明显季节性。在库存管理和人员排班上,完全可以按照这个节奏提前做计划——旺季前多备货、多排班,淡季则控制库存成本。
第三个是利润和销售额的同步性。销售额最高的月份,利润也是最高,但两者增幅并不完全同步。有的月份销售额增长明显但利润增速放缓,这种现象一般指向促销冲量,卖得多但折扣也大。
3.2 品类与地区维度的交叉分析
时间维度做完之后,我接着做了品类和地区的交叉分析。
品类的分析结果比较直接:生鲜食品类销售额占比最高,但利润率并不高;日用百货类销售额排名第二,利润率相对稳定;家用电器类虽然销售额占比不大,但单笔客单价高,是贡献利润的重要品类。
地区维度我用了分组聚合,按省份统计销售额,再取前十名:
python复制region_sales = df.groupby('省份')['销售额'].sum().sort_values(ascending=False).head(10)
这个结果可以用来回答一个问题:哪些区域是这家超市的核心市场,哪些区域是潜力市场。我的分析结论是,广东、江苏、浙江占据了前三位,销售额都超过了千万级别,头部效应非常明显。但结合订单量的数据看,广东的单均销售额偏低,说明广东门店以高频小额消费为主;浙江的单均销售额明显更高,说明这里的客群更倾向于囤货型的集中采购。
还有一组值得关注的交叉维度是“品类-地区”。同一种商品在不同地区的表现差异很大,比如家电品类在广东卖得好,但东北地区更偏向日用百货。这种差异化信息可以指导不同区域的选品和营销策略。
3.3 用Pyecharts输出可交互图表
可视化环节,我用Pyecharts生成了五类图表,分别是折线图、柱状图、饼图、地图和箱线图。下面展示核心的代码写法,关键是记住一点:Pyecharts的图表是链式调用,配置项一层层叠加,写起来确实直观。
折线图:月份销售额趋势
python复制from pyecharts.charts import Line
from pyecharts import options as opts
line = (
Line()
.add_xaxis(monthly_sales['月份'].astype(str).tolist())
.add_yaxis(
'销售额',
monthly_sales['总销售额'].tolist(),
is_smooth=True,
label_opts=opts.LabelOpts(is_show=False)
)
.set_global_opts(
title_opts=opts.TitleOpts(title='月度销售额趋势'),
tooltip_opts=opts.TooltipOpts(trigger='axis'),
yaxis_opts=opts.AxisOpts(name='销售额(元)')
)
)
line.render('monthly_sales.html')
柱状图:品类销售额对比
python复制from pyecharts.charts import Bar
bar = (
Bar()
.add_xaxis(category_names)
.add_yaxis(
'销售额',
category_values,
label_opts=opts.LabelOpts(position='top')
)
.set_global_opts(
title_opts=opts.TitleOpts(title='各品类销售额对比'),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=30))
)
)
bar.render('category_sales.html')
交互式图表在展示效果上确实比静态图好很多。老师看报告的时候,鼠标移到图上就能看到具体数值,不用再看底下密密麻麻的数据表,这个体验还是很加分的。
分享一个格式上的小技巧:如果品类名称比较多,X轴文字会重叠,用
axislabel_opts=opts.LabelOpts(rotate=30)把文字旋转30度,图看起来会整洁很多。这个细节我是在跑完图发现标签叠成一片之后才想起要调的。
3.4 地图可视化与箱线图的加入
地图和箱线图是这次作业里比较出彩的两个图。
地图用Pyecharts的Map类型,按省份展示销售额分布。这个图对报告整体观感的提升非常明显,一眼就能看出哪些区域颜色深、哪些区域颜色浅。但有个坑:Pyecharts地图依赖地图数据包,如果用旧版本可能要额外安装,新版一般已经集成了。如果Map渲染出来后是空白,大概率是地图数据没有正常加载,建议检查一下pyecharts的版本,或者到GitHub上看有没有对应的地图数据包需要补充安装。
箱线图是用来展示“各地区客单价分布”的。这个维度很有意思,因为它反映的不仅仅是总量,更是每个地区消费能力的差异。箱线图能呈现出最大值、最小值、中位数和四分位数,一眼就能看出哪个地区的客单价分布更集中、哪个地区存在明显的极端值。
有了这些图之后,报告的分析维度一下丰富起来了:趋势用折线图,对比用柱状图,占比用饼图,地理分布用地图,分布形态用箱线图。每一张图都在回答一个特定的问题,不是为了凑图而图。
4. 常见问题与排查技巧实录
4.1 中文乱码和字体问题
中文乱码是最常见也最容易让人抓狂的问题。在Pyecharts系列中,中文标题和标签乱码的问题不多见,因为新版Pyecharts内部做了字体处理。但用Matplotlib画图时,中文乱码几乎是必现的。如果你不是非要用Matplotlib不可,可以直接用Pyecharts避开这个坑。
如果确实需要用Matplotlib输出中文,标准解法是手动指定中文字体:
python复制import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
Windows一般有这个字体,Mac把SimHei换成Arial Unicode MS或PingFang SC就行。这个配置我每次用Matplotlib都会先写上,不然图里的中文全是小方块。
4.2 时间序列排序错乱
这是个很典型的坑。如果“月份”列是按字符串存储的,Pandas的sort_values()在排序时会按字典序排,结果就是“2月”排在“10月”的前面,因为字符串比较是逐字符比较,2比1大,但2后面跟的是右斜杠之类的字符,排序结果就乱了。
解决办法是先把日期转成datetime类型再排序,或者直接在上一步聚合时把月份列转成Period类型:
python复制df['月份'] = df['订单日期'].dt.to_period('M')
这样得到的月份列本身就有时间顺序,聚合后按它排序就是正确的。
4.3 Pyecharts图表渲染失效
遇到图表渲染不出来,排查步骤一般是这样的:先确认render()方法执行完,有没有生成HTML文件;生成之后用浏览器打开,如果页面是空白,可能是JS资源加载失败;如果控制台报错,优先检查选择的图表类型版本和Pyecharts主版本是否兼容。
处理这类问题,最高效的方案是升级到较新的Pyecharts版本,因为它把很多依赖内置了,不用额外安装。调试新图时,不要一次性写完所有配置项,先用一个最简单的例子跑通,再加标题、加提示框、加坐标轴样式。一层层叠加上去,出问题时能快速定位是哪一步写错了。
4.4 数值单位不统一
原始数据里“销售额”字段有整数也有负数,单位看起来都是元。但我之前做数据处理时就曾经碰到过“元和万元混用”的情况,这次的原始数据虽然是统一单位,我还是习惯性检查了一轮最大值和最小值:
python复制df['销售额'].describe()
最大销售额是19800元,最小是4.5元,都在正常范围内。这种基本检查花不了一分钟,但能防止一些特别离谱的错误。比如有些报表系统导出的数据,销售额单位是万元,而你又没有意识到,做出来的图就会比别人大了四个数量级,直接社死。
4.5 一个容易被忽略的问题:数据口径一致性
分析过程中我反复提醒自己一件事:图表里对比的对象,口径必须一致。比如你统计“销售额”,就要明确是含税还是不含税;统计“订单量”,就要明确是去重前的原始记录数还是去重后的有效订单数。如果清洗前后各算了一遍,而且没写清楚,报告里的数字就会自相矛盾。
我的习惯是在报告里开一个小节,专门写清楚数据口径。比如“本次分析中,销售额均指去重后的有效订单销售额,已剔除退款记录”,下一句话就能避免读者对你的数据产生疑问。
5. 这次作业之后的一些心得体会
第七次作业做完,我最明显的一个感受是:作业量大小本身不是最难的,难的是从“会操作某个函数”到“能完整分析一个业务问题”的转变。前六次作业都在练单点技能,但到了这个程度,你需要自己决定每一步做什么、为什么这样做、怎么跟下一步衔接。
如果你也卡在类似的作业或者任务上,我的建议是先别急着写代码,先用纸笔把“我要回答什么问题”列出来。你不需要一开始就把所有事情想清楚,但至少要把方向定住——我在分析什么,给谁看,他们关心什么。带着这些问题去做分析,产出的图表和结论才不会零散。
这个项目里我个人操作上比较满意的一个小细节是:我在清洗完数据之后,先跑了一遍清洗前后的数据量对比,把差值记录在报告的附录里。去重删了多少行、剔除退款删了多少行,每一项都有据可查。这个动作本身花不了多少时间,但让整个报告的可信度提高了很多,也方便自己回头看有没有清洗过头。
最后再分享一个实用建议:别忘了给自己的分析报告写一个简短的“结论摘要”,放在报告最开头。哪怕后面内容很多,读者看摘要就能知道核心发现是什么。我当时在摘要里写了两条最重要的信息:全年销售额高峰集中在9月和11月;核心市场集中在华东和华南地区。整个报告的核心价值,其实就在这几句话里。会画图的人很多,但能把数据变成一句清晰结论的人,才是真正被需要的人。
