数学建模国赛倒计时 2 天 ——《缺失值处理一(R语言)》

文章目录

    • [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. 最后两句叮嘱

  1. 分类变量(男/女、是/否) 不能用均值,要用众数填充:

    r 复制代码
    # 找出出现次数最多的值
    mode_val <- names(sort(table(df$性别), decreasing = TRUE))[1]
    df$性别[is.na(df$性别)] <- mode_val
  2. 如果数据有分组 (比如男生一组、女生一组),要分组分别填充,不要在整个数据集上求均值。


把上面每段代码单独复制到 RStudio 里跑一遍,观察每一步 colSums(is.na(df)) 的变化,这个流程就掌握了。比赛加油!💪

相关推荐
小宋10211 天前
MCP 工具也有供应链风险:注册中心、签名校验、版本锁定与白名单
开发语言·网络·人工智能·php
凉茶钱1 天前
【C++】入门基础语法
开发语言·c++
个 人 练 习 生1 天前
C++中的内存管理
开发语言·c++·经验分享·学习
jingli91 天前
本地向量库索引把 C 盘塞爆 48.6G:一次 LanceDB 膨胀排查与根治实录
c语言·开发语言
言乐61 天前
Python排名统计折线图
开发语言·python·django·virtualenv·pygame
我是小白呀1 天前
19-Temporal项目实战:将客户开通流程迁移到持久执行架构
java·开发语言·人工智能·架构·workflow
励志不掉头发的内向程序员1 天前
【从零写一个CAD 02】画完第一条线之后:实体容器、Esc 取消,和按了没反应的键盘
开发语言·c++·qt·学习·系统架构
摇滚侠1 天前
《On Java 中文版 基础卷》阅读笔记 安装 Java 和本书示例 02
java·开发语言·笔记
FfHUCisI1 天前
sync.Once 与 sync.Cond 源码与并发控制陷阱
服务器·开发语言·后端·golang
阿钱真强道1 天前
21 嵌入式操作系统 | JSON 与 json-c:生成与解析
c语言·开发语言·json·json-c