从矩阵行中选取前 n% 的元素,每行的元素数量不同

Picking top n% percent of elements from matrix rows, different number of elements on each row

我在选取前 n% 最大和最小元素时遇到问题 来自每个数据矩阵行。具体来说,我想找到那些前 n% 元素的列号。如果每行具有相同数量的非 NA 元素,这将不是问题,但在这种情况下,每行选择的元素数量不同。这是一个情况示例(实际数据矩阵是 195x1030,所以我不会在这里使用它),其中前 40% 被选中

data=     
      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,]    1   NA   100  98   200  78   80   35   NA    55
[2,]   32   67   15   73   NA   12   91   230  3     99
[3,]   NA   NA   NA   45   53   26   112  64   80    41
[4,]   54   38   60   70   163  69   109  205  5     31
[5,]   107  28   296  254  30   40   NA   18   28    90

生成的前 40% 列数矩阵应如下所示(所选元素的数量是通过向下舍入计算的,就像函数 as.integer 所做的那样)

largest=                              smallest=
      [,1] [,2] [,3] [,4]                   [,1] [,2] [,3] [,4]  
[1,]    5   3    4    NA              [1,]    1   8    10   NA
[2,]    8   10   7    NA              [2,]    9   6    3    NA
[3,]    7   9    NA   NA              [3,]    6   10   NA   NA
[4,]    8   5    7    4               [4,]    9   10   2    1
[5,]    3   4    1    10              [5,]    8   9    2    5

因此,仅查看行的非 NA 元素来选择排名靠前的数字。例如,数据矩阵的第一行仅包含 8 个非 NA 数,因此选择了 40%*8=3,2~ 3 个元素。这为结果矩阵创建了 NA。

再一次,我尝试使用 for 循环(这段代码是为了找到最大的 40%):

   largest <- matrix(rep(NA, 20), nrow = 5)
 for(i in 1:5){
   largest[i,]<-order(data[i,], decreasing=T)   
 [1:as.integer(0.4*nrow(data[complete.cases(data[,i]),]))]
 }

但是 R returns 一个错误:"number of items to replace is not a multiple of replacement length",我认为这意味着由于在循环时并非原始最大矩阵的所有元素都没有被替换,所以这个 for 循环不能使用。我说得对吗?

这种采摘怎么做到的?

以下重现了您的预期输出

# Determine number of columns for output matrix as
# maximum of 40% of all non-NA values per row
ncol <- max(floor(apply(mat, 1, function(x) sum(!is.na(x))) * 0.4))

# Top 40% largest
t(apply(mat, 1, function(x) {
    n <- floor(sum(!is.na(x)) * 0.4);
    replace(rep(NA, ncol), 1:n, order(x, decreasing = T)[1:n])
}))
#     [,1] [,2] [,3] [,4]
#[1,]    5    3    4   NA
#[2,]    8   10    7   NA
#[3,]    7    9   NA   NA
#[4,]    8    5    7    4
#[5,]    3    4    1   NA


# Top 40% smallest
t(apply(mat, 1, function(x) {
    n <- floor(sum(!is.na(x)) * 0.4);
    replace(rep(NA, ncol), 1:n, order(x, decreasing = F)[1:n])
}))
#     [,1] [,2] [,3] [,4]
#[1,]    1    8   10   NA
#[2,]    9    6    3   NA
#[3,]    6   10   NA   NA
#[4,]    9   10    2    1
#[5,]    8    2    9   NA

解释:我们首先确定两个输出矩阵的最大列数;然后我们逐行遍历 mat,确定对应于所有非 NA 数字的 40% 的非 NA 条目的特定行数 n该行和 return 前 40% decreasing/increasing 条目中的一列 vectorNA 填充。最终转置给出了预期的输出。

以函数的形式发布我的(不太准确但非常相似的)答案,这可能很方便:

toppct <- function(x, p, largest = TRUE){
  t(apply(x, 1, function(y){
    c(which(y %in% sort(y, decreasing = largest)[1:floor(length(which(!is.na(y)))*p)]), 
      rep(NA, floor(length(y)*p) - floor(length(which(!is.na(y)))*p)))
  }))
}

这会产生问题的输出,只是没有对最高百分比位置进行排序。对于 smallest,只需设置 largest = FALSE.

> toppct(mat, .4)
     [,1] [,2] [,3] [,4]
[1,]    3    4    5   NA
[2,]    7    8   10   NA
[3,]    7    9   NA   NA
[4,]    4    5    7    8
[5,]    1    3    4   NA

> toppct(mat, .4, largest = FALSE)
     [,1] [,2] [,3] [,4]
[1,]    1    8   10   NA
[2,]    3    6    9   NA
[3,]    6   10   NA   NA
[4,]    1    2    9   10
[5,]    2    8    9   NA

我想强调的是,我认为 Maurits 的回答是可以接受的,因为他得到的输出完全符合预期。