第二次作业,这四个字放在课程目录里平平无奇,放在我的学习记录里却是一道实打实的分水岭。第一周交上去的第一次作业,代码能跑,图表能出,结论勉强写了一段,但老师批注里只留了一句话:讲清楚你的思路。所以在做第二次作业的时候,我给自己定的目标很简单:不止要把结果做出来,还要让每一步都经得起追问。这次作业是一份数据分析与可视化任务,课程提供了一张脱敏后的电商订单表,大约十万行,要求完成数据清洗、销售维度分析和可视化报告,最终提交一份可复现的分析文档。
如果你也正在为课程作业、实训项目或者自学练手发愁,尤其是那种“数据给你了,剩下的全靠自己折腾”的作业,这篇内容应该能给你一些参考。我不打算只贴最后的成品效果,而是把整个从零到一的过程拆开讲,包括我踩过的坑、反复修改的图表、以及最后让我拿高分的几个关键习惯。这些经验放之四海皆准,换一个数据集、换一门课,思路完全能复用。
1. 第一次作业到第二次作业,差的是习惯
1.1 第一次作业暴露了什么
我的第一次作业其实做完得很早,但问题同样很明显。代码全部堆在同一个单元格里,从上到下几十行,中间没有任何注释,读起来跟天书一样。当时自己跑通了,还觉得挺顺利,结果老师一打开就露馅了:文件路径写的是本地绝对路径,换一台电脑根本跑不了;图表用的是默认样式,中文字体还变成了方框。
这些细节放在代码能跑的层面看,确实不影响结果。但如果把一道作业当成一个小项目来看,它就是不及格的。第一次作业告诉我最重要的一件事:过程的组织能力和结果的正确性同样重要。一个能跑通但思路混乱的notebook,和一个逻辑清晰的notebook,差的不是代码量,而是背后对问题的梳理程度。所以第二次作业我在动手之前,先做了一遍完整的复盘,把第一次作业里不合理的地方列成了一个清单,逐项规避。
1.2 第二次作业的评价标准不一样了
第二次作业给我的感受是:评价维度从“功能是否完整”升级到了“逻辑是否成立”。也就是你的结论必须经得起数据和图表的支撑,而不是拍脑袋写出来的总结。老师没有明说评分标准,但从作业要求和批注风格能看出来,他更关注的是这几点。
- 数据清洗是否考虑了业务场景,而不是无脑删除或填空。
- 分析维度是否覆盖了多个角度,而不是只挑好看的数据讲。
- 图表是否有标题、有标签、有结论注解,能不能独立表达信息。
- 报告的每一段结论,在图表和数据里是否找得到对应证据。
说实话,这套标准和公司里做数据分析报告的思路几乎一模一样。第二节课就遇到了这种要求,对我来说是好事,因为越早养成这样的习惯,后面做任何数据相关的事情都会省力很多。
1.3 把作业拆成三层的通用方法
在正式开始写代码之前,我先把“第二次作业”拆成了三个层次:数据层、分析层、表达层。
- 数据层:读取数据、检查字段、清洗缺失值、处理异常值、统一数据类型。
- 分析层:确定分析维度,比如时间趋势、地区分布、商品类目对比,然后分组统计。
- 表达层:选合适的图表、写结论、组织整个notebook的叙述顺序。
我当时给自己画了一张时间分配表,数据层占四成时间,分析层占三成,表达层占三成。最后执行下来,数据层实际花了接近五成时间,因为脏数据的形态永远超出预期,这里想提醒各位:如果你觉得清洗数据比想象中慢,说明你是真的在处理数据,不是走过场。这个时间比例一点都不亏,数据这一步做扎实了,后面分析和画图都会特别顺。
2. 这次作业的选题与技术方案
2.1 作业题目与需求拆解
这次“第二次作业”的完整题目是:对一份脱敏电商订单数据进行预处理,然后从时间、地区、商品类目三个维度完成销售分析,每个维度至少输出两张可视化图表,最后写一份不少于八百字的分析报告,报告要求有具体的数字和图表作为支撑。
拿到题目我先做了一件事:把需求拆成“显性要求”和“隐性考核点”。显性要求就是题目里写明白的,比如三个维度、两张图表、八百字报告。隐性考核点是我自己推出来的,包括数据清洗是否到位、维度拆解是否合理、图表选型是否准确、结论是否有数据支持。把这套拆解做完之后,我对作业的难度和方向就有了比较清楚的把握。
这个步骤看起来简单,但很多人会跳过,直接开始读数据。结果往往是做到一半发现某个维度分析不了,或者清洗完数据才发现缺失值比例高到影响整体结论,只能回头重来。所以我的习惯是:先花二十分钟读懂需求,再花十分钟规划路径,最后才打开代码编辑器。投入产出比极高。
2.2 为什么选 Python + Pandas + Matplotlib
技术选型没有悬念,课程指定的语言是Python,数据处理用Pandas,可视化用Matplotlib加Seaborn,整个作业在Jupyter Notebook里完成。作业本身不限制工具,我也考虑过Excel和Tableau,但最后还是用回了Python,说几个理由。
Excel处理十万行数据不是不行,但肉眼检查太累,每一个清洗步骤也难以清晰记录。Tableau做可视化确实好看,但作业要求“可复现”,交一个twbx文件和交一份完整notebook,给老师的印象完全不同。Python这套组合最大的优势是全程代码化,每一步操作都有记录,换一台电脑可以原样复现。
还有一个被很多人忽略的好处:notebook里可以穿插Markdown单元格,把每一段代码的“为什么这么做”写清楚。这正好呼应了老师那句“讲清楚你的思路”。数据清洗的代码本身只是几行,但你在旁边写一句“删除订单金额为空的记录,因为这些记录占比不到1%,且无法从其他字段推断”,整份作业的质感立刻就不一样了。
2.3 环境准备与数据概况
环境的准备比较省事,直接装了Anaconda,Python版本是3.11,Pandas版本是2.0以上。为了避免中文显示问题和其他坑,我在notebook第一行就配置了中文字体和负号显示,这个细节我后面在常见问题部分还会详细说。
数据文件是课程提供的orders.csv,一共十万行,字段包括订单编号、用户编号、下单日期、收货城市、商品类目、订单金额、支付方式,还有一个订单状态字段。拿到文件以后,我的习惯是先做三件小事:看一眼文件大小、看字段名和类型、看前五行长什么样。这三件事加起来不到一分钟,但对后续的理解很有帮助。
3. 数据清洗与处理的实操过程
3.1 字段体检与缺失值判断
打开数据之后,我用df.info()和df.isnull().sum()做了第一轮体检,情况如下表所示。
| 字段名 | 数据类型 | 缺失数量 | 缺失占比 | 处理方案 |
|---|---|---|---|---|
| order_id | object | 0 | 0% | 无需处理 |
| user_id | object | 0 | 0% | 无需处理 |
| order_date | object | 0 | 0% | 转为datetime |
| city | object | 1203 | 1.2% | 用“未知”填充 |
| category | object | 3 | 不到0.01% | 删除对应记录 |
| amount | float64 | 0 | 0% | 检查异常值 |
| pay_type | object | 0 | 0% | 无需处理 |
| status | object | 0 | 0% | 检查取值分布 |
缺失值处理的核心原则是先看比例,再定方案。category字段缺失只有三条记录,占总体比例可以忽略不计,直接删除这三行没有任何影响。city字段缺失占比1.2%,如果直接删掉,城市维度的分析会少一千多条数据,占分析样本的比例不可忽视,所以用“未知”单独标记,既不丢样本,也不影响其他城市的统计。这类决策看起来很小,但恰恰是数据分析师日常工作里最常遇到的判断场景。
3.2 重复值、类型转换与异常值处理
接下来是查重。订单数据的唯一键当然是order_id,我用duplicated()查了一下,发现有十四行是重复的。我的处理逻辑是保留第一次出现的记录,因为同一张订单在数据导出时被重复记录的概率高于同一订单号被二次使用的概率,这也是订单数据去重的通用做法。
日期字段转类型是个容易踩坑的点。order_date读进来以后是字符串,直接groupby也是能跑的,但你会发现按月份分组时顺序是乱的,按周、按季度统计更是无从下手。我用一行代码把它转成了datetime类型,然后顺手提取了month和weekday两个派生字段,后面做时间趋势分析非常方便。
异常值方面,我发现amount字段存在少量负数记录。这里要注意,负数不是清洗时用中位数填充就能糊弄过去的,因为订单金额为负在实际业务里可能表示退款。退款订单本身是有分析价值的,但它在单纯的“销售金额”口径下会拉低整体数值,所以我在做销售额分析之前,先单独区分了正常订单和退款订单,分开统计。这一点也写进了报告的数据说明部分,让读者清楚每一个数字的统计口径是什么。
import pandas as pd df = pd.read_csv('orders.csv') # 删除类目缺失的记录 df = df[df['category'].notna()].copy() # 城市字段缺失用“未知”填充 df['city'] = df['city'].fillna('未知') # 订单号去重,保留第一次出现的记录 df = df.drop_duplicates(subset='order_id', keep='first') # 日期转换并提取分析字段 df['order_date'] = pd.to_datetime(df['order_date']) df['month'] = df['order_date'].dt.month df['weekday'] = df['order_date'].dt.weekday print(df.shape) print(df['amount'].describe())清洗完成之后,数据从十万行变成了九万八千多行,整体量级没有大的损失。这里要特别强调一点:清洗的每一步都要记录删了多少行、为什么删,最好在notebook里写上理由。因为你交的是一份作业,老师不仅看你做了什么,更看你判断得对不对。
3.3 清洗顺序的讲究
清洗顺序也是经验活。我的顺序是先处理缺失值,再处理重复值,最后做类型转换和异常值检查。原因是三个操作之间存在天然的依赖关系:重复值不处理,后面按订单号聚合时会将同样的订单算两次;类型不转换,日期维度的分析无法进行;异常值不标记,金额统计就会被少数极端值污染。
还有一个细节:我会在清洗前保留一份原始数据的副本,用df_raw = df.copy()这种方式存下来。万一后面处理过程中出现了问题,或者分析时发现某个清洗操作做错了,可以直接从原始数据重新开始,不需要回滚一大堆代码。这个习惯在真实项目中同样适用,我现在做任何数据处理都会保留原始数据层的快照。
4. 可视化与作业报告的输出
4.1 每张图表都要对应一个结论
画图的思路我是这样定的:先想清楚每个维度要回答什么问题,再决定用什么图表。时间维度回答的是“销售走势怎么样”,所以我用了折线图看月度趋势;地区维度回答的是“哪些城市贡献了大部分销售额”,所以我截取了销售额TOP10的城市画水平条形图;商品类目维度回答的是“不同类目的销量和金额结构”,所以我画了柱状图和饼图。
图表选型有一个经验可以参考:趋势数据用折线图,对比数据用柱状图,占比数据用饼图或环形图,数据分布用直方图和箱线图。千万不要反过来用。我第一次作业就犯过把时间序列画成柱状图的错误,销售额TOP10用折线图,画出来乱七八糟,信息根本没法读。图表不是装饰品,它是用来辅助理解数据的,选型准确比画得好看重要得多。
4.2 图表细节与排版规范
图表细节是这次作业分数提升的大功臣。我给自己定了一条律:每张图必须有标题、坐标轴标签、数据来源或口径说明。图表标题要像论文标题一样,能概括整张图的核心信息,比如“2024年H1月度销售额走势,6月达到峰值”。坐标轴标签要写清楚单位,金额类的图表我会在y轴标注“单位:元”或者把数值换算成“万元”。
中文字体这块踩过坑的同学应该都有印象。Matplotlib默认字体不支持中文,画出来的图全是方格。我在notebook第一行加了两行配置解决这个问题:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False第一行指定了中文字体,第二行解决负号显示成方块的问题。如果你用的是Mac,把SimHei换成Arial Unicode MS或Heiti TC也能达到同样效果。这个配置每次新建notebook都要写一遍,属于日常操作里最不起眼但最影响观感的一环。
4.3 报告的组织方式
报告采用了“结论先行”的结构。开头先用一小段总述,说明本次分析的数据范围、清洗概况和三个最核心的发现。接下来每一个维度的小节,都按“一句话结论 + 图表 + 数据佐证 + 可能原因”的方式展开。
举一个具体例子。在时间维度分析里,我发现四月销售额出现了一个明显的低谷,而我前三次课程作业的原始版本里只写了一句话:“四月销售额最低。”这个结论是对的,但没头没尾,没有任何分析价值。修改后我补了一段:四月销售额为87万元,较三月下降约18%,结合数据中支付方式的结构变化和该月份的订单量下滑,推测可能受到季节性需求回落影响。这样的写法,图表佐证和数据细节都在,结论一下子就扎实了。
报告的最后我加了一页“数据处理说明”,简要记录清洗步骤和统计口径。这一步做出来,报告的完整度直接提升了一个台阶。老师打开文件的时候,不需要主动问我这个数字是怎么来的,因为文档自己会说话。
5. 常见问题与排查技巧实录
5.1 中文字体显示成方框
这个问题太典型了,单独拿出来说。第一次遇到的时候我以为是自己代码写错了,检查了很久才发现是Matplotlib的字体问题。配置两行代码就能解决,但如果不配置,后面所有图表的标题、标签都会变成方块或问号,观感很差。我在这个环节还踩过一个附加坑:只配置了正文字体,没配置axes.unicode_minus,结果到了画图的时候,坐标轴的负号又变成了方块。所以两个配置要一起写,别漏。
5.2 日期分组后顺序错乱
这个问题在第一次作业里就出现过,这次差点又踩一次。字符串格式的order_date直接参与groupby,结果出来的月份顺序是1月、10月、11月、12月、2月……字典序排的,完全没法看。解决办法就是先把字段转成datetime类型。如果你用的是其他工具,原理也一样:分组之前先确认分组字段的类型是你预期中的类型。
5.3 饼图比例太小看不清
品类维度分析里,我一开始画了完整饼图,十几个类目的比例挤在一起,小类目的标签全部重叠看不清楚。后来我把占比低于百分之一的小类目合并成了“其他”,饼图瞬间清爽了很多。这个处理在业务报告里叫“长尾合并”,适用于品类过多、重点不突出的场景。如果你的作业也有类似的场景,不妨用这个方法处理一下。
5.4 提交前自查清单
每次提交前,我都会对着下面这张表过一遍,确认没有低级错误。
| 确认项 | 说明 |
|---|---|
| 代码能否从头到尾运行一遍 | 特别是别人拿到文件后能不能直接跑通 |
| 文件路径是否使用相对路径 | 绝对路径换电脑必崩 |
| 图表是否有标题和坐标轴标签 | 无标签的图表信息不完整 |
| 报告中每个结论是否有数字支撑 | 不能只有定性描述没有定量证据 |
| 是否有数据处理说明 | 让读者知道统计口径和清洗逻辑 |
| 是否保留了中间文件或数据快照 | 事后调整分析时能找回原始状态 |
这个清单我打印出来贴在显示器边上,每次作业、每个小项目都过一遍。它帮我挡住了很多次“提交了才发现忘了保存最终版本”的尴尬。
5.5 额外想强调的两个坑
第一个是关于read_csv的编码问题。作业的数据文件是UTF-8编码,直接用pd.read_csv没问题。但如果你是Windows环境,有些数据集是GBK编码,默认读取会报乱码或解码错误,这时需要加encoding='gbk'参数。具体用哪个编码,要看数据本身的格式,不确定的时候可以先打开文件看一眼。
第二个是notebook的保存习惯。我会在完成代码部分后,把notebook导出成HTML文件再整体提交。这样老师不需要打开Jupyter就能看到全部图表和代码,视觉效果比直接丢一个.ipynb文件要友好很多。导出后我也会打开HTML快速下拉一遍,确认没有渲染问题,这一步用不了两分钟,但对最终交付的体验提升非常大。
6. 第二次作业之后,我开始坚持的事
6.1 建立自己的作业模板
第二次作业结束以后,我把整个流程沉淀成了一个模板,存成了自己的notebook骨架。模板里面预设好了代码结构、标题层级、Markdown常用写法、中文字体配置,甚至连“数据处理说明”那一段的位置都留好了。每次接到新作业,我先复制模板,再往里面填具体内容,省去了大量重复的组织工作。
这个习惯帮我省下的时间,比想象中多得多。因为作业写多了你会发现,大部分数据分析作业的结构是高度相似的:先描述数据,再清洗数据,然后分组统计,最后画图写结论。一旦把骨架固定下来,剩下的精力就可以集中放在具体数据和分析逻辑上,效率翻倍。
6.2 复盘时问自己三个问题
每次作业提交后,我会等结果出来再做一次复盘,复盘时只问三个问题。
- 我有没有重复踩上一次作业踩过的坑?
- 这次写的代码里,有哪些部分可以抽出复用?
- 如果数据量变成现在的一百倍,我的代码还跑得动吗?
第三个问题很关键。它能逼着你在写分组统计时认真想一想,自己的代码到底是一次性脚本,还是可扩展的数据处理流程。作业数据是十万行,学会处理十万行,和学会处理一千万行的思路是不同的。虽然课程作业不需要考虑那么大的数据量,但养成这个习惯,对以后面对真实数据时非常有帮助。
6.3 给后来者的三个小建议
第一个建议:不要最后一天开始写,宁可提前两天开始、留出修改余地,也不要赶在DDL前熬夜输出。第二个建议:把代码当成一个故事来写,每一个步骤的为什么写清楚,你的notebook就是一个完整的故事,评分自然会上去。第三个建议:多读优秀的公开notebook,学习别人是怎么组织分析思路的,模仿是提升最快的路径之一。
第二次作业让我真正明白了一个道理,学生作业和项目作品之间的距离,没有想象中那么大。你把每一次练习当作正经项目对待,收获就会远远超过那一个分数。