如何从一个巨大的矩阵中获得尽可能少的行NA,并获得最大的可能的列序列?

时间:2018-12-04 13:17:50

标签: r cluster-analysis powerset traminer sequence-analysis

我想从数据框中选择列,以使所得的连续列序列尽可能长,而具有NA的行数则尽可能少,因为它们必须然后放下。

(我想这样做的原因是,我想运行TraMineR::seqsubm()以自动获得过渡成本矩阵(按过渡概率),然后在其上运行cluster::agnes()。{{1 }}不喜欢TraMineR::seqsubm()状态,矩阵中带有NA状态的cluster::agnes()不一定有意义。)

为此,我已经编写了一个有效的function,可以根据原理计算所有可能的功率子集,并检查它们中的NA。此玩具数据NA代表10x5矩阵,效果很好:

d

现在的问题是,我实际上想将该算法应用于表示 34235 x 17矩阵的数据!

我的代码已通过“代码审查”进行了审查,但仍然无法应用于真实数据。

我知道,使用这种方法将需要大量的计算。 (对于非超级计算机来说可能太大了吗?!)

有人知道更合适的方法吗?

我向您展示了代码审查中已经enhanced function by @minem的内容:

> d
   id X1 X2 X3 X4 X5
1   A  1 11 21 31 41
2   B  2 12 22 32 42
3   C  3 13 23 33 NA
4   D  4 14 24 34 NA
5   E  5 15 25 NA NA
6   F  6 16 26 NA NA
7   G  7 17 NA NA NA
8   H  8 18 NA NA NA
9   I  9 NA NA NA NA
10  J 10 NA NA NA NA
11  K NA NA NA NA NA

产量:

seqRank2 <- function(d, id = "id") {
  require(matrixStats)

  # change structure, convert to matrix
  ii <- as.character(d[, id])
  dm <- d
  dm[[id]] <- NULL
  dm <- as.matrix(dm)
  rownames(dm) <- ii

  your.powerset = function(s){
    l = vector(mode = "list", length = 2^length(s))
    l[[1]] = numeric()
    counter = 1L
    for (x in 1L:length(s)) {
      for (subset in 1L:counter) {
        counter = counter + 1L
        l[[counter]] = c(l[[subset]], s[x])
      }
    }
    return(l[-1])
  }

  psr <- your.powerset(ii)
  psc <- your.powerset(colnames(dm))

  sss <- lapply(psr, function(x) {
    i <- ii %in% x
    lapply(psc, function(y) dm[i, y, drop =  F])
    })

  cn <- sapply(sss, function(x)
    lapply(x, function(y) {

      if (ncol(y) == 1) {
        if (any(is.na(y))) return(NULL)
          return(y)
        }

      isna2 <- matrixStats::colAnyNAs(y)
      if (all(isna2)) return(NULL)
      if (sum(isna2) == 0) return(NA)
      r <- y[, !isna2, drop = F]
      return(r)
      }))

  scr <- sapply(cn, nrow)
  scc <- sapply(cn, ncol)

  namesCN <- sapply(cn, function(x) paste0(colnames(x), collapse = ", "))
  names(scr) <- namesCN
  scr <- unlist(scr)

  names(scc) <- namesCN
  scc <- unlist(scc)

  m <- t(rbind(n.obs = scr, sq.len = scc))
  ag <- aggregate(m, by = list(sequence = rownames(m)), max)
  ag <- ag[order(-ag$sq.len, -ag$n.obs), ]
  rownames(ag) <- NULL
  return(ag)
}

在这种情况下,我会选择> seqRank2(d) sequence n.obs sq.len 1 X1, X2, X3, X4 4 4 2 X1, X2, X3 6 3 3 X1, X2, X4 4 3 4 X1, X3, X4 4 3 5 X2, X3, X4 4 3 6 X1, X2 8 2 7 X1, X3 6 2 8 X2, X3 6 2 9 X1, X4 4 2 10 X2, X4 4 2 11 X3, X4 4 2 12 X1 10 1 13 X2 8 1 14 X3 6 1 15 X4 4 1 16 X5 2 1 > system.time(x <- seqRank2(d)) user system elapsed 1.93 0.14 2.93 X1, X2, X3, X4X1, X2, X3,因为它们是连续的并产生适当数量的观测值。

预期输出:

因此对于玩具数据X2, X3, X4,预期输出将类似于:

d

最后,函数应该在巨大的矩阵> seqRank2(d) sequence n.obs sq.len 1 X1, X2, X3, X4 4 4 2 X1, X2, X3 6 3 3 X2, X3, X4 4 3 4 X1, X2 8 2 5 X2, X3 6 2 6 X3, X4 4 2 7 X1 10 1 8 X2 8 1 9 X3 6 1 10 X4 4 1 11 X5 2 1 上正确运行,这会导致当前错误:

d.huge

玩具数据> seqRank2(d.huge) Error in vector(mode = "list", length = 2^length(s)) : vector size cannot be infinite

d

