如何在 R 中替换缺失值 (NA):na.omit 和 na.rm

⚡ 智能摘要

R 语言中的“替换缺失值”部分涵盖了检测 NA 值、使用 `na.omit()` 删除不完整行以及使用 `mutate()` 函数用均值或中位数填充缺失值。本教程使用泰坦尼克号数据集,其中年龄和票价数据均存在缺失值。

  • 🔎 先检测: colSums(is.na(df)) 在决定如何处理缺失值之前,统计每列的缺失值数量。
  • 🗑️ 行删除: na.omit() 删除所有包含 NA 的行,将泰坦尼克号数据从 1,309 行减少到 1,045 行。
  • 📐 均值插补: apply() with na.rm = TRUE 计算列均值,mutate() with ifelse() 将其写入新列。
  • 📊 中位数替代方案: 中位数不受异常值的影响,因此对于票价等偏斜变量来说,中位数是更安全的选择。
  • 批量插补: sapply() 或 across() 将相同的规则应用于一条语句中的每个数值列。
  • ⚠️ 已知的权衡取舍: 均值插补法会缩小方差并削弱相关性,因此绝不应盲目应用。

在 R 语言中替换缺失值 NA

R语言中的缺失值是什么?

当观测值在某一列中没有记录值,或者本应是数字的位置被非数字占位符占据时,就会出现缺失值。在进行任何计算之前,必须删除或替换这些缺失值,因为大多数 R 函数在遇到缺失值时都会返回 NA。

本教程展示了如何使用 dplyr 库(tidyverse 数据分析生态系统的一部分)处理缺失值。

在 R 中替换缺失值

第一步总是要找出缺失的值有多少以及缺失的位置。

如何在 R 语言中检测和统计缺失值

在决定如何处理缺失值之前,你需要知道缺失值的数量以及它们位于哪些列中。R 提供了四种检查方法,从简单的“是”或“否”判断到逐列统计。

# 1. Is there any NA at all?
anyNA(df_titanic)

# 2. How many in total?
sum(is.na(df_titanic))

# 3. How many per column, the most useful view
colSums(is.na(df_titanic))

# 4. Percentage missing per column
round(colMeans(is.na(df_titanic)) * 100, 1)

在实际应用中,应该优先使用 `colSums()` 函数。它返回一个命名向量,每列包含一个计数,可以立即显示变量是否缺少少量值或大部分为空。

计算完整行数。 complete.cases() 对于没有任何缺失值的行返回 TRUE,这可以提前告诉你 na.omit() 会丢弃多少数据:

sum(complete.cases(df_titanic))       # rows that would survive
sum(!complete.cases(df_titanic))      # rows that would be dropped

关于占位符的警告。 R 语言只识别 NA 值。数据集通常会将缺失值编码为空字符串、空格、“N/A”、“-”或哨兵数字(例如 -99 或 999)。这些编码方式会绕过上述所有检查而不被发现。导入时需要转换这些编码,以确保后续工作流程正常运行:

df <- read.csv(PATH, na.strings = c("", " ", "NA", "N/A", "-", "-99"))

导入数据后,务必检查每个数值列的范围。年龄为 -99 或票价为 9999 比正常的 NA 值危险得多,因为没有任何函数会对此发出警告。

缺失数据类型:完全随机缺失 (MCAR)、随机缺失 (MAR) 和随机非随机缺失 (MNAR)。

缺失值的原因决定了是否可以安全地进行插补。统计学家们认为存在三种机制。

  • MCAR,完全随机缺失。 缺失的概率与任何因素无关,无论是否已被观察到,例如传感器随机失效。ping 或者说,对这些行进行插补不会引入偏差,只会降低精度。
  • MAR,随机缺失。 概率取决于其他观测变量,但与缺失值本身无关。如果年龄较大的乘客不太可能被记录年龄,那么根据其他列的数据,年龄信息可能为随机缺失值(MAR)。使用这些列进行插补可以很好地处理这种情况。
  • MNAR,即非随机缺失。 概率取决于未观测值本身,例如高收入者拒绝透露其收入。仅凭现有数据,任何插补方法都无法解决这个问题,而且缺失值本身也包含值得记录在标记列中的信息。

本教程中使用的均值插补法仅在完全随机缺失 (MCAR) 和简单随机缺失 (MAR) 的情况下才适用。即使如此,它也存在已知的代价:用相同的数值填充所有缺失值会降低该列的方差,并削弱其与其他所有列的相关性。对于更严谨的工作,请使用基于模型的方法,例如 mice 包,并始终保留一个标记列来指示哪些值是插补的。

