news 2026/10/5 3:01:51

数据分析入门实战:用Pandas搞定数据清洗三大硬骨头

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据分析入门实战:用Pandas搞定数据清洗三大硬骨头

1. 任务定位:一个“看名字就知道要求”的开胃题

Wk.2_HW,拆开就是 Week 2 Homework。但凡经历过几个像样的项目制课程或内部训练营,一看到这种命名的目录,基本就能猜到它背后是一套有节奏的任务体系:第一周打基础,第二周开始上手做“能跑通的小闭环”,而这个名字本身,就是助教或讲师布置作业时的默认约定。

我当年带过一批新人,也复盘过这类“第二周作业”的设计意图。它通常不是为了刁难谁,而是要完成三个层面的训练:熟悉数据获取的基本手感、掌握最基础的数据清洗手法、以及第一次完整走通“看到数据—理解数据—处理数据—交出结果”的过程。说白了,第二周作业就是在真实项目压力来之前,先给你一块“仿真靶场”。

这篇复盘就围绕我当时做 Wk.2_HW 的完整过程展开。它看起来只是一个小作业,但里面塞进去的思路——需求拆解、数据探查、清洗策略、交付规范——基本就是你未来做任何正经数据分析项目都要反复用到的骨架。适合正在入门数据分析、被作业或小项目卡住的朋友参考,也适合那些想从“照着教程敲一遍”过渡到“拿到一堆乱数据能独立撸出结果”的读者。

1.1 解读作业编号背后的课程设计逻辑

先聊标题命名这件事。为什么叫 Wk.2_HW,而不是直接叫“作业二”?因为这种命名背后有一个很实际的工程习惯:文件、项目目录、作业包,都要在命名里自带信息。Wk 代表第几周,HW 代表任务性质,后面还可以跟版本号、提交人。这就跟你以后在工作里看到2024_Q3_report_v2_final这种文件名一样,命名本身就是在降低沟通成本。

这个习惯在学习阶段就可以开始养成。我当时把作业包整理成了这样的目录结构:

Wk.2_HW/ ├── data/ # 原始数据,只读不写 ├── output/ # 清洗后的数据和结果文件 ├── code/ │ ├── 01_explore.ipynb # 数据探查 │ ├── 02_clean.py # 清洗主流程 │ └── 03_report.py # 生成交付摘要 ├── Wk.2_HW_说明.md └── README.md

很多人在做作业时只管“把代码跑出来”,完全不管文件组织,等回头要复现自己两周前的结果,两眼一黑。说实话,作业阶段的目录习惯会直接迁移到未来的真实项目里。谁也不想在一个只放了作业2_final_最终版.py的文件夹里找东西。

1.2 本周作业究竟在训练什么能力

从课程设计角度看,第二周往往是从“工具认知”跨向“第一场实战”的过渡。第一周可能在教 Python 基础或 Pandas 语法,而第二周作业的核心就不再是“认识函数”,而是“用函数解决问题”。

Wk.2_HW 这种任务通常会给一份带噪音的原始数据,比如包含缺失值、重复记录、格式混乱的日期、单位不一致的字段。你的任务不是把每个函数背出来,而是在一堆表面问题中抽丝剥茧,找出数据真正“脏”在哪里,并且用代码做出可复现的处理。

我当时拿到的模拟数据大概是这样的:一个零售订单表,包含订单编号、客户ID、下单日期、商品类别、数量、单价、总价、备注等字段,共几千条。数据里有明显缺失,有整行重复,也有“看起来是数字其实是字符串”的金额字段,还有日期格式混杂着2023/3/5、03-05-2023、20230305三种写法。

这里面没有任何一个问题是“需要调用什么冷门库”才能解决的。它就是考验你能不能把基础工具组合起来使用。做完之后我最大的体会是:数据清洗 90% 的工作量不是写处理代码,而是先搞清楚“每条脏数据到底是怎么产生的”。不理解原因,你只是在瞎修。

2. 核心细节:数据清洗三大硬骨头与实操要点

很多人一拿到数据就急着写dropna()删缺失、drop_duplicates()删重复,我最初也这样干过,后来发现这是在拿“省事”赌“正确”。数据清洗不是机械地执行几个 API,而是先回答三个问题:缺失值为什么会存在?重复记录算不算真重复?字段类型不对会导致什么后果?

把这三个问题想清楚,清洗方案自然就出来了。我下面逐项拆解。

2.1 缺失值处理:先问“为什么缺”再动手

缺失值在 Pandas 里显示为 NaN,很多人看到就是简单dropna()一行带走。但在我那次作业里,光是“为什么缺失”就能分出三种情况:

一种是完全随机缺失,比如用户下单时某个备注字段没填,这种字段本身是辅助信息,缺了不影响核心分析,可以直接保留或填一个占位符。

第二种是与你观察到的其他字段有关联的缺失。例如订单金额缺失时,仔细一看,这些记录的数量和单价都还在,那金额就是因为程序故障没算出来,完全可以按数量 * 单价补算。这种缺失如果直接删除,等于白白扔掉一条有效订单。

第三种是需要谨慎对待的缺失,比如某个关键维度的缺失比例超过 50%,而且缺失模式没有规律。这种情况下强行填充反而会引入噪音,最优选择往往是标记字段状态,而不是盲目补值。

我在作业里实际做的事是先跑一段聚合统计,看缺失值在哪些字段、按什么维度分布:

import pandas as pd df = pd.read_csv("data/orders.csv") missing_info = df.isna().sum() missing_rate = missing_info[missing_info > 0] / len(df) * 100 print(missing_rate)

这一步的输出直接决定后续策略。备注字段缺了 23%,不影响主流程;金额字段缺了 1.2%,但同一批记录的数量和单价都齐全,所以这个缺失几乎都可以修复。最终我选择对金额做fillna补算,对备注填一个"无"占位,保留数据完整性。

注意:fillna是填充,dropna是删除,interpolate是插值。没有一步到位的“万能清洗”,你必须先搞明白缺失原因,才谈得上选哪个策略。

2.2 重复值检测:全字段去重未必是正确答案

drop_duplicates()默认会拿所有字段做比对,一旦完全一致就删除。但业务场景里很多重复不是“所有字段都一致”,而是“关键业务字段一致”。如果只看全字段,有些真正的重复会因为备注里多打了一个空格而逃过检测;反过来,同一客户在合法时间买了同款商品,全字段是一样的,可这明明是两笔独立交易,全字段去重反而会误删有效数据。

我当时的处理思路是:先做一次精确去重,清除完全一致的记录;然后再针对“业务唯一性”做一次有条件的去重。比如这个订单表的业务逻辑是“同一个客户同一天对同一个商品只能下一笔订单”,那么客户ID、下单日期、商品类别这三个字段就构成一个业务唯一键。

# 先检查业务维度是否存在重复 dup_mask = df[["客户ID", "下单日期", "商品类别"]].duplicated(keep=False) print(dup_mask.sum()) # 输出duplicated(keep=False)代表全部重复的行数,注意对应head样本数据实际计数会偏大 # 精确去重 df = df.drop_duplicates() # 业务维度的可疑重复,保留第一条并单独标记 suspect = df[df[["客户ID", "下单日期", "商品类别"]].duplicated(keep=False)] print("业务维度疑似重复:", len(suspect))

这一步得到的“疑似重复”不会直接删,而是导出让我人肉确认。为什么这么谨慎?因为你一旦在作业阶段习惯了“有重复就删”的粗暴操作,等到了真实业务里,就可能把用户的正常复购误判成异常单,那影响就不是扣作业分,而是赔钱。

2.3 数据类型矫正:时间、金额这些“披着字符串外衣”的字段

数据清洗里最容易被忽略的就是 dtype。原始数据从 CSV 导入后,Pandas 会自作主张地推断类型,但推断失败时它不会报错,而是默默把整个字段变成 object 字符串。订单金额列"1,299.00"带着千分位逗号,日期列有3/5/2023、05-03-2023两种写法,都会被当成字符串。如果你不矫正,后续排序、计算、按月聚合全是错的。

我对日期字段的处理是统一成 datetime 类型:

df["下单日期"] = pd.to_datetime(df["下单日期"], format="mixed", dayfirst=False)

Pandas 2.x 及以上支持format="mixed",能解析多种格式混杂的日期。但我实际作业里用的是更稳妥的分步解法:先识别哪些行是哪种格式,再逐一转换,最后拼接,避免某些日期被dayfirst误解析成另一种含义。

import numpy as np date_formats = {0: "%Y/%m/%d", 1: "%m/%d/%Y", 2: "%d-%m-%Y"} df["日期解析结果"] = np.nan for fmt in date_formats: mask = df["下单日期"].str.contains(date_formats[fmt].replace("%", "").replace("/", "."), na=False) df.loc[mask, "日期解析结果"] = pd.to_datetime(df.loc[mask, "下单日期"], format=date_formats[fmt]) # 检查未能解析的数量 cannot_parse = df["日期解析结果"].isna().size - df["日期解析结果"].notna().size

