r语言 - 将NA替换为行中位数和列中位数的最小值



我有一个看起来像

的数据集
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

最新更新