变异()

mutate() 是 dplyr 动词,用于创建新变量或覆盖现有变量,因此是构建列的干净副本的自然工具。

我们将分两部分进行。我们将学习如何:

  • 从数据框中排除缺失值
  • 用平均值和中位数填补缺失值

动词 mutate() 非常容易使用。我们可以按照以下语法创建一个新变量:

mutate(df, name_variable_1 = condition, ...)
arguments:
-df: Data frame used to create a new variable
-name_variable_1: Name and the formula to create the new variable
-...: No limit constraint. Possibility to create more than one variable inside mutate()

排除缺失值(NA)

`na.omit()` 是 R 的基础函数,并非 dplyr 函数,但它同样可以干净利落地执行。它会删除所有包含至少一个 NA 值的行。这是最快的选择,但通常并非最佳选择,因为一个缺失值就会导致整个观测值被丢弃。

为了解决缺失观测值的问题,我们将使用泰坦尼克号数据集。在这个数据集中,我们可以访问悲剧发生期间船上乘客的信息。这个数据集有许多需要处理的 NA。

从互联网加载 CSV 文件,然后列出包含 NA 值的列:

PATH <- "https://raw.githubusercontent.com/guru99-edu/R-Programming/master/test.csv"
df_titanic <- read.csv(PATH, sep = ",")
# Return the column names containing missing observations
list_na <- colnames(df_titanic)[ apply(df_titanic, 2, anyNA) ]
list_na

输出:

## [1] "age"  "fare"

在这里,

colnames(df_titanic)[apply(df_titanic, 2, anyNA)]

返回至少包含一个缺失值的列的名称。

年龄和票价列缺少值。

我们可以使用 na.omit() 删除它们。

library(dplyr)
# Exclude the missing observations
df_titanic_drop <-df_titanic %>%
na.omit()		
dim(df_titanic_drop)

输出:

## [1] 1045   13

新的数据集包含 1045 行,而原始数据集包含 1309 行。

排除缺失值

使用均值和中位数填补缺失数据。

你还可以使用均值或中位数来填补缺失值。一个好的做法是创建两个单独的变量分别存储均值和中位数。创建完成后,我们就可以用这些新创建的变量替换缺失值。

我们将使用 apply 方法来计算 NA 列的平均值。让我们看一个例子

步骤1) 在本教程的前面,我们将包含缺失值的列名称存储在名为 list_na 的列表中。我们将使用此列表

步骤2) 使用参数 na.rm = TRUE 计算均值。此参数是必需的,因为这些列存在缺失值,它告诉 R 忽略这些缺失值。

# Create mean
average_missing <- apply(df_titanic[,colnames(df_titanic) %in% list_na],
      2,
      mean,
      na.rm =  TRUE)
average_missing

Code 说明:

我们在apply方法中传递了4个参数。

  • df:df_titanic[,colnames(df_titanic) %in% list_na]。此代码将从 list_na 对象中返回列名称(即“age”和“fare”)
  • 2:计算列上的函数
  • 平均值:计算平均值
  • na.rm = TRUE:忽略缺失值

输出:

##      age     fare 
## 29.88113 33.29548

我们成功创建了包含缺失观测值的列的平均值。这两个值将用于替换缺失的观测值。

步骤3) 替换 NA 值

dplyr 库中的动词 mutate 在创建新变量时很有用。我们不一定想更改原始列,因此我们可以创建一个没有 NA 的新变量。mutate 易于使用,我们只需选择一个变量名并定义如何创建此变量。以下是完整代码

# Create a new variable with the mean and median
df_titanic_replace <- df_titanic %>%
   mutate(replace_mean_age  = ifelse(is.na(age), average_missing[1], age),
   replace_mean_fare = ifelse(is.na(fare), average_missing[2], fare))

Code 说明:

我们创建两个变量,replace_mean_age 和 replace_mean_fare 如下:

  • replace_mean_age = ifelse(is.na(年龄), average_missing[1], 年龄)
  • replace_mean_fare = ifelse(is.na(fare), average_missing[2],fare)

如果 age 列有缺失值,则用 average_missing 的第一个元素(年龄平均值)替换,否则保留原始值。fare 的逻辑相同

sum(is.na(df_titanic_replace$age))

输出:

## [1] 263

替换后,新列中完全没有缺失值:

sum(is.na(df_titanic_replace$replace_mean_age))

输出:

## [1] 0

原始年龄列有 263 个缺失值,而新的 replace_mean_age 列已将每个缺失值都填充为平均年龄。

步骤4) 我们也可以用中位数替换缺失的观测值。