这个案例想说明的核心是:类型矫正不能光靠一个函数,而是要靠“发现异常 → 检查样本 → 分段处理 → 验证结果”的思路。金额字段的处理也类似,先去掉逗号和货币符号,再转成 float。如果你以后碰到1.299,00这种欧洲格式,还得先搞清楚它是“金额三位分隔”还是“小数点分隔”,这就是典型的文本清洗加类型转换组合拳。

为了直观,我做了一张清洗前后对照表:

字段清洗前问题清洗后
下单日期字符串,三种格式混杂无法排序/按月聚合datetime 类型,统一为YYYY-MM-DD
订单金额"1,299.00"字符串无法计算,无法比较float 类型 1299.0
数量少量负数负数量无业务意义绝对值处理并标记异常
客户ID前后带空格join 时匹配不上统一 strip 并转大写

3. 实操记录:从拿到原始表到交付干净数据的完整流程

这部分我按当时的实际操作顺序来写,你可以直接照着跑一遍,也可以把它当模板迁移到自己的数据上。整个流程分五步:环境准备、数据导入、全表探查、清洗执行、结果校验。

3.1 环境准备与数据导入

我的作业环境用的是 Python 3.10 + Pandas 2.1 + Jupyter Notebook。为什么要单独提版本?因为format="mixed"这个参数就是 Pandas 2.0 之后才有的。如果你的 Pandas 还是 1.x,代码可能直接跑不动。这不是什么高深问题,但版本不对真的会浪费很长时间。

pip install pandas==2.1.0 openpyxl matplotlib

数据导入时我会刻意先把原始文件备份一份,放在data/raw下,然后才在分析环境里读取副本。作业阶段这看起来是多此一举,但它能保证你后续无论怎么清洗,随时都能回到“原始状态”重新开始,不会把自己改到无法回头。

读取 CSV 时最容易踩的坑是编码问题。Windows 下导出的 CSV 常见gbk编码,而 Jupyter 里默认用的可能是 UTF-8,一读就乱码。我的建议是读取时显式指定编码,并且先用nrows参数抽样一批看看效果:

df_sample = pd.read_csv("data/orders.csv", encoding="utf-8", nrows=5)

如果这一步就报解码错误,再换encoding="gbk"试。确定没问题后,再全量读取。

3.2 全表探查:先画“数据体检报告”

拿到数据不要急着写清洗逻辑,而是先做一次整体体检。我定义的体检指标包括:数据量、字段数、每列的非空数量、唯一值数量、最常出现的取值分布。这步做得好,后续所有决策都有事实依据;跳过这步,后面全是在猜。

核心代码我当时写在 01_explore.ipynb 里:

def overview(df): return pd.DataFrame({ "非空数": df.notna().sum(), "空值数": df.isna().sum(), "唯一值": df.nunique(), "类型": df.dtypes }) overview(df)

光看这个还不够,我还会对每个数值字段跑一个.describe(),看它的分位数分布,尤其注意有没有极端值和负数;对每个字符型字段抽 3 到 5 个不同的样本值,肉眼看格式。这一步一定要“肉眼看”。数据清洗的经验很大程度就是建立在这种“看多了脏数据”之上的。

我当时通过 describe 发现数量字段最小值是 -3,这就是一个典型的逻辑异常,计算总销量的时候如果不去掉,结果会比实际少一截。

3.3 清洗主流程:分四步写完,每步一验证

清洗主流程我在 02_clean.py 里分成四个步骤,每步都有输入输出注释和验证断言,异常处理放在单独的函数里。

第一步,统一文本格式。所有字符串列去首尾空格,客户ID统一转大写,商品类别里中文全角冒号替换成英文冒号。这步解决的是“看起来一样其实不一样”的问题。

str_cols = df.select_dtypes(include=["object"]).columns for col in str_cols: df[col] = df[col].astype("string").str.strip() df["客户ID"] = df["客户ID"].str.upper() df["商品类别"] = df["商品类别"].str.replace(":", ":")

第二步,处理日期格式。把混合格式按前文描述的方法统一成 datetime。

第三步,处理金额和数量。金额先去掉逗号、货币符号再转 float,数量取绝对值并标记异常行。这里我不直接把负数删掉,而是新增一列是否异常数量,保留原始记录方便复盘。

df["订单金额"] = df["订单金额"].str.replace(",", "", regex=True).str.replace("¥", "", regex=True).astype(float) df["数量_清洗后"] = df["数量"].abs() df["数量异常标记"] = df["数量"] < 0

第四步,去重与缺失值处理。我按照 2.1 和 2.2 节说过的逻辑执行,没有用全字段一键去重。

