Pandas数据清洗与预处理

缺失值、重复值与异常值处理——基于Pandas的完整数据清洗实战指南

一、数据清洗概述

数据清洗是数据分析流程中最为关键且耗时最长的环节。真实世界中的数据往往是"脏"的,包含缺失值、重复记录、异常数据、格式不一致等诸多问题。据业界统计,数据科学家通常将约80%的工作时间用于数据清洗和准备工作。Pandas作为Python生态系统中最核心的数据分析库,提供了丰富而高效的数据清洗工具集。

数据清洗的核心目标:

  • 完整性: 处理缺失数据,使数据完整可用
  • 唯一性: 消除重复记录,避免统计偏差
  • 一致性: 统一数据格式和类型,确保可比性
  • 准确性: 检测并处理异常值,提高数据质量
  • 规范性: 清理字符串空格、特殊字符等格式问题

二、缺失值处理

缺失值是数据清洗中最常见的问题。在Pandas中,缺失值通常用 NaN(Not a Number)表示。处理缺失值前首先需要高效地检测和定位缺失数据。

2.1 缺失值检测

Pandas提供了一系列用于检测缺失值的核心函数:

核心检测方法一览

方法功能返回值
isna()检测缺失值布尔型DataFrame/Series
isnull()isna的别名,二者等价布尔型DataFrame/Series
notna()检测非缺失值布尔型DataFrame/Series
info()概览DataFrame信息列名、非空计数、数据类型等
import pandas as pd import numpy as np # 创建包含缺失值的示例数据 df = pd.DataFrame({ '姓名': ['张三', '李四', '王五', '赵六', '钱七'], '年龄': [28, 35, np.nan, 42, np.nan], '薪资': [15000, np.nan, 22000, 18000, 25000], '部门': ['技术部', '市场部', '技术部', np.nan, '财务部'] }) # isna() 逐元素检测是否为缺失值 print(df.isna())
姓名 年龄 薪资 部门 0 False False False False 1 False False True False 2 False True False False 3 False False False True 4 False True False False
# info() 快速概览数据集缺失情况 df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 5 entries, 0 to 4 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 姓名 5 non-null object 1 年龄 3 non-null float64 2 薪资 4 non-null float64 3 部门 4 non-null object dtypes: float64(2), object(2) memory usage: 292.0+ bytes
# 统计每列缺失值的数量 print(df.isna().sum()) # 统计每列非缺失值的数量 print(df.notna().sum())
姓名 0 年龄 2 薪资 1 部门 1 dtype: int64 姓名 5 年龄 3 薪资 4 部门 4 dtype: int64

2.2 缺失值删除——dropna()

当缺失数据占比很小或缺失行可舍弃时,可直接删除包含缺失值的行或列。

# 删除包含任意缺失值的行(默认axis=0, how='any') df_clean = df.dropna() print(df_clean) # 删除整行全部为缺失值的行 df_clean2 = df.dropna(how='all') # 删除超过2个缺失值的行 df_clean3 = df.dropna(thresh=2) # 仅在指定列的子集中检测缺失值 df_clean4 = df.dropna(subset=['年龄', '薪资']) # 删除全部为NaN的列 df_clean5 = df.dropna(axis=1, how='all')
姓名 年龄 薪资 部门 0 张三 28.0 15000.0 技术部

注:只有第0行没有缺失值,因此dropna()默认只保留了这一行。

2.3 前向填充与后向填充——ffill() / bfill()

在时间序列数据中,缺失值通常使用邻近的数据进行填充。前向填充(ffill)用上一个有效值填充,后向填充(bfill)用下一个有效值填充。

