查看: 209|回复: 3

Pandas数据清洗与预处理7个核心方法及代码实现

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在数据分析流程中,数据清洗与预处理往往比建模本身更耗时。Pandas 提供了一整套 API 来覆盖缺失值、重复值、类型、结构、文本、分箱等环节。本文按“发现问题是哪一类→用哪个函数→参数怎么写”的顺序,把 7 类高频操作整理成可直接复用的代码片段,所有示例均基于 numpy 与 pandas 的常规接口,版本差异影响很小。

一、缺失值处理:优先填充,其次删除

处理缺失值的第一原则是“能填充不删除”。删除只适用于缺失比例极低(经验值小于 5%)且无填充意义的场景,通过 dropna(subset=[...]) 精确指定判断列,避免误删整行。填充又分几档:固定值填充适合有业务默认值的字段(如未知性别填 'unknown');统计值填充中,数值列用均值或中位数,分类列用众数;时序数据用 ffill/bfill 方向填充;需要按维度分别填充时,用 groupby + transform + fillna 组合,保证填充值来自同组而不是全局。
  1. import pandas as pd
  2. import numpy as np
  3. # 构造测试数据
  4. df = pd.DataFrame({
  5.     '地区': ['北京', '北京', '上海', '上海', '广州', '广州'],
  6.     '销售额': [100, np.nan, 200, 150, np.nan, 300],
  7.     '品类': ['家电', np.nan, '数码', '数码', '家电', '家电']
  8. })
  9. # 1. 删除缺失值(仅删除销售额列有缺失的行)
  10. df_drop = df.dropna(subset=['销售额'])
  11. # 2. 数值列用分组均值填充(按地区填充该地区销售额均值)
  12. df['销售额'] = df.groupby('地区')['销售额'].transform(lambda x: x.fillna(x.mean()))
  13. # 3. 分类列用众数填充
  14. df['品类'] = df['品类'].fillna(df['品类'].mode()[0])
  15. # 4. 时序数据方向填充(示例)
  16. df_time = pd.DataFrame({'温度': [25, np.nan, 27, np.nan, 28]},
  17.                        index=pd.date_range('2026-03-01', periods=5))
  18. df_time['温度'] = df_time['温度'].fillna(method='ffill')  # 前向填充
复制代码

这里的关键点是 transform:它返回与原始行数对齐的结果,因此可以直接赋值回原列;如果直接用 groupby().mean() 会得到按地区聚合后的短表,无法回填。

二、重复数据处理:精准识别与保留规则

duplicated() 返回布尔序列用于标记重复行,默认要求全部列相同才算重复,用 subset 参数可以改判重口径(例如只按 ID 判重)。drop_duplicates() 负责真正删除,keep='first' 保留首次出现,keep='last' 保留最后一次。进阶用法是先按业务指标排序再去重,从而实现“同一 ID 只保留金额最大的那条”。
  1. # 构造测试数据
  2. df = pd.DataFrame({
  3.     'ID': [1, 1, 2, 3, 3],
  4.     '姓名': ['张三', '张三', '李四', '王五', '王五'],
  5.     '金额': [100, 200, 150, 300, 300]
  6. })
  7. # 1. 检测重复(默认全部列相同才视为重复)
  8. df['是否重复'] = df.duplicated()
  9. # 2. 按指定列检测重复(仅 ID 相同即视为重复)
  10. df['ID重复'] = df.duplicated(subset=['ID'])
  11. # 3. 删除重复行(保留第一次出现的行)
  12. df_clean = df.drop_duplicates()
  13. # 4. 按 ID 去重,保留金额最大的行(进阶用法)
  14. df_clean = df.sort_values('金额', ascending=False).drop_duplicates(subset=['ID'], keep='first')
复制代码

三、数据类型转换:隐性坑最多的一步

类型必须与业务含义匹配。astype() 用于数值、字符串、分类类型互转;pd.to_numeric() 负责清洗非标准数值,带逗号的 "5,000" 需要先用 str.replace 去掉逗号,异常字符串则可用 errors='coerce' 直接转为 NaN;pd.to_datetime() 能自动识别多种日期格式;低基数列转 astype('category') 可节省内存并加速分组。日期列还能通过 dt 访问器派生年、月、星期等字段,其中 dt.dayofweek 以 0 表示周一、6 表示周日。
  1. # 构造测试数据
  2. df = pd.DataFrame({
  3.     '年龄': ['25', '30', '未知'],
  4.     '收入': ['5,000', '8,000', '10,000'],
  5.     '日期': ['2026-03-01', '2026/03/02', '20260303'],
  6.     '性别': ['男', '女', '男']
  7. })
  8. # 1. 字符串转数值(处理异常值)
  9. df['年龄'] = pd.to_numeric(df['年龄'], errors='coerce')   # "未知" 转为 NaN
  10. df['收入'] = pd.to_numeric(df['收入'].str.replace(',', ''))
  11. # 2. 字符串转日期(自动识别多种格式)
  12. df['日期'] = pd.to_datetime(df['日期'])
  13. # 3. 转为分类类型(节省内存,加速分组)
  14. df['性别'] = df['性别'].astype('category')
  15. # 4. 日期提取年/月/日(衍生字段)
  16. df['年'] = df['日期'].dt.year
  17. df['月'] = df['日期'].dt.month
  18. df['星期'] = df['日期'].dt.dayofweek
