R:在0和1的矩阵中找到包含最多1的列的集合

wh6knrhe  于 2023-11-14  发布在  其他
关注(0)|答案(3)|浏览(123)

我有一个由1和0组成的矩阵,行是个人,列是事件,1表示事件发生在个人身上,0表示没有。
我想找出哪一组(在示例中)5列/事件覆盖了最多的行/个人。

测试数据

#Make test data
set.seed(123)
d <- sapply(1:300, function(x) sample(c(0,1), 30, T, c(0.9,0.1)))
colnames(d) <- 1:300
rownames(d) <- 1:30

字符串

我的努力

我最初的尝试只是基于将5列的集合与最高的colMeans相结合:

#Get top 5 columns with highest row coverage
col_set <- head(sort(colMeans(d), decreasing = T), 5)

#Have a look the set
col_set

>
      197       199        59        80        76 
0.2666667 0.2666667 0.2333333 0.2333333 0.2000000
#Check row coverage of the column set
sum(apply(d[,colnames(d) %in% names(col_set)], 1, sum) > 0) / 30 #top 5

>
[1] 0.7

然而,这个集合并没有覆盖大多数行。我通过伪随机抽样10.000个不同的5列集合来测试,然后找到覆盖率最高的集合:

#Get 5 random columns using colMeans as prob in sample
##Random sample 10.000 times
set.seed(123)
result <- lapply(1:10000, function(x){
  col_set2 <- sample(colMeans(d), 5, F, colMeans(d))
  cover <- sum(apply(d[,colnames(d) %in% names(col_set2)], 1, sum) > 0) / 30 #random 5
  list(set = col_set2, cover = cover)
})

##Have a look at the best set
result[which.max(sapply(result, function(x) x[["cover"]]))]

>
[[1]]
[[1]]$set
        59        169        262         68        197 
0.23333333 0.10000000 0.06666667 0.16666667 0.26666667 

[[1]]$cover
[1] 0.7666667


提供colMeanssample的原因是具有最高覆盖率的列是我最感兴趣的列。
因此,使用伪随机抽样,我可以收集一组覆盖率比只使用前5列更高的列。然而,由于我的实际数据集比示例更大,我正在寻找一种更有效和合理的方法来找到具有最高覆盖率的列集。

编辑

对于感兴趣的,我决定microbenchmark提供的3个解决方案:

#Defining G. Grothendieck's coverage funciton outside his solutions
coverage <- function(ix) sum(rowSums(d[, ix]) > 0) / 30

#G. Grothendieck top solution
solution1 <- function(d){
  cols <- tail(as.numeric(names(sort(colSums(d)))), 20)
  co <- combn(cols, 5)
  itop <- which.max(apply(co, 2, coverage))
  co[, itop]
}

#G. Grothendieck "Older solution"
solution2 <- function(d){
  require(lpSolve)
  ones <- rep(1, 300)
  res <- lp("max", colSums(d), t(ones), "<=", 5, all.bin = TRUE, num.bin.solns = 10)
  m <- matrix(res$solution[1:3000] == 1, 300)
  cols <- which(rowSums(m) > 0)
  co <- combn(cols, 5)
  itop <- which.max(apply(co, 2, coverage))
  co[, itop]
}

#user2554330 solution
bestCols <- function(d, n = 5) {
  result <- numeric(n)
  for (i in seq_len(n)) {
    result[i] <- which.max(colMeans(d))
    d <- d[d[,result[i]] != 1,, drop = FALSE]
  }
  result
}

#Benchmarking...
microbenchmark::microbenchmark(solution1 = solution1(d),
                               solution2 = solution2(d),
                               solution3 = bestCols(d), times = 10)

>
Unit: microseconds
      expr        min         lq       mean      median         uq       max neval
 solution1 390811.850 497155.887 549314.385 578686.3475 607291.286 651093.16    10
 solution2  55252.890  71492.781  84613.301  84811.7210  93916.544 117451.35    10
 solution3    425.922    517.843   3087.758    589.3145    641.551  25742.11    10

ymdaylpp

ymdaylpp1#

这看起来像是一个相对困难的优化问题,因为列之间的交互方式。一个近似的策略是选择具有最高均值的列;然后删除该列中具有1的行,然后重复。这样你不一定会找到最佳解决方案,但你应该会得到一个相当不错的解决方案。
例如,

