如何在R数据框中将NA值替换为零?

961

我有一个数据框,其中一些列具有NA值。

我该如何用零替换这些NA值?


16
对 https://dev59.com/SGw05IYBdhLWcg3wXQwh 进行了小修改(我是通过搜索“[r] replace NA with zero”找到的)。 - Ben Bolker
@psychonomics,你的评论和最佳答案有什么区别? - user2110417
1
@svp - 可能什么都没有?我看不到我的评论。我是否已经删除了它,而你仍然可以查看? - psychonomics
30个回答

1152

请查看@gsk3答案中的我的评论。以下是一个简单示例:

> m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
> d <- as.data.frame(m)
   V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  3 NA  3  7  6  6 10  6   5
2   9  8  9  5 10 NA  2  1  7   2
3   1  1  6  3  6 NA  1  4  1   6
4  NA  4 NA  7 10  2 NA  4  1   8
5   1  2  4 NA  2  6  2  6  7   4
6  NA  3 NA NA 10  2  1 10  8   4
7   4  4  9 10  9  8  9  4 10  NA
8   5  8  3  2  1  4  5  9  4   7
9   3  9 10  1  9  9 10  5  3   3
10  4  2  2  5 NA  9  7  2  5   5

> d[is.na(d)] <- 0

> d
   V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  3  0  3  7  6  6 10  6   5
2   9  8  9  5 10  0  2  1  7   2
3   1  1  6  3  6  0  1  4  1   6
4   0  4  0  7 10  2  0  4  1   8
5   1  2  4  0  2  6  2  6  7   4
6   0  3  0  0 10  2  1 10  8   4
7   4  4  9 10  9  8  9  4 10   0
8   5  8  3  2  1  4  5  9  4   7
9   3  9 10  1  9  9 10  5  3   3
10  4  2  2  5  0  9  7  2  5   5

无需应用apply。=)

编辑

您还应该查看norm软件包。它具有许多用于缺失数据分析的不错功能。=)


3
我在你发布这段代码之前就已经尝试过了,但是它并没有起作用。因此我才发布了问题。但是现在我重新尝试了一下,它完美地运行了。我想我之前做错了什么。 - Renato Dinhani
15
如果你已经尝试过某些事情,那么在提问时分享这些信息会很有帮助;它有助于缩小问题可能出现的范围。 - Aaron left Stack Overflow
2
d[is.na(d)] <- 0 对我来说没有意义。这似乎是相反的?R如何处理这个语句? - user798719
18
"<-" 是 R 语言的赋值运算符,可以理解为:对右边的内容进行某些操作,然后将其赋值给左边的位置或名称。在这个例子中,我们并没有真正“做”什么——只是创建了一些零值。左侧表示:查看 d 对象,在 d 对象内(方括号中),找到所有返回 TRUE 的元素(is.na(d) 对每个元素返回一个逻辑值)。一旦找到它们,就用值 0 替换它们(“分配它们”)。这样可以保留所有非 NA 值,并仅替换那些缺失值。 - Twitch_City
7
如果你有一个数据框,并且只想将替换应用于特定的数字向量(例如,将字符串保留为 NA),可以使用以下命令:df[19:28][is.na(df[19:28])] <- 0 - jtdoud
显示剩余3条评论

470

dplyr混合选项现在比基本R子集重新分配快30%。在一个1亿数据点的数据框中,mutate_all(~replace(., is.na(.), 0)) 比基本R d[is.na(d)] <- 0 选项快半秒钟。需要特别避免使用 ifelse()if_else()。 (完整的600次试验分析耗时超过4.5小时,主要是因为包括了这些方法。)请参见下面的基准分析获取完整结果。

如果您正在处理大型数据框,则data.table是所有选项中最快的:比标准Base R方法快40%。它还可以直接修改数据,有效地使您能够一次处理将近两倍的数据。


其他有用的tidyverse替代方法聚类

位置:

  • 索引 mutate_at(c(5:10), ~replace(., is.na(.), 0))
  • 直接引用 mutate_at(vars(var5:var10), ~replace(., is.na(.), 0))
  • 固定匹配 mutate_at(vars(contains("1")), ~replace(., is.na(.), 0))
  • 或者使用 ends_with(),starts_with() 替换 contains()
  • 模式匹配 mutate_at(vars(matches("\\d{2}")), ~replace(., is.na(.), 0))

条件:
(仅更改单个类型并保留其他类型不变。)

  • 整数 mutate_if(is.integer, ~replace(., is.na(.), 0))
  • 数字 mutate_if(is.numeric, ~replace(., is.na(.), 0))
  • 字符串 mutate_if(is.character, ~replace(., is.na(.), 0))