每完成一步,我都会打印一个摘要:

print("步骤1后行列数:", df.shape) print("步骤2后日期类型:", df["下单日期"].dtype) print("步骤3后金额最小值:", df["订单金额"].min()) print("步骤4后重复行数:", df.duplicated().sum())

这种“一步一验证”的习惯,比全部清洗完再统一检查要高效得多。一旦某一步出了问题,你能立刻指出来是哪个环节导致,不用从头排查。真实项目里,这个习惯能帮你省下大量 debug 时间。

3.4 结果校验:洗得干不干净,要拿数据说话

清洗完成后,我生成了一份交付摘要,包括清洗前后行数变化、缺失值变化、重复值变化、金额总量变化、异常标记数量。这些数字是对作业本身的“体检报告”,也是未来写周报时直接能用的素材。

summary = { "清洗前行数": original_rows, "清洗后行数": len(df), "删除精确重复": original_rows - len(df), "补全缺失金额": (df["订单金额"] - original_amount).sum() if ... else "看业务口径", "异常标记行数": df["数量异常标记"].sum() }

如果你希望更直观,可以画一张清洗前后的关键指标对比图。比如商品类别的订单数量分布,清洗前可能有空白类别名,清洗后归并成统一名称,图形上的差异一眼可见。图表不需要多精美,只要能作为“清洗改变了解析口径”的证据就行。

4. 提交前必看的自查清单与提交流程

作业做完了,不是把ipynb往平台上一丢就完事。我见过太多写得不错的代码因为交付不规范被打回重改。这里我整理一份“提交前自查清单”,不仅适用于 Wk.2_HW,也适用于任何分析类作业或小型团队项目。

4.1 交付物清单与命名规范

一次完整的作业交付,至少应该包含四样东西:可复跑的代码、清洗后的数据、说明文档、结果摘要。代码可以是.py或.ipynb,但必须保证在一台干净环境中能跑通,不要依赖本地绝对路径。

我当时用的路径是相对路径,所有数据都放在项目目录内,这样换一台电脑、换一个用户名,代码一样能跑。命名方面,日期用YYYYMMDD,版本用_v1、_v2,千万别出现最终版、改改改_真的最终、打死不改了这种名字。自己看着搞笑,别人看着崩溃。

提示:提交之前,把你的代码放到一个新的空白文件夹里,从头执行一遍。只要能一步不卡地跑出结果,才算“可复现”。这一个习惯能挡掉至少一半的提交事故。

4.2 代码可读性:指标和结果的交付不是一回事

很多新人会觉得“代码能跑完就说明作业完成了”,但作业和工程项目的差别在你交付的东西是否具备可读性。一份能通过评审的作业,代码里应当有:清晰的模块划分、关键步骤的注释、结果的输出展示、必要的断言校验。

我当时在每个函数顶部都写了三行 docstring,说明输入是什么、输出是什么、为什么这么设计。这样做至少有两个好处:一是半个月后回看代码,不需要重新猜;二是别人 review 时能很快抓到你思路的重点,不用每一行都扒。

更关键的是结果展示。清洗前后各有一个摘要表格,并附一段文字说明“我做了什么、为什么这样做、结果如何”。这一小段说明的分数占比,往往比代码本身还高。它证明的不是你会调用函数,而是你理解数据、理解业务、能表达清楚自己的处理逻辑。

4.3 常见错误避坑表

根据我这几年看作业、带新人的经验,以下问题是 Wk.2_HW 里出现频率最高的:

错误类型典型表现避免方法
路径写死C:/Users/xxx/Desktop/data.csv全部改为相对路径
编码乱码中文读出来是乱码读文件时显式指定encoding
版本依赖Pandas 1.x 跑 2.x 的代码requirements.txt锁定版本
数据被原地覆盖清洗后找不到原始参照原始数据放入data/raw,不可覆盖
逻辑含混删除行不给理由清洗每步写注释,说明删除或填充的原因
缺失处理一刀切不管原因全dropna()先查缺失分布,按缺失原因分策略处理
类型不检查日期算完才发现是字符串清洗第一步就统一 dtype
结果不验证跑完直接交清洗后重新做一次 describe,对比异常值

这张表不是凑篇幅,是真金白银踩出来的。当年我交作业的时候,光“路径写死”这一条就被打回过两次。不是代码逻辑错,而是老师换了一台机器就跑不通了。从那以后,我所有分析代码都坚持用相对路径加全局os.path.join的方式,再也没出过同类问题。

5. 二次深入:这份作业还能怎么扩展成项目

Wk.2_HW 如果只当成一次作业,做完就完了,价值有限。我后来复盘时发现,这种第二周作业其实是一个很好的“项目种子”。顺着它往下扩展,你可以在一周内做出一个非常像样的数据项目原型。

扩展方向有三个。第一个是把清洗后的数据接上后续分析,比如订单的月度趋势、品类销售构成、客户复购率。只需要多写几十行分组聚合和可视化代码,作业就变成了一个完整的数据分析小报告。

第二个是增加数据质量监控的视角。你清洗过了这批数据,但如果下一周又来了新一批数据呢?你有办法自动检测它是否出现了同样的脏数据问题吗?我当时加了一个数据校验脚本,读入新数据后自动检查缺失率、重复率、日期可解析率,超过阈值就报警。这个“数据质量监控”的思路在真实的数据平台团队里非常值钱。

第三个是把这个流程脚本化。将清洗逻辑封装成函数,做成一个clean_orders()的标准方法,后续任何订单表都能复用。这一步做完,相当于拥有了自己的第一份“数据爬虫—清洗—入库—分析”最小数据管道。

讲这些是想说明一件事:作业本身只是起点,真正让你成长的,是把作业当项目来做的那股认真劲。Wk.2_HW 这个名字虽然简单,但它帮你练的每一招都是实打实的基础功。基础功扎实了,后面遇到再乱的业务数据,你也不会慌,因为你已经知道:先摸清缺失的规律,再讲重复的语义,再修类型的毛病,最后带着一张干净的表格去见业务方。这套打法,能管很久。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 3:01:43

慈善钓鱼:灾害背后的双向诈骗与识别防护

每次大型灾害过后&#xff0c;社交媒体上都会被两类内容刷屏&#xff1a;一类是真实的求助与募捐&#xff0c;另一类是趁乱生长的伪装帖。后者在安全圈里有个专门说法&#xff0c;叫“慈善钓鱼”——拿灾难当鱼饵&#xff0c;拿同情心当鱼钩&#xff0c;等捐款人咬住钩子再完成…

作者头像 李华
网站建设 2026/10/5 3:01:00

GPU租赁实战指南:从算力瓶颈到PyTorch环境配置一次讲透

上周一个做视觉检测的朋友问我要怎么上深度学习&#xff0c;说公司预算卡得紧&#xff0c;两片RTX 4090就要四万多&#xff0c;实在下不去手。我给他指了条租GPU的路子&#xff0c;按小时包一台带RTX 4090的云主机&#xff0c;第一天跑下来成本不到一百块钱&#xff0c;效果和自…

作者头像 李华
网站建设 2026/10/5 2:59:39

of_graph_get_remote_port解析:嵌入式Linux设备树图形绑定核心机制

1. 这个函数名背后藏着嵌入式Linux设备树驱动开发的底层逻辑of_graph_get_remote_port——光看这个名字&#xff0c;你可能以为它只是个普通API调用。但如果你正在调试一块带MIPI CSI摄像头的ARM开发板&#xff0c;或者在移植一个HDMI音频编解码器驱动&#xff0c;又或者正被某…

作者头像 李华
网站建设 2026/10/5 2:59:24

WPF DataGrid点击单元格立即进入编辑模式的完整实现

上个月在给公司内部的数据录入工具做交互改造&#xff0c;WPF的DataGrid用了这么久&#xff0c;收到最多的抱怨就是录数效率低&#xff1a;鼠标点到一个格子&#xff0c;以为能直接打字了&#xff0c;结果系统只是把单元格选中而已&#xff0c;还得再点一下、按F2、或者双击&am…

作者头像 李华
网站建设 2026/10/5 2:58:31

SpringBoot+MyBatis+MySQL实战:游戏介绍系统开发与部署全解析

这个项目是我帮学生做的一个课程设计&#xff0c;名字叫《逃跑吧少年》介绍系统。说白了就是一个游戏官网式的信息展示平台&#xff0c;把角色图鉴、地图玩法、攻略资讯这些内容做成一个能看能管的完整站点。技术栈选了SpringBootMyBatisMySQL&#xff0c;SpringBoot负责把整个…

作者头像 李华
网站建设 2026/10/5 2:57:29

近红外光谱回归突破R²=0.85瓶颈的专用深度学习方案

简介&#xff1a;本资源是一套面向科研人员与工程实践者的深度学习建模方案&#xff0c;聚焦近红外光谱&#xff08;NIR&#xff09;数据的回归分析任务&#xff0c;适用于化学计量学、食品检测、农业快检等需高精度定量预测的场景。压缩包共9个文件&#xff0c;含8个Python脚本…

作者头像 李华