set.seed(123)
d <- sapply(1:300, function(x) sample(c(0,1), 30, T, c(0.9,0.1)))
colnames(d) <- 1:300
rownames(d) <- 1:30
bestCols <- function(d, n = 5) {
  result <- numeric(n)
  for (i in seq_len(n)) {
    result[i] <- which.max(colMeans(d))
    d <- d[d[,result[i]] != 1,, drop = FALSE]
  }
  cat("final dim is ", dim(d))
  result
}
col_set <- bestCols(d)
sum(apply(d[,colnames(d) %in% col_set], 1, sum) > 0) / 30 #top 5

字符串
覆盖率达到90%。

anhgbhbe

anhgbhbe2#

下面提供了一个寻找近似解的启发式方法。找到N=20列,比如说,最多的列cols,然后使用蛮力找到这20列中的每一个5列的子集。具有最高覆盖率的子集如下所示,其覆盖率为93.3%。

coverage <- function(ix) sum(rowSums(d[, ix]) > 0) / 30

N <- 20
cols <- tail(as.numeric(names(sort(colSums(d)))), N)

co <- combn(cols, 5)
itop <- which.max(apply(co, 2, coverage))
co[, itop]
## [1]  90 123 197 199 286

coverage(co[, itop])
## [1] 0.9333333

字符串
对N=5、10、15和20重复此操作,我们得到覆盖率为83.3%、86.7%、90%和93.3%。N越高,覆盖率越好,但N越低,运行时间越短。

老方案

我们可以用一个背包问题来近似这个问题,这个背包问题使用整数线性规划来选择5个具有最大数量的列。
我们得到这个近似问题的10个最佳解,得到10个解中至少有一个解的所有列。有14个这样的列,然后我们使用蛮力找到14列中5列的哪个子集具有最高的覆盖率。

library(lpSolve)

ones <- rep(1, 300)
res <- lp("max", colSums(d), t(ones), "<=", 5, all.bin = TRUE, num.bin.solns = 10)

coverage <- function(ix) sum(rowSums(d[, ix]) > 0) / 30

# each column of m is logical 300-vector defining possible soln
m <- matrix(res$solution[1:3000] == 1, 300)

# cols is the set of columns which are in any of the 10 solutions
cols <- which(rowSums(m) > 0)
length(cols)
## [1] 14

# use brute force to find the 5 best columns among cols
co <- combn(cols, 5)
itop <- which.max(apply(co, 2, coverage))
co[, itop]
## [1]  90 123 197 199 286
coverage(co[, itop])
## [1] 0.9333333

qkf9rpyu

qkf9rpyu3#

您可以尝试测试是否有更好的列,并将其与当前选择中的列交换。

n <- 5 #Number of columns / events
i <- rep(1, n)
for(k in 1:10) { #How many times to iterate
  tt <- i
  for(j in seq_along(i)) {
    x <- +(rowSums(d[,i[-j]]) > 0)
    i[j] <- which.max(colSums(x == 0 & d == 1))
  }
  if(identical(tt, i)) break
}
sort(i)
#[1]  90 123 197 199 286
mean(rowSums(d[,i]) > 0)
#[1] 0.9333333

字符串
考虑到,初始条件影响的结果,你可以采取随机开始.

n <- 5 #Number of columns / events
x <- apply(d, 2, function(x) colSums(x == 0 & d == 1))
diag(x) <- -1
idx <- which(!apply(x==0, 1, any))
x <- apply(d, 2, function(x) colSums(x != d))
diag(x) <- -1
x[upper.tri(x)] <- -1
idx <- unname(c(idx, which(apply(x==0, 1, any))))
res <- sample(idx, n)
for(l in 1:100) {
  i <- sample(idx, n)
  for(k in 1:10) { #How many times to iterate
    tt <- i
    for(j in seq_along(i)) {
      x <- +(rowSums(d[,i[-j]]) > 0)
      i[j] <- which.max(colSums(x == 0 & d == 1))
    }
    if(identical(tt, i)) break
  }
  if(sum(rowSums(d[,i]) > 0) > sum(rowSums(d[,res]) > 0)) res  <- i
}
sort(res)
#[1]  90 123 197 199 286
mean(rowSums(d[,res]) > 0)
#[1] 0.9333333

相关问题