ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

数据分析入门实战:用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(keepFalse) print(dup_mask.sum()) # 输出duplicated(keepFalse)代表全部重复的行数注意对应head样本数据实际计数会偏大 # 精确去重 df df.drop_duplicates() # 业务维度的可疑重复保留第一条并单独标记 suspect df[df[[客户ID, 下单日期, 商品类别]].duplicated(keepFalse)] print(业务维度疑似重复, len(suspect))这一步得到的“疑似重复”不会直接删而是导出让我人肉确认。为什么这么谨慎因为你一旦在作业阶段习惯了“有重复就删”的粗暴操作等到了真实业务里就可能把用户的正常复购误判成异常单那影响就不是扣作业分而是赔钱。2.3 数据类型矫正时间、金额这些“披着字符串外衣”的字段数据清洗里最容易被忽略的就是 dtype。原始数据从 CSV 导入后Pandas 会自作主张地推断类型但推断失败时它不会报错而是默默把整个字段变成 object 字符串。订单金额列1,299.00带着千分位逗号日期列有3/5/2023、05-03-2023两种写法都会被当成字符串。如果你不矫正后续排序、计算、按月聚合全是错的。我对日期字段的处理是统一成 datetime 类型df[下单日期] pd.to_datetime(df[下单日期], formatmixed, dayfirstFalse)Pandas 2.x 及以上支持formatmixed能解析多种格式混杂的日期。但我实际作业里用的是更稳妥的分步解法先识别哪些行是哪种格式再逐一转换最后拼接避免某些日期被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(/, .), naFalse) df.loc[mask, 日期解析结果] pd.to_datetime(df.loc[mask, 下单日期], formatdate_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。为什么要单独提版本因为formatmixed这个参数就是 Pandas 2.0 之后才有的。如果你的 Pandas 还是 1.x代码可能直接跑不动。这不是什么高深问题但版本不对真的会浪费很长时间。pip install pandas2.1.0 openpyxl matplotlib数据导入时我会刻意先把原始文件备份一份放在data/raw下然后才在分析环境里读取副本。作业阶段这看起来是多此一举但它能保证你后续无论怎么清洗随时都能回到“原始状态”重新开始不会把自己改到无法回头。读取 CSV 时最容易踩的坑是编码问题。Windows 下导出的 CSV 常见gbk编码而 Jupyter 里默认用的可能是 UTF-8一读就乱码。我的建议是读取时显式指定编码并且先用nrows参数抽样一批看看效果df_sample pd.read_csv(data/orders.csv, encodingutf-8, nrows5)如果这一步就报解码错误再换encodinggbk试。确定没问题后再全量读取。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(,, , regexTrue).str.replace(¥, , regexTrue).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 这个名字虽然简单但它帮你练的每一招都是实打实的基础功。基础功扎实了后面遇到再乱的业务数据你也不会慌因为你已经知道先摸清缺失的规律再讲重复的语义再修类型的毛病最后带着一张干净的表格去见业务方。这套打法能管很久。
返回列表