# 创建时间序列示例 ts_data = pd.Series( [100, np.nan, np.nan, 120, 130, np.nan, 145], index=pd.date_range('2024-01-01', periods=7, freq='D') ) # 前向填充:用上一个非缺失值填充 print("前向填充 (ffill):") print(ts_data.ffill()) # 后向填充:用下一个非缺失值填充 print("\n后向填充 (bfill):") print(ts_data.bfill()) # 限制最大填充步数(只向前填充1步) print("\n限制填充步数 (limit=1):") print(ts_data.ffill(limit=1))
前向填充 (ffill): 2024-01-01 100.0 2024-01-02 100.0 2024-01-03 100.0 2024-01-04 120.0 2024-01-05 130.0 2024-01-06 130.0 2024-01-07 145.0 Freq: D, dtype: float64 后向填充 (bfill): 2024-01-01 100.0 2024-01-02 120.0 2024-01-03 120.0 2024-01-04 120.0 2024-01-05 130.0 2024-01-06 145.0 2024-01-07 145.0 Freq: D, dtype: float64

2.4 插值填充——interpolate()

interpolate()方法利用数学插值算法来填补缺失值,适用于具有一定变化趋势的数据。

# 线性插值 ts_linear = ts_data.interpolate(method='linear') print("线性插值:") print(ts_linear) # 时间插值(考虑时间间隔) ts_time = ts_data.interpolate(method='time') print("\n时间插值:") print(ts_time) # 多项式插值(order=2) ts_poly = ts_data.interpolate(method='polynomial', order=2) print("\n多项式插值 (order=2):") print(ts_poly)
线性插值: 2024-01-01 100.0 2024-01-02 106.7 2024-01-03 113.3 2024-01-04 120.0 2024-01-05 130.0 2024-01-06 137.5 2024-01-07 145.0 Freq: D, dtype: float64

2.5 灵活填充——fillna() 多种策略

fillna()是Pandas中最灵活、最强大的缺失值填充方法,支持常量填充、字典填充、统计值填充等。

# 1. 常量填充 df['年龄'] = df['年龄'].fillna(0) # 2. 填充统计值(均值、中位数、众数) df['年龄'] = df['年龄'].fillna(df['年龄'].mean()) df['薪资'] = df['薪资'].fillna(df['薪资'].median()) # 3. 分组填充(按部门分组后填充中位数) df['薪资'] = df.groupby('部门')['薪资'].transform( lambda x: x.fillna(x.median()) ) # 4. 使用字典对不同列填充不同值 fill_values = {'年龄': 30, '薪资': 20000, '部门': '未知'} df_filled = df.fillna(value=fill_values) print(df_filled)
姓名 年龄 薪资 部门 0 张三 28.0 15000.0 技术部 1 李四 35.0 20000.0 市场部 2 王五 30.0 22000.0 技术部 3 赵六 42.0 18000.0 未知 4 钱七 30.0 25000.0 财务部
# 5. 填充前一个非缺失值(DataFrame级别) df.fillna(method='ffill', inplace=True) # 6. 填充后一个非缺失值 df.fillna(method='bfill', inplace=True) # 7. 向前填充并限制步数 df.fillna(method='ffill', limit=2, inplace=True)

三、重复值处理

重复数据会导致统计结果出现偏差,尤其是在聚合计算(求和、计数、均值等)中影响尤为显著。Pandas提供了完整的重复值检测与删除工具。

3.1 重复值检测——duplicated()

# 创建包含重复行的数据 df_dup = pd.DataFrame({ '产品': ['A', 'B', 'A', 'C', 'B', 'A'], '价格': [100, 200, 100, 150, 200, 100], '数量': [10, 20, 10, 15, 20, 30] }) # 检测完全重复的行(默认保留第一次出现) print(df_dup.duplicated()) # 检测指定列的重复值 print(df_dup.duplicated(subset=['产品'])) # keep参数控制标记方式:'first'/'last'/False print(df_dup.duplicated(keep='last')) print(df_dup.duplicated(keep=False))
0 False 1 False 2 True # 行0和行2完全重复 3 False 4 True # 行1和行4完全重复 5 False # 行5价格、产品和行0相同,但数量不同,不算完全重复 dtype: bool 按'产品'列检测: 0 False 1 False 2 True 3 False 4 True 5 True dtype: bool

