
1. 拿到“第二次作业”先别急着写代码第二次作业这个标题看起来平平无奇但我把它当成一个正经项目来对待了。原因很简单大部分课程或培训里的第一次作业本质上是让你熟悉环境和工具链基本照着示例就能跑通。到了第二次作业难度开始上一截台阶通常会叠加真实数据的噪声、多个知识点的组合以及一份说得过去的报告要求。换句话说第二次作业是第一个真正能暴露问题的地方。我这边的任务是处理一份带噪声的零售门店销售数据要求完成数据清洗、统计分析、可视化并产出一份完整的分析报告。数据是CSV格式两万多行包含日期、商品类别、销售数量、单价、门店编号、销售员编号、订单状态等字段。听起来不复杂但实际做起来远不是“导入pandas画几张图”那么简单。这篇博文不是什么标准答案而是我完成这份第二次作业的全过程记录包括我怎么拆题、怎么定方案、踩过哪些坑以及最终怎么把一份“能交差”的作业打磨成“拿得出手”的报告。如果你也正卡在类似的分析作业上或者刚入门Python数据处理这篇文章应该能帮你省下不少冤枉时间。先说一个我自己的教训拿到作业别急着打开编辑器就开始写代码。我见过很多同学包括我自己犯同一个错误——花半小时把数据读进来打印出几十行结果然后对着屏幕发呆不知道下一步该干什么。根源就在于没有先做题目的拆解。2. 拆题是门手艺从作业要求里读出隐藏评分点2.1 作业要求里被忽略的那行字这次作业的官方要求写得很简短大致是“对销售数据进行清洗、分析输出可视化图表和分析报告。”好家伙就这一句话信息量却极大。我把这句话拆成了四个层面去理解。第一个层面是功能要求数据清洗要做哪些操作、分析要得出什么结论、图表要画几张。第二个层面是质量要求数据清洗到什么程度算干净分析结论是否可信图表是否清晰。第三个层面是表达要求报告结构是否完整、逻辑是否通顺、有没有把图表和分析结合起来。第四个层面是细节要求代码注释、文件命名、输出格式这些零零碎碎的东西。把要求拆开之后我就给自己列了一份实际交付清单。数据清洗至少包括缺失值处理、重复值检查、异常值识别这三点是默认标配。统计分析要有总销售额、月度趋势、门店对比、品类结构分析这四个维度因为零售销售数据最能讲出故事的就是这四块。可视化图表对应上面每一个分析维度至少四张图。报告部分要有摘要、数据说明、分析过程、结论建议四个段落。我后来做了个有几分道理的判断作业表面上是让你做一次数据分析但真正的隐藏评分点其实是“展示你的思路和判断”。也就是老师想看到你面对脏数据时怎么决策面对模糊要求时怎么定范围。理解了这一点整个作业的形态就不一样了代码反而只是实现手段。2.2 明确工具和运行环境的边界工具选型也是一道关卡。我选择了Python 3.10 pandas Matplotlib Seaborn这套组合旁边放着Jupyter Notebook做交互式探索最后用VS Code整理最终代码。为什么不直接用Excel因为两万多行的数据量Excel虽然能打开但对数据进行可复现的清洗和统计时透视表的操作步骤很难被记录和复用。这里说一个环境配置的实际问题我一开始直接在系统Python里用pip安装了最新版pandas。结果新版本pandas对字符串类型的处理方式有变化导致我按照旧教程写的代码直接报错。这类“版本坑”在数据分析作业里非常常见。我的建议是动手之前先用以下命令把核心库装齐并且锁定版本。pip install pandas2.0.3 matplotlib3.7.2 seaborn0.12.2 jupyter notebook锁定版本的好处是让结果可复现。我在报告里也写清楚了环境版本这在专业报告里属于基本素养也让评分的人知道你的结果是在什么条件下跑出来的。2.3 数据字典不了解字段就谈不上分析拿到数据第一件事不是统计和画图而是先读数据字典。但我这次遇到的真实情况是——没有数据字典。这就很有意思了你得靠猜和探查去理解字段含义。销售日期是日期格式字符串商品类别里有“食品”“饮料”“日用品”等分类销售数量是整数单价是浮点数门店编号是类似ST001这样的文本销售员编号也类似订单状态有“已完成”“已退款”“处理中”三种。我还发现有一个几乎全空的备注字段但里面偶尔能看到退货原因。我建了一张字段理解表这既是我分析的起点也成了报告里的数据说明部分。字段名类型含义推测备注order_id字符串订单编号唯一性待查sale_date字符串销售日期需转为日期类型category字符串商品类别分类聚合的依据quantity整数销售数量异常值重点关注unit_price浮点数商品单价缺失值处理重点store_id字符串门店编号门店维度对比salesperson_id字符串销售员编号个人业绩维度order_status字符串订单状态是否纳入销售额统计remark字符串备注几乎为空后期忽略做出这张表之后我整个思路就清晰了。每一列数据都不是孤立的它们对应着后面每一个分析维度的可行性。很多初学者最大的问题不是不会写代码而是根本没想过字段背后代表什么业务含义。3. 数据清洗真正的硬仗从读入数据那一刻开始3.1 读入数据后先做三件事数据清洗听起来像是苦力活但做得好的话后面分析会顺滑很多。我读完第一版数据之后固定执行三件事看形状、看字段类型、看缺失值占比。这三个动作几乎能告诉你数据大概是个什么状态。import pandas as pd df pd.read_csv(sales_data.csv, encodingutf-8) print(df.shape) print(df.info()) print(df.isnull().sum())输出结果里我是这么解读的。两万一千多行、九列的数据数据结构本身不大。但info()结果里unit_price和quantity都有缺失备注字段基本全是缺失值。日期列显示的是object类型说明需要做类型转换。这里我要特别强调一个容易忽略的点read_csv的时候就要处理编码问题。很多csv文件是UTF-8签名还是GBK编码直接读会报错或乱码。经验做法是先用Python的codecs模块探测文件编码。我这次运气不错UTF-8读完没乱码但我在代码里还是加了一行编码声明并且在报告里说明假设数据文件统一采用UTF-8编码。3.2 缺失值处理不是删掉就行缺失值的正确处理方法是先看缺失比例再决定策略。我统计下来quantity缺失37行unit_price缺失52行remark字段缺失一万八千多行其他字段没有缺失。remark字段直接选择整体丢弃它缺失率接近85%几乎没有分析价值。quantity和unit_price的缺失比例都很低不到0.3%但这种少量缺失反而需要谨慎处理因为它可能不是随机缺失。我做了两个判断。第一个判断是quantity和unit_price是否成对缺失。通过索引对齐发现有31行同时缺这两个字段另外21行只有unit_price缺失6行只有quantity缺失。第二个判断是这些缺失值有没有明显的时间集中度或门店集中度。我用分组统计跑了一下发现这些缺失订单散落在各个时间段和各个门店没有明显的聚集模式。于是我的处理逻辑就明确了同时缺失quantity和unit_price的订单无法推算销售额直接删除只有unit_price缺失的用该商品类别的中位数填充避免均值被极端值拉偏只有quantity缺失的用该订单同类商品的平均数量向上取整填充。为什么填充而不是直接删掉老实说对一个分析作业来说删掉几十行影响不大。但从“展示判断力”的角度讲逐类处理缺失值能体现你的思考过程。而且后面每个月销售额统计时这几十个订单的差异确实会微弱影响月度趋势的精度虽然不影响总体结论但能处理还是尽量处理干净。3.3 重复值和异常值这里最容易翻车重复值检查我用的是subset参数重点关注order_id这一列。因为订单编号理论上应该唯一据此去重是合理的。查出来有19个重复的order_id进一步看是同一订单被导出了两次这种直接保留第一次出现即可。异常值处理才是真正的坑。我单看quantity的describe()结果时最小值和最大值分别是-5和2000。负数数量的订单肯定是数据录入错误或退货退款逻辑混进来了单笔数量2000也极不合理多半是把数量和小计金额搞混了。我的处理方案分三层负数quantity检查对应order_status发现这些订单都是“已退款”状态。对于它们我直接过滤掉整个订单而不是只剔除负数因为退款订单的整条数据都可能存在金额逻辑混乱。quantity超过300的单笔订单先看unit_price和quantity的乘积是否符合常理定位出8条可能的录入错误用一个经验阈值封顶处理超过300的按300计算。单价和数量的乘积新增一列sales_amount quantity * unit_price用于后面所有销售额相关计算。这个新列比单看任何一个原始字段都可靠。这一套处理做完之后我的数据变成了这样两万零几百行没有缺失值没有重复值没有负数和极端异常值同时多了一个sales_amount字段。整个过程我写进代码注释里每一步为什么这么处理都有记录。这部分内容其实比最终的报告结论更能展示数据分析的硬功夫。3.4 清洗过程的复盘记录我在做作业的过程中始终保持一个习惯把每一次处理前和处理后的记录数、关键统计量的变化都存到一个小本本里。比如清洗前总行数两万一千多清洗后两万零几百共删除X行。这个数字最后写进报告摘要里就成了“数据质量说明”的一部分。我见到很多同学做完清洗直接画图完全不记录清洗的变化。这样一来后面如果发现分析结论不对劲你根本不知道是数据处理导致的还是分析逻辑导致的。记录清洗过程本质上是给自己留一条可以回溯的路径。4. 统计分析和可视化让图表替你把话说清楚4.1 总销售额和总体概览要先算清楚清洗完数据第一步永远是算总量因为这决定了后面所有比例类指标的基准值。total_sales df[sales_amount].sum() order_count df[order_id].nunique() avg_order_value total_sales / order_count print(f总销售额: {total_sales:.2f}) print(f有效订单数: {order_count}) print(f客单价: {avg_order_value:.2f})这里有几个细节值得讲。第一订单数要用nunique()而不是count()因为count()统计的是行数而一个订单如果有多个商品行数会大于订单数。第二客单价是总销售额除以订单数不是除以总行数。这两个细节直接关系到后面所有结论的质量算错基准的话图表就算画得再漂亮也是错的方向。我算下来的总销售额大概在一百多万有效订单数接近两万客单价几十块符合零售快消品的数据特征。这个基准量级让我对整体数据有了一种“感觉”后续每一张图出来我都会先对照这个基准看是否合理。4.2 月度销售趋势先看长线再抠细节接下来是趋势分析。销售数据的核心叙事线就是时间维度。我按月聚合销售额画了一张折线图。import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False df[sale_date] pd.to_datetime(df[sale_date]) df[month] df[sale_date].dt.to_period(M) monthly df.groupby(month)[sales_amount].sum().reset_index() monthly[month] monthly[month].astype(str) plt.figure(figsize(12, 5)) sns.lineplot(datamonthly, xmonth, ysales_amount, markero) plt.xticks(rotation45) plt.title(每月销售额趋势) plt.tight_layout() plt.savefig(monthly_trend.png, dpi150) plt.show()这里有一个中国用户最容易踩的雷Matplotlib默认字体不包含中文字符如果不加plt.rcParams[font.sans-serif] [SimHei]这一行图表的标题和图例全都会变成方块格子。这个坑几乎每个用Matplotlib做中文可视化的人都会遇到一次。月度趋势的结论在我这组数据里非常清晰整体稳中有升中间某个月有一个明显的低谷。深入一看那个月的低谷恰好和一个大型促销活动的时间吻合导致部分用户在活动前延迟购买。这个发现很有意思它说明单纯画一张趋势图是不够的还要结合业务背景去解释曲线变化背后的原因。4.3 门店和品类对比横向对比的套路门店对比我用的柱状图品类结构用的饼图加横向条形图。门店对比这一块最需要小心的是销售额绝对值排名可能会被门店规模干扰。但我手上没有门店面积或店员数量这些辅助数据所以我只能在报告里说明“本次分析仅基于销售额规模维度不涉及门店效率对比”。品类分析的代码比较直接通过groupby聚合拿占比再用sort_values排序。category df.groupby(category)[sales_amount].sum().sort_values(ascendingFalse) category_ratio category / category.sum() * 100 plt.figure(figsize(8, 8)) plt.pie(category_ratio.values, labelscategory_ratio.index, autopct%.1f%%, startangle90) plt.title(各品类销售额占比) plt.tight_layout() plt.savefig(category_pie.png, dpi150) plt.show()品类占比做出来之后我注意到一个细节“生鲜”品类销售额占比不高但订单数量占比却明显高于其他品类。这说明生鲜是低客单价高频次商品而“家电”类虽然订单少单价高贡献了最多的销售额。如果没有把销售额和订单数分开看很容易得出错误结论。这两组对比合起来可以讲的故事是门店维度没有特别异常的短板或长板但品类维度呈现出明显的结构性差异。分析报告到这里就有一个非常自然的切入点下一步该关注什么。4.4 订单状态与退款分析另一个被忽略的宝藏我额外多做了一个维度按order_status聚合看退款和处理中订单对销售额的影响。这部分不在基础作业要求里但我认为它属于“做了会加分”的题。分析结论是已退款订单占总销售额的比例约百分之零点几比例很低说明业务整体健康。处理中订单数量很少但在某些门店有轻微聚集现象这可能是订单同步延迟导致。我把这个发现写进了报告的建议部分作为后续运营可以关注的细节。这种超出基本要求的分析在评分者眼里代表的是你有“主动挖掘数据价值”的意识而不是机械完成任务。即便结论本身不重要这个动作本身就传递了你的专业态度。5. 绘制图表过程中我踩过的真实雷区和救场方案5.1 中文乱码和坐标轴被裁断中文乱码问题前面已经提过。除了字体设置还有一个小细节保存图片时如果用了bbox_inchestight能让坐标轴标签不被裁掉。我一开始没加这个参数结果好几张图的横轴标签“十一月”和“十二月”在保存出来的PNG里被截了一半非常难看。统一处理方式plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False plt.savefig(output.png, dpi150, bbox_inchestight)如果你用的是Mac系统SimHei字体不一定存在可以改用[Arial Unicode MS]或者[PingFang SC]。跨平台做数据分析的人都有过这种困扰不同操作系统的字体名不一样最好写一个判断逻辑去适配。写进代码里的好处是换一台电脑跑代码图表依然能正常出图。5.2 时间序列的月份排序问题用字符串形式保存月份后绘制折线图时会出现一个典型问题X轴上的月份不是按照时间顺序排列而是按照字母顺序排列。比如“二月”会排在“十二月”前面因为拼音首字母排序时E排在S前面。这种排序错误对新手来说非常隐蔽因为图一眼看上去似乎正常但曲线的时间顺序被彻底打乱了。这次我用的是to_period后再转字符串的方式。to_period生成的Period对象本身是带时序顺序的但如果转成字符串就丢失了时间信息。更好的做法是不转字符串直接用PeriodIndex做横轴或者用日期时间格式做横轴然后用格式化函数让横轴标签只显示年份和月份。这段代码我改了三遍才满意df[sale_date] pd.to_datetime(df[sale_date]) df[month] df[sale_date].dt.to_period(M) monthly df.groupby(month)[sales_amount].sum() plt.figure(figsize(12, 5)) plt.plot(monthly.index.astype(str), monthly.values, markero) plt.xticks(rotation45) plt.title(各月销售额变化)关键点是groupby后的index是PeriodIndex绘图时我先在内部保持时间顺序只是显示时转成字符串。如果数据跨年比如2023年12月和2024年1月相邻单看字符串形式它们确实会连在一起这没问题。但如果有多个年份的数据字符串形式的“12月”和“1月”就会混在一起这时必须用完整的“2024-01”这种格式。5.3 配色和风格的克制Seaborn默认的主题和颜色在屏幕上看很舒服但打印出来或者放进论文报告里颜色对比度可能不够。我用的是sns.set_theme(stylewhitegrid)加少量颜色区分没有用彩虹色。一份数据分析作业图表最重要的不是好看而是信息传达准确。柱状图里我给每个门店分配一个颜色但数量有限。门店要是超过十家颜色就会重复或难以区分。这时候更好的做法是按数值排序后搭配渐变色或者用横向条形图按从大到小排列。横向条形图有一个额外的好处门店名称很长时也不会互相重叠。6. 分析报告的结构用一条主线串起所有图表6.1 报告不等于代码注释的拼凑很多同学会把报告写成“我读了数据、清洗了数据、画了图、得到结论”这种流水账。这种报告最大的问题是没有主线读的人看完之后不知道你想表达什么。我这次给自己定了一条叙事主线这家零售企业的整体销售情况如何分时间、分门店、分品类三个维度有什么结构特征数据里暴露出的业务风险有哪些以及下一步建议做点什么。主线定下来之后所有图表和结论都围绕这条线展开。报告结构我用了五个部分摘要用一段话说清数据来源、核心发现、整体结论。数据说明与清洗简述字段含义、样本量、清洗逻辑。统计分析分四个小节分别展示总量、趋势、门店、品类四个维度的图表。异常发现与归因列出订单状态、异常数据等有价值但不在任务要求里的洞察。结论与建议用三到五条清单收尾每条建议都对应前面的分析发现。6.2 图表、数字和结论三者要能互相印证报告写作中有一个容易被忽略的点图表里展示的每一个数字都要在正文里有对应的文字解释而不能只放图不解释。反过来说正文里给出每一个结论都必须能从前面的图表或数字里找到依据。我在写“生鲜品类订单量高但销售额占比低”这个结论时专门补了一个表格把各品类的订单数占比和销售额占比并列用数据证明结论的可靠性。这样做出来的报告每一句话都落在实处评分的人一眼就能看出你是认真做过分析的。6.3 结论建议要克制不要过度发散报告最后的建议部分最容易犯的毛病是从数据跳得太远。比如数据显示某品类销售额低就直接建议“增加该品类的营销投入”但数据根本没有告诉你营销投入和销售额之间的因果联系。更靠谱的做法是写“该品类销售额占比低于订单数占比建议进一步调查研究其定价或促销策略以判断是否为正常业务结构”。这种措辞既体现了你的数据分析判断力又承认了分析的边界。我在报告里始终用“建议进一步关注”而不是“应当立即调整”因为我的数据支撑不了那么强的因果断言。7. 从错误信息反推问题十条真实报错排查手记写代码过程中我遇到了数量可观的报错。我把其中最有代表性的几条记下来它们基本上涵盖了pandas和Matplotlib初学者会遇到的绝大多数问题。7.1 文件读取类报错UnicodeDecodeError: utf-8 codec cant decode byte 0xc4 in position ...这个报错最经典。原因就是文件不是UTF-8编码大概率是GBK或者GB2312。解决方案不是硬套utf-8而是先探测编码。with open(sales_data.csv, rb) as f: raw f.read(10000) import chardet print(chardet.detect(raw))如果检测结果是GBK就用pd.read_csv(sales_data.csv, encodinggbk)。这是一个小工具函数几乎每个做数据分析的人都应该备一份在工具箱里。7.2 类型转换类报错ValueError: could not convert string to float: 1,234这个报错也很常见。数据里的数字可能带千分位逗号或人民币符号pandas在转类型时会直接报错。处理方法是先去掉这些符号再转换df[unit_price] df[unit_price].astype(str).str.replace(,, ).str.replace(¥, ).astype(float)这里要注意一个细节replace操作后要确保没有空字符串或非数字字符否则astype(float)照样会失败。稳妥的做法是先把替换后的异常值定位出来再统一处理。7.3 索引类报错KeyError: sales_amount这种报错的常见原因是你把透视表的结果直接当成原数据框去使用列名已经不是原来的列名了。比如groupby之后当你不加as_indexFalse时聚合列变成了index容易引起困惑。我的习惯是groupby时一律as_indexFalse这样结果是一个普通的DataFrame列名清晰不容易犯KeyError。7.4 重复列名和数据框合并时踩的坑有一次我想关联两张表结果因为两个DataFrame都有“store_id”列合并后出现“store_id_x”和“store_id_y”两列后面引用时写错导致结果完全乱掉。后来改用合并时指定suffixes参数让两张表重复列名自动区分。df pd.merge(store_df, sales_df, onstore_id, suffixes(_store, _sale))这个小注释看起来简单实际排查花了将近四十分钟。所谓报错排查很多时候不是代码语法问题而是DataFrame语义层面的混乱。这些报错记录我全部写进了作业的附录里既展示了踩坑过程也让评分者看到我解决问题的能力。这部分内容在报告里不是必须的但我认为它证明了这份作业是真实执行出来的而不是简单照抄某个教程。8. 从第二次作业里我提炼出的通用方法论8.1 建立自己的“作业检查清单”完成这份作业之后我把整个过程的经验沉淀成一份可复用的检查清单。这份清单不仅适用于数据分析作业也适用于任何类似的项目型任务。需求拆解作业要求是否包含隐藏评分点交付物清单是什么。数据理解每个字段的业务含义缺失和异常的可能性数据字典存在与否。清洗策略删除、填充、转换三类操作分别用在哪些场景。分析维度至少覆盖整体概况、时间维度、对比维度三个层次。可视化检查字体、坐标轴、图片分辨率导出后是否被裁切。报告完整性图表是否配了解释结论是否能从数字中找到依据。可复现性代码注释是否清晰依赖库版本是否锁定。自审复盘如果重新做一遍哪些地方能做得更好。这份清单我计划在后续的每次作业里都用起来。因为第二次作业让我意识到技能类的评分从来不只是考察你的代码跑没跑通而是考察你在限定条件下做出合理决策的能力。8.2 “第二次”为什么是个分水岭如果只从字面看“第二次作业”平淡无奇但实际做完一遍后我有了更深的理解。第一次作业是给你确定性的路径比如“用print输出一段文字”“调用一个函数处理列表”。第二次作业则是给你一个半开放的问题你需要自己去定义解题路径同时还要展示出你对工具的熟练程度和理解深度。能不能跨过这个分水岭很大程度取决于你的学习方式。如果之前学pandas时只是跟着教程敲代码没有思考每个函数在真实数据场景下到底解决什么问题那么第二次作业就很容易让你手忙脚乱。反过来如果你在第一轮学习时就把每个知识点的“为什么”解决了那么第二次作业其实是一次自由发挥的机会。8.3 比作业更重要的事留下自己的分析模板我做完了这份作业最大的收获不是什么高深的算法而是一套属于我自己的数据分析项目模板。从字段理解表、清洗决策记录、图表检查流程到报告结构这些都是可以复用的资产。下一次再接新的分析任务我不需要从零开始只需要把模板里的数据路径、字段字典换掉就能迅速进入状态。这份模板就是我简历里引用得最多、面试时讲得最流畅的实战经验来源。第二次作业让我学会了“做一份工作沉淀一套方法”这笔账怎么算都值。如果你也正在做类似的数据分析作业我想分享一个建议把你做作业的每一个决策记录下来不论对错。这些记录将会是你的分析思路的最完整呈现。从功利的角度说这是加分项。从长期角度看这是你建立自己判断体系的第一步。