median_missing <- apply(df_titanic[,colnames(df_titanic) %in% list_na],
      2,
      median,
      na.rm =  TRUE)
df_titanic_replace <- df_titanic %>%
            mutate(replace_median_age  = ifelse(is.na(age), median_missing[1], age), 
            replace_median_fare = ifelse(is.na(fare), median_missing[2], fare))
head(df_titanic_replace)

输出:

使用平均值和中位数填补缺失数据

步骤5) 对于大型数据集,逐步处理的方法会变得繁琐。`sapply()` 函数将整个过程简化为一条语句,但代价是无法看到填充后的值。

sapply 不会创建 数据框,因此我们可以将 sapply() 函数包装在 data.frame() 中来创建一个数据框对象。

# Quick code to replace missing values with the mean
df_titanic_impute_mean <- data.frame(
    sapply(
        df_titanic,
        function(x) ifelse(is.na(x),
            mean(x, na.rm = TRUE),
            x)))

使用 replace_na() 和 across() 进行现代插补

上面的 apply() 和 sapply() 方法仍然有效,但 tidyr 和 dplyr 现在以更安全、更易读的方式表达相同的操作。

replace_na() 用于固定值。 tidyr::replace_na() 接受一个命名列表,其中包含列及其替换值:

library(tidyr)

df_titanic %>%
    replace_na(list(age = 29.88, fare = 33.30))

对每个数值列执行 across()。 这是 sapply() 单行函数的直接、类型安全的替代方案,而且与 sapply() 不同的是,它永远不会触及字符列或因子列:

library(dplyr)

# Mean-impute every numeric column
df_titanic %>%
    mutate(across(where(is.numeric),
                  ~ ifelse(is.na(.x), mean(.x, na.rm = TRUE), .x)))

# Median-impute instead
df_titanic %>%
    mutate(across(where(is.numeric),
                  ~ ifelse(is.na(.x), median(.x, na.rm = TRUE), .x)))

⚠️ 为什么使用 sapply() 快捷方式存在风险: 单行 sapply() 示例运行时间超过 每周 该列包含字符型和因子型列。对这些列调用 mean() 函数会返回 NA 值并发出警告,而 sapply() 函数会将整个结果强制转换为字符型。上面的 across(where(is.numeric), …) 版本完全避免了这个问题。

对备用列进行 coalesce() 操作。 当第二列可以提供缺失值时,coalesce() 返回其参数中第一个非缺失值:

df %>% mutate(age_final = coalesce(age, age_estimated, 0))

记录下你所做的更改。 在进行插补之前添加一个标志,以便后续模型能够从缺失值这一事实中学习:

df_titanic %>%
    mutate(age_was_missing = is.na(age),
           age = ifelse(is.na(age), mean(age, na.rm = TRUE), age))

R 语言中缺失值的处理:方法参考

本教程涵盖三种方法:

  • 排除所有缺失的观测值
  • 用平均值估算
  • 使用中位数进行估算

下表总结了检测和清除情况:

图书馆 目的 Code
基地 列出缺失的观察结果
colnames(df)[apply(df, 2, anyNA)]
基地 删除所有包含 NA 的行。
na.omit(df)

用平均值或中位数进行插补可以通过两种方式进行

  • 使用 apply
  • 使用 sapply
付款方式 信息 优势 缺点
逐步应用 检查缺失的列,计算平均值/中位数,存储值,用 mutate() 替换 您可以看到估算的均值或中位数。 执行时间更长。处理大数据集时速度会比较慢
使用 sapply 的快捷方式 使用 sapply() 和 data.frame() 自动搜索缺失值并用平均值/中位数替换 代码短、速度快 估算值永远不会显示。

常见问题

NA 表示向量中缺少某个值。NULL 表示对象不存在,长度为零。NaN 是未定义数值运算(例如零除以零)的结果。

对于票价或收入等偏态分布的变量,应使用中位数,因为异常值会拉高平均值。只有当数据列大致对称且没有极端值时,才应使用平均值。

用相同的值填充所有空白会降低该列的方差,并削弱其与其他变量的相关性。而基于模型的方法,例如 mice 包,则能更好地保留这些关系。

大多数算法无法接受缺失值(NA),会报错或静默删除相应行。不严谨的插补会导致训练集和测试集之间信息泄露,因此务必仅在训练集上计算插补值。

是的。AI 助手可以根据缺失值模式推荐方法并生成 dplyr 代码。请根据您自己的 colSums(is.na()) 输出以及数据缺失原因的领域知识来验证所选方法。

总结一下这篇文章: