我有一个数据框,有些列有 NA
值。
如何用零替换这些 NA
值?
请参阅我在@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
包。它有很多很好的缺失数据分析功能。 =)
dplyr 杂交选项现在比重新分配的 Base R 子集快约 30%。在 100M 数据点上,数据帧 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)) 或代替 contains(),尝试 ends_with(),starts_with()
或代替 contains(),尝试 ends_with(),starts_with()
模式匹配 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(
hybrid.ifelse = hybrid.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.608 6 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()
https://i.stack.imgur.com/6BoFk.png
试验的颜色编码散点图(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)))
https://i.stack.imgur.com/x1uaK.png
关于其他高绩效者的说明
当数据集变大时,Tidyr 的 replace_na
在历史上一直处于领先地位。凭借当前要运行的 100M 数据点集合,它的性能几乎与 Base R For 循环一样好。我很想知道不同大小的数据框会发生什么。
可以在此处找到 mutate
和 summarize
_at
和 _all
函数变体的其他示例:https://rdrr.io/cran/dplyr/man/summarise_all.html 此外,我还可以在此处找到有用的演示和示例集合:https://blog.exploratory.io/dplyr-0-5-is-awesome-heres-why-be095fd4eb8a
归因和赞赏
特别感谢:
Tyler Rinker 和 Akrun 演示微基准测试。
alexis_laz 帮助我理解 local() 的使用,以及(在 Frank 的耐心帮助下)无声强制在加速许多这些方法中所起的作用。
ArthurYip 用于添加更新的 coalesce() 函数并更新分析。
Gregor 推动了 data.table 功能的完善,最终将它们包含在阵容中。
基本 R For 循环:alexis_laz
data.table 循环:Matt_Dowle
Roman 用于解释什么 is.numeric() 真正测试。
(当然,如果您觉得这些方法有用,也请过来给他们点赞。)
注意我对 Numerics 的使用:如果您确实有一个纯整数数据集,那么您的所有函数都会运行得更快。有关详细信息,请参阅 alexiz_laz's work。 IRL,我不记得遇到过包含超过 10-15% 整数的数据集,所以我在全数字数据帧上运行这些测试。
硬件使用 3.9 GHz CPU 和 24 GB RAM
df1[j][is.na(df1[j])] = 0
是错误的,应该是df1[[j]][is.na(df1[[j]])] = 0
coalesce()
选项并一直重新运行。感谢您推动更新。
mutate_at
和 mutate_all
的 dplyr 1.0.2 更新:function(x) { mutate(across(x, ~replace_na(., 0))) }
对于单个向量:
x <- c(1,2,NA,4,5)
x[is.na(x)] <- 0
对于 data.frame,从上面创建一个函数,然后将其 apply
到列。
下次请提供一个可重现的示例,如下所述:
How to make a great R reproducible example?
is.na
是通用函数,具有 data.frame
类对象的方法。所以这个也适用于data.frame
!
methods(is.na)
时,我就像 whaaa?!?。我喜欢这样的事情发生! =)
dplyr 示例:
library(dplyr)
df1 <- df1 %>%
mutate(myCol1 = if_else(is.na(myCol1), 0, myCol1))
注意:这适用于每个选定的列,如果我们需要对所有列执行此操作,请参阅 @reidjax 使用 mutate_each 的答案。
如果我们在导出时尝试替换 NA
,例如在写入 csv 时,那么我们可以使用:
write.csv(data, "data.csv", na = "0")
我知道这个问题已经得到解答,但这样做可能对某些人更有用:
定义这个函数:
na.zero <- function (x) {
x[is.na(x)] <- 0
return(x)
}
现在,每当您需要将向量中的 NA 转换为零时,您可以执行以下操作:
na.zero(some.vector)
也可以使用 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)))
mutate_*
动词现在被 across()
取代
在矩阵或向量中使用 replace()
将 NA
替换为 0
的更一般的方法
例如:
> 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))
levels(A$x) <- append(levels(A$x), "notAnswered") A$x <- replace(A$x,which(is.na(A$x)),"notAnswered")
which
,您可以使用 x1 <- replace(x,is.na(x),1)
。
NA
替换为 0
,并且此函数 replace()
工作得最有效,同时也最简单。
使用 dplyr
0.5.0,您可以使用 coalesce
功能,通过执行 coalesce(vec, 0)
可以轻松地将其集成到 %>%
管道中。这将 vec
中的所有 NA 替换为 0:
假设我们有一个带有 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
要替换数据框中的所有 NA,您可以使用:
df %>% replace(is.na(.), 0)
本来会评论@ianmunoz 的帖子,但我没有足够的声誉。您可以组合 dplyr
的 mutate_each
和 replace
来处理 NA
到 0
的替换。使用来自@aL3xa 答案的数据框...
> m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
> d <- as.data.frame(m)
> d
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1 4 8 1 9 6 9 NA 8 9 8
2 8 3 6 8 2 1 NA NA 6 3
3 6 6 3 NA 2 NA NA 5 7 7
4 10 6 1 1 7 9 1 10 3 10
5 10 6 7 10 10 3 2 5 4 6
6 2 4 1 5 7 NA NA 8 4 4
7 7 2 3 1 4 10 NA 8 7 7
8 9 5 8 10 5 3 5 8 3 2
9 9 1 8 7 6 5 NA NA 6 7
10 6 10 8 7 1 1 2 2 5 7
> d %>% mutate_each( funs_( interp( ~replace(., is.na(.),0) ) ) )
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1 4 8 1 9 6 9 0 8 9 8
2 8 3 6 8 2 1 0 0 6 3
3 6 6 3 0 2 0 0 5 7 7
4 10 6 1 1 7 9 1 10 3 10
5 10 6 7 10 10 3 2 5 4 6
6 2 4 1 5 7 0 0 8 4 4
7 7 2 3 1 4 10 0 8 7 7
8 9 5 8 10 5 3 5 8 3 2
9 9 1 8 7 6 5 0 0 6 7
10 6 10 8 7 1 1 2 2 5 7
我们在这里使用标准评估 (SE),这就是为什么我们需要在“funs_
”上加上下划线。我们还使用 lazyeval
的 interp
/~
和 .
引用“我们正在使用的一切”,即数据框。现在有零了!
另一个使用 imputeTS 包的例子:
library(imputeTS)
na.replace(yourDataframe, 0)
如果要替换因子变量中的 NA,这可能很有用:
n <- length(levels(data.vector))+1
data.vector <- as.numeric(data.vector)
data.vector[is.na(data.vector)] <- n
data.vector <- as.factor(data.vector)
levels(data.vector) <- c("level1","level2",...,"leveln", "NAlevel")
它将因子向量转换为数字向量并添加另一个人工数字因子级别,然后将其转换回具有您选择的额外“NA 级别”的因子向量。
用于此目的的专用函数 nafill
和 setnafill
在 data.table
中。只要可用,它们就会在多个线程上分配要计算的列。
library(data.table)
ans_df <- nafill(df, fill=0)
# or even faster, in-place
setnafill(df, fill=0)
无需使用任何库。
df <- data.frame(a=c(1,3,5,NA))
df$a[is.na(df$a)] <- 0
df
您可以使用 replace()
例如:
> x <- c(-1,0,1,0,NA,0,1,1)
> x1 <- replace(x,5,1)
> x1
[1] -1 0 1 0 1 0 1 1
> x1 <- replace(x,5,mean(x,na.rm=T))
> x1
[1] -1.00 0.00 1.00 0.00 0.29 0.00 1.00 1.00
NA
的索引时才实用。如您的示例所示,这对于小向量很好。
x1 <- replace(x,is.na(x),1)
无需明确列出索引值即可工作。
cleaner
包有一个 na_replace()
泛型,默认用零替换数值,用 FALSE
替换逻辑,用今天替换日期等:
library(dplyr)
library(cleaner)
starwars %>% na_replace()
na_replace(starwars)
它甚至支持向量化替换:
mtcars[1:6, c("mpg", "hp")] <- NA
na_replace(mtcars, mpg, hp, replacement = c(999, 123))
文档:https://msberends.github.io/cleaner/reference/na_replace.html
另一个与tidyr
方法 replace_na
兼容的 dplyr
管道选项适用于多个列:
require(dplyr)
require(tidyr)
m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
d <- as.data.frame(m)
myList <- setNames(lapply(vector("list", ncol(d)), function(x) x <- 0), names(d))
df <- d %>% replace_na(myList)
您可以轻松地限制为例如数字列:
d$str <- c("string", NA)
myList <- myList[sapply(d, is.numeric)]
df <- d %>% replace_na(myList)
从 Datacamp 中提取的这个简单函数可以帮助:
replace_missings <- function(x, replacement) {
is_miss <- is.na(x)
x[is_miss] <- replacement
message(sum(is_miss), " missings replaced by the value ", replacement)
x
}
然后
replace_missings(df, replacement = 0)
编写它的一种简单方法是使用 hablar
中的 if_na
:
library(dplyr)
library(hablar)
df <- tibble(a = c(1, 2, 3, NA, 5, 6, 8))
df %>%
mutate(a = if_na(a, 0))
返回:
a
<dbl>
1 1
2 2
3 3
4 0
5 5
6 6
7 8
dplyr >= 1.0.0
在较新版本的 dplyr
中:
cross() 取代了“范围变体”系列,如 summarise_at()、summarise_if() 和 summarise_all()。
df <- data.frame(a = c(LETTERS[1:3], NA), b = c(NA, 1:3))
library(tidyverse)
df %>%
mutate(across(where(anyNA), ~ replace_na(., 0)))
a b
1 A 0
2 B 1
3 C 2
4 0 3
此代码将强制 0
成为第一列中的字符。要根据列类型替换 NA
,您可以在 where
中使用类似 purrr 的公式:
df %>%
mutate(across(where(~ anyNA(.) & is.character(.)), ~ replace_na(., "0")))
替换数据框中的 is.na & NULL。
带列的数据框
A$name[is.na(A$name)]<-0
或者
A$name[is.na(A$name)]<-"NA"
与所有数据框
df[is.na(df)]<-0
在数据框中用空白替换 na
df[is.na(df)]<-""
将 NULL 替换为 NA
df[is.null(df)] <- 不适用
如果您想在本例列 V3 中更改特定列中的 NA 后分配一个新名称,请使用您也可以这样做
my.data.frame$the.new.column.name <- ifelse(is.na(my.data.frame$V3),0,1)
我想添加一个使用流行的 Hmisc
package 的下一个解决方案。
library(Hmisc)
data(airquality)
# imputing with 0 - all columns
# although my favorite one for simple imputations is Hmisc::impute(x, "random")
> dd <- data.frame(Map(function(x) Hmisc::impute(x, 0), airquality))
> str(dd[[1]])
'impute' Named num [1:153] 41 36 12 18 0 28 23 19 8 0 ...
- attr(*, "names")= chr [1:153] "1" "2" "3" "4" ...
- attr(*, "imputed")= int [1:37] 5 10 25 26 27 32 33 34 35 36 ...
> dd[[1]][1:10]
1 2 3 4 5 6 7 8 9 10
41 36 12 18 0* 28 23 19 8 0*
可以看出,所有插补元数据都被分配为属性。因此它可以在以后使用。
在 data.frame 中,不需要通过 mutate 创建新列。
library(tidyverse)
k <- c(1,2,80,NA,NA,51)
j <- c(NA,NA,3,31,12,NA)
df <- data.frame(k,j)%>%
replace_na(list(j=0))#convert only column j, for example
结果
k j
1 0
2 0
80 3
NA 31
NA 12
51 0
这并不是一个全新的解决方案,但我喜欢编写内联 lambdas 来处理我无法让包完成的事情。在这种情况下,
df %>%
(function(x) { x[is.na(x)] <- 0; return(x) })
因为 R 不会像您在 Python 中看到的那样“通过对象”,所以此解决方案不会修改原始变量 df
,因此与大多数其他解决方案完全相同,但对复杂性的需求要少得多特定软件包的知识。
注意函数定义周围的括号!虽然这对我来说似乎有点多余,但由于函数定义用花括号括起来,因此需要在 magrittr
的括号内定义内联函数。
df[19:28][is.na(df[19:28])] <- 0