查看全课大纲(第 45 / 101 节)
  1. 1.数据分析基本概念
  2. 2.学习数据分析的一般路线
  3. 3.数据分析的流程
  4. 4.数据类型
  5. 5.环境部署(1)
  6. 6.环境部署(2)
  7. 7.课程介绍
  8. 8.TXT文件操作
  9. 9.JSON文件操作
  10. 10.CSV文件操作
  11. 11.Excel文件操作
  12. 12.数据库及SQL常用语法
  13. 13.数据库基本操作
  14. 14.数据库多表连接
  15. 15.实战:欧洲职业足球数据库分析
  16. 16.爬虫简介
  17. 17.URL管理模块
  18. 18.网页下载模块
  19. 19.网页解析模块(1)
  20. 20.网页解析模块(2)
  21. 21.Scrapy简介
  22. 22.Scrapy使用步骤(1)
  23. 23.Scrapy使用步骤(2)
  24. 24.Scrapy使用步骤(3)
  25. 25.Scrapy使用步骤(4)
  26. 26.实战:获取国内城市空气质量指数数据
  27. 27.NumPy和SciPy介绍
  28. 28.多维数组
  29. 29.多维数组操作
  30. 30.NumPy的常用方法
  31. 31.向量化介绍
  32. 32.向量化及通用函数
  33. 33.实战:2016美国大选分析
  34. 34.数据结构-Series
  35. 35.数据结构-DataFrame
  36. 36.数据结构-Index
  37. 37.Series的索引操作
  38. 38.DataFrame的索引操作
  39. 39.索引操作总结
  40. 40.运算与对齐
  41. 41.函数应用操作(1) -- map
  42. 42.函数应用操作 (2) -- apply applymap
  43. 43.文件读写操作
  44. 44.排序操作
  45. 45.数据清洗--处理缺失数据
  46. 46.数据清洗--处理重复数据
  47. 47.数据清洗--替换数据
  48. 48.常用统计方法(1) -- describe quantile
  49. 49.常用统计方法(2) -- sum mean median count
  50. 50.常用统计方法(3) -- max min idxmax idxmin
  51. 51.常用统计方法(4) -- mad var std cumsum
  52. 52.实战:全球食品数据分析
  53. 53.层级索引
  54. 54.分组与聚合介绍
  55. 55.分组操作(1) -- GroupBy对象及常用聚合操作
  56. 56.分组操作(2) -- 自定义分组及聚合操作
  57. 57.透视表介绍
  58. 58.透视表操作
  59. 59.数据规整(1) -- 数据合并concat
  60. 60.数据规整(2) -- 数据连接merge
  61. 61.数据重构(3) -- 数据重构stack unstack
  62. 62.实战:互联网电影资料库分析
  63. 63.探索性数据分析EDA介绍
  64. 64.EDA的目的
  65. 65.EDA常用工具
  66. 66.Matplotlib绘图基本介绍
  67. 67.Matplotlib画布
  68. 68.散点图和柱状图的绘制
  69. 69.直方图的绘制
  70. 70.矩阵绘图
  71. 71.子图的使用
  72. 72.Matplotlib颜色、标记、线型
  73. 73.Matplotlib坐标刻度、标签、图例、标题
  74. 74.Seaborn介绍
  75. 75.数据集分布可视化(1) -- 单变量分布、双变量分布
  76. 76.数据集分布可视化(2) -- 变量关系可视化
  77. 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
  78. 78.交互式数据可视化工具Bokeh介绍
  79. 79.Bokeh绘制散点图、柱状图、盒子图、弦图
  80. 80.Bokeh绘制常用图形元素
  81. 81.3D绘图 -- mplot3d
  82. 82.3D曲线可视化
  83. 83.3D散点图可视化
  84. 84.3D柱状图可视化
  85. 85.Pandas绘图
  86. 86.实战:Lending Club借贷数据探索性分析及可视化
  87. 87.机器学习介绍及应用场景
  88. 88.机器学习建模介绍 (1) -- 分类
  89. 89.机器学习建模介绍 (2) -- 回归
  90. 90.机器学习建模介绍 (3) -- 聚类
  91. 91.机器学习分类
  92. 92.机器学习工具scikit-learn
  93. 93.使用scikit-learn的流程
  94. 94.数据集准备及划分
  95. 95.模型选择
  96. 96.数据预处理及特征工程
  97. 97.过拟合与欠拟合
  98. 98.模型调参介绍
  99. 99.模型调参方法
  100. 100.模型测试及评价
  101. 101.实战:通过移动设备行为数据预测性别和年龄

数据清洗--处理缺失数据

约 13 分钟

📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)

缺失数据的识别与处理

小象实战讲义 · Python数据分析实战

在数据分析的实际工作中,我们拿到的原始数据往往并不“干净”,其中可能包含缺失值、重复值或无效值。这些“脏数据”会严重影响后续的分析、处理和建模工作的准确性与可靠性。本节我们将聚焦于数据清洗中最常见的问题——缺失数据的处理,学习如何使用 pandas 的强大功能来识别、判断和处理这些“数据空洞”。

💡 核心导读

  • 数据清洗的重要性:理解数据清洗是数据分析流程中不可或缺的迭代步骤,直接决定分析结果的质量。
  • 缺失值检测:掌握使用 isnull() 方法识别缺失值,并结合 any() 方法快速定位存在缺失值的行或列。
  • 缺失值处理策略:学习两种核心处理方式:直接丢弃 (dropna) 与填充 (fillna)。
  • 智能填充方法:了解并实践基于数据顺序的前向填充 (ffill) 与后向填充 (bfill),理解其适用场景与前提条件。

数据清洗:从“脏数据”到“干净数据”

数据清洗并非一次性任务,而是一个贯穿数据分析项目始终的迭代过程。我们首先要明确几个关键问题:数据是否需要修改?需要修改什么?如何调整数据才能使其适用于后续的分析与挖掘?数据清洗正是回答这些问题的实践,其常见操作包括处理缺失数据、处理重复数据以及处理无效数据。本节我们将深入探讨如何处理缺失数据。

识别缺失值:isnull() 与 any() 的组合拳

处理缺失值的第一步是发现它们。在 pandas 中,缺失值通常由 NaN(Not a Number)表示。我们可以使用 isnull() 方法来检测数据中的每个元素是否为缺失值。

isnull() 方法会返回一个与原数据结构(Series 或 DataFrame)形状相同的布尔型对象,其中 True 表示该位置是缺失值,False 则表示不是。

然而,直接查看每个元素的布尔值并不高效。我们更关心的是:哪些列或行存在缺失值? 这时,可以结合 any() 方法。any() 的作用是:在给定的轴向上,只要有一个 True,结果就返回 True。默认情况下,axis=0 表示按列计算,axis=1 表示按行计算。

下面我们通过一个模拟的用户行为日志数据来演示。

import pandas as pd
import numpy as np

# 构造一个模拟的用户播放日志DataFrame,包含缺失值
data = {
    'time': [4424, 4424, 4425, 4426, 4427, 4428],
    'user': ['ch', 'sue', 'ch', 'sue', 'ch', 'sue'],
    'video': ['v1', 'v1', 'v2', 'v2', 'v3', 'v3'],
    'playbackposition': [0, 5, 10, 15, 20, 25],
    'paused': [False, np.nan, np.nan, True, np.nan, False],
    'volume': [10, np.nan, 5, np.nan, 10, np.nan]
}
df = pd.DataFrame(data)
print("原始模拟数据:")
print(df)
原始模拟数据:
   time user video  playbackposition paused  volume
0  4424   ch    v1                 0  False    10.0
1  4424  sue    v1                 5    NaN     NaN
2  4425   ch    v2                10    NaN     5.0
3  4426  sue    v2                15   True     NaN
4  4427   ch    v3                20    NaN    10.0
5  4428  sue    v3                25  False     NaN

现在,我们来检测并定位缺失值。

# 检测缺失值并汇总 (此代码块完全自包含)
import pandas as pd
import numpy as np

# 重新构造数据,确保本代码块独立
df_local = pd.DataFrame({
    'time': [4424, 4424, 4425, 4426, 4427, 4428],
    'user': ['ch', 'sue', 'ch', 'sue', 'ch', 'sue'],
    'video': ['v1', 'v1', 'v2', 'v2', 'v3', 'v3'],
    'playbackposition': [0, 5, 10, 15, 20, 25],
    'paused': [False, np.nan, np.nan, True, np.nan, False],
    'volume': [10, np.nan, 5, np.nan, 10, np.nan]
})

print("1. 使用 isnull() 检测每个元素:")
print(df_local.isnull())

print("\n2. 检查哪些列包含缺失值 (any, axis=0):")
print(df_local.isnull().any())

print("\n3. 检查哪些行包含缺失值 (any, axis=1):")
print(df_local.isnull().any(axis=1))
1. 使用 isnull() 检测每个元素:
    time   user  video  playbackposition  paused  volume
0  False  False  False             False   False   False
1  False  False  False             False    True    True
2  False  False  False             False    True   False
3  False  False  False             False   False    True
4  False  False  False             False    True   False
5  False  False  False             False   False    True

2. 检查哪些列包含缺失值 (any, axis=0):
time                False
user                False
video               False
playbackposition    False
paused               True
volume               True
dtype: bool

3. 检查哪些行包含缺失值 (any, axis=1):
0    False
1     True
2     True
3     True
4     True
5     True
dtype: bool

从输出可以看出:

  • isnull() 返回了每个单元格的布尔状态。
  • 结合 any(axis=0) 发现,paused 和 volume 两列存在缺失值。
  • 结合 any(axis=1) 发现,除了第0行,其他行都至少包含一个缺失值。

处理缺失值:丢弃与填充

发现缺失值后,我们有两种主要的处理策略:丢弃和填充。选择哪种策略需要结合业务背景和数据情况来决定。

丢弃缺失值:dropna()

如果缺失值数量不多,或者经过与业务方沟通后确认可以丢弃,那么 dropna() 是最直接的方法。默认情况下,dropna() 会丢弃任何列包含缺失值的整行。

# 处理缺失值:丢弃与填充 (此代码块完全自包含)
import pandas as pd
import numpy as np

# 重新构造数据
df_local = pd.DataFrame({
    'time': [4424, 4424, 4425, 4426, 4427, 4428],
    'user': ['ch', 'sue', 'ch', 'sue', 'ch', 'sue'],
    'video': ['v1', 'v1', 'v2', 'v2', 'v3', 'v3'],
    'playbackposition': [0, 5, 10, 15, 20, 25],
    'paused': [False, np.nan, np.nan, True, np.nan, False],
    'volume': [10, np.nan, 5, np.nan, 10, np.nan]
})

print("1. 丢弃任何列包含缺失值的行 (dropna):")
df_dropped_all = df_local.dropna()
print(df_dropped_all)

print("\n2. 只丢弃 'paused' 列有缺失值的行 (dropna, subset=['paused']):")
df_dropped_paused = df_local.dropna(subset=['paused'])
print(df_dropped_paused)

print("\n3. 用固定值 -1 填充所有缺失值 (fillna):")
df_filled_const = df_local.fillna(-1)
print(df_filled_const)
1. 丢弃任何列包含缺失值的行 (dropna):
   time user video  playbackposition paused  volume
0  4424   ch    v1                 0  False    10.0

2. 只丢弃 'paused' 列有缺失值的行 (dropna, subset=['paused']):
   time user video  playbackposition paused  volume
0  4424   ch    v1                 0  False    10.0
3  4426  sue    v2                15   True     NaN
5  4428  sue    v3                25  False     NaN

3. 用固定值 -1 填充所有缺失值 (fillna):
   time user video  playbackposition paused  volume
0  4424   ch    v1                 0  False    10.0
1  4424  sue    v1                 5     -1    -1.0
2  4425   ch    v2                10     -1     5.0
3  4426  sue    v2                15   True    -1.0
4  4427   ch    v3                20     -1    10.0
5  4428  sue    v3                25  False    -1.0

关键点解析:

  1. dropna():由于只有第0行没有缺失值,所以丢弃后只剩一行。
  2. subset 参数:通过指定 subset=['paused'],我们只关注 paused 列是否有缺失值。因此,paused 列不为 NaN 的行(第0、3、5行)被保留了下来,即使它们的 volume 列可能有缺失值。
  3. fillna(value):使用一个固定的值(这里是-1)填充所有缺失值。这种方法简单,但可能引入偏差,需谨慎使用。

智能填充:前向与后向填充

在时间序列或有序数据中,我们常常使用相邻的数据来填充缺失值,即前向填充(ffill) 和后向填充(bfill)。

  • ffill:用缺失值前面的有效值进行填充。(旧版 pandas 中它也叫 pad,该别名已在 pandas 3.0 移除,请统一使用 ffill。)
  • bfill:用缺失值后面的有效值进行填充。(旧别名 backfill 同样已移除。)

重要前提:使用这两种方法时,数据必须是有序的(例如按时间排序),否则“前”、“后”将失去意义。

# 智能填充:前向与后向填充 (此代码块完全自包含)
import pandas as pd
import numpy as np

# 重新构造数据
df_local = pd.DataFrame({
    'time': [4424, 4424, 4425, 4426, 4427, 4428],
    'user': ['ch', 'sue', 'ch', 'sue', 'ch', 'sue'],
    'video': ['v1', 'v1', 'v2', 'v2', 'v3', 'v3'],
    'playbackposition': [0, 5, 10, 15, 20, 25],
    'paused': [False, np.nan, np.nan, True, np.nan, False],
    'volume': [10, np.nan, 5, np.nan, 10, np.nan]
})

print("1. 对数据按时间和用户排序:")
df_sorted = df_local.sort_values(by=['time', 'user'])
print(df_sorted)

print("\n2. 应用前向填充 (ffill):")
df_ffill = df_sorted.ffill()
print(df_ffill)

print("\n3. 应用后向填充 (bfill):")
df_bfill = df_sorted.bfill()
print(df_bfill)
1. 对数据按时间和用户排序:
   time user video  playbackposition paused  volume
0  4424   ch    v1                 0  False    10.0
1  4424  sue    v1                 5    NaN     NaN
2  4425   ch    v2                10    NaN     5.0
3  4426  sue    v2                15   True     NaN
4  4427   ch    v3                20    NaN    10.0
5  4428  sue    v3                25  False     NaN

2. 应用前向填充 (ffill):
   time user video  playbackposition paused  volume
0  4424   ch    v1                 0  False    10.0
1  4424  sue    v1                 5  False    10.0
2  4425   ch    v2                10  False     5.0
3  4426  sue    v2                15   True     5.0
4  4427   ch    v3                20   True    10.0
5  4428  sue    v3                25  False    10.0

3. 应用后向填充 (bfill):
   time user video  playbackposition paused  volume
0  4424   ch    v1                 0  False    10.0
1  4424  sue    v1                 5   True     5.0
2  4425   ch    v2                10   True     5.0
3  4426  sue    v2                15   True    10.0
4  4427   ch    v3                20  False    10.0
5  4428  sue    v3                25  False     NaN

填充逻辑解读:

  • 前向填充 (ffill):
    • 第1行:paused 用上一行(第0行)的 False 填充;volume 用上一行的 10.0 填充。
    • 第2行:paused 继续用上一行(现在是填充后的第1行)的 False 填充。
    • 第3行:paused 本身是 True,这是一个新值,后续的填充将基于此新值。volume 用上一行(第2行)的 5.0 填充。
    • 第4行:paused 用上一行(第3行)的 True 填充;volume 用上一行的 5.0 填充?等等,这里输出是 10.0。仔细看原始数据,第4行的 volume 原始值就是 10.0,并非缺失值,所以它不会被填充,而是保持原值。paused 列第4行是缺失值,所以被填充为 True。
    • 第5行:paused 本身是 False,非缺失;volume 是缺失值,用上一行(第4行)的 10.0 填充。
  • 后向填充 (bfill):
    • 从下往上用后面的值填充前面的缺失值。
    • 第5行:volume 是缺失值,但后面没有行了,所以无法填充,保持 NaN。
    • 第4行:paused 是缺失值,用下一行(第5行)的 False 填充。
    • 第3行:volume 是缺失值,用下一行(第4行)的 10.0 填充。
    • 第2行:paused 是缺失值,用下一行(第3行)的 True 填充。
    • 第1行:paused 和 volume 都是缺失值,分别用下一行(第2行)的 True 和 5.0 填充。

📝 动手练一练

  1. 概念辨析:df.isnull().any() 和 df.isnull().any(axis=1) 返回的结果分别代表什么含义?在什么业务场景下你会更关注行的缺失情况,什么场景下更关注列的缺失情况?
👉 点击查看参考答案
  • df.isnull().any() (默认 axis=0)返回一个 Series,索引是列名,布尔值表示该列是否至少包含一个缺失值。它帮助我们快速定位哪些特征(列)存在数据缺失。
  • df.isnull().any(axis=1) 返回一个 Series,索引是行号,布尔值表示该行是否至少包含一个缺失值。它帮助我们快速定位哪些样本(行)存在数据不完整。
  • 业务场景:
    • 关注列缺失:当进行特征工程或建模时,我们需要知道哪些特征的数据质量有问题,可能需要考虑删除该特征、进行专门填充或使用能处理缺失值的算法。
    • 关注行缺失:当样本非常珍贵,或者分析是基于完整用户行为序列时,我们需要知道哪些用户的记录不完整,可能会影响对用户整体行为的分析,从而决定是剔除该用户还是想办法补全其数据。
  1. 动手实践:假设你有一个小的销售数据 DataFrame,包含产品 A 和 B 的每日销售额,其中有很多缺失值。请先按日期排序,然后分别用前向填充和后向填充处理缺失值,并观察最后一天和第一天的数据在两种填充方式下的结果有何不同。
    # 初始数据
    import pandas as pd
    import numpy as np
    df_sales = pd.DataFrame({
        'date': ['2023-01-02', '2023-01-01', '2023-01-04', '2023-01-03'],
        'A': [100, np.nan, np.nan, 150],
        'B': [np.nan, 50, 80, np.nan]
    })
👉 点击查看参考答案
import pandas as pd
import numpy as np

df_sales = pd.DataFrame({
    'date': ['2023-01-02', '2023-01-01', '2023-01-04', '2023-01-03'],
    'A': [100, np.nan, np.nan, 150],
    'B': [np.nan, 50, 80, np.nan]
})

# 1. 按日期排序
df_sales['date'] = pd.to_datetime(df_sales['date'])
df_sorted = df_sales.sort_values('date')
print("排序后数据:")
print(df_sorted)

# 2. 前向填充
df_ffill = df_sorted.ffill()
print("\n前向填充结果:")
print(df_ffill)

# 3. 后向填充
df_bfill = df_sorted.bfill()
print("\n后向填充结果:")
print(df_bfill)

# 观察首尾
print("\n观察:")
print("- 前向填充:第一天(2023-01-01)的A列因前面无值,仍为NaN;最后一天(2023-01-04)的A、B列均用前值填充。")
print("- 后向填充:最后一天(2023-01-04)的A列因后面无值,仍为NaN;第一天(2023-01-01)的A、B列均用后值填充。")
排序后数据:
        date      A     B
1 2023-01-01    NaN  50.0
0 2023-01-02  100.0   NaN
3 2023-01-03  150.0   NaN
2 2023-01-04    NaN  80.0

前向填充结果:
        date      A     B
1 2023-01-01    NaN  50.0
0 2023-01-02  100.0  50.0
3 2023-01-03  150.0  50.0
2 2023-01-04  150.0  80.0

后向填充结果:
        date      A     B
1 2023-01-01  100.0  50.0
0 2023-01-02  100.0  80.0
3 2023-01-03  150.0  80.0
2 2023-01-04    NaN  80.0

观察:
- 前向填充:第一天(2023-01-01)的A列因前面无值,仍为NaN;最后一天(2023-01-04)的A、B列均用前值填充。
- 后向填充:最后一天(2023-01-04)的A列因后面无值,仍为NaN;第一天(2023-01-01)的A、B列均用后值填充。

本章小结

本节我们深入学习了数据清洗中处理缺失数据的关键技能。数据清洗是保障分析质量的基础,而处理缺失值是其中的首要任务。

📋 行动清单

  • 掌握检测技能:对于任何新拿到手的数据集,第一件事就是用 df.isnull().any() 和 df.isnull().sum() 快速扫描缺失情况。
  • 明确处理策略:根据业务背景和数据缺失的严重程度,与业务方沟通后,决定采用丢弃 (dropna)、固定值填充 (fillna) 还是智能填充 (ffill/bfill)。
  • 理解填充前提:牢记使用 ffill 或 bfill 时,必须确保数据在相关维度上(如时间)是有序的,否则结果将误导分析。
  • 迭代验证:处理缺失值后,务必再次检查数据,确保处理方式没有引入新的问题或偏差,并理解这很可能是一个需要多次调整的迭代过程。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 本节课件:数据清洗--处理缺失数据
    PDF · 215KB
    下载
  • 全套课件打包(第1-5章)
    ZIP · 12.8MB
    下载
  • 全套课件打包(第6-8章)
    ZIP · 15MB
    下载
  • 实战数据集:AppleStore 应用商城分析
    ZIP · 329KB
    下载
  • 实战数据集:女性服装电商分析
    ZIP · 2.8MB
    下载
  • Python 数据分析环境搭建指南
    PDF · 2MB
    下载
  • Scrapy 安装教程
    PDF · 12.7MB
    下载
  • 附加实战项目:AppleStore 应用商城数据分析
    ZIP · 0.3MB · ipynb + CSV 数据
    下载
  • 附加实战项目:银行电话营销数据分析
    ZIP · 0.4MB · ipynb + CSV 数据
    下载
  • 附加实战项目:女性服装电商评论数据分析
    ZIP · 2.7MB · ipynb + CSV 数据
    下载
  • 附加实战项目:美国化学学会杂志数据分析
    ZIP · 34.2MB · ipynb + SQLite 数据库
    下载