复制代码

四、数据重塑:长表与宽表互换

重塑的目的是让数据结构适配分析视角。melt() 把多列压成“键值对”形式实现宽转长,pivot() 反过来把某个字段的取值展开为列实现长转宽,pivot_table() 则在透视的同时做聚合,用 aggfunc 指定 sum、mean 等聚合方式。三者常配合使用:先 melt 后 pivot 可以验证转换是否可逆。
  1. # 构造测试数据
  2. df = pd.DataFrame({
  3.     '地区': ['北京', '北京', '上海', '上海'],
  4.     '品类': ['家电', '数码', '家电', '数码'],
  5.     '2025年': [100, 200, 150, 250],
  6.     '2026年': [120, 220, 180, 280]
  7. })
  8. # 1. 宽转长(将年份列转为行)
  9. df_long = df.melt(
  10.     id_vars=['地区', '品类'],          # 保留的标识列
  11.     value_vars=['2025年', '2026年'],   # 要转换的列
  12.     var_name='年份',                   # 原列名放入的新列
  13.     value_name='销售额'                # 数值放入的新列
  14. )
  15. # 2. 长转宽(恢复宽表)
  16. df_wide = df_long.pivot(
  17.     index=['地区', '品类'],
  18.     columns='年份',
  19.     values='销售额'
  20. ).reset_index()
  21. # 3. 透视表(聚合统计)
  22. df_pivot = df.pivot_table(
  23.     index='地区',
  24.     columns='品类',
  25.     values=['2025年', '2026年'],
  26.     aggfunc='sum'
  27. )
复制代码

五、文本数据处理:标准化后再提取

文本清洗的通用套路是先去噪再抽取。str.strip() 去首尾空格,str.lower()/str.upper() 统一大小写,str.replace() 清掉横线、空格、特殊字符;str.split(',', expand=True) 把逗号分隔的多值列拆成多列,再配合 pd.concat(axis=1) 拼回主表;str.extract() 用正则捕获组提取关键片段,str.contains() 用于按内容筛选行。
  1. # 构造测试数据
  2. df = pd.DataFrame({
  3.     '姓名': [' 张三 ', '李四 ', ' 王五'],
  4.     '手机号': ['138-1234-5678', '139 9876 5432', '13788889999'],
  5.     '地址': ['北京市朝阳区XX小区', '上海市浦东新区XX路', '广州市天河区XX街'],
  6.     '爱好': ['篮球,足球', '读书,旅游', '跑步,游泳']
  7. })
  8. # 1. 格式标准化
  9. df['姓名'] = df['姓名'].str.strip()
  10. df['地址'] = df['地址'].str.replace('市', '')
  11. # 2. 清理特殊字符
  12. df['手机号'] = df['手机号'].str.replace('-', '').str.replace(' ', '')
  13. # 3. 拆分多值列
  14. df_hobby = df['爱好'].str.split(',', expand=True)
  15. df_hobby.columns = ['爱好1', '爱好2']
  16. df = pd.concat([df, df_hobby], axis=1)
  17. # 4. 提取关键信息(从地址提取城市名)
  18. df['省份'] = df['地址'].str.extract('(北京|上海|广州)')
  19. # 5. 筛选包含指定内容的行
  20. df_bj = df[df['地址'].str.contains('北京')]
复制代码

六、数据分箱与离散化

把连续数值转成区间标签,常用于统计报表和特征工程。pd.cut() 是等宽分箱,bins 传整数表示区间个数,也可传自定义边界列表,right=False 表示左闭右开;pd.qcut() 是等频分箱,q 为分位组数,保证每组样本量接近。分箱结果列可直接参与 groupby 做分组统计。
  1. # 构造测试数据
  2. df = pd.DataFrame({
  3.     '年龄': [18, 25, 32, 45, 58, 65, 72, 80],
  4.     '收入': [3000, 8000, 15000, 25000, 50000, 80000, 100000, 150000]
  5. })
  6. # 1. 等宽分箱(年龄分为 4 个等宽区间)
  7. df['年龄分档_等宽'] = pd.cut(df['年龄'], bins=4, labels=['青年', '中年', '中老年', '老年'])
  8. # 2. 等频分箱(收入分为 3 个区间,每个区间样本数相同)
  9. df['收入分档_等频'] = pd.qcut(df['收入'], q=3, labels=['低收入', '中收入', '高收入'])
  10. # 3. 自定义分箱(按业务规则分年龄)
  11. age_bins = [0, 18, 30, 50, 100]
  12. age_labels = ['未成年', '青年', '中年', '老年']
  13. df['年龄分档_自定义'] = pd.cut(df['年龄'], bins=age_bins, labels=age_labels, right=False)
  14. # 4. 分箱后统计(各年龄档的收入均值)
  15. df.groupby('年龄分档_自定义')['收入'].mean()
