我有一个看起来像
的数据集a1 a2 a3
A 1 1 1
B 2 NA 2
C 1 1 1
我想用列中值和行中值的最小值来代替NA。由于行中位数(对于行"B")是2,列中位数(对于列"a2")是1,我想用1代替NA并得到
a1 a2 a3
A 1 1 1
B 2 1 2
C 1 1 1
我知道如何使用dplyr将NA替换为列中位数:
mutate_if(is.numeric, ~replace_na(., median(., na.rm = TRUE)))
但是如何得到我真正需要的?
我想用列中位数和行中位数的最小值替换NA。
设矩阵为mat
。(如果你有一个数据帧df
,提取它的数字列,并使用mat <- as.matrix(df[sapply(df, is.numeric)])
将它们强制到一个矩阵。)
即使在一行或列中有多个NA
,一个可靠的向量化解是:
## you may need to install package "matrixStats" first
rmed <- matrixStats::rowMedians(mat, na.rm = TRUE)
cmed <- matrixStats::colMedians(mat, na.rm = TRUE)
ij <- which(is.na(mat), arr.ind = TRUE)
mat[ij] <- pmin(rmed[ij[, 1]], cmed[ij[, 2]])
mat
代码给出了3 × 3玩具示例的预期结果。这里有一个更复杂的测试:
mat <- structure(c(6L, 4L, NA, NA, 2L, 8L, 8L, NA, NA, 7L, 7L, 4L, 4L,
NA, NA, NA, 5L, 8L, NA, 9L, 6L, NA, 5L, 5L, 10L, 5L, NA, 6L,
NA, 9L, NA, 6L, 5L, 1L, 10L, 7L, 3L, 1L, 2L, NA, 7L, NA, 8L,
1L, 10L, 8L, 2L, 1L, NA, NA, 2L, NA, NA, 2L, NA, 10L, 6L, 6L,
NA, 9L, 10L, 1L, 5L, 10L), dim = c(8L, 8L))
# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
#[1,] 6 NA 5 10 5 7 NA 6
#[2,] 4 7 8 5 1 NA NA 6
#[3,] NA 7 NA NA 10 8 2 NA
#[4,] NA 4 9 6 7 1 NA 9
#[5,] 2 4 6 NA 3 10 NA 10
#[6,] 8 NA NA 9 1 8 2 1
#[7,] 8 NA 5 NA 2 2 NA 5
#[8,] NA NA 5 6 NA 1 10 10
处理结果为:
# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
#[1,] 6 5.5 5.0 10 5 7.0 2 6
#[2,] 4 7.0 8.0 5 1 5.5 2 6
#[3,] 6 7.0 5.5 6 10 8.0 2 6
#[4,] 6 4.0 9.0 6 7 1.0 2 9
#[5,] 2 4.0 6.0 5 3 10.0 2 10
#[6,] 8 5.0 5.0 9 1 8.0 2 1
#[7,] 8 5.0 5.0 5 2 2.0 2 5
#[8,] 6 5.5 5.0 6 3 1.0 10 10
注意,向量x
的中位数可以是不在x
中的值。例如,median(1:6)
是3.5。
另一种可能的解决方案,基于outer
:
library(tidyverse)
outer(1:nrow(df), 1:ncol(df),
Vectorize((z,w) if (is.na(df[z,w]))
min(apply(df, 2, (x) median(x, na.rm = T))[z],
apply(df, 1, (x) median(x, na.rm = T))[w])
else df[z,w])) %>%
as.data.frame %>%
set_names(names(df)) %>%
`rownames<-`(rownames(df))
#> a1 a2 a3
#> A 1 1 1
#> B 2 1 2
#> C 1 1 1