玩具数据d <- structure(list(id = structure(1:11, .Label = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"), class = "factor"), X1 = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, NA), X2 = c(11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L, NA, NA, NA), X3 = c(21L, 22L, 23L, 24L, 25L, 26L, NA, NA, NA, NA, NA), X4 = c(31L, 32L, 33L, 34L, NA, NA, NA, NA, NA, NA, NA), X5 = c(41L, 42L, NA, NA, NA, NA, NA, NA, NA, NA, NA)), row.names = c(NA, -11L), class = "data.frame")

d.huge

附录(请参阅评论最新答案):

d.huge <- setNames(data.frame(matrix(1:15.3e5, 3e4, 51)), 
                   c("id", paste0("X", 1:50)))
d.huge[, 41:51] <- lapply(d.huge[, 41:51], function(x){
  x[which(x %in% sample(x, .05*length(x)))] <- NA
  x
})

3 个答案:

答案 0 :(得分:1)

转换为矩阵并计算每列的Na计数:

dm <- is.na(d[, -1])
na_counts <- colSums(dm)
x <- data.frame(na_counts = na_counts, non_na_count = nrow(dm) - na_counts)
x <- as.matrix(x)

# create all combinations for column indexes:
nx <- 1:nrow(x)
psr <- do.call(c, lapply(seq_along(nx), combn, x = nx, simplify = FALSE))
# test if continuous:
good <- sapply(psr, function(y) !any(diff(sort.int(y)) != 1L))
psr <- psr[good == T] # remove non continuous
# for each combo count nas and non NA:
s <- sapply(psr, function(y) colSums(x[y, , drop = F]))

# put all together in table:
res <- data.frame(var_count = lengths(psr), t(s))
res$var_indexes <- sapply(psr, paste, collapse = ',')
res
#    var_count na_counts non_na_count var_indexes
# 1          1         1           10           1
# 2          1         3            8           2
# 3          1         5            6           3
# 4          1         7            4           4
# 5          1         9            2           5
# 6          2         4           18         1,2
# 7          2         8           14         2,3
# 8          2        12           10         3,4
# 9          2        16            6         4,5
# 10         3         9           24       1,2,3
# 11         3        15           18       2,3,4
# 12         3        21           12       3,4,5
# 13         4        16           28     1,2,3,4
# 14         4        24           20     2,3,4,5
# 15         5        25           30   1,2,3,4,5

# choose

当对var索引进行排序时,为了提高速度,我们可以简单地使用:

good <- sapply(psr, function(y) !any(diff(y) != 1L))

答案 1 :(得分:1)

这花费不到一秒钟的大量数据

l1 = combn(2:length(d), 2, function(x) d[x[1]:x[2]], simplify = FALSE)
# If you also need "combinations" of only single columns, then uncomment the next line
# l1 = c(d[-1], l1)
l2 = sapply(l1, function(x) sum(complete.cases(x)))

score = sapply(1:length(l1), function(i) NCOL(l1[[i]]) * l2[i])
best_score = which.max(score)
best = l1[[best_score]]

关于如何对各种组合进行排名的问题尚不清楚。我们可以使用不同的评分公式来生成不同的偏好。例如,要分别权衡行数与列数

col_weight = 2
row_weight = 1
score = sapply(1:length(l1), function(i) col_weight*NCOL(l1[[i]]) +  row_weight * l2[i])

答案 2 :(得分:1)

只需澄清一下,seqsubm中的TraMineR函数对于NA或不同长度的序列都没有问题。但是,该函数需要一个状态序列对象(使用seqdef创建)作为输入。

函数seqsubm用于通过不同方法计算状态之间的替代成本(即,相异性)。您可能会参考从观测到的过渡概率得出成本的方法('TRATE'),即2- p(i | j)- p(j | i) ,其中 p(i | j)是当我们处于状态时处于 t 中处于状态 i 的概率在 t -1中> j 。因此,我们所需要的只是转移概率,可以很容易地从一组长度不同或带有缺口的序列中进行估算。

我在下面使用ex1附带的TraMineR数据进行说明。 (由于您的玩具示例中存在大量不同状态,因此替换成本矩阵对于此插图而言太大(28 x 28)。)

library(TraMineR)
data(ex1)
sum(is.na(ex1))

# [1] 38

sq <- seqdef(ex1[1:13])
sq

#    Sequence                 
# s1 *-*-*-A-A-A-A-A-A-A-A-A-A
# s2 D-D-D-B-B-B-B-B-B-B      
# s3 *-D-D-D-D-D-D-D-D-D-D    
# s4 A-A-*-*-B-B-B-B-D-D      
# s5 A-*-A-A-A-A-*-A-A-A      
# s6 *-*-*-C-C-C-C-C-C-C      
# s7 *-*-*-*-*-*-*-*-*-*-*-*-*

sm <- seqsubm(sq, method='TRATE')
round(sm,digits=3)

#      A-> B->   C-> D->
# A->   0 2.000   2 2.000
# B->   2 0.000   2 1.823
# C->   2 2.000   0 2.000
# D->   2 1.823   2 0.000

现在,我不清楚您要如何处理状态差异。在聚类算法中输入它们后,您将对状态进行聚类。如果要对序列进行聚类,则应首先计算序列之间的差异(使用seqdist并可能将seqsubm返回的替代成本矩阵作为sm参数传递),然后输入聚类算法中得到的距离矩阵。