19 Pandas(二):数据清洗与缺失值处理——真实数据的"洗澡"指南

现实世界的数据永远是脏的:缺失值、重复行、格式不统一、异常值……据业界统计,数据分析师80%的时间都花在数据清洗上。Pandas提供了一整套数据清洗工具,让你用几行代码完成原本需要几小时的手工清理。

本篇讲透:缺失值处理、重复值处理、数据类型转换、字符串清洗


一、缺失值(NaN)是什么?

在Pandas里,缺失值用 NaN(Not a Number)表示,它来自 numpy.nan

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name':   ['Tom', np.nan, 'Bob'],
    'age':    [18, 19, np.nan],
    'height': [167, 177, 178]
}, index=['n1', 'n2', 'n3'])

print(df)
#    name   age  height
# n1  Tom  18.0   167.0
# n2  NaN  19.0   177.0
# n3  Bob   NaN   178.0

在这里插入图片描述

1.1 检测缺失值:isnull() 和 notnull()

df = pd.DataFrame({
    'name':   ['Tom', np.nan, 'Bob'],
    'age':    [18, 19, np.nan],
    'height': [167,    177,   178]
})

# isnull():是NaN的地方返回True
print(df.isnull())
#    name    age  height
# n1  False  False   False
# n2   True  False   False
# n3  False   True   False

# notnull():不是NaN的地方返回True(isnull的倒数)
print(df.notnull())
#    name   age  height
# n1   True  True    True
# n2  False  True    True
# n3   True  False    True

实用技巧:结合 sum() 快速统计每列有多少缺失值:

missing_count = df.isnull().sum()
print(missing_count)
# name      1
# age       1
# height    0
# dtype: int64

📸 [图2:用isnull()+sum()统计各列缺失值数量——柱状图展示]


二、删除缺失值:dropna()

dropna() 是最直接的缺失值处理方式——把有NaN的行或列直接删掉

2.1 基础用法

df = pd.DataFrame({
    'name':   ['Tom', np.nan, 'Bob'],
    'age':    [np.nan, np.nan, 19],
    'height': [177,    182,   179]
})

print(df)
#    name   age  height
# n1  Tom   NaN   177.0
# n2  NaN   NaN   182.0
# n3  Bob  19.0   179.0

# 默认:删除任何含有NaN的行(axis=0, how='any')
print(df.dropna())
#    name   age  height
# n3  Bob  19.0   179.0  ← 只有第3行没有任何NaN

2.2 关键参数详解

参数 可选值 说明
axis 0(默认)或 1 0=删行,1=删列
how 'any'(默认)或 'all' 'any'=有NaN就删,'all'=全为NaN才删
thresh 整数 至少保留有N个非NaN值的行/列
subset 列名列表(当axis=0时) 只检查指定列的NaN
inplace True/False 是否原地修改
df = pd.DataFrame({
    'name':   ['Tom', np.nan, 'Bob'],
    'age':    [np.nan, np.nan, 19],
    'height': [177,    182,   179]
})

# axis=1:删除含有NaN的列
print(df.dropna(axis=1))
#    height
# n1   177.0
# n2   182.0
# n3   179.0

# how='all':只有整行/整列全是NaN才删
df2 = pd.DataFrame({
    'A': [np.nan, np.nan],
    'B': [1,       np.nan]
})
print(df2.dropna(how='all'))  # 两行都不满足"全为NaN",所以不删任何行

# thresh=2:至少保留有2个非NaN值的行
df3 = pd.DataFrame({
    'A': [1, np.nan, np.nan],
    'B': [np.nan, 2, np.nan],
    'C': [np.nan, np.nan, 3]
})
print(df3.dropna(thresh=2))  # 没有一行有≥2个非NaN,所以全删了
# 修改一下:
df3.loc['n1', 'B'] = 2
print(df3.dropna(thresh=2))
#        A    B   C
# n1  1.0  2.0 NaN  ← 有2个非NaN,保留

# subset:只根据指定列判断NaN
df = pd.DataFrame({
    'name':   ['Tom', np.nan, 'Bob'],
    'age':    [18,     19,     np.nan],
    'height': [167,    177,    178]
})
# 只根据'name'和'height'列的NaN来删除行
print(df.dropna(subset=['name', 'height']))
#    name   age  height
# n1  Tom  18.0   167.0  ← n2的name是NaN,被删;n3的age是NaN但不在subset里,保留
# n3  Bob   NaN   178.0

在这里插入图片描述


三、填充缺失值:fillna()

实际工作中,删除缺失值要慎重(会丢失数据)。更常见的做法是填充(impute)

3.1 用标量填充

df = pd.DataFrame({
    'A': [np.nan, 2, np.nan, 0],
    'B': [       4, 5, np.nan, 1],
    'C': [       0, 0, np.nan, np.nan],
    'D': [       1, 0,      0, 1]
})

# 所有NaN填充为0
print(df.fillna(0))
#      A    B    C  D
# 0  0.0  4.0  0.0  1
# 1  2.0  5.0  0.0  0
# 2  0.0  0.0  0.0  0
# 3  0.0  1.0  0.0  1

3.2 用字典按列填充不同值

# A列填充6,C列填充7
print(df.fillna({'A': 6, 'C': 7}))
#      A    B    C  D
# 0  6.0  4.0  0.0  1
# 1  2.0  5.0  0.0  0
# 2  6.0  0.0  7.0  0
# 3  0.0  1.0  7.0  1

3.3 用前后值填充(向前填充/向后填充)

这是时间序列数据中最常用的技巧!

# method='ffill'(pad):用前一个非NaN值填充
print(df.fillna(method='ffill'))
#      A    B    C  D
# 0  NaN  4.0  0.0  1  ← A列第0行前面没有值,还是NaN
# 1  2.0  5.0  0.0  0
# 2  2.0  5.0  0.0  0  ← C列第2行:前面最近的非NaN是0.0
# 3  0.0  1.0  0.0  1

# method='bfill'(backfill):用后一个非NaN值填充
print(df.fillna(method='bfill'))
#      A    B    C  D
# 0  2.0  4.0  0.0  1
# 1  2.0  5.0  0.0  0
# 2  0.0  1.0  NaN  0  ← C列第2行后面没有非NaN,还是NaN
# 3  0.0  1.0  NaN  1

沿列方向填充(axis=1):

print(df.fillna(method='ffill', axis=1))  # 向左填充
print(df.fillna(method='bfill', axis=1))  # 向右填充

3.4 限制填充数量

# limit=2:每列最多填充2个NaN
print(df.fillna(method='ffill', limit=2))

在这里插入图片描述


四、删除重复值:drop_duplicates()

重复数据在爬虫、日志分析、用户行为数据中极其常见。

4.1 基础用法

df = pd.DataFrame({
    'A': [1, 3, 3, 1],
    'B': [0, 2, 5, 0],
    'C': [4, 0, 4, 4],
    'D': [1, 0, 0, 1]
})
print(df)
#    A  B  C  D
# 0  1  0  4  1
# 1  3  2  0  0
# 2  3  5  4  0
# 3  1  0  4  1  ← 和第0行完全重复

# 默认:删除重复行,保留第一次出现的(keep='first')
print(df.drop_duplicates())
#    A  B  C  D
# 0  1  0  4  1
# 1  3  2  0  0
# 2  3  5  4  0

4.2 关键参数详解

参数 可选值 说明
subset 列名列表 只根据指定列判断是否重复
keep 'first'(默认)、'last'False 保留第一次/最后一次/删除所有重复
inplace True/False 是否原地修改
# keep='last':保留最后一次出现的重复项
print(df.drop_duplicates(keep='last'))
#    A  B  C  D
# 1  3  2  0  0
# 2  3  5  4  0
# 3  1  0  4  1  ← 第0行被删,第3行(重复的最后一次)保留

# keep=False:所有重复行都删掉(一个不留!)
print(df.drop_duplicates(keep=False))
#    A  B  C  D
# 1  3  2  0  0
# 2  3  5  4  0  ← 第0行和第3行都删了

# subset:只根据指定列判断是否重复
print(df.drop_duplicates(subset=['A', 'D']))
#    A  B  C  D
# 0  1  0  4  1
# 1  3  2  0  0
# 2  3  5  4  0  ← 第0行和第3行的A、D列相同,但第3行被删了(keep='first')