复制代码

注意 labels 的长度必须与区间个数一致,否则会直接抛错;自定义边界时 right 参数决定区间开闭,边界值落在哪一档要提前确认清楚。

七、其他高频转换

剩下的零散操作同样日常:列间运算生成新列、map 按字典做值映射、sort_values 多列排序、reset_index(drop=True) 重整不连续索引、rename 重命名列、布尔索引过滤行。
  1. # 构造测试数据
  2. df = pd.DataFrame({
  3.     'A': [1, 2, 3, 4],
  4.     'B': [10, 20, 30, 40],
  5.     'C': ['a', 'b', 'a', 'b']
  6. })
  7. # 1. 列运算(创建新列)
  8. df['A+B'] = df['A'] + df['B']
  9. df['A*B'] = df['A'] * df['B']
  10. df['B/A'] = df['B'] / df['A']
  11. # 2. 映射替换(按字典替换值)
  12. map_dict = {'a': '类别1', 'b': '类别2'}
  13. df['C_映射'] = df['C'].map(map_dict)
  14. # 3. 排序(按多列排序)
  15. df_sorted = df.sort_values(by=['C', 'A'], ascending=[True, False])
  16. # 4. 重置索引(清洗后索引不连续时)
  17. df_reset = df_sorted.reset_index(drop=True)
  18. # 5. 重命名列
  19. df_rename = df.rename(columns={'A': '列1', 'B': '列2', 'C': '列3'})
  20. # 6. 过滤行(按条件筛选)
  21. df_filter = df[df['B'] > 20]
复制代码

执行顺序与工程化建议

这 7 类方法几乎覆盖了日常清洗的绝大多数场景,执行顺序建议固定为:先处理缺失值和重复值,再做类型转换,最后根据业务需求做重塑、分箱、文本提取等进阶操作。原因是缺失值会影响均值、众数等统计结果,重复行会放大分箱与透视的计数,而类型不正确会让日期解析、数值比较直接失败。

工程化角度,复杂清洗链路建议封装成函数,把每一步的输入输出、异常捕获和日志记录一并写进去,既方便复用,也便于出问题时定位是哪一步改变了数据形态。函数内部按“参数化列名 + 显式返回新 DataFrame”的方式实现,可以避免链式赋值带来的副本与视图混淆问题。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Pandas数据清洗与预处理7个核心方法及代码实现

楼主这篇整理得很实在,尤其是把“缺失值优先填充”和 transform 那个坑点出来,确实很多人第一次用 groupby 均值回填时会踩到对齐问题。dropna 用 subset 精确指定列这点也值得强调,见过不少新手直接 dropna() 把整行删掉,结果样本量掉了一大截。 另外几个小地方我也挺认同:to_numeric 配 errors='coerce' 对付脏数据几乎是标配,带千分位的字符串先 replace 再转也是常见套路;日期列用 dt 派生周几时注意 0 是周一,这个细节容易被忽略。 不过看正文到“数据重塑”第四部分好像没贴完,melt 和 pivot 的示例代码断在品类那一行了,后面还有文本处理和分箱几类也没看到。方便的话楼主可以补一下后面三部分,尤其是 pivot_table 的 aggfunc 写法,以及文本清洗那块的常用正则,挺想看看你的整理思路。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Pandas数据清洗与预处理7个核心方法及代码实现

这篇整理得很清楚,按“问题类型—函数—参数—代码”来组织,确实很适合日常查漏补缺。缺失值部分把 transform 为什么能直接回填讲明白了,这个点很关键;去重部分用排序后 drop_duplicates 保留金额最大行,也是业务里特别常见的写法。类型转换里提醒带逗号数值和异常字符串用 errors='coerce',还有低基数列转 category,都是容易踩坑但很实用的细节。 另外看到第四部分数据重塑的代码好像到一半截断了,melt、pivot、pivot_table 的完整示例没显示出来,后面几类方法也没展开,如果方便的话可以补全一下,整体会更完整。还有一个小提醒:新版 pandas 里 fillna(method='ffill') 可能会提示参数弃用,改成 df_time['温度'].ffill() 会更稳妥一些。整体内容很扎实,收藏备用很合适。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Pandas数据清洗与预处理7个核心方法及代码实现

整理得很清楚,按“问题类型—函数—参数”来组织很适合当速查表。缺失值那部分把 transform 和直接 groupby().mean() 的区别点出来很关键,按组均值回填确实容易踩坑;重复值里先排序再 drop_duplicates 保留金额最大行的写法也很实用。类型转换部分提醒先去掉逗号再 to_numeric、用 errors='coerce' 处理异常值,以及 dt.dayofweek 的起始规则,都是实际清洗时容易忽略的细节。不过帖子到第四部分数据重塑的示例好像没贴完,后面几类方法还没展开,期待楼主补全,先收藏了。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-11 04:51 , Processed in 0.022106 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部