数学建模国赛倒计时 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)) 的变化,这个流程就掌握了。比赛加油!💪

相关推荐
HineMusk1 小时前
如何使用 Codex 为数学建模论文高效绘图:以 2023 国赛 C 题为例
数学建模·流程图·画图
嘻哈baby1 小时前
大量消息在 MQ 里长时间积压,该如何解决?
开发语言
qq_570398571 小时前
Three.js基础使用-案例
开发语言·javascript·ecmascript
Suxing92 小时前
C语言基础分享:C语言文件操作:从“写日记”到“拍电影”的存盘指南
c语言·开发语言
右耳朵猫AI2 小时前
Node.js周刊2026W36 | NestJS 12发布、Remix 3 RC、pnpm 12 Rust重写、Node.js 26.8.0
开发语言·rust·node.js
乱七八糟的屋子2 小时前
NLopt 超详细实战教程(C++版)算法选型|多目标优化|批量寻优|动态约束|闭环仿真控制
数学建模·非线性优化·nlopt·多目标优化·机器人算法·工业仿真·c++优化
统计学小王子2 小时前
数学建模国赛倒计时 2 天 ——《异常值的处理(R语言)》
开发语言·数学建模·r语言
z落落5 小时前
C# WinForm Socket 转串口设备服务器+Modbus CRC16校验+Socket 客户端
服务器·开发语言·c#
小灰灰搞电子10 小时前
Rust+Slint 实现动态消息提示框源码分享
开发语言·后端·rust