3.2 重复值删除——drop_duplicates()

# 删除完全重复的行(默认保留第一次出现) df_unique = df_dup.drop_duplicates() print("删除完全重复行:") print(df_unique) # 删除指定列的重复行 df_unique_subset = df_dup.drop_duplicates(subset=['产品']) print("\n按'产品'列去重 (保留首次):") print(df_unique_subset) # keep='last' 保留最后一次出现的重复行 df_unique_last = df_dup.drop_duplicates(subset=['产品'], keep='last') print("\n按'产品'列去重 (保留末次):") print(df_unique_last) # keep=False 删除所有重复行(一行不留) df_unique_all = df_dup.drop_duplicates(subset=['产品'], keep=False) print("\n按'产品'列去重 (删除所有重复):") print(df_unique_all) # inplace=True 直接修改原DataFrame df_dup.drop_duplicates(inplace=True) # ignore_index=True 重置索引 df_reset = df_dup.drop_duplicates(ignore_index=True)
删除完全重复行: 产品 价格 数量 0 A 100 10 1 B 200 20 3 C 150 15 5 A 100 30 按'产品'列去重 (保留首次): 产品 价格 数量 0 A 100 10 1 B 200 20 3 C 150 15

duplicated()和drop_duplicates()的keep参数对比:

  • keep='first'(默认): 标记/保留第一次出现的行,后续重复行视为重复
  • keep='last': 标记/保留最后一次出现的行,前面的重复行视为重复
  • keep=False: 所有重复行都标记为重复 / 全部删除

四、异常值检测与处理

异常值(Outlier)是指明显偏离其他观测值的数据点。异常值可能由数据录入错误、测量误差或真实的极端情况引起。正确的做法是检测出异常值后,结合业务场景判断是修正还是保留。

4.1 IQR方法(四分位距法)

IQR方法基于数据的四分位数来识别异常值。通常将低于Q1-1.5*IQR或高于Q3+1.5*IQR的数据点视为异常值。

# IQR方法检测异常值 data = pd.Series([10, 12, 11, 13, 100, 9, 11, 12, 10, 500]) Q1 = data.quantile(0.25) Q3 = data.quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = data[(data < lower_bound) | (data > upper_bound)] print(f"Q1 = {Q1}, Q3 = {Q3}, IQR = {IQR}") print(f"下界 = {lower_bound}, 上界 = {upper_bound}") print(f"异常值:\n{outliers}") # 封装为函数 def detect_outliers_iqr(series): Q1 = series.quantile(0.25) Q3 = series.quantile(0.75) IQR = Q3 - Q1 lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 * IQR return series[(series < lower) | (series > upper)]
Q1 = 10.0, Q3 = 12.5, IQR = 2.5 下界 = 6.25, 上界 = 16.25 异常值: 4 100 9 500 dtype: int64

4.2 Z-score方法(标准差法)

Z-score基于正态分布假设,计算每个数据点与均值的标准差距离。通常将Z-score绝对值大于3的数据点视为异常值。

from scipy import stats # Z-score检测异常值 z_scores = np.abs(stats.zscore(data)) threshold = 3 outliers_z = data[z_scores > threshold] print("Z-scores:", z_scores.round(2)) print(f"Z-score方法检测到的异常值:\n{outliers_z}") # 手动计算Z-score验证 mean_val = data.mean() std_val = data.std() manual_z = (data - mean_val) / std_val print("\n手动计算Z-scores:", manual_z.round(2))
Z-scores: [0.32 0.31 0.32 0.31 1.93 0.32 0.32 0.31 0.32 2.24] Z-score方法检测到的异常值: Series([], dtype: float64)

注:本例中数据量小且100和500的Z-score未超过3,说明Z-score在数据量较小时对极端值敏感度不如IQR方法。实际应用中建议结合多种方法。

4.3 百分位数法

百分位数法直接指定数据分布的上限和下限百分位,适用于对数据分布有明确业务约束的场景。

# 百分位数法:剔除前1%和后99%的数据 def detect_outliers_percentile(series, lower_pct=0.01, upper_pct=0.99): lower = series.quantile(lower_pct) upper = series.quantile(upper_pct) return series[(series < lower) | (series > upper)] outliers_pct = detect_outliers_percentile(data, 0.1, 0.9) print("百分位数法(10%-90%)异常值:", outliers_pct.tolist()) # 直接截断(将超出边界的值替换为边界值) lower_10 = data.quantile(0.1) upper_90 = data.quantile(0.9) data_capped = data.clip(lower=lower_10, upper=upper_90) print("截断后:", data_capped.tolist())
百分位数法(10%-90%)异常值: [100.0, 500.0] 截断后: [10.0, 12.0, 11.0, 13.0, 13.0, 9.0, 11.0, 12.0, 10.0, 13.0]

4.4 条件筛选法

结合业务规则进行条件筛选,是最灵活且最贴近实际应用场景的方法。

# 基于业务规则的异常值筛选 df_biz = pd.DataFrame({ '员工': ['A', 'B', 'C', 'D', 'E'], '年龄': [28, 35, 150, 42, -5], '月薪': [15000, 20000, 999999, 18000, 0], '入职年份': [2020, 2018, 2023, 2015, 2099] }) # 业务规则:年龄应在18-65岁之间 age_rule = (df_biz['年龄'] >= 18) & (df_biz['年龄'] <= 65) # 业务规则:月薪应在2000-100000之间 salary_rule = (df_biz['月薪'] >= 2000) & (df_biz['月薪'] <= 100000) # 业务规则:入职年份不应超过当前年份 year_rule = df_biz['入职年份'] <= 2026 # 综合筛选:标记所有异常行 df_biz['是否异常'] = ~(age_rule & salary_rule & year_rule) print(df_biz) # 筛选出正常数据 df_normal = df_biz[age_rule & salary_rule & year_rule].copy() print("\n正常数据:") print(df_normal)
员工 年龄 月薪 入职年份 是否异常 0 A 28 15000 2020 False 1 B 35 20000 2018 False 2 C 150 999999 2023 True # 年龄和月薪异常 3 D 42 18000 2015 False 4 E -5 0 2099 True # 年龄、月薪、入职年份均异常

五、数据替换与条件修改

5.1 replace()——精确替换

replace()用于将DataFrame或Series中的特定值替换为目标值,支持单值替换、列表替换和字典替换。

# 创建示例数据 df_rep = pd.DataFrame({ '城市': ['北京', '上海', '广州', '深圳', 'BJ', 'SH'], '等级': ['A', 'B', 'C', 'A', 'B+', 'A-'], '评分': [95, 85, 70, 90, 88, 92] }) # 单值替换 df_rep['城市'] = df_rep['城市'].replace('BJ', '北京') # 多值替换(列表对应) df_rep['城市'] = df_rep['城市'].replace(['SH', 'GZ'], ['上海', '广州']) # 字典替换 df_rep['等级'] = df_rep['等级'].replace({ 'A': '优秀', 'B': '良好', 'C': '一般', 'B+': '良好+', 'A-': '优秀-' }) # 正则表达式替换 df_rep['城市'] = df_rep['城市'].replace(r'[A-Z]+', '其他', regex=True) # 全表替换(对整个DataFrame) df_rep.replace({'城市': {'北京': '首都'}, '评分': {95: 100}})

5.2 mask()与where()——条件替换

mask()将满足条件的值替换为指定值,where()将满足条件的值替换为指定值。二者互为补充。

# mask(): 满足条件的数据被替换 s = pd.Series([1, 2, 3, 4, 5, 6]) # 将大于3的值替换为0 print("mask (x>3 → 0):") print(s.mask(s > 3, 0)) # where(): 不满足条件的数据被替换 print("\nwhere (x<=3 → 0):") print(s.where(s > 3, 0)) # 等价于:将<=3的值替换为0 # 实际应用:将异常月薪替换为中位数 median_salary = df_biz.loc[df_biz['月薪'].between(2000, 100000), '月薪'].median() df_biz['月薪_修正'] = df_biz['月薪'].mask( ~df_biz['月薪'].between(2000, 100000), median_salary ) print("\n月薪修正结果:") print(df_biz[['员工', '月薪', '月薪_修正']])
mask (x>3 → 0): 0 1 1 2 2 3 3 0 4 0 5 0 dtype: int64 where (x<=3 → 0): 0 0 1 0 2 0 3 4 4 5 5 6 dtype: int64 月薪修正结果: 员工 月薪 月薪_修正 0 A 15000 15000.0 1 B 20000 20000.0 2 C 999999 18000.0 # 替换为中位数 3 D 18000 18000.0 4 E 0 18000.0 # 替换为中位数

六、字符串空格清理

从外部导入的文本数据往往包含多余的空格、换行符或制表符。Pandas的str访问器提供了便捷的字符串清理方法。

6.1 strip()系列方法

# 创建包含空格问题的数据 df_str = pd.DataFrame({ '姓名': [' 张三 ', ' 李四', '王五 ', ' 赵 六 '], '邮箱': ['zhangsan@test.com\n', ' lisi@test.com', 'wangwu@test.com', 'zhaoliu@test.com '], '备注': [' 正常 ', ' 异常\t', '待确认\n', ' 已完成 '] }) # strip(): 去除首尾空格 df_str['姓名'] = df_str['姓名'].str.strip() # lstrip(): 仅去除左侧空格 df_str['邮箱'] = df_str['邮箱'].str.strip() # rstrip(): 仅去除右侧空格和换行符 df_str['备注'] = df_str['备注'].str.strip() # 去除所有空格(包括中间空格) df_str['姓名无空格'] = df_str['姓名'].str.replace(' ', '') print(df_str) # 批量清理所有字符串列 str_cols = df_str.select_dtypes(include=['object']).columns for col in str_cols: df_str[col] = df_str[col].str.strip()
姓名 邮箱 姓名无空格 0 张三 zhangsan@test.com 张三 1 李四 lisi@test.com 李四 2 王五 wangwu@test.com 王五 3 赵 六 zhaoliu@test.com 赵六
# 其他常用字符串清理操作 # 去除空格并统一大小写 df_str['邮箱'] = df_str['邮箱'].str.strip().str.lower() # 去除制表符和换行符 df_str['备注'] = df_str['备注'].str.replace(r'\s+', ' ', regex=True) # 替换空字符串为NaN df_str['姓名'] = df_str['姓名'].replace('', np.nan) # 检查空字符串 print("空字符串统计:") print((df_str == '').sum())

七、数据类型转换

数据类型不匹配是数据分析中常见的"隐形"问题。错误的类型会导致运算异常、排序错误、内存浪费等问题。

7.1 astype()——通用类型转换

# 创建包含类型问题的数据 df_type = pd.DataFrame({ 'ID': ['1001', '1002', '1003', '1004'], '年龄': ['28', '35', '42', '29'], '价格': ['99.9', '150.5', '200.0', '88.8'], '活跃': ['是', '否', '是', '是'] }) print("转换前的类型:") print(df_type.dtypes) # 基本类型转换 df_type['ID'] = df_type['ID'].astype('int') df_type['年龄'] = df_type['年龄'].astype('int') df_type['价格'] = df_type['价格'].astype('float') # 转换分类类型(节省内存) df_type['活跃'] = df_type['活跃'].astype('category') # 使用pd.CategoricalDtype自定义分类顺序 cat_order = pd.CategoricalDtype(categories=['是', '否'], ordered=True) df_type['活跃'] = df_type['活跃'].astype(cat_order) print("\n转换后的类型:") print(df_type.dtypes) print(f"\n内存占用: {df_type.memory_usage(deep=True)}")
转换前的类型: ID object 年龄 object 价格 object 活跃 object dtype: object 转换后的类型: ID int64 年龄 int64 价格 float64 活跃 category dtype: object

7.2 to_numeric()——智能数值转换

相比astype(),to_numeric()能智能处理各种数值格式字符串,遇到无法转换的值时可选择报错、忽略或强制转为NaN。

# 包含"脏"数值的数据 s_messy = pd.Series(['100', '200.5', '$300', '400元', 'N/A', '五佰']) # errors='coerce': 无法转换的变为NaN s_clean = pd.to_numeric(s_messy, errors='coerce') print("errors='coerce':") print(s_clean) # errors='raise': 遇到无法转换的值抛出异常(默认) try: pd.to_numeric(s_messy, errors='raise') except Exception as e: print(f"\nerrors='raise' 抛出异常: {e}") # errors='ignore': 返回原始输入(不转换) s_ignore = pd.to_numeric(s_messy, errors='ignore') print("\nerrors='ignore':") print(s_ignore) # downcast参数优化类型 s_downcast = pd.to_numeric(s_clean, downcast='integer') print(f"\ndowncast后类型: {s_downcast.dtype}")
errors='coerce': 0 100.0 1 200.5 2 NaN 3 NaN 4 NaN 5 NaN dtype: float64 errors='ignore': 0 100 1 200.5 2 $300 3 400元 4 N/A 5 五佰 dtype: object

7.3 to_datetime()——智能日期转换

日期格式的多样性是数据清洗中的常见挑战。Pandas的to_datetime()能够自动识别绝大多数常见日期格式。

# 各种常见日期格式 dates = pd.Series([ '2024-01-15', '2024/02/20', '03-15-2024', '2024年4月10日', '2024.05.01', '2024-06-30 14:30:00' ]) # 自动推断日期格式 dates_parsed = pd.to_datetime(dates) print("解析后的日期:") print(dates_parsed) # 指定日期格式(更高效,避免歧义) dates_format = pd.to_datetime(dates, format='mixed') # 注意:format='mixed'让Pandas自动检测每行的格式 # 处理包含无效日期的数据 dates_messy = pd.Series([ '2024-01-15', 'not-a-date', '2024-13-01', # 不存在月份 '2024-02-30' # 不存在日期 ]) # errors='coerce' 将无效日期变为NaT dates_clean = pd.to_datetime(dates_messy, errors='coerce') print("\n无效日期处理 (errors='coerce'):") print(dates_clean) # 提取日期组件 df_date = pd.DataFrame({ 'date': dates_parsed, 'year': dates_parsed.dt.year, 'month': dates_parsed.dt.month, 'day': dates_parsed.dt.day, 'dayofweek': dates_parsed.dt.dayofweek, 'quarter': dates_parsed.dt.quarter }) print("\n日期组件提取:") print(df_date)
解析后的日期: 0 2024-01-15 00:00:00 1 2024-02-20 00:00:00 2 2024-03-15 00:00:00 3 2024-04-10 00:00:00 4 2024-05-01 00:00:00 5 2024-06-30 14:30:00 dtype: datetime64[ns] 无效日期处理 (errors='coerce'): 0 2024-01-15 1 NaT 2 NaT 3 2024-02-29 # 2024年是闰年,2月29日有效! dtype: datetime64[ns]

日期清洗最佳实践:

  • 先使用errors='coerce'安全转换,避免程序中断
  • 转换后检查NaT的数量,评估数据质量
  • 对于已知格式,指定format参数可极大提高解析速度(10倍以上)
  • 利用dt访问器可以方便地提取年、月、日、星期等组件

八、综合实战案例:完整数据清洗流水线

下面展示一个从CSV文件读取脏数据到输出干净数据的完整流水线:

import pandas as pd import numpy as np # 模拟读取原始数据 raw_data = pd.DataFrame({ '姓名': [' 张三 ', '李四', ' 王五 ', '赵六', ' 钱七 ', '张三'], '年龄': ['28', '35', 'N/A', '-5', '42', '28'], '月薪': ['15000', '20000', 'N/A', '999999', '25000', '15000'], '入职日期': ['2020-01-15', '2021/03/20', '无效日期', '2019/06/01', '2022.11.30', '2020-01-15'], '部门': ['技术部', ' 市场部 ', '技术部', '财务部', '市场部', '技术部'] }) # ====== 完整清洗流水线 ====== # Step 1: 备份原始数据 df = raw_data.copy() print(f"原始数据: {df.shape}") # Step 2: 字符串空格清理 str_cols = df.select_dtypes(include=['object']).columns for col in str_cols: df[col] = df[col].str.strip() # Step 3: 删除全空行和完全重复行 df = df.dropna(how='all').drop_duplicates() print(f"去重后: {df.shape}") # Step 4: 类型转换和缺失值处理 df['年龄'] = pd.to_numeric(df['年龄'], errors='coerce') df['月薪'] = pd.to_numeric(df['月薪'], errors='coerce') df['入职日期'] = pd.to_datetime(df['入职日期'], errors='coerce') # Step 5: 异常值修正 median_age = df.loc[df['年龄'].between(18, 65), '年龄'].median() median_salary = df.loc[df['月薪'].between(2000, 100000), '月薪'].median() df['年龄'] = df['年龄'].mask( ~df['年龄'].between(18, 65), median_age ) df['月薪'] = df['月薪'].mask( ~df['月薪'].between(2000, 100000), median_salary ) # Step 6: 缺失值填充 df['年龄'] = df['年龄'].fillna(median_age) df['月薪'] = df['月薪'].fillna(median_salary) # Step 7: 重置索引 df = df.reset_index(drop=True) print(f"\n最终清洗结果:") print(df) print(f"\n数据类型:\n{df.dtypes}")
原始数据: (6, 5) 去重后: (5, 5) # 删除了一条完全重复的"张三"记录 最终清洗结果: 姓名 年龄 月薪 入职日期 部门 0 张三 28.0 15000.0 2020-01-15 技术部 1 李四 35.0 20000.0 2021-03-20 市场部 2 王五 28.0 20000.0 NaT 技术部 3 赵六 42.0 20000.0 2019-06-01 财务部 4 钱七 42.0 25000.0 2022-11-30 市场部 数据类型: 姓名 object 年龄 float64 月薪 float64 入职日期 datetime64[ns] 部门 object dtype: object

九、核心要点总结

十、进一步学习建议

推荐学习路径

  1. 基础夯实: 熟练掌握本文介绍的每种方法,用真实数据集反复练习
  2. 进阶技巧: 学习pandas-profiling / ydata-profiling自动生成数据质量报告
  3. 可视化辅助: 结合matplotlib/seaborn绘制箱线图、直方图辅助异常值判断
  4. 大数据场景: 研究Dask、Modin等并行计算框架中的数据处理方法
  5. 自动化流水线: 将清洗逻辑封装为可复用的函数或类,构建自动化ETL流程

常用Pandas数据清洗速查表

任务推荐方法
检测缺失值df.isna().sum()
删除缺失行df.dropna()
填补缺失值df.fillna(value)
前向填充df.ffill() / df.fillna(method='ffill')
线性插值df.interpolate()
检测重复行df.duplicated()
删除重复行df.drop_duplicates()
替换特定值df.replace(old, new)
条件替换df.mask(cond, value) / df.where(cond, value)
去除首尾空格df['col'].str.strip()
转换数值类型pd.to_numeric(df['col'], errors='coerce')
转换日期类型pd.to_datetime(df['col'], errors='coerce')
箱线图异常检测df.boxplot(column=['col'])