平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“Pandas清洗数据和预处理的7种核心做法”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。
1. 缺失值处理
实际处理时,缺失值是数据清洗的首要问题,核心原则是「能填充不删除」,以下是不同场景的最优处理方案:
| 操作 | 方法 | 适用场景 |
|---|---|---|
| 删除缺失值 | dropna() | 缺失值占比极低(<5%)且无填充意义 |
| 固定值填充 | fillna(固定值) | 业务上有明确默认值(如未知性别填 'unknown') |
| 统计值填充 | fillna(mean/median/mode) | 数值列(均值 / 中位数)、分类列(众数) |
| 方向填充 | fillna(method='ffill/bfill') | 时序数据(如价格、传感器数据) |
| 分组填充 | groupby + transform + fillna | 需按维度填充(如按地区填充该地区均值) |
示例代码
import pandas as pd
import numpy as np
# 构造测试数据
df = pd.DataFrame({
'地区': ['北京', '北京', '上海', '上海', '广州', '广州'],
'销售额': [100, np.nan, 200, 150, np.nan, 300],
'品类': ['家电', np.nan, '数码', '数码', '家电', '家电']
})
# 1. 删除缺失值(仅删除销售额列有缺失的行)
df_drop = df.dropna(subset=['销售额'])
# 2. 数值列用分组均值填充(按地区填充该地区销售额均值)
df['销售额'] = df.groupby('地区')['销售额'].transform(lambda x: x.fillna(x.mean()))
# 3. 分类列用众数填充
df['品类'] = df['品类'].fillna(df['品类'].mode()[0])
# 4. 时序数据方向填充(示例)
df_time = pd.DataFrame({'温度': [25, np.nan, 27, np.nan, 28]},
index=pd.date_range('2026-03-01', periods=5))
df_time['温度'] = df_time['温度'].fillna(method='ffill') # 前向填充
2. 重复数据处理
重复数据会导致分析结果偏差,重点是「精准识别 + 合理保留」:
| 操作 | 方法 | 说明 |
|---|---|---|
| 检测重复 | duplicated() | 得到布尔值,标记重复行 |
| 删除重复 | drop_duplicates() | 可指定列、保留规则 |
示例代码
# 构造测试数据
df = pd.DataFrame({
'ID': [1, 1, 2, 3, 3],
'姓名': ['张三', '张三', '李四', '王五', '王五'],
'金额': [100, 200, 150, 300, 300]
})
# 1. 检测重复(默认全部列相同才视为重复)
df['是否重复'] = df.duplicated()
# 2. 按指定列检测重复(仅ID相同即视为重复)
df['ID重复'] = df.duplicated(subset=['ID'])
# 3. 删除重复行(保留第一次出现的行)
df_clean = df.drop_duplicates()
# 4. 按ID去重,保留金额最大的行(进阶用法)
df_clean = df.sort_values('金额', ascending=False).drop_duplicates(subset=['ID'], keep='first')
3. 数据类型转换
数据类型错误是隐性坑,需确保「类型匹配业务含义」:
| 操作 | 方法 | 适用场景 |
|---|---|---|
| 基础转换 | astype() | 数值 / 字符串 / 分类类型互转 |
| 数值转换 | pd.to_numeric() | 清洗非标准数值(如带逗号的 "1,000") |
| 日期转换 | pd.to_datetime() | 字符串 / 数字转日期 |
| 分类类型 | astype('category') | 低基数分类列(如性别、地区) |
示例代码
# 构造测试数据
df = pd.DataFrame({
'年龄': ['25', '30', '未知'],
'收入': ['5,000', '8,000', '10,000'],
'日期': ['2026-03-01', '2026/03/02', '20260303'],
'性别': ['男', '女', '男']
})
# 1. 字符串转数值(处理异常值)
df['年龄'] = pd.to_numeric(df['年龄'], errors='coerce') # "未知"转为NaN
df['收入'] = pd.to_numeric(df['收入'].str.replace(',', '')) # 去掉逗号再转数值
# 2. 字符串转日期(自动识别多种格式)
df['日期'] = pd.to_datetime(df['日期'])
# 3. 转为分类类型(节省内存,加速分组)
df['性别'] = df['性别'].astype('category')
# 4. 日期提取年/月/日(衍生字段)
df['年'] = df['日期'].dt.year
df['月'] = df['日期'].dt.month
df['星期'] = df['日期'].dt.dayofweek # 0=周一,6=周日
4. 数据重塑与变形
数据重塑用来调整数据结构,适配分析需求,核心是「长表↔宽表」转换:
| 操作 | 方法 | 适用场景 |
|---|---|---|
| 透视表 | pivot_table() | 聚合统计(如按地区 + 品类统计销售额) |
| 长转宽 | pivot() | 单列多值转为多列 |
| 宽转长 | melt() | 多列转为键值对形式 |
| 堆叠 / 解堆 | stack()/unstack() | 层级索引转换 |
示例代码
# 构造测试数据
df = pd.DataFrame({
'地区': ['北京', '北京', '上海', '上海'],
'品类': ['家电', '数码', '家电', '数码'],
'2025年': [100, 200, 150, 250],
'2026年': [120, 220, 180, 280]
})
# 1. 宽转长(将年份列转为行)
df_long = df.melt(
id_vars=['地区', '品类'], # 保留的标识列
value_vars=['2025年', '2026年'], # 要转换的列
var_name='年份', # 新列名(原列名)
value_name='销售额' # 新值列名
)
# 2. 长转宽(恢复宽表)
df_wide = df_long.pivot(
index=['地区', '品类'],
columns='年份',
values='销售额'
).reset_index()
# 3. 透视表(聚合统计)
df_pivot = df.pivot_table(
index='地区',
columns='品类',
values=['2025年', '2026年'],
aggfunc='sum' # 聚合方式
)
5. 文本数据处理
文本数据易出现格式混乱,需「标准化 + 提取关键信息」:
| 操作 | 方法 | 说明 |
|---|---|---|
| 格式标准化 | str.strip()/lower()/upper() | 去空格、统一大小写 |
| 替换 / 删除 | str.replace() | 清理特殊字符 |
| 拆分 / 合并 | str.split()/str.cat() | 拆分多值列、合并列 |
| 匹配 / 提取 | str.contains()/str.extract() | 筛选、提取关键信息 |
示例代码
# 构造测试数据
df = pd.DataFrame({
'姓名': [' 张三 ', '李四 ', ' 王五'],
'手机号': ['138-1234-5678', '139 9876 5432', '13788889999'],
'地址': ['北京市朝阳区XX小区', '上海市浦东新区XX路', '广州市天河区XX街'],
'爱好': ['篮球,足球', '读书,旅游', '跑步,游泳']
})
# 1. 格式标准化
df['姓名'] = df['姓名'].str.strip() # 去空格
df['地址'] = df['地址'].str.replace('市', '') # 去掉"市"字
# 2. 清理特殊字符
df['手机号'] = df['手机号'].str.replace('-', '').str.replace(' ', '') # 去掉横线和空格
# 3. 拆分多值列
df_hobby = df['爱好'].str.split(',', expand=True)
df_hobby.columns = ['爱好1', '爱好2']
df = pd.concat([df, df_hobby], axis=1)
# 4. 提取关键信息(从地址提取省份)
df['省份'] = df['地址'].str.extract('(北京|上海|广州)')
# 5. 筛选包含指定内容的行
df_bj = df[df['地址'].str.contains('北京')]
6. 数据分箱与离散化
将连续数值转为分类(如年龄分档、收入分级),适配统计 / 建模需求:
| 操作 | 方法 | 说明 |
|---|---|---|
| 等宽分箱 | pd.cut() | 区间宽度相同(如 0-20,20-40...) |
| 等频分箱 | pd.qcut() | 每个区间样本数相同 |
| 自定义分箱 | pd.cut( bins=自定义列表 ) | 按业务规则分箱 |
示例代码
# 构造测试数据
df = pd.DataFrame({
'年龄': [18, 25, 32, 45, 58, 65, 72, 80],
'收入': [3000, 8000, 15000, 25000, 50000, 80000, 100000, 150000]
})
# 1. 等宽分箱(年龄分为4个等宽区间)
df['年龄分档_等宽'] = pd.cut(df['年龄'], bins=4, labels=['青年', '中年', '中老年', '老年'])
# 2. 等频分箱(收入分为3个区间,每个区间样本数相同)
df['收入分档_等频'] = pd.qcut(df['收入'], q=3, labels=['低收入', '中收入', '高收入'])
# 3. 自定义分箱(按业务规则分年龄)
age_bins = [0, 18, 30, 50, 100]
age_labels = ['未成年', '青年', '中年', '老年']
df['年龄分档_自定义'] = pd.cut(df['年龄'], bins=age_bins, labels=age_labels, right=False)
# 4. 分箱后统计(各年龄档的收入均值)
df.groupby('年龄分档_自定义')['收入'].mean()
7. 其他常用转换
涵盖日常清洗中高频但未归类的操作:
示例代码
# 构造测试数据
df = pd.DataFrame({
'A': [1, 2, 3, 4],
'B': [10, 20, 30, 40],
'C': ['a', 'b', 'a', 'b']
})
# 1. 列运算(创建新列)
df['A+B'] = df['A'] + df['B'] # 简单运算
df['A*B'] = df['A'] * df['B'] # 乘法
df['B/A'] = df['B'] / df['A'] # 除法
# 2. 映射替换(按字典替换值)
map_dict = {'a': '类别1', 'b': '类别2'}
df['C_映射'] = df['C'].map(map_dict)
# 3. 排序(按多列排序)
df_sorted = df.sort_values(by=['C', 'A'], ascending=[True, False])
# 4. 重置索引(清洗后索引不连续时)
df_reset = df_sorted.reset_index(drop=True)
# 5. 重命名列
df_rename = df.rename(columns={'A': '列1', 'B': '列2', 'C': '列3'})
# 6. 过滤行(按条件筛选)
df_filter = df[df['B'] > 20] # B列大于20的行
总结
- 核心逻辑:数据清洗的本质是「发现问题→针对性处理→适配分析需求」,以上 7 类方法覆盖 95% 以上的日常场景。
- 优先级:先处理缺失 / 重复值,再做类型转换,最后根据业务需求做重塑、分箱等进阶操作。
- 技巧:复杂清洗任务建议封装为函数,配合异常处理和日志,便于复用和排查问题。

