首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >R语言数据清洗

R语言数据清洗

作者头像
邓飞
发布2026-04-02 16:19:11
发布2026-04-02 16:19:11
640
举报

大家好,我是邓飞。

今天介绍一下数据分析前最重要的一份工作:数据清洗。

辛辛苦苦跑完一个混合模型,结果出来一堆显著,一顿汇报,领导一问: "这个品种的产量负值是怎么回事?"

然后你打开原始数据,发现……某行的 y3 写的是 -99,是当年数据录入时缺失值的占位符,根本不是真实值。

模型没有错,数据先错了。

今天这篇文章,就来聊聊做正式分析之前,那个最容易被忽视、却最容易坑人的环节——数据清洗


数据清洗是什么?为什么重要?

有一句话在数据圈广为流传:

Garbage in, garbage out.(垃圾进,垃圾出。)

数据清洗,说白了就是在"正式开饭"之前,先把食材洗干净、切好、检查有没有坏的。

特别是农业多环境试验数据——多地点、多品种、多性状、多年份,数据量一大,手工录入误差、仪器故障、田间意外几乎无法避免。不清洗直接建模,等于用没洗的菜炒了一道大厨级别的菜,卖相再好,吃了也可能拉肚子。

今天以一份一年10地点、9个品种、4个区组、5个性状的农业试验数据为例,带你过一遍完整的清洗流程。


第一步:读取数据,先认识一下

代码语言:javascript
复制
library(tidyverse)
library(data.table)

dat <- fread("one-year-multi-site.csv")

dim(dat)    # 多少行多少列?
head(dat)   # 长什么样?
str(dat)    # 每列是什么类型?

fread()data.table 包的函数,读大文件比 read.csv() 快好几倍,是我的日常主力。

看完之后,你要在心里有个基本印象:

  • 有几列?每列叫什么?
  • 分组变量(地点、品种、区组)识别出来了吗?
  • 数值列的量纲大致在什么范围?

我们这份数据长这样:

辛辛苦苦跑完一个混合模型,结果出来一堆显著,一顿汇报,领导一问: "这个品种的产量负值是怎么回事?"

然后你打开原始数据,发现……某行的 y3 写的是 -99,是当年数据录入时缺失值的占位符,根本不是真实值。

模型没有错,数据先错了。

今天这篇文章,就来聊聊做正式分析之前,那个最容易被忽视、却最容易坑人的环节——数据清洗

五个性状,量纲差异极大——y1 在 1~3 之间,y5 能跑到 130+。这个信息先记下来,后面标准化用得上。


第二步:纠正数据类型——别让因子冒充数字

这一步很多人跳过,然后在后面某个步骤踩坑,找半天原因。

locculblock 是分组变量,应该是因子,不是字符串,更不是数字:

代码语言:javascript
复制
dat <- dat %>%
  mutate(
    loc   = factor(loc),
    cul   = factor(cul),
    block = factor(block)
  )

# 检查因子水平数
nlevels(dat$loc)    # 应为10
nlevels(dat$cul)    # 应为9
nlevels(dat$block)  # 应为4

如果 cul(品种)是 1~9 的数字,R 默认读成 integer,一旦建模的时候忘了转因子,就会把品种当成连续变量处理,结果会让你怀疑人生。


第三步:缺失值检查——数据有没有"空窗期"

代码语言:javascript
复制
# 各列缺失值数量
colSums(is.na(dat))

# 缺失比例
round(colSums(is.na(dat)) / nrow(dat) * 100, 2)

如果缺失比例小,可以考虑插补;如果某一列缺了 30% 以上,这列数据的可靠性就要打个大问号了。

推荐用 naniar 包可视化缺失模式,一张图胜过一百行代码:

代码语言:javascript
复制
library(naniar)
vis_miss(dat)   # 缺哪里,一目了然

别忘了检查试验设计是否平衡:

代码语言:javascript
复制
dat %>%
  count(loc, cul, block) %>%
  filter(n > 1)   # 有输出 = 有重复记录,需要排查

理论上 10 地点 × 9 品种 × 4 区组 = 360 行,实际行数对不上,必须找原因。


第四步:汇总统计——摸清数据的脾气

代码语言:javascript
复制
summary(dat)            # 基础版
huizong(dat[, -c(1:3)]) # learnasreml包的增强版

除了看全局,还要分组看:

代码语言:javascript
复制
# 各地点性状均值
dat %>%
  group_by(loc) %>%
  summarise(across(y1:y5, ~round(mean(.x, na.rm = TRUE), 3)))

地点间差异大是正常的——这正是多环境试验的意义所在。但如果某个地点的均值是其他地点的 10 倍,就需要认真核实一下田间记录了。


第五步:异常值检测——揪出混在数据里的捣蛋鬼

这是整个清洗流程的重头戏。

方法一:箱线图,肉眼侦察

代码语言:javascript
复制
dat_long <- dat %>%
  pivot_longer(cols = y1:y5, names_to = "trait", values_to = "value")

ggplot(dat_long, aes(x = loc, y = value, fill = loc)) +
  geom_boxplot(outlier.colour = "red", outlier.shape = 16) +
  facet_wrap(~trait, scales = "free_y") +
  theme_bw() +
  labs(title = "各性状箱线图(红点为潜在异常值)")

