【问题标题】:Counting the number of ordered pairs in matrix in R计算R中矩阵中有序对的数量
【发布时间】:2016-11-09 13:17:30
【问题描述】:

给定矩阵m 如下(1-5 的逐行排列):

    # [,1] [,2] [,3] [,4] [,5]
 # [1,]    1    5    2    4    3
 # [2,]    2    1    4    3    5
 # [3,]    3    4    1    2    5
 # [4,]    4    1    3    2    5
 # [5,]    4    3    1    2    5
 # [6,]    1    4    2    3    5
 # [7,]    4    3    2    5    1
 # [8,]    4    1    3    5    2
 # [9,]    1    2    3    4    5
# [10,]    4    3    2    1    5

我想知道每个元素 1-5 出现在每行另一个元素之前的次数(即考虑所有可能的对)

例如,对于 (1, 5) 对,15 之前,在所有行中出现 9 次。另一个示例,对于 (3, 1) 对,31 之前,在所有行中出现 4 次。我希望所有行中所有可能的对都有相同的结果。也就是说,

# (1, 2), (1, 3), (1, 4), (1, 5)
# (2, 1), (2, 3), (2, 4), (2, 5)
# (3, 1), (3, 2), (3, 4), (3, 5)
# (4, 1), (4, 2), (4, 3), (4, 5)
# (5, 1), (5, 2), (5, 3), (5, 4)

m <- structure(c(1L, 2L, 3L, 4L, 4L, 1L, 4L, 4L, 1L, 4L, 5L, 1L, 4L, 
1L, 3L, 4L, 3L, 1L, 2L, 3L, 2L, 4L, 1L, 3L, 1L, 2L, 2L, 3L, 3L, 
2L, 4L, 3L, 2L, 2L, 2L, 3L, 5L, 5L, 4L, 1L, 3L, 5L, 5L, 5L, 5L, 
5L, 1L, 2L, 5L, 5L), .Dim = c(10L, 5L))

如何在 R 中有效地做到这一点?

编辑

你会如何对这个矩阵做同样的事情?

      # [,1] [,2] [,3] [,4] [,5]
 # [1,]    3    4    1    5    0
 # [2,]    1    2    5    3    0
 # [3,]    3    5    0    0    0
 # [4,]    4    5    0    0    0
 # [5,]    3    4    1    5    2
 # [6,]    3    1    2    0    0
 # [7,]    4    1    5    2    0
 # [8,]    4    3    5    2    0
 # [9,]    5    2    0    0    0
# [10,]    5    4    2    0    0

m <- structure(c(3, 1, 3, 4, 3, 3, 4, 4, 5, 5, 4, 2, 5, 5, 4, 1, 1, 
3, 2, 4, 1, 5, 0, 0, 1, 2, 5, 5, 0, 2, 5, 3, 0, 0, 5, 0, 2, 2, 
0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0), .Dim = c(10L, 5L))

【问题讨论】:

  • m 真的包含排列吗?即,在每一行中,每个数字只出现一次(除了 0)?你的实际m 有多大?
  • @alexis_laz 是的,每个数字每行只出现一次。 m 的大小可以为 1000-10000 左右,列数为 2-10 左右。
  • 另外,0 是否总是聚集在每行的末尾?

标签: r matrix permutation


【解决方案1】:

首先,我们如何使用硬编码的数字来做到这一点:

apply(m, 1, function(r) { which(r == 1) < which(r == 5) })
#  [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
sum(apply(m, 1, function(r) { which(r == 1) < which(r == 5) }))
# [1] 9

要对1:5 的所有组合(除了相同)自动执行此操作,这是一个包含所有对的 data.frame:

df <- expand.grid(a = 1:5, b = 1:5)
df <- df[ df$a != df$b, ]
head(df)
#   a b
# 2 2 1
# 3 3 1
# 4 4 1
# 5 5 1
# 6 1 2
# 8 3 2

现在我们只需要迭代它的每一行(我想我们可以使用一个矩阵和另一个apply):

df$seqs <- sapply(seq_len(nrow(df)), function(i) {
  sum(apply(m, 1, function(r) which(r == df$a[i]) < which(r == df$b[i])))
})
head(df)
#   a b seqs
# 2 2 1    3
# 3 3 1    4
# 4 4 1    6
# 5 5 1    1
# 6 1 2    7
# 8 3 2    6

或者,我认为这是使用mapply的好时机:

myfunc <- function(a, b, m) sum(apply(m, 1, function(r) which(r == a) < which(r == b)))
df$seqs <- mapply(myfunc, df$a, df$b, list(m))
head(df)
#   a b seqs
# 2 2 1    3
# 3 3 1    4
# 4 4 1    6
# 5 5 1    1
# 6 1 2    7
# 8 3 2    6

当然,我使用了一个正式的函数而不是一个匿名函数(上面也可以这样做),但这展示了这种方法的一些优雅。

编辑:新约束,现在m 中可能不匹配。以上失败,因为which 在没有匹配项时返回logical(0),导致sapply 返回异构列表。解决它的一种方法是使用快速帮助函数:

apply(m, 1, function(r) which(r == a) < which(r == b))
# [[1]]
# logical(0)
# [[2]]
# [1] FALSE
# ...

emptyF <- function(x) sapply(x, function(y) if (! length(y)) FALSE else y)
emptyF(apply(m, 1, function(r) which(r == a) < which(r == b)))
# [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

现在myfunc 变为:

myfunc <- function(a, b, m) sum(emptyF(apply(m, 1, function(r) which(r == a) < which(r == b))))

(注意:我更喜欢Colonel Beauvel's answer,因为它是矢量化的,因此可能更快。它也将从这个和类似的补救措施中受益。)

【讨论】:

    【解决方案2】:

    这是一个矢量化的解决方案,没有apply

    func <- function(a,b) sum((which(!t(m-b)) - which(!t(m-a)))>0)
    
    #> func(1,5)
    #[1] 9
    #> func(5,1)
    #[1] 1
    

    要生成所有想要的组合,您只需这样做:

    N = combn(1:5, 2)
    cbind(N, N[nrow(N):1,])
    

    然后您只需要一个循环来遍历列并应用该函数。

    【讨论】:

      【解决方案3】:

      试试这个

      library(plyr)
      combns <- expand.grid(unique(as.vector(m)),unique(as.vector(m)))
      combns <- combns[combns$Var1!=combns$Var2,]
      combns <- combns[with(combns,order(Var1)),]
      combns$count <- sapply(1:nrow(combns),function(u) sum(unlist(apply(apply(m,1,function(t) match(t,combns[u,])),2,function(s) na.exclude(count(unlist(sapply(seq(length(s)),function(t) diff(s,lag=t))))$freq[count(unlist(sapply(seq(length(s)),function(t) diff(s,lag=t))))$x==1]))),na.rm = T))
      

      【讨论】:

        【解决方案4】:

        知道(1)每一行没有重复,(2)每一行的0都聚集在最后,(3)nrow(m)ncol(m)大2-3个数量级,我们可以循环在列上搜索特定数字的外观,减少到达 0 时不必要的计算:

        ff = function(x, a, b)
        {
            ia = rep_len(NA_integer_, nrow(x)) # positions of 'a' in each row
            ib = rep_len(NA_integer_, nrow(x)) # -//- of 'b'
            notfound0 = seq_len(nrow(x))  # rows that have not, yet, a 0
            for(j in seq_len(ncol(x))) {
                xj = x[notfound0, j]
                if(!length(xj)) break
        
                ia[notfound0[xj == a]] = j
                ib[notfound0[xj == b]] = j
        
                notfound0 = notfound0[xj != 0L]  # check if any more rows have 0 now on
            }
        
            i = ia < ib ## is 'a' before 'b'?
        
            ## return both a - b and b - a; no need to repeat computations
            data.frame(a = c(a, b), 
                       b = c(b, a), 
                       n = c(sum(i, na.rm = TRUE), sum(!i, na.rm = TRUE)))
        }
        

        在编辑后的m

        ff(m, 3, 2)
        # a b n
        #1 3 2 3
        #2 2 3 1
        ff(m, 5, 1)
        #  a b n
        #1 5 1 0
        #2 1 5 4
        

        对于所有对:

        xtabs(n ~ a + b, 
              do.call(rbind, 
                      combn(5, 2, function(x) ff(m, x[1], x[2]), 
                            simplify = FALSE)))
        #   b
        #a   1 2 3 4 5
        #  1 0 4 1 0 4
        #  2 0 0 1 0 1
        #  3 3 3 0 2 4
        #  4 3 4 1 0 5
        #  5 0 5 1 1 0
        

        而且,在更大的范围内似乎也可以容忍:

        set.seed(007)
        MAT = do.call(rbind, combinat::permn(8))[sample(1e4), ]
        MAT[sample(length(MAT), length(MAT)*0.4)] = 0L #40% 0s
        MAT = t(apply(MAT, 1, function(x) c(x[x != 0L], rep_len(0L, sum(x == 0L)))))
        dim(MAT)
        #[1] 10000     8
        
        ## including colonel's answer for a quick comparison
        colonel = function(x, a, b)
        {
            i = (which(!t(x - b)) - which(!t(x - a))) > 0L
            data.frame(a = c(a, b), b = c(b, a), n = c(sum(i), sum(!i)))
        } 
        
        microbenchmark::microbenchmark(ff(MAT, 7, 2), colonel(MAT, 7, 2))
        #Unit: milliseconds
        #               expr      min       lq     mean   median       uq       max neval cld
        #      ff(MAT, 7, 2) 3.795003 3.908802 4.500453 3.972138 4.096377 45.926679   100   b
        # colonel(MAT, 7, 2) 2.156941 2.231587 2.423053 2.295794 2.404894  3.775516   100  a 
        #There were 50 or more warnings (use warnings() to see the first 50)
        

        因此,只需将该方法简单地转换为循环就证明是足够有效的。更多的 0 也会进一步减少计算时间。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-01-15
          • 1970-01-01
          • 1970-01-01
          • 2020-02-24
          • 2018-09-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多