文章目录
-
- [1. 先看一眼数据长什么样](#1. 先看一眼数据长什么样)
- [2. 诊断缺失情况------这是第一步,不能跳](#2. 诊断缺失情况——这是第一步,不能跳)
- [3. 方法一:删除缺失值(适合缺失很少的情况)](#3. 方法一:删除缺失值(适合缺失很少的情况))
- [4. 方法二:均值填充(国赛最常用,没有之一)](#4. 方法二:均值填充(国赛最常用,没有之一))
- [5. 方法三:中位数填充(数据有极端值时用)](#5. 方法三:中位数填充(数据有极端值时用))
- [6. 方法四:线性插值(时间序列数据专用)](#6. 方法四:线性插值(时间序列数据专用))
- [7. 方法五:多重插补(精准但慢,谨慎使用)](#7. 方法五:多重插补(精准但慢,谨慎使用))
- [8. 实战总结:这 4 种情况直接套用](#8. 实战总结:这 4 种情况直接套用)
- [9. 论文里直接抄这段(修改列名和数字就行)](#9. 论文里直接抄这段(修改列名和数字就行))
- [10. 最后两句叮嘱](#10. 最后两句叮嘱)
0、引言
倒计时2天,这时候整理缺失值处理的R语言方案正是时候。赛场上数据往往不完美,有一套清晰的预处理思路能省下不少时间。我把缺失值处理的思路和常用R代码整理成了下面这个流程,你可以直接参考:
赛前最后两天,别再纠结理论了。本文用 R 自带的
airquality数据集,给你一套直接能跑的缺失值处理流程,复制粘贴就能用。
1. 先看一眼数据长什么样
r
# 加载 R 自带的纽约空气质量数据
data("airquality")
df <- airquality
# 看看前几行
head(df)
输出:
| Ozone | Solar.R | Wind | Temp | Month | Day | |
|---|---|---|---|---|---|---|
| 1 | 41 | 190 | 7.4 | 67 | 5 | 1 |
| 2 | 36 | 118 | 8.0 | 72 | 5 | 2 |
| 3 | 12 | 149 | 12.6 | 74 | 5 | 3 |
| 4 | 18 | 313 | 11.5 | 62 | 5 | 4 |
| 5 | NA | NA | 14.3 | 56 | 5 | 5 |
| 6 | 28 | NA | 14.9 | 66 | 5 | 6 |
可以看到第 5 行 Ozone 和 Solar.R 都是缺失值(NA)。
2. 诊断缺失情况------这是第一步,不能跳
r
# 每列缺了多少个
colSums(is.na(df))
运行结果:
r
Ozone Solar.R Wind Temp Month Day
37 7 0 0 0 0
信息很清晰:
- Ozone:缺 37 个,占 24.2%(总共 153 行)
- Solar.R:缺 7 个,占 4.6%
- 其他 4 列:完整
结论:Ozone 缺失需要认真处理,Solar.R 缺失较少,简单处理即可。
3. 方法一:删除缺失值(适合缺失很少的情况)
Solar.R 只缺 7 个(4.6%),直接删掉这几行,对整体数据影响很小。
r
# 只删除 Solar.R 这一列中有缺失的行
df_drop <- df[!is.na(df$Solar.R), ]
# 验证:Solar.R 的缺失变成 0 了
colSums(is.na(df_drop))
运行结果:
Ozone Solar.R Wind Temp Month Day
37 0 0 0 0 0
Ozone 的 37 个缺失还在,但 Solar.R 已经干净了。
什么时候用删除法? 缺失率低于 5% 时,直接删行最省事。
4. 方法二:均值填充(国赛最常用,没有之一)
Ozone 缺了 24.2%,直接删会损失太多信息,用均值填进去。
r
# 从原始数据重新开始
df <- airquality
# 计算 Ozone 的均值(忽略缺失值)
mean_ozone <- mean(df$Ozone, na.rm = TRUE)
mean_ozone
运行结果:
[1] 42.12931
Ozone 的平均值是 42.13,接下来把所有的 NA 替换成这个数。
r
# 把 Ozone 中的 NA 替换成均值
df$Ozone[is.na(df$Ozone)] <- mean_ozone
# 验证:Ozone 的缺失变成 0 了
colSums(is.na(df))
运行结果:
Ozone Solar.R Wind Temp Month Day
0 7 0 0 0 0
现在只剩 Solar.R 还有 7 个缺失了。
5. 方法三:中位数填充(数据有极端值时用)
如果数据里有特别大或特别小的异常值,中位数比均值更稳健。
先看看 Ozone 的分布:
r
summary(df$Ozone)
运行结果:
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.00 18.00 31.50 42.13 63.50 168.00
最大值 168,最小值 1,差距很大,说明有极端值。这种情况下中位数(31.5)比均值(42.13)更代表"典型值"。
r
# 用中位数填充
df <- airquality # 重新加载原始数据
median_ozone <- median(df$Ozone, na.rm = TRUE)
df$Ozone[is.na(df$Ozone)] <- median_ozone
colSums(is.na(df))
什么时候选均值?什么时候选中位数?
- 数据大致对称、没有极端值 → 用均值
- 数据有偏斜、有明显极端值 → 用中位数
- 不确定的话,优先选中位数,因为它更稳健
6. 方法四:线性插值(时间序列数据专用)
airquality 是按 5 月到 9 月每天记录的数据,有明确的时间顺序。对这类数据,用前后两天的平均值来填,比用全局均值更合理。
r
library(zoo)
df <- airquality # 重新加载原始数据
# 对 Ozone 做线性插值
df$Ozone <- na.approx(df$Ozone)
# 检查首尾是否还有 NA(na.approx 无法插值首尾)
colSums(is.na(df))
运行结果:
Ozone Solar.R Wind Temp Month Day
1 7 0 0 0 0
Ozone 还剩 1 个 NA(如果第 1 行或最后 1 行是缺失,na.approx 填不了),用 na.fill 补上:
r
df$Ozone <- na.fill(df$Ozone, "extend") # 用相邻值延伸填充
colSums(is.na(df))
运行结果全部为 0。
7. 方法五:多重插补(精准但慢,谨慎使用)
如果缺失比例很高(比如超过 30%),且你后续要做回归或统计检验,mice 包的多重插补是最严谨的方法。
r
library(mice)
df <- airquality
# 只选有缺失的列做插补
df_sub <- df[, c("Ozone", "Solar.R", "Wind", "Temp")]
# 生成 5 个完整数据集
imp <- mice(df_sub, m = 5, method = "pmm", seed = 123, printFlag = FALSE)
# 提取第 1 个完整数据集
df_complete <- complete(imp, 1)
colSums(is.na(df_complete))
运行结果全部为 0。
但是 ,mice 运行需要时间,国赛时间紧张,除非缺失非常严重,否则优先用均值/中位数填充。
8. 实战总结:这 4 种情况直接套用
| 缺失比例 | 你的数据长什么样 | 直接用这段代码 |
|---|---|---|
| < 5% | 个别行缺了一两个值 | df <- df[!is.na(df$列名), ] |
| 5%-30%,数值正常 | 普通连续型数据 | df$列[is.na(df$列)] <- mean(df$列, na.rm = TRUE) |
| 5%-30%,有极端值 | 最大最小差好几倍 | df$列[is.na(df$列)] <- median(df$列, na.rm = TRUE) |
| 有前后顺序 | 按天/年记录的数据 | library(zoo); df$列 <- na.approx(df$列) |
9. 论文里直接抄这段(修改列名和数字就行)
原始数据中,Ozone 变量存在 37 个缺失值,占样本总量的 24.2%。由于该变量存在极端值(最小值 1,最大值 168),为避免均值受异常值影响,本文采用中位数替代法对缺失值进行填充。填充后各变量均无缺失,有效样本量为 153 条。
10. 最后两句叮嘱
-
分类变量(男/女、是/否) 不能用均值,要用众数填充:
r# 找出出现次数最多的值 mode_val <- names(sort(table(df$性别), decreasing = TRUE))[1] df$性别[is.na(df$性别)] <- mode_val -
如果数据有分组 (比如男生一组、女生一组),要分组分别填充,不要在整个数据集上求均值。
把上面每段代码单独复制到 RStudio 里跑一遍,观察每一步 colSums(is.na(df)) 的变化,这个流程就掌握了。比赛加油!💪