图出来之后,凡是飘在箱体之外的红点,都值得重点审查。

方法二:IQR法,定量判断

代码语言:javascript
复制
find_outliers_iqr <- function(x, k = 3) {
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR <- Q3 - Q1
  x < (Q1 - k * IQR) | x > (Q3 + k * IQR)
}

# 统计各性状异常值数量
dat %>%
  summarise(across(y1:y5, ~sum(find_outliers_iqr(.x), na.rm = TRUE)))

这里用的是 k = 3(比标准的 1.5 更保守),农业数据本身变异就大,标准不要定太严,不然把真实的高产品种也删掉了,那就太冤了。

重要提醒: 异常值≠错误值。发现异常值之后,第一步是标记,而不是删除。留给专业人员判断:是录入错误?仪器故障?还是真实的极端表现?

代码语言:javascript
复制
dat_flagged <- dat %>%
  mutate(
    flag_y1 = find_outliers_iqr(y1),
    # ... 其他性状
    any_outlier = flag_y1 | flag_y2 | flag_y3 | flag_y4 | flag_y5
  )

# 输出异常记录供核查
dat_flagged %>% filter(any_outlier)

第六步:分布检验——数据的"体检报告"

很多统计方法(方差分析、混合模型)都有正态假设,做一下分布检验是好习惯:

代码语言:javascript
复制
# Shapiro-Wilk 正态性检验
dat %>%
  summarise(across(y1:y5, ~shapiro.test(.x)$p.value))

p > 0.05:不拒绝正态假设,放心用。 p < 0.05:分布有偏,考虑对数变换或非参数方法。

配合直方图+密度曲线,结果更直观:

代码语言:javascript
复制
ggplot(dat_long, aes(x = value)) +
  geom_histogram(aes(y = after_stat(density)), fill = "steelblue", alpha = 0.7) +
  geom_density(color = "red", linewidth = 1) +
  facet_wrap(~trait, scales = "free") +
  theme_bw()

第七步:相关性分析——性状们之间有没有"秘密"

代码语言:javascript
复制
library(Hmisc)
library(corrplot)

trait_mat <- dat %>% select(y1:y5) %>% as.matrix()
corr_result <- rcorr(trait_mat)

# 相关热图
corrplot(
  corr_result$r,
  method = "color", type = "upper",
  addCoef.col = "black",
  p.mat = corr_result$P, sig.level = 0.05, insig = "blank"
)

热图里空白的格子表示相关性不显著(p > 0.05),颜色越深相关性越强。

如果 y3 和 y5 的相关系数高达 0.95,那这两个性状在模型里几乎是同一回事,做多性状分析时要小心共线性问题。


第八步:标准化(可选)——消除量纲歧视

我们这份数据 y1 在 1~3 之间,y5 在 10~130 之间,量纲差距悬殊。如果要做主成分分析或者多性状综合指数,不标准化的话,y5 会直接"碾压"其他性状,让分析结果严重失真。

代码语言:javascript
复制
# Z-score标准化:均值0,标准差1
dat_scaled <- dat %>%
  mutate(across(y1:y5, ~as.numeric(scale(.x))))

第九步:导出清洗结果

代码语言:javascript
复制
# 带标记版(供人工核查)
fwrite(dat_flagged, "data_with_outlier_flags.csv")

# 干净版(供建模使用)
dat_clean <- dat %>%
  filter(!find_outliers_iqr(y1), !find_outliers_iqr(y2),
         !find_outliers_iqr(y3), !find_outliers_iqr(y4),
         !find_outliers_iqr(y5))

fwrite(dat_clean, "data_clean.csv")

总结:数据清洗的九步流程

步骤

内容

关键函数

1

读取数据,了解基本结构

fread(), str(), head()

2

纠正数据类型

mutate(), factor()

3

缺失值检查 + 设计完整性

is.na(), vis_miss(), count()

4

汇总统计

summary(), group_by()

5

异常值检测 + 标记

boxplot, IQR法, Z-score法

6

分布检验

shapiro.test(), 直方图

7

相关性分析

rcorr(), corrplot()

8

标准化(按需)

scale(), Min-Max

9

导出清洗结果

fwrite()


数据清洗这件事,没有一劳永逸的方案,每份数据都有自己的"个性"。但有了这套流程,至少能确保你在开始建模之前,对数据的脾气摸了个七七八八。

模型跑出来的结论,才经得起追问。

使用包:tidyverse / data.table / learnasreml / naniar / Hmisc / PerformanceAnalytics / corrplot

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 育种数据分析之放飞自我 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据清洗是什么?为什么重要?
  • 第一步:读取数据,先认识一下
  • 第二步:纠正数据类型——别让因子冒充数字
  • 第三步:缺失值检查——数据有没有"空窗期"
  • 第四步:汇总统计——摸清数据的脾气
  • 第五步:异常值检测——揪出混在数据里的捣蛋鬼
  • 第六步:分布检验——数据的"体检报告"
  • 第七步:相关性分析——性状们之间有没有"秘密"
  • 第八步:标准化(可选)——消除量纲歧视
  • 第九步:导出清洗结果
  • 总结:数据清洗的九步流程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档