
看到这个标题你可能觉得奇怪“第二次作业”算什么项目但如果你正在上一门需要写代码的课或者你身边有正在上课的朋友你会明白这四个字的分量。第一次作业是热身大家都在同一起跑线照着模板敲一遍也能过第三次以后你已经摸清了老师的套路开始学会偷懒和抱团唯独第二次作业是你第一次一个人面对模糊的题面、一堆不太干净的数据以及“我好像学过但怎么都用不出来”的挫败感。我这次复盘的是自己补修的一门Python数据分析课的第二次作业题目叫“销售订单数据的清洗与基础分析”。题面只有一句话读取给定的CSV文件完成数据清洗做至少三个维度的分析输出两张以上图表最后写一份Markdown格式的作业报告。当时看到这个题面我的第一反应是“就这”真正动手之后才发现从“看懂题面”到“交出一份能看的作业”中间隔着的不是代码量而是思路。第一节课我们学了变量、循环、列表和字典第一次作业是打印九九乘法表、统计一段文本里的词频属于那种“只要照着教程敲总能跑通”的练习。第二次作业直接跳到了真实场景三千多行销售数据有空的订单号有负数的数量有格式五花八门的日期还有对不上的金额。教程里用的都是干净整洁的示例数据集现实里的数据不会等你准备好才变脏。这份作业考察的不只是Pandas的API熟不熟练更是你面对一团乱麻时有没有一套自己的处理逻辑。1. 第二次作业到底意味着什么想要把这份作业做好先得理解它为什么存在。很多同学拿到题面就开始查Pandas的文档这当然没错但方向错了。这不是一道“你记得多少函数”的考试而是一道“你面对实际问题时怎么组织思路”的考试。函数忘了可以查思路乱了才是真的灾难。1.1 作业题面到底想考察什么一份看似简单的作业题面背后往往藏着好几个评分维度。我把这次作业的考察点拆开来看主要有四层第一层是工具使用。能不能正确读取CSV能不能识别文件编码问题能不能用Pandas完成基础的筛选、分组、聚合操作。这层考察的是你会不会用工具属于底线要求。第二层是数据清洗意识。拿到数据之后你是直接开始算平均值还是先检查空值、重复值、异常值这层考察的是你有没有“脏数据”的概念。很多第一次接触真实数据的人直接栽在这一步因为他们根本没想到数据会缺、会重、会错。第三层是分析思路。老师不会告诉你“你应该分析哪个维度”题面只说“至少三个维度”。这意味着你自己得判断哪些分析是有业务意义的哪些分析只是把数据打印出来。月度趋势、品类结构、客户复购这三个方向是我最后选定的因为它们分别对应了“业务好不好”“什么东西卖得好”“客户留不留得住”三个问题合起来才是一个完整的小故事。第四层是表达与交付。分析结果不是甩一张图表就完事你得用文字把图表里的信息讲清楚让一个没看过数据的人也能看懂你的结论。这一点在作业评分里占的比重往往比大多数人想象中更高。四层能力叠在一起就是这份作业真正的考察目标你能不能独立完成一个“从数据到结论”的小闭环。放在实际工作里这个闭环的长度可能会拉长到几周、几个月但逻辑是完全一样的。所以第二次作业虽然名字平平无奇它其实是课程设计里最关键的一次“能力体检”。1.2 为什么第二次作业最容易翻车我观察了一下周围同学的翻车情况发现一个规律第一次作业大家分数都差不多第二次作业开始出现巨大的分差。不是代码能力突然拉开了而是处理问题的策略拉开了。第一次作业是“路径已知”的任务目标明确、步骤明确、连报错信息都能在搜索引擎里找到一模一样的。第二次作业开始变成“路径未知”的任务文件可能读不进来日期可能有几种格式同一个字段里可能混着数字和文本。这时候习惯照着教程一步步走的人会卡住因为教程里没有教他“读不进来怎么办”而习惯先看数据、再想方案、最后动手的人反而会觉得没那么难。这里可以打一个比方第一次作业像驾校里的直线行驶教练把车给你摆正了你只需要握稳方向盘踩油门第二次作业像倒车入库没有参照物你得自己看后视镜、判断距离、微调方向。很多人不是输在手上是输在“以为倒车入库还是直线行驶”的心态上。我自己一开始也犯了这个毛病。拿到CSV文件后没有先打开看直接写了pd.read_csv(sales.csv)结果报编码错误改成encodingutf-8还是报错最后试到gbk才读进来。后来我才意识到花两分钟先打开文件看一眼比在代码里盲猜编码快得多。这个坑踩得很值因为它让我养成一个习惯任何数据任务第一步永远是“看数据”而不是“写代码”。2. 开工之前先把这些准备做好第二次作业的核心难点是“想清楚再动手”。我大概花了半天时间准备实际写代码加改报告用了两个晚上。如果回到最开始我会把准备阶段做得更充分一些因为后面所有返工几乎都是因为前期有一些东西没想清楚。2.1 环境与工具清单先说我用的环境Windows 11Python 3.10Jupyter NotebookPandas 2.0.3Matplotlib 3.7.2。有些同学用VS Code写.py文件但对我来说做数据清洗和分析这类需要不断看中间结果的任务Jupyter Notebook比脚本顺手得多。原因很简单Notebook可以分格执行读取数据后先看一看长什么样再决定下一步怎么写脚本则要一次性跑完整个文件中间任何一步报错都会中断调试成本高不少。如果你想复现这次作业可以准备这样一个requirements.txtpandas2.0.3 matplotlib3.7.2 openpyxl3.1.2 jupyter1.0.0安装命令是pip install -r requirements.txt。openpyxl可能有人觉得用不上但如果老师要求你输出Excel格式的结果表没有它to_excel会直接报错。提前装好省得临时补。还有一个小建议把作业相关的文件单独放一个目录比如second_homework/里面分data/、code/、output/、report/四个子目录。数据文件放data/notebook放code/生成的图表和结果表放output/最后的报告放report/。看起来有点小题大做但后面你就知道这样分目录能省掉大量“这个文件存哪了”的找文件时间。2.2 先读懂数据再动手写代码拿到数据之后我做的第一件事不是写代码而是用文本编辑器直接打开CSV文件看了前几十行。这一步非常重要因为Pandas读取时遇到的所有编码问题、分隔符问题、列名问题你都能在看原始文件时提前发现。这份数据是模拟的某电商平台2023年销售订单一共14个字段、3200多行。字段包括订单号、订单行号、下单时间、客户ID、客户城市、商品ID、商品名称、品类、单价、数量、销售额、支付方式、订单状态、备注。用Pandas读进来之后我又跑了一遍df.info()和df.describe()。df.info()能看到每列的非空数量和数据类型哪列缺数据、哪列类型不对一眼就能看出来df.describe()能给出数值列的均值、标准差、最小值、最大值异常值的线索通常也藏在这里。比如我这份数据里数量列的最小值是-2正常人不会买-2件商品这显然就是异常值。2.3 把作业拆成任务清单准备工作里最重要的一步是把作业拆成可执行的任务清单。我是这么拆的数据读取与编码处理预计0.5小时数据概览结构、类型、缺失、分布预计0.5小时数据清洗空值、重复值、异常值处理预计1小时数据加工类型转换、新增辅助列预计0.5小时三维度分析月度趋势、品类结构、客户复购预计1.5小时可视化折线图、柱状图、保存图表预计1小时报告撰写整理过程、结论、附录代码预计1.5小时拆完这个清单你会发现作业其实没有想象中那么大。很多人翻车是因为盯着一个空白notebook不知道从哪里开始或者写了一会儿发现思路乱了。任务清单的好处是你随时知道“我现在在做什么”“下一步做什么”心态会稳很多。3. 完整实现从读取到报告的全过程下面进入正题把这次作业的完整实现过程过一遍。代码是简化过的版本但核心步骤都是这次作业里真实用到的。3.1 数据读取与环境检查读取文件的代码很简单但坑都在细节里import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(data/sales_data.csv, encodinggbk) print(df.shape) print(df.head())第一次跑的时候报错信息是UnicodeDecodeError: utf-8 codec cant decode byte...原因是文件里含有中文列名和中文内容但保存时用的不是UTF-8编码。CSV文件本身没有携带编码信息Pandas默认按UTF-8读取遇到中文就炸了。解决办法是换成gbk或者gb18030gb18030比gbk覆盖的字符更全遇到生僻字也不容易报错。读进来之后先跑df.shape看行列数再跑df.head()看前几行。这一步的核心目的是确认“文件成功读进来了”以及“读进来的内容长什么样”。我习惯把df.head()的结果截图存到一个临时目录里后面写报告的时候可以直接用。3.2 数据清洗三板斧空值、重复、异常数据清洗是整个作业里工时最长、也最容易被扣分的部分。我总结了三个主题空值、重复值、异常值。先看空值情况print(df.isnull().sum())isnull().sum()会按列统计空值数量。我这份数据里备注列有几百个空值下单时间有少量空值订单号也有几十个空值。处理方式是订单号为空的行直接删除因为订单号是主键缺失了就没有分析价值。下单时间为空的行先尝试用同订单号的其他行填充时间填充不了的再删除。备注为空的行保留备注本来就是可选项缺失不影响分析。这里有一个关键原则空值处理不是“越多越好”或“删得越多越干净”而是“每一处处理都有依据”。比如备注列直接填“无”就行根本不用删行而订单号缺失不能随意填充因为编一个订单号会污染数据。再看重复值print(df.duplicated().sum())duplicated()返回的是布尔序列sum()可以直接统计出重复行数。我这份数据里重复行不多只有二十几行直接用df df.drop_duplicates()去掉。需要提醒的是drop_duplicates()默认是“整行完全一样才算重复”如果你希望按照“订单号商品ID”这样的组合键去重要加上subset参数df df.drop_duplicates(subset[订单号, 商品ID])最后是异常值。数据里有一些明显不符合业务逻辑的记录数量为负、单价为0、销售额不等于“单价乘以数量”。我写了一个筛选条件把所有异常记录挑出来看cond_amount_negative df[数量] 0 cond_unit_price_zero df[单价] 0 cond_sales_mismatch df[销售额] ! df[单价] * df[数量] print(df[cond_amount_negative | cond_unit_price_zero | cond_sales_mismatch].head())逐条检查后发现这些异常记录的分布没有规律更像是录入错误或模拟时故意埋的雷所以直接删除。删除前我把异常记录数打出来写报告时作为“清洗前后数据量对比”的依据。清洗前的数据是3200多行清洗后剩3000行左右。不要小看这个数字报告里写清楚“原始数据3218行清洗后3002行删除异常记录216行”这段内容在评分老师眼里是很加分的因为它证明你真的检查过数据而不是直接把数据塞进模型里。3.3 数据加工类型转换与新增列清洗完数据下一步是让数据类型更“好用”。默认读进来的日期列是字符串金额列也未必是数值类型直接做聚合计算会得到一堆奇怪的结果。df[下单时间] pd.to_datetime(df[下单时间], errorscoerce) df[销售额] df[销售额].astype(float)pd.to_datetime可以把字符串转成Pandas的日期时间类型但数据里可能会有个别日期格式不对这时候加errorscoerce转不了的会变成NaT缺失时间后面统一处理。astype(float)则是把销售额强制转成浮点数确保后续求和、求均值不会出问题。日期转好之后我新增了两个辅助列月份和星期。df[月份] df[下单时间].dt.month df[星期] df[下单时间].dt.dayofweek月份用来做月度趋势分析星期用来观察一周里哪天订单多。后面分析阶段你会发现这两个辅助列帮了大忙。如果一开始没有转日期类型.dt.month这种操作根本调用不了报错信息是AttributeError: Can only use .dt accessor with datetimelike values看到这个报错你就知道日期类型没转成功。3.4 三个角度的分析我的三个分析维度分别是月度销售额趋势、品类销售结构、客户复购情况。下面逐个说。按月统计销售额和订单量monthly df.groupby(月份)[销售额].agg([sum, mean, count]).reset_index() print(monthly)groupby(月份)会按月份分组agg([sum, mean, count])在同一组上同时计算总和、均值、数量。.reset_index()把分组键月份从索引转回普通列方便后面绘图。结果大体上是全年销售额呈“中间高两头低”的形态六七月份有一个明显峰十一月份也有一个小高峰。这个结论可以进一步写成“夏季促销带动了整体销售额双十一也有拉动作用”分析就不再是干巴巴的数字了。再看品类结构category df.groupby(品类)[销售额].sum().sort_values(ascendingFalse).reset_index() print(category.head(5))sort_values(ascendingFalse)按销售额从高到低排序取前五名得到销售额最高的几个品类。我这份数据里排在前面的品类是“数码配件”“家居日用”“美妆个护”三个品类合计占了总销售额的六成左右。这说明品类的头部集中度很高平台做运营时可以优先保障这几个品类的库存和流量位。第三维度是客户复购情况repurchase df.groupby(客户ID)[订单号].nunique() repurchase_counts repurchase.value_counts() print(repurchase_counts.head(10))nunique()统计每个客户ID下不同订单号的数量也就是每个客户一共下过几单。再把“下单次数”做一次value_counts()就能看到“只买过一单的客户有多少买过两单的客户有多少”。这份数据里只下过一单的客户占了接近70%复购率偏低。从业务角度说这类平台的增长重点应该放在提升老客户复购上而不是一味拉新。3.5 两张图从数据到结论图表是这次作业的硬性要求。Matplotlib画图本身不难难在中文显示和图表的美观程度。先看一个很容易踩的坑图表里的中文全部变成方块。解决办法是在画图前设置中文字体plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] FalseSimHei是Windows自带的黑体如果你的系统没有这个字体可以换Microsoft YaHei微软雅黑。第二行axes.unicode_minus是负号正常显示的开关如果不设置坐标轴上的负号会显示成一个小方块。这个坑几乎每个用Matplotlib画中文图的人都会踩建议直接写成默认配置。画折线图展示月度销售额趋势fig, ax plt.subplots(figsize(10, 5)) ax.plot(monthly[月份], monthly[sum], markero, linewidth2) ax.set_title(2023年月度销售额趋势) ax.set_xlabel(月份) ax.set_ylabel(销售额元) plt.savefig(output/monthly_sales.png, dpi150, bbox_inchestight) plt.show()figsize控制图的大小markero在每个数据点上画圆点方便看具体数值savefig里的dpi150是输出分辨率bbox_inchestight会自动裁剪空白边缘让图看起来更紧凑。保存图片这一步经常有人漏掉只plt.show()不保存最后写报告时发现没有图可插又得回来补跑一遍浪费时间。再画一张柱状图展示品类Top5fig, ax plt.subplots(figsize(10, 5)) ax.bar(category.head(5)[品类], category.head(5)[销售额], colorsteelblue) ax.set_title(销售额Top5品类) ax.set_xlabel(品类) ax.set_ylabel(销售额元) plt.xticks(rotation30) plt.savefig(output/category_top5.png, dpi150, bbox_inchestight) plt.show()rotation30让x轴的品类名称旋转30度避免文字太长互相重叠。颜色我选了steelblue看着比较稳不会花里胡哨。图不是画完就完事了。我在这两张图下面各写了一段解读文字比如“从折线图可以看出6-7月销售额明显高于其他月份推测与夏季促销相关11月有小幅回升可能受双十一带动。整体趋势呈季节性波动Q3为销售旺季。”图表是给结论服务的一张没有文字解读的图在作业里只是装饰品。3.6 报告撰写让老师一眼看到你的思路最后的Markdown报告我按这个结构组织作业题目数据说明数据来源、字段含义、数据量清洗过程每一步做了什么、为什么做、清洗前后对比分析与结论三个维度每个维度都有一段文字加一张图表附录完整代码和运行说明写报告的时候我给自己定了一个规矩每个分析结论必须有数据支撑不能只说“销售额上升了”要说“Q3月份的平均销售额比Q2高18.6%”。多写一个数字结论的可信度立刻不一样。还有一个容易被忽略的细节报告里插入图片后图片的路径要是相对路径比如output/monthly_sales.png而不是你本机的绝对路径C:/Users/xxx/...。如果老师把你的报告文件拷到别处打开绝对路径的图片会全部失效这是很尴尬的交付事故。4. 踩坑记录我前后改了四版才交这次作业前前后后改了四版每一版都有不同的问题。这里把最典型的几个坑记录下来希望能帮后面做作业的人少走点弯路。4.1 数据读不进来编码问题第一版卡在最开始的数据读取上报错UnicodeDecodeError。这个报错的本质是文件里的字节序列用当前指定的编码方式无法解码。CSV文件是没有编码元信息的你只能靠试。我的经验是拿到一个中文CSV优先试utf-8报错就试gbk还不行就试gb18030。你也可以先用文本编辑器打开文件看右下角的编码提示但这招不是每次都准最后还是以Pandas能不能读进来为准。4.2 链式赋值的警告第二版遇到的是SettingWithCopyWarning警告。代码如下df_sub df[df[品类] 数码配件] df_sub[备注] 数码运行后Pandas弹出一长串警告大意是“你正在尝试在DataFrame的切片副本上设置值”。这个概念第一次接触确实容易懵。简单说df[条件]返回的可能不是原数据的一个视图而是副本你在副本上改东西原数据可能没变或者以后运行结果不稳定。正确做法是使用.locdf.loc[df[品类] 数码配件, 备注] 数码这样是明确告诉Pandas我要在原DataFrame上、按行条件、修改指定列。以后只要遇到“按条件修改某列”统一用.loc不要用链式赋值能少踩很多坑。4.3 图表的文字全部变成方块第三版的问题是图表里的中文显示成方块原因是Matplotlib默认字体里没有中文字符。我用了上面的rcParams配置解决。如果你在Jupyter里改了配置还是不生效可以试试重启内核或者换字体名称再试一下。这个坑比较玄学我的经验是SimHei和Microsoft YaHei都写进代码里哪个能用用哪个。4.4 日期格式不统一导致转换报错第四版遇到日期字符串格式不统一的问题。数据里大多数日期是2023-03-15 12:30:00但混着几条2023/03/15。pd.to_datetime会自动识别这两种格式但偶尔会遇到类似于20230315这种纯数字字符串这时候Pandas可能识别不了报错或者转成NaT。我的处理方式是不用Pandas默认猜而是手动指定格式df[下单时间] pd.to_datetime(df[下单时间], format%Y-%m-%d %H:%M:%S, errorscoerce)format参数告诉Pandas这个字符串的标准格式解析速度更快也更不容易出错errorscoerce保证解析不了的值变成NaT而不是直接抛出异常后续再针对NaT做过滤。整理一下这四个坑其实都指向同一个底层能力读报错信息。第一次见这些报错的人会觉得天都要塌了但你只要记住“报错信息里最上面的那行英文或中文翻译才是关键”慢慢就会习惯从报错入手倒推问题。我的一个笨办法是把每次遇到的报错截图保存在一个叫error_log/的文件夹里后面再遇到同样的报错直接去文件夹里翻对策比自己重新想快得多。5. 复盘做完这次作业我整理了这些经验作业提交之后我找老师要了一份评语也对照评分标准重新看了一遍自己的代码和报告。结合这些反馈把这次作业里沉淀下来的经验整理成几条算是给自己下一次作业立个规矩。第一条是“作业完成的判断标准不是代码能跑而是每一步都能讲清楚为什么”。当初我写第一版的时候代码跑通了但老师一问“你为什么删除这些空值”我愣住了因为当时只是看着不舒服就顺手删了。后来我强迫自己给每个处理步骤写一行注释为什么删除、为什么填充、为什么保留。写不出来的地方说明自己还没真正想通。第二条是“把每一次作业都当成一个小项目来管理”。目录分好、任务拆好、时间预估好、中间产物保存好哪怕这次作业只有几百行代码。这听起来很笨但正是这种笨方法让我在第四次修改时没有推倒重来因为前面的每一步都有记录可以快速定位是哪一版、哪一步出了问题。第三条是“尽量让报告里的每个结论都有数字化支撑”。老师给的高分范例里几乎每句话都能对应到一个具体数字“销售额同比下降12%”“复购率低于行业均值15个百分点”。这种表达方式不仅出现在作业里也是工作中汇报数据分析结果的基本要求。第四条是“为下一次作业准备一套模板”。我把这次的notebook开头部分做成了一个小模板读取环境检查、读取数据、概览、清洗、加工、分析、可视化每一步都有对应的代码骨架。下次拿到新数据我只需要替换字段名和具体的清洗逻辑框架可以直接复用。这个模板帮我节省了大量重复劳动也让思路始终清晰。最后再分享一个实际体验。做第二次作业那两天我一度觉得自己是最笨的人因为每个小问题都要查半天。但交完之后回头一看恰恰是这些“卡住”的时刻让我真正学会了怎么读报错、怎么搜资料、怎么验证自己的猜测。第一次作业里那些“照着敲就能跑”的代码早就忘光了第二次作业里踩过的每一个坑到现在都还记得清清楚楚。这可能就是这类作业设计的用意它不指望你一次掌握所有东西但它用一道让人不舒服的题目逼着你把之前学的东西真正用起来。“第二次作业”这个标题以后可能没人会记得但做完它之后建立起来的处理问题的流程感我是真的留住了。