##完整分析 - 更新为 dplyr 0.8.0:函数使用 purrr 格式的 ~ 符号,替换已弃用的 funs() 参数。

###测试的方法:

# Base R: 
baseR.sbst.rssgn   <- function(x) { x[is.na(x)] <- 0; x }
baseR.replace      <- function(x) { replace(x, is.na(x), 0) }
baseR.for          <- function(x) { for(j in 1:ncol(x))
    x[[j]][is.na(x[[j]])] = 0 }

# tidyverse
## dplyr
dplyr_if_else      <- function(x) { mutate_all(x, ~if_else(is.na(.), 0, .)) }
dplyr_coalesce     <- function(x) { mutate_all(x, ~coalesce(., 0)) }

## tidyr
tidyr_replace_na   <- function(x) { replace_na(x, as.list(setNames(rep(0, 10), as.list(c(paste0("var", 1:10)))))) }

## hybrid 
hybrd.ifelse     <- function(x) { mutate_all(x, ~ifelse(is.na(.), 0, .)) }
hybrd.replace_na <- function(x) { mutate_all(x, ~replace_na(., 0)) }
hybrd.replace    <- function(x) { mutate_all(x, ~replace(., is.na(.), 0)) }
hybrd.rplc_at.idx<- function(x) { mutate_at(x, c(1:10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.nse<- function(x) { mutate_at(x, vars(var1:var10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.stw<- function(x) { mutate_at(x, vars(starts_with("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.ctn<- function(x) { mutate_at(x, vars(contains("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.mtc<- function(x) { mutate_at(x, vars(matches("\\d+")), ~replace(., is.na(.), 0)) }
hybrd.rplc_if    <- function(x) { mutate_if(x, is.numeric, ~replace(., is.na(.), 0)) }

# data.table   
library(data.table)
DT.for.set.nms   <- function(x) { for (j in names(x))
    set(x,which(is.na(x[[j]])),j,0) }
DT.for.set.sqln  <- function(x) { for (j in seq_len(ncol(x)))
    set(x,which(is.na(x[[j]])),j,0) }
DT.nafill        <- function(x) { nafill(df, fill=0)}
DT.setnafill     <- function(x) { setnafill(df, fill=0)}

###这个分析的代码:

library(microbenchmark)
# 20% NA filled dataframe of 10 Million rows and 10 columns
set.seed(42) # to recreate the exact dataframe
dfN <- as.data.frame(matrix(sample(c(NA, as.numeric(1:4)), 1e7*10, replace = TRUE),
                            dimnames = list(NULL, paste0("var", 1:10)), 
                            ncol = 10))
# Running 600 trials with each replacement method 
# (the functions are excecuted locally - so that the original dataframe remains unmodified in all cases)
perf_results <- microbenchmark(
    hybrd.ifelse     = hybrd.ifelse(copy(dfN)),
    dplyr_if_else    = dplyr_if_else(copy(dfN)),
    hybrd.replace_na = hybrd.replace_na(copy(dfN)),
    baseR.sbst.rssgn = baseR.sbst.rssgn(copy(dfN)),
    baseR.replace    = baseR.replace(copy(dfN)),
    dplyr_coalesce   = dplyr_coalesce(copy(dfN)),
    tidyr_replace_na = tidyr_replace_na(copy(dfN)),
    hybrd.replace    = hybrd.replace(copy(dfN)),
    hybrd.rplc_at.ctn= hybrd.rplc_at.ctn(copy(dfN)),
    hybrd.rplc_at.nse= hybrd.rplc_at.nse(copy(dfN)),
    baseR.for        = baseR.for(copy(dfN)),
    hybrd.rplc_at.idx= hybrd.rplc_at.idx(copy(dfN)),
    DT.for.set.nms   = DT.for.set.nms(copy(dfN)),
    DT.for.set.sqln  = DT.for.set.sqln(copy(dfN)),
    times = 600L
)
###结果摘要
> print(perf_results)
Unit: milliseconds
              expr       min        lq     mean   median       uq      max neval
      hybrd.ifelse 6171.0439 6339.7046 6425.221 6407.397 6496.992 7052.851   600
     dplyr_if_else 3737.4954 3877.0983 3953.857 3946.024 4023.301 4539.428   600
  hybrd.replace_na 1497.8653 1706.1119 1748.464 1745.282 1789.804 2127.166   600
  baseR.sbst.rssgn 1480.5098 1686.1581 1730.006 1728.477 1772.951 2010.215   600
     baseR.replace 1457.4016 1681.5583 1725.481 1722.069 1766.916 2089.627   600
    dplyr_coalesce 1227.6150 1483.3520 1524.245 1519.454 1561.488 1996.859   600
  tidyr_replace_na 1248.3292 1473.1707 1521.889 1520.108 1570.382 1995.768   600
     hybrd.replace  913.1865 1197.3133 1233.336 1238.747 1276.141 1438.646   600
 hybrd.rplc_at.ctn  916.9339 1192.9885 1224.733 1227.628 1268.644 1466.085   600
 hybrd.rplc_at.nse  919.0270 1191.0541 1228.749 1228.635 1275.103 2882.040   600
         baseR.for  869.3169 1180.8311 1216.958 1224.407 1264.737 1459.726   600
 hybrd.rplc_at.idx  839.8915 1189.7465 1223.326 1228.329 1266.375 1565.794   600
    DT.for.set.nms  761.6086  915.8166 1015.457 1001.772 1106.315 1363.044   600
   DT.for.set.sqln  787.3535  918.8733 1017.812 1002.042 1122.474 1321.860   600
### 结果的箱线图
ggplot(perf_results, aes(x=expr, y=time/10^9)) +
    geom_boxplot() +
    xlab('Expression') +
    ylab('Elapsed Time (Seconds)') +
    scale_y_continuous(breaks = seq(0,7,1)) +
    coord_flip()

箱线图: 程序执行时间比较

以颜色编码的试验散点图(y轴为对数刻度)

qplot(y=time/10^9, data=perf_results, colour=expr) + 
    labs(y = "log10 Scaled Elapsed Time per Trial (secs)", x = "Trial Number") +
    coord_cartesian(ylim = c(0.75, 7.5)) +
    scale_y_log10(breaks=c(0.75, 0.875, 1, 1.25, 1.5, 1.75, seq(2, 7.5)))

All Trial Times的散点图

关于其他高绩效者的说明

当数据集变得更大时,Tidyrreplace_na 函数一直处于领先地位。在当前 1 亿个数据点的集合中运行时,它的表现几乎与一个 Base R For Loop 相同。我很想看看不同大小的数据框会发生什么。

有关 mutatesummarize _at_all 函数变体的其他示例可以在这里找到:https://rdrr.io/cran/dplyr/man/summarise_all.html 此外,我在这里找到了有用的演示和示例集:https://blog.exploratory.io/dplyr-0-5-is-awesome-heres-why-be095fd4eb8a

致谢

特别感谢:

  • Tyler RinkerAkrun 展示了微基准测试。
  • alexis_laz 为我解释了 local() 的用法,并在 Frank 的耐心帮助下,加快了许多方法的速度。
  • ArthurYip 提供了添加较新的 coalesce() 函数并更新分析的提示。
  • Gregor 敦促我弄清楚 data.table 函数,以便最终将它们包括在内。
  • Base R For Loop: alexis_laz
  • data.table For Loops: Matt_Dowle
  • Roman 解释了 is.numeric() 到底测试了什么。

(当然,如果你认为这些方法有用,请给它们点赞。)

我的数字使用说明:如果你有一个纯整数数据集,所有的函数都将运行得更快。更多信息请参见alexiz_laz的工作。在现实生活中,我记不起来遇到过超过10-15%整数的数据集,因此我正在对完全数值化的数据帧进行这些测试。

使用的硬件:3.9 GHz CPU with 24 GB RAM


2
@Frank - 感谢您发现了这个差异。参考文献已经全部清理完毕,并且结果已经在单台机器上重新运行并重新发布。 - leerssej
我不明白。当前的代码将它们转换成什么?z <- c(1,2,3); z[2] <- 0L; str(z)仍然是数字类型。 - Frank
1
@UweBlock - 很好的问题:它让我能够使用所有在完全相同数据框上运行的函数进行子集左赋值操作。由于我必须将本地包装在该函数周围,因此为了科学的名义[你只有一个工作!],我将其包装在所有函数周围,以便比赛场地是毫无疑问的平等。有关更多信息,请参见此处:https://dev59.com/X57ha4cB1Zd3GeqPeiCB 我已经缩减了之前冗长的答案 - 但是讨论的这部分内容很好添加回去。谢谢! - leerssej
1
@ArthurYip - 我已经添加了 coalesce() 选项并重新运行了所有时间。感谢您的提醒更新。 - leerssej
5
dplyr 1.0.2的更新已经移除了mutate_atmutate_all函数,可以使用以下代码来替代:function(x) { mutate(across(x, ~replace_na(., 0))) },该代码的作用是将数据框某些列中的缺失值替换成0。 - Jeff Parker
显示剩余18条评论

160

对于单个向量:

x <- c(1,2,NA,4,5)
x[is.na(x)] <- 0

针对一个数据框,将上述内容制作成一个函数,然后使用 apply 函数应用于列。

下次请提供可复现的示例,详见:

如何创建一个好的 R 可重现性示例?


19
is.na 是一个通用函数,它有适用于 data.frame 类型对象的方法。因此,这个函数也可以用于处理 data.frame 对象! - aL3xa
3
当我第一次运行methods(is.na)时,我就像“哇?!?”我喜欢这样的事情发生!=) - aL3xa
10
假设您有一个名为df的数据框(data frame),而不是单个向量,您只想替换名为X3的单列中的缺失观测值。您可以使用以下代码行实现:df $ X3 [is.na(df $ X3)] <- 0。 - Mark Miller
9
假设您只想将数据框 my.df 中第4-6列中的 NA 替换为 0。您可以使用以下代码:my.df[,4:6][is.na(my.df[,4:6])] <- 0 - Mark Miller
你为什么要将 'x' 传递给 is.na(x)?有没有办法知道 R 中哪些库例程是矢量化的? - uh_big_mike_boi

95
library(dplyr)

df1 <- df1 %>%
    mutate(myCol1 = if_else(is.na(myCol1), 0, myCol1))

注意: 这适用于所选列,如果我们需要对所有列进行操作,请参见 @reidjax 的答案,使用mutate_each


69

如果我们在导出数据,例如写入csv时想要替换NA,那么可以使用以下方法:

  write.csv(data, "data.csv", na = "0")

69

您也可以使用 tidyr::replace_na

    library(tidyr)
    df <- df %>% mutate_all(funs(replace_na(.,0)))

编辑(dplyr > 1.0.0):

df %>% mutate(across(everything(), .fns = ~replace_na(.,0))) 

2
mutate_* verbs are now superseded by across() - Ömer An
我很好奇为什么我需要将 replace(is.na(.), 0) 函数包装在 mutate() 中。为什么不能直接将其输入到管道中? - Faustin Gashakamba
mutate 可以创建或替换变量。 - Julien
dplyr 1.1.0 开始,您应该这样编写替换 mutate(data, across(.cols = everything(), \(x) replace_na(x, 0))) - ha-pu
@ha-pu 从1.1.0版本开始,匿名函数现在内联支持(不一定替代),并提供了轻微的性能提升。 - LMc
从1.1.0版本开始,匿名函数现在可以内联支持(不一定要替换~),并且可以提供轻微的性能提升。 - undefined

58

我知道这个问题已经有答案了,但是按照这种方式做可能对某些人更有用:

定义这个函数:

na.zero <- function (x) {
    x[is.na(x)] <- 0
    return(x)
}

现在,每当您需要将向量中的 NA 转换为 0 时,可以执行以下操作:

na.zero(some.vector)

32

使用replace()在矩阵或向量中替换NA0的更通用方法。

例如:

> x <- c(1,2,NA,NA,1,1)
> x1 <- replace(x,is.na(x),0)
> x1
[1] 1 2 0 0 1 1

这也是在 dplyr 中使用 ifelse() 的替代方法。

df = data.frame(col = c(1,2,NA,NA,1,1))
df <- df %>%
   mutate(col = replace(col,is.na(col),0))

3
我的专栏是一个因素,所以我不得不添加我的替代值 levels(A$x) <- append(levels(A$x), "notAnswered") A$x <- replace(A$x,which(is.na(A$x)),"notAnswered") - Climbs_lika_Spyder
1
这里不需要使用 which,你可以使用 x1 <- replace(x,is.na(x),1) - lmo
我尝试了这个线程中提出的许多方法,在一个大数据框的一个特定列中将NA替换为0,而这个replace()函数既最有效,又最简单。 - Gerry

31

使用 dplyr 0.5.0 版本,您可以使用 coalesce 函数,并通过执行 coalesce(vec, 0) 轻松将其集成到 %>% 管道中。这将用 0 替换 vec 中的所有 NAs:

假设我们有一个带有 NA 的数据框:

library(dplyr)
df <- data.frame(v = c(1, 2, 3, NA, 5, 6, 8))

df
#    v
# 1  1
# 2  2
# 3  3
# 4 NA
# 5  5
# 6  6
# 7  8

df %>% mutate(v = coalesce(v, 0))
#   v
# 1 1
# 2 2
# 3 3
# 4 0
# 5 5
# 6 6
# 7 8

我测试了coalesce函数,它的表现与replace函数差不多。但是coalesce函数是目前最简单的! - Arthur Yip
3
请问需要翻译成中文吗?如果是的话,我的翻译如下:如果您能展示如何将此应用于包含两列或更多列的 tibble 的所有列,那将非常有用。 - jangorecki
mutate(across(where(is.character), ~ coalesce(.x, 0))) - LMc
mutate(across(where(is.character), ~ coalesce(.x, 0))) - undefined

26

要替换数据框中的所有NA值,您可以使用:

df %>% replace(is.na(.), 0)


3
这不是一个新的解决方案。 - jogo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接