在这里插入图片描述


五、数据排序:sort_values()

数据分析中经常需要按某列排序(比如按成绩从高到低)。

df = pd.DataFrame({
    'col1': [4, 1, 2, np.nan, 5, 2],
    'col2': [2, 1, 9, 8, 7, 6],
    'col3': [0, 1, 9, 4, 2, 3]
})

# 按col1升序排列(默认ascending=True)
print(df.sort_values(by='col1'))
#    col1  col2  col3
# 1   1.0     1     1
# 2   2.0     9     9
# 5   2.0     6     3  ← col1都是2,按其他列保持相对稳定
# 0   4.0     2     0
# 4   5.0     7     2
# 3   NaN     8     4  ← NaN默认排在最后

# 按col1降序
print(df.sort_values(by='col1', ascending=False))

# 按多列排序:先按col1,再按col2
print(df.sort_values(by=['col1', 'col2']))

# NaN排在最前面(na_position='first')
print(df.sort_values(by='col1', na_position='first'))

# 对列排序(沿axis=1)
print(df.sort_values(by=5, axis=1))  # 按第5行的值对列排序

原地排序

df.sort_values(by='col1', inplace=True)  # 直接修改原DataFrame

在这里插入图片描述


六、数据类型转换

有时候数据的类型不对(比如数字被读成了字符串),需要转换。

6.1 查看数据类型:dtypes

df = pd.DataFrame({
    'name':   ['Tom', 'Bob'],
    'age':    [18, 19],
    'height': [167.5, 177.0]
})
print(df.dtypes)
# name       object
# age         int64
# height    float64
# dtype: object

6.2 转换数据类型:astype()

# 把age列转成字符串
df['age'] = df['age'].astype(str)
print(df.dtypes)
# name       object
# age        object  ← 现在是字符串了
# height    float64
# dtype: object

# 把height列转成整数(会丢失小数位)
df['height'] = df['height'].astype(int)
print(df.dtypes)
# name       object
# age        object
# height     int64

6.3 处理混合类型列

如果一列里既有数字又有字符串,astype() 会报错。这时可以用 pd.to_numeric()errors 参数:

s = pd.Series(['1', '2', 'x', '4'])

# errors='raise'(默认):遇到不能转换的就报错
# print(pd.to_numeric(s))  # ❌ ValueError!

# errors='coerce':不能转换的变成NaN
print(pd.to_numeric(s, errors='coerce'))
# 0    1.0
# 1    2.0
# 2    NaN   ← 'x'不能转成数字,变成NaN
# 3    4.0

# errors='ignore':不能转换的就保持原样
print(pd.to_numeric(s, errors='ignore'))
# 0      1
# 1      2
# 2      x  ← 保持原样
# 3      4

七、字符串清洗:str属性

Pandas为Series提供了 .str 属性,可以对整列字符串进行批量操作(类似Python的字符串方法,但是向量化的)。

s = pd.Series(['  Apple ', 'Banana', '  Cherry  ', 'Date'])

# 去除两边空格
print(s.str.strip())
# 0      Apple
# 1     Banana
# 2     Cherry
# 3       Date

# 转大写/小写
print(s.str.upper())
print(s.str.lower())

# 判断是否包含某子串(返回布尔Series)
print(s.str.contains('a'))
# 0    False  ('Apple'里没有'a',但有'A')
# 1     True  ('Banana'里有'a')
# 2     True
# 3    False

# 替换字符串
print(s.str.replace('a', '@'))
# 0      Apple
# 1     B@n@n@
# 2      Cherry
# 3      D@te

# 拆分字符串
s2 = pd.Series(['Tom-18', 'Bob-19', 'Linda-20'])
print(s2.str.split('-', expand=True))  # expand=True:拆成多列
#       0   1
# 0   Tom  18
# 1   Bob  19
# 2  Linda 20

在这里插入图片描述


八、综合案例:清洗一份真实的学生成绩数据

把本篇学到的所有技巧串起来,做一个完整的数据清洗流程。

import pandas as pd
import numpy as np

# 模拟一份"脏"数据(从CSV读入的效果)
data = {
    '姓名':   ['张三', '李四', '  王五  ', '张三', np.nan, '赵六'],
    '年龄':   [18,     19,    20,         18,    21,    22],
    '成绩':   [85,     np.nan, 92,         85,    88,    np.nan],
    '城市':   ['北京', '上海', '北京',     '北京', '广州',  '深圳'],
    '备注':   ['A',    'B',   'A',        'A',    'C',    'B']
}
df = pd.DataFrame(data)
print("=== 原始脏数据 ===")
print(df)
print(f"\n形状: {df.shape}")

# 步骤1:查看缺失值情况
print("\n=== 各列缺失值统计 ===")
print(df.isnull().sum())

# 步骤2:删除完全重复的行(基于所有列)
print("\n=== 删除完全重复的行 ===")
df = df.drop_duplicates()
print(df)

# 步骤3:填充姓名的NaN为"未知"
df['姓名'] = df['姓名'].fillna('未知')

# 步骤4:去除姓名列的两边空格
df['姓名'] = df['姓名'].str.strip()

# 步骤5:填充成绩的NaN为平均值
age_mean = df['成绩'].mean()  # 注意:mean()会自动忽略NaN
df['成绩'] = df['成绩'].fillna(age_mean)
print(f"\n成绩平均值: {age_mean:.1f}")

# 步骤6:按成绩降序排列
df = df.sort_values(by='成绩', ascending=False)

# 步骤7:重置索引(因为删行和排序后索引乱了)
df = df.reset_index(drop=True)

print("\n=== 清洗后的干净数据 ===")
print(df)
print(f"\n最终形状: {df.shape}")

输出结果:

=== 原始脏数据 ===
    姓名   年龄   成绩  城市  备注
0  张三   18  85.0  北京   A
1  李四   19   NaN  上海   B
2   王五    20  92.0  北京   A
3  张三   18  85.0  北京   A
4  NaN   21  88.0  广州   C
5  赵六   22   NaN  深圳   B

各列缺失值统计 ===
姓名    1
年龄    0
成绩    2
城市    0
备注    0

=== 删除完全重复的行 ===
  (第3行和第0行重复,被删除)

成绩平均值: 88.3

=== 清洗后的干净数据 ===
    姓名   年龄   成绩  城市  备注
0   王五    20  92.0  北京   A
1  Unknown 21  88.0  广州   C
2   张三   18  88.3  北京   A
3   李四   19  88.3  上海   B
4   赵六   22  88.3  深圳   B

在这里插入图片描述


九、本文总结

操作 函数 核心参数
检测缺失值 isnull() / notnull()
删除缺失值 dropna() axis, how, thresh, subset
填充缺失值 fillna() value, method='ffill'/'bfill'
删除重复值 drop_duplicates() subset, keep
排序 sort_values() by, ascending, na_position
类型转换 astype() 目标类型字符串
安全转数字 pd.to_numeric() errors='coerce'/'ignore'
字符串清洗 .str.xxx() 同Python字符串方法

数据清洗的通用流程

  1. isnull().sum() 查看缺失值
  2. drop_duplicates() 删除重复行
  3. fillna()dropna() 处理缺失值
  4. .str.xxx() 清洗字符串列
  5. astype()pd.to_numeric() 转换数据类型
  6. sort_values()reset_index() 整理顺序

动手练习

  1. 创建一个包含NaN的DataFrame(3行4列),然后:(a)删除所有包含NaN的行;(b)只用前2列来判断是否有NaN并删除对应行;(c)用0填充所有NaN。

  2. 有一个DataFrame df = pd.DataFrame({'A':[1,2,2,3], 'B':[4,5,5,6], 'C':[7,8,9,10]}),删除重复行,但保留最后一次出现的(即保留第2行而不是第1行)。

  3. 创建一个Series s = pd.Series(['1','2','x','4','5']),把所有能转成数字的值转成int,不能转的填为0(提示:结合pd.to_numericfillna)。

  4. 有一个学生成绩DataFrame,包含姓名成绩两列,其中成绩列有部分NaN。请:(a)用同列的平均值填充NaN;(b)按成绩降序排列;(c)添加一个新列等级,成绩>=90为’A’,60-89为’B’,其余为’C’。


下一篇预告:《20 Pandas(三):分组聚合与合并连接——数据分析的核心操作》

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