
1. 拿到hica第一次作业后的第一反应这份作业到底在考什么报名hica课程之前我在数据分析这条路上已经摸爬滚打了一阵子自认为Excel函数、SQL基础查询这些都不算陌生。但真正打开hica第一次作业的题目文档时我还是愣了一下题目描述非常简短核心就一句话——对给定数据集完成清洗、特征构造与基础可视化并输出一份分析报告。没有任何多余的提示没有字段字典没有期望输出格式。这种开放式作业反而是最考验人的。hica第一次作业在圈内口碑一直很两极分化有人觉得简单到像走流程有人觉得无从下手。我属于后者偏前者——说难吧每一步拆开都是基本功说不难吧把基本功连成一条完整链路还得保证每一步都禁得起追问这就不太容易了。我把这次作业的价值拆成三层来看也建议正在做或准备做这份作业的朋友先建立这个整体认知表面考核能不能完成数据导入、清洗、可视化和报告撰写这套标准流程。进阶考核面对不完整的字段说明和脏数据能不能自己定义什么是干净并对每个处理动作给出合理依据。隐藏考核报告能不能让一个完全不了解数据集的人看懂你的决策过程以及每一步处理对后续结果产生了什么影响。很多人交完作业后只觉得我做完了却答不上来你为什么要删掉这些行为什么用中位数而不是均值填充。hica第一次作业真正想逼出来的就是这种对自己操作了然于胸的底气。这份作业的前置知识门槛其实不高会Python基础语法、会用pandas读写csv、能调用matplotlib画图就足够起步。难点从来不在API而在数据理解。所以这篇博文我不想只贴一段标准答案而是完整还原我从接到题目到交付报告的整个过程包括中间走错的路、查资料的过程、以及最终被我自己推翻的三个方案。2. 环境准备与数据集初探先别急着写代码把数据摸透再说2.1 最小可用的环境清单hica第一次作业对运行环境没有硬性要求官方推荐的是本地Python环境或者在线notebook。我自己用的是本地Anaconda管理环境Python版本3.10核心库版本如下供你参考pandas 2.0.3 numpy 1.24.3 matplotlib 3.7.2 seaborn 0.12.2 openpyxl 3.1.2用于导出Excel格式结果版本不需要完全一致但建议pandas不低于1.5否则一些链式操作语法和字符串方法的行为会有差异。安装命令很简单一行搞定pip install pandas numpy matplotlib seaborn openpyxl如果说有什么环境层面的坑那就是matplotlib默认不支持中文显示。作业数据里恰好有中文分类字段第一次画图时所有中文标签都会变成方块。解决方案是显式指定字体我会在后面可视化段落详细展开。2.2 数据集的字段全貌与第一印象拿到数据后我第一件事不是写处理代码而是用最原始的眼睛扫描法看整体结构。这里给新手一个建议拿到任何数据集先别急着用info和describe先把数据当做一个真实业务场景下的表格用直觉感受一下每列是什么、列和列之间可能有什么关系。hica第一次作业提供的数据集是一份模拟的电商交易记录包含大约一万条订单明细。我读取后看到的字段如下字段名数据类型含义推测初步印象order_idobject订单编号同一个订单可能有多行多商品order_dateobject下单日期疑似含时间部分需要解析customer_idobject客户编号重复值非常多product_categoryobject商品品类中文文本可能含有空白字符product_nameobject商品名称存在同物不同名的情况quantityint64购买数量正常范围1~10unit_pricefloat64单价存在0值和异常小值total_amountfloat64总金额与quantity和unit_price关系需要验证光是这段初步观察我就已经有了几个明确疑点order_date为什么是object而不是datetime说明清洗时必须要做类型转换。单价存在0值这在真实业务里可能是赠品、可能是数据错误需要结合数量和其他字段推断不能武断删除。total_amount和quantity×unit_price是否完全相等这直接决定了总金额列能不能作为可靠的分析依据。我把这些疑点写在一张草稿纸上然后才开始跑代码。这一步看似笨拙但它让我在后续每做一个处理动作时都有明确的目标而不是因为大家都在这么做所以我也这么做。2.3 info与describe结果里藏着哪些坑跑完df.info()和df.describe()之后我拿到了更具体的问题清单缺失值情况product_category缺失约120行product_name缺失约85行unit_price缺失约30行。占比都不超过2%但也正因如此处理方式会对结果稳定性产生不成比例的影响。describe显示unit_price的最小值是0但25%分位数是29.9中位数是59.9。这说明0值并不是常态更像是异常点。数据量约一万行不算大所以性能不需要担心反而可以放开手脚做多次试验性操作。这里我需要说一个很多人容易忽略的点不要只盯着缺失值的数量更要看缺失值分布在哪些特征组合里。比如我发现product_category缺失的120行里有近100行的quantity大于5这个分布特征完全影响了我后续对缺失行的处理策略——如果只是简单按行删除等于把这批大单凭空抹掉了非常可惜。这个维度我在第4节还会具体展开。3. 数据清洗的关键动作每一刀砍下去都要能说出为什么3.1 日期字段的标准化处理order_date列我看到的样本长这样2024/3/15 14:32和2024-03-15 14:32混存。这种同一列两种分隔符的情况在真实数据里太常见了Excel导出的、手工录入的、系统拼接的都会造成这类问题。我的处理方式是直接用pandas的to_datetime并设置format参数为混合匹配import pandas as pd df[order_date] pd.to_datetime( df[order_date], formatmixed, # pandas 2.0以上支持自动混合格式解析 errorscoerce # 无法解析的置为NaT后续统一处理 )使用formatmixed会让解析速度比默认稍慢但在这个数据量级完全无感。errorscoerce是一个保险策略它能把无法识别的日期值变成NaT而不是直接抛出异常导致脚本中断。转换后我顺手检查了解析失败的数量print(日期解析失败的行数, df[order_date].isna().sum())结果是0。也就是说这个数据集的日期列只是格式混乱没有真正的非法值。但如果你的数据集出现解析失败不要急着删行先打印出失败样本看看是格式问题还是内容问题。很多时候Excel里的2024/2/30这类不存在日期也会被解析成NaT这时候需要业务判断是修正还是剔除。3.2 文本字段清洗隐藏的空格和同义不同名中文字段最阴间的坑不是缺失而是看似相同实则不同的取值。我用value_counts()统计product_category后发现 数码产品和数码产品同时存在前者开头多了一个空格。这种肉眼难查的差异在分组统计时会直接变成两个类别导致图表里出现莫名其妙的独立长条。解决方式非常直接# 去除首尾空白字符 df[product_category] df[product_category].str.strip() df[product_name] df[product_name].str.strip() # 统一大小写针对英文字段虽然本数据集没有但建议养成习惯 # df[customer_id] df[customer_id].str.upper()做完这一步我再统计类别数量从17类降到了14类。换句话说有3个类别纯粹是空格造成的幽灵分类。如果你跳过这步后面所有基于品类的分析图都会带着错误信息而且你很难第一时间察觉因为图表整体形状看起来挺正常。product_name的清洗稍微复杂一点。我发现了类似苹果Apple iPhone15和iPhone15 苹果这样的顺序颠倒问题。完全自动化的去重合并需要文本相似度算法对这份作业来说有点杀鸡用牛刀。我的处理思路是基于关键词提取一级类别把product_name映射到上级分类后再做统计。具体做法是在构造特征时从商品名里提取品牌关键词和型号关键词这个我在第4节细说。3.3 缺失值处理策略不是填或删二选一而是看业务后果缺失值处理是清洗环节的灵魂考题。hica第一次作业里三类缺失值分别出现在product_category、product_name和unit_price上。我逐一分析product_category缺失的120行考虑到前面提到的大单聚集现象删掉这批行会导致后续按品类聚合的订单金额分布失真。补的话怎么补这批行仍然有product_name所以最合理的办法是从product_name里反推品类。比如product_name包含手机就归入数码产品包含洗面奶就归入个护美妆。我写了一个基于关键词映射的函数来处理。product_name缺失的85行这比category缺失更麻烦因为没有信息可以反推商品名。但这批行的product_category是完整的所以它们仍然有分析价值。我的做法是不删行而是将product_name填充为未知商品_[品类]这样既保留了记录又让后续文本分析知道这是一个待补全样本。unit_price缺失的30行单价是最棘手的因为它直接参与金额计算。我查看了这批行的quantity和total_amount发现total_amount是完整的。于是我用total_amount / quantity反推出单价再用品类中位数核对误差在合理范围内。这样处理比直接用均值或中位数填充更有说服力因为你使用了行内信息而不是全局信息。# 反推缺失单价 mask df[unit_price].isna() df.loc[mask, unit_price] df.loc[mask, total_amount] / df.loc[mask, quantity] # 对反推结果做合理性校验与品类中位数偏差超过10倍则置为异常 import numpy as np cat_median df.groupby(product_category)[unit_price].transform(median) deviation df[unit_price] / cat_median df.loc[mask (deviation 10), unit_price] np.nan第二段代码是我后来补的因为反推结果里有两条记录高得离谱明显是total_amount本身录错了。这时候用品类中位数兜底才算是把反推法和统计填充法结合起来了。对于用均值还是中位数填充我的原则是先看分布是否对称再看是否存在极端值。单价分布明显右偏均值会被高价商品拉高这时中位数是更稳健的中心趋势度量。你可以自己看一眼df[unit_price].hist()再决定而不是机械套用缺失率低所以用均值。3.4 去重逻辑不是所有重复行都该删hica第一次作业的数据里完全重复的行所有字段都一样有17条这种我直接drop_duplicates()删掉了。但有一种伪重复更有意思同一个order_id、同一个product_name出现了两次但quantity不同。这种不该删除因为它可能代表一个订单里同款商品分两次加入购物车。我给出的去重策略是# 真正完全重复的行 df df.drop_duplicates() # 保留伪重复按order_id product_name unit_price分组 # 如果quantity不同则合并为一行并将quantity相加 dup_key [order_id, product_name, unit_price] df df.groupby(dup_key, as_indexFalse).agg({ order_date: first, customer_id: first, product_category: first, quantity: sum, total_amount: sum })这步做完我回头重新计算了一次总金额确认数据量和业务语义都对得上才继续。清洗环节必须有一个自检时刻——你不能闷头处理完就往前走要停下来看看关键指标的变化是否符合预期。这里我建议至少检查三件事总行数变化、缺失值清零情况、关键字段的sum与清洗前对比。4. 特征构造与业务洞察一万行数据的价值在变出新维度4.1 从日期里拆出星期几和时间段洞察用户下单习惯hica第一次作业只要求做基础可视化但基础不等于平庸。我从清洗后的order_date里拆出了几个有意义的新特征星期几order_weekday周一对应0周日对应6。下单时段order_hour取hour然后划分为早间/午间/晚间/凌晨四个区间。是否为周末is_weekend。这三个特征的价值在于把时间维度从某一天升级成行为模式。我在后续分析中就发现了一个有意思的现象周末订单的客单价明显高于工作日而晚间时段的购买频次在全周都偏高。如果只看原始日期数据这些规律完全发现不了。df[order_weekday] df[order_date].dt.dayofweek df[order_hour] df[order_date].dt.hour def time_band(hour): if 6 hour 12: return 早间(6-12) elif 12 hour 18: return 午间(12-18) elif 18 hour 24: return 晚间(18-24) else: return 凌晨(0-6) df[time_band] df[order_hour].apply(time_band) df[is_weekend] df[order_weekday].apply(lambda x: 1 if x 5 else 0)这里有个细节值得说time_band的区间划分没有绝对标准但你要能在报告里解释为什么这样切。我选择这四个区间是因为它贴合用户的自然作息而不是把0~23h平均切成三段。分析特征的产出必须能讲出业务含义否则就是在堆砌变量。4.2 客单价与关联购买用简单特征表达复杂行为第二个我构造的特征是order_total和order_items_count把订单ID归一到一行order_stats df.groupby(order_id).agg( order_total(total_amount, sum), order_items(quantity, sum), unique_categories(product_category, nunique), order_date(order_date, first), customer_id(customer_id, first) ).reset_index() order_stats[avg_item_price] order_stats[order_total] / order_stats[order_items]这个order_stats是我后面所有订单级分析的主表。它能回答几个关键问题平均每个订单买几件商品如果均值和中位数接近说明购买行为比较集中。每个订单涉及几个品类如果unique_categories大多是1说明用户倾向于单品类购买那么品类关联分析就没什么必要。avg_item_price的分布能反映客单价结构比直接看total_amount更抗波动。我还基于订单统计表构造了一个连带率特征一个订单里包含的品类数占比。连带率高说明用户倾向于在一个订单里跨品类选购这是电商场景非常关注的指标也是跨品类推荐策略的数据基础。你不需要在作业里构造很复杂的机器学习特征但把订单级统计做透已经能让报告提升一个档次。另外一个很容易被忽视的特征是用户首次购买时间。我用groupby(customer_id)[order_date].min()得到每个用户的首次下单时间然后计算首单距今的天数作为用户生命周期长度的代理指标。配合总消费金额就能画一个很直观的散点图老用户的消费贡献是否显著更高这直接关联到用户价值分层。4.3 金额验证为什么我说能不用total_amount就别用清洗时单纯依赖total_amount列很危险。我在做特征构造前先做了一次全量验证df[calc_amount] df[quantity] * df[unit_price] df[amount_diff] df[calc_amount] - df[total_amount] error_rows df[abs(df[amount_diff]) 0.01] print(f金额不一致行数{len(error_rows)}占比{len(error_rows)/len(df):.4%})结果显示约有0.8%的行存在微小的金额差异多半是四舍五入导致的但个别行的总金额是quantity×unit_price的两倍或三倍明显是录入时把合价当单价填了。我最后的处理是统一以quantity×unit_price作为计算口径把total_amount列保留但不作为核心分析字段。这个决定影响深远——后面所有订单总额、客单价、消费分层全部基于重新计算的口径。虽然作业没有明确要求但我在报告里专门用了一小节说明这个口径选择还附上了前后对比表。最后指导老师给我的反馈中特别认可了这一点说这是把数据当业务看而不是当练习题做的典型动作。5. 可视化的正确打开方式先想清楚要回答什么问题再选图5.1 从画着好看到讲清结论的转变很多人做可视化有个误区先画一堆图然后挑好看的放进报告。我的习惯是先列出看完这份数据我要回答哪几个问题再为每个问题选择最合适的图形。hica第一次作业的四个核心问题我定为销售走势是否存在周期性哪些品类贡献了主要收入用户的消费额分布是健康的金字塔形还是被少数大额订单主导不同时间段的购买行为差异是否显著对应图表选择如下问题图表类型选择理由销售走势周期性折线图按日聚合展示随时间连续变化的趋势品类贡献度水平柱状图类别名称较长时横向更易读用户消费分层的偏态情况直方图或箱线图直观展示分布形态和极端值周末与工作日的差异分组柱状图或小提琴图同时比较频次与分布形态把问题先行放前面做图的时候就会非常收敛不会东画一张西画一张。最终报告里的每一张图都能在文字分析中找到对应解释而不是为了凑页数。5.2 中文显示与配色基础但必须处理的演示细节matplotlib中文方块问题解决方式如下import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False # 解决负号为方块的问题如果你用的在线notebook环境没有预装中文字体另一种方式是注册系统字体。我在本地Windows环境用微软雅黑效果不错。这条配置的目的就是让输出直接可交付避免截图再P标签。配色方面我选了seaborn默认的深色渐变来区分不同品类没有额外自定义调色板。原因很简单作业场景下保守不出错比花哨更重要。如果AI绘图一眼看过去颜色冲突、标签重叠、图例遮挡技术含量再高也会被扣印象分。5.3 画平均客单价的环比变化一张图看出业务波动这一张图是我整个作业里最满意的一张也是我认为最容易复用到真实工作场景的。思路很简单把每天订单总额除以订单数得到日均客单价再按周做滚动平均消除噪音daily df.groupby(df[order_date].dt.date).agg( total_amount(total_amount, sum), order_count(order_id, nunique) ).reset_index() daily[avg_amount] daily[total_amount] / daily[order_count] # 7日滚动平均 daily[avg_amount_smooth] daily[avg_amount].rolling(7, min_periods1).mean()滚动平均是我特别想强调的一个技巧。原始日均客单价曲线毛刺多肉眼几乎看不出趋势但7日滚动平均之后周末的抬升和节后的回落一目了然。这张图在汇报时被老师点名说很好地呈现了时间维度上的波动结构。做数据分析要学会的一个底层思维就是不要直接呈现噪音先做降噪处理再呈现信号。5.4 品类的帕累托分析找出那20%的品类帕累托图柱状图累计百分比折线是我强烈建议放进作业的图表。它的信息密度极高横轴排列品类贡献度左纵轴是销售额右纵轴是累计占比一条斜向上的折线清晰地划分出头部品类和长尾品类。cat_sum df.groupby(product_category)[total_amount].sum().sort_values(ascendingFalse) cat_cumsum cat_sum.cumsum() / cat_sum.sum() fig, ax1 plt.subplots(figsize(10, 6)) ax1.bar(cat_sum.index, cat_sum.values, color#4C72B0) ax2 ax1.twinx() ax2.plot(cat_sum.index, cat_cumsum.values, color#C44E52, markero, linewidth2) ax2.axhline(0.8, linestyle--, colorgray)从图里可以看出前4个品类贡献了约78%的销售额基本符合二八分布。这个结论的意义在于后续如果想做商品运营策略优化资源应该优先投放在这四个品类上长尾品类维持基本覆盖即可。这张图把清洗→聚合→可视化→业务建议整条链路完整串起来了。6. 报告撰写的完整链路复盘那些差点让我翻车的问题6.1 第一次报错的完整排查链路从报错信息到根因我必须承认整个过程不是一帆风顺的。最有代表性的一次报错发生在做分组聚合时ValueError: cannot reindex on an axis with duplicate labels第一次遇上这个报错时我整个人是懵的。报错信息说重复标签但我的order_id明明已经去过重了。排查步骤如下第一步检查groupby后是不是有重复索引。我发现groupby时键选的是order_id而order_id在清洗阶段确实还有重复。第二步回溯重复来源打印出重复的order_id行。结果发现这些重复其实来自两个地方一是原始数据里同一order_id对应不同product_name的多行记录——这是正常的order_id本来就不是唯一标识二是在做groupby聚合时我没有以order_id为唯一分组键而是混入了customer_id导致某些order_id对应多个不同customer_id。第三步重新定义聚合逻辑。订单表才是订单级唯一数据所以一切按order_id聚合并保留每个分组的第一个customer_id。把业务唯一键想清楚后报错自然消失了。这条排查链路是我最想分享的经验因为它说明了一个通用原则遇到聚合报错不要直接改代码参数先回去审视你定义的分组键是否真的是业务唯一键。这个问题在真实工作中同样常见特别是从明细表跑到汇总表时。6.2 日期格式化陷阱为什么我的折线图横坐标是乱序的第二个让我印象深刻的坑是第一次画日销售额折线图时横坐标顺序完全是乱的一月数据和三月数据交替出现。原因是我用了df.groupby(df[order_date].dt.date)而dt.date返回的是Python的date对象虽然pandas能识别为日期但如果你后续把结果转成list或再次排序时用了字符串排序就会得到2024/11/1排在2024/9/2前面的诡异顺序。最可靠的修复方式是把分组键一直接着转换为pandas的datetime类型df[order_date_only] df[order_date].dt.normalize() # 去掉时间部分保留日期 daily df.groupby(order_date_only).agg(...)这样横轴数据天然就是时间有序的matplotlib也能正确识别时间刻度。建议大家在处理任何时间序列聚合时优先用dt.normalize()而不是dt.date前者始终是Timestamp类型不会在后续操作中变成Python原生对象。6.3 零值和异常单价的处理删掉还是保留前面提到unit_price存在0值。我一开始的直觉是直接删掉这十几行因为它们会影响单价均值。但后来我检查了这些订单的quantity和product_name发现几乎所有0单价的商品都对应赠品或样品标识。这类订单在真实业务里具有独立意义——它们在引流、清库存、会员维系方面都有作用。最终处理是保留这些行但单独构造一个is_gift特征并在计算销售额时排除赠品订单。这样既不污染正常的金额分析又保留了这批订单在频次分析中的价值。这个决策我在报告里单独写了一段话评审老师后来评价说处理得很灵活不是教科书式的删除或填充。这类问题没有标准答案关键是你能不能用数据本身的上下文支撑你的选择。删有删的道理留有留的道理最怕的是看着碍眼就删。6.4 阈值选择的前后验证基于分布的合理性检验在判断异常单价时我用过均值±3倍标准差这个经典阈值但很快发现单价分布高度右偏标准差受极值影响极大导致上界高到离谱几乎过滤不掉任何异常值。后来我改用IQR四分位距方法Q1 df[unit_price].quantile(0.25) Q3 df[unit_price].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQRIQR方法对偏态分布的容忍度更好因为它基于分位数而不是均值和方差。筛选出的异常值我也没有直接删除而是单独存到一个表里人工核对——其中一部分是录入错误另一部分是高价值订单比如单价上千元的组合装商品这类不是异常只是正常的高值。这里有一个重要的认知异常值检测的输出不能直接等于删除清单它只是待审查清单。你的业务判断才是最终决定因素。7. 从交作业到验收老师最看重的四个维度与我的备赛笔记7.1 报告结构先有结论再展示证据hica第一次作业报告的核心结构我最终确定为数据概览→清洗说明→特征构造→可视化分析→结论与建议。很多人把大量篇幅花在数据清洗代码上但老师反馈中最重要的一句话是报告不是代码粘贴簿你的每个代码块都得有目的说明和结果解读。我最终的呈现方式是每段代码后面紧跟两段话——第一段说明这段代码在做什么、为什么这样处理第二段展示输出结果并解释它对最终结论有何影响。这种方式大大提高了报告的可读性也让读者能够沿着我的思路一路走到结论而不是在代码和文字之间来回跳跃。7.2 分析师视角从数据到建议要有完整逻辑链作业要求是输出分析报告但分析报告的核心是建议可落地不是数据罗列。我在报告结论部分写了三条业务建议每一条都遵循了现象→原因→行动的逻辑链例如现象晚间时段的订单频次最高但客单价低于白昼时段。 原因晚间用户更多进行碎片化浏览倾向于购买低单价商品决策路径短。 行动在18~22点之间推送高频复购的低单价商品组合券拉高晚间连带率。这类建议不需要数据分析模型支持但它把可视化里呈现的现象真正转化成了可执行的业务语言。老师很明确地点过一句hica第一次作业的评分维度里结论与建议的落地性权重非常高。7.3 数据完整性与可复现性让别人按你的代码能跑出一模一样的图报告末尾我附上了完整的代码文件并且保证了一个前提任何人按顺序执行都能得到相同的结果。这要求你在脚本里把所有随机因子固定虽然本例没用随机算法把文件路径写成相对路径把环境依赖版本号写在文件头部注释里。我在头部加了一段# 运行环境Python 3.10 / pandas 2.0.3 / numpy 1.24.3 # 数据路径./data/sales_data.csv # 输出目录./output/这个动作看起来不起眼但它是分析师职业素养的体现。日后你进入团队你会发现别人能复现你的结果比你得出一个正确结论更难、更稀缺。7.4 答辩时的三个高频追问以及我如何准备提交报告后还有一次口头验收老师提了三个问题我觉得非常值得提前准备第一个问题你如何处理缺失值为什么这样处理——这就回到我第3节的思路分列分析能反推的反推不能反推的也要保留业务信息。每个处理动作都要能说出业务依据。第二个问题如果让你再清洗一次你会改变哪些步骤——这个问题其实在考察你对处理过程的反思能力。我当时回答如果Dataset再大十倍我不会再逐列人工检查而会先写一个数据质量报告函数把所有列的缺失率、唯一值、分布偏度自动跑出来再决定清洗顺序。这个回答老师很认可。第三个问题你这些结论里哪些是数据给出的哪些是你先入为主的想法——说实话这是一个很难的问题。我的回答是我确实带着周末销售额更高的预期去看数据但数据告诉我周末订单量确实更高客单价却下降最终日总额没有显著差异。这等于用数据修正了我的预期。这种坦诚的回答比假装自己绝对客观更能赢得信任。8. 我踩过的坑与优化心得如果你也在做这份作业这些经验直接抄8.1 保留中间过程的快照每走一步存一次我吃过最大的亏就是闷头写代码一路处理到底最后发现某一步清洗逻辑有问题不得不从头再跑。第二次我学乖了建立了这样的习惯每个核心处理阶段都导出一次中间结果。比如清洗阶段我按原始数据→格式标准化→缺失值处理→去重四个节点各存一个csv到output目录。这样做的好处是如果后续报告需要某个处理步骤前后的对比我随时有数据可用不需要重新运行如果评审发现某个处理有问题我也可以快速定位到具体环节而不是整个清洗流程整体重来。df_stage1.to_csv(./output/stage1_raw.csv, indexFalse) df_stage2.to_csv(./output/stage2_standardized.csv, indexFalse) df_stage3.to_csv(./output/stage3_missing_filled.csv, indexFalse) df_stage4.to_csv(./output/stage4_deduplicated.csv, indexFalse)这个习惯在真实工作里会更重要。数据管道越复杂中间快照越关键。你永远不知道什么时候需要回溯。8.2 用assert做数据质量闸门让代码自己报警写清洗代码最怕的是处理完以后对数据质量一无所知。我给关键环节都加上了断言一旦数据不满足预期条件程序立刻停止并报错assert df[order_date].isna().sum() 0, 日期仍有缺失 assert (df[unit_price] 0).all(), 存在负单价 assert df[total_amount].sum() 0, 总金额异常这个技巧在作业中可能显得有点小题大做但它在数据量大了之后非常有价值。你把数据清洗从手动检查变成一个自动校验的管道每一步处理完系统自动确认结果有异常立刻中断。它保证了运行完成和结果正确两个概念在代码层面是等价的。8.3 不要只画柱状图和折线图试试透视表热力图hica第一次作业里还有一个隐藏加分项就是品类×时段的交叉分析。我用了pandas透视表配合seaborn热力图一张图就呈现出14个品类在四个时间段的销售分布pivot pd.pivot_table( df, valuestotal_amount, indexproduct_category, columnstime_band, aggfuncsum, fill_value0 ) import seaborn as sns plt.figure(figsize(12, 6)) sns.heatmap(pivot, annotTrue, fmt.0f, cmapYlOrRd) plt.title(品类×时段销售额交叉分析) plt.tight_layout() plt.show()这张图的神奇之处在于它能一眼看出哪些品类是全天候畅销型哪些品类是特定时段驱动型。比如数码产品在午间和晚间几乎一样高而生鲜食品则明显集中在午间。这种交叉维度的洞察是单张柱状图给不了的而且实现代码非常简单强烈建议放进作业。8.4 成品交付前的自检清单我最后一次检查报告用了这样一个清单你可以直接复制使用所有图表是否都有标题、轴标签、数据来源说明每个清洗决策是否都有明确理由而非默认操作代码是否按顺序能完整运行输出是否可复现结论是否都能追溯到具体图表或统计量有没有隐藏的硬编码路径、绝对路径缺失值处理后的数据量变化是否有记录是否有任何一步操作我自己也说不清为什么这么做第七条是唯一一条我靠自觉而不是脚本来完成的自检项。如果一个操作你无法用一句话向别人解释清楚它就极有可能是个多余或错误的操作。删掉它你的报告会更干净。hica第一次作业看起来只是一次练习但它的完整链路覆盖了分析师的基本功从模糊需求到数据理解从清洗到特征从特征到表达从表达到业务建议。这套流程你练透了往后任何数据分析任务都会轻松很多。我个人做完这次作业的最大体会是数据处理没有标准答案真正有价值的是你为自己的每一步决策提供依据的能力——这个能力的培养远比记住几个pandas函数重要得多。