转换为矩阵但在 R 中将一个对角线保留为 NULL

Convert to matrix but keep one diagonal to NULL in R

我有一个庞大的数据集,看起来像这样。 为了节省一些内存,我想计算成对距离但保留 矩阵的上对角线为 NULL。

library(tidyverse)
library(stringdist)
#> 
#> Attaching package: 'stringdist'
#> The following object is masked from 'package:tidyr':
#> 
#>     extract

df3 <- tibble(fruits=c("apple","banana","ananas","apple","ananas","apple","ananas"),
              position=c("135","135","135","136","137","138","138"), 
              counts = c(100,200,100,30,40,50,100))

stringdistmatrix(df3$fruits, method=c("osa"), nthread = 4) %>% 
  as.matrix()
#>   1 2 3 4 5 6 7
#> 1 0 5 5 0 5 0 5
#> 2 5 0 2 5 2 5 2
#> 3 5 2 0 5 0 5 0
#> 4 0 5 5 0 5 0 5
#> 5 5 2 0 5 0 5 0
#> 6 0 5 5 0 5 0 5
#> 7 5 2 0 5 0 5 0

reprex package (v2.0.1)

创建于 2022-03-01

然而,当我将 stringdistmatrix 转换为矩阵时(这一步对我来说很重要), 我的上对角线填满了数字。

有没有转换为矩阵但保持上对角线为NULL并节省内存的方法?

我希望我的数据看起来像这样

  1 2 3 4 5 6
2 5          
3 5 2        
4 0 5 5      
5 5 2 0 5    
6 0 5 5 0 5  
7 5 2 0 5 0 5

我认为您可能需要使用稀疏矩阵。包 Matrix 有这种可能性。您可以在以下位置了解有关稀疏矩阵的更多信息:Sparse matrix

library(Matrix)

m <- sparseMatrix(i = c(1:3, 2:3, 3), j=c(1:3,1:2, 1), x = 1, triangular = T)

m

#> 3 x 3 sparse Matrix of class "dtCMatrix"
#>           
#> [1,] 1 . .
#> [2,] 1 1 .
#> [3,] 1 1 1

要检查矩阵的大小,可以使用函数 object.size

对于小矩阵,使用稀疏矩阵似乎没有什么区别,但是,对于大矩阵,节省内存是相当可观的:

library(Matrix)

n <- 30
m1 <- matrix(1,n,n)
m2 <- Matrix(m1, sparse = TRUE) 

object.size(m1)
#> 7416 bytes

object.size(m2)
#> 7432 bytes

n <- 300
m1 <- matrix(1,n,n)
m2 <- Matrix(m1, sparse = TRUE) 

object.size(m1)
#> 720216 bytes

object.size(m2)
#> 544728 bytes

如果您担心内存问题,那么 Matrix 可能不是答案,原因有二:

  • 距离矩阵稀疏。在 n×n 距离矩阵中有 n*(n-1)/2 个非冗余元素,并且它们都可以是非零的。渐近地,那是一半的元素!将这些数据存储在 sparseMatrix 对象中是低效的,因为除了非零元素之外,您还需要存储它们在矩阵中的位置。长度为 n*(n-1)/2 的两个整数向量 ij 将在内存中占用至少 4*n*(n-1) 字节(当 n = 5e+04 时约为 10 GB)。

  • Matrix 实现 class dspMatrix 以有效存储密集对称矩阵,包括距离矩阵。但是 dist 对象存储对角线下方的 n*(n-1)/2 元素,而 dspMatrix 对象存储那些元素 对角线元素。因此,如果不为新的 n*(n+1)/2-length double 分配 4*n*(n+1) 字节(同样,当 n = 5e+04 时约 10 GB),您不能从 dist 强制转换为 dspMatrix矢量.

最有效的方法是保留 dist 对象并根据您正在进行的任何计算的需要直接对其进行索引。 您可以利用以下事实:n-by-n 距离矩阵的下三角中的元素 [i, j] 存储在相应 [=25] 的元素 [k] 中=] 对象,其中 k = i + (2 * (n - 1) - j) * (j - 1) / 2.

例如,获取由dist对象指定的距离矩阵的列(或行)j x 无需构造整个矩阵,你可以使用这个函数:

getDistCol <- function(x, j) {
    p <- length(x)
    n <- as.integer(round(0.5 * (1 + sqrt(1 + 8 * p)))) # p = n * (n - 1) / 2
    if (j == 1L) {
        return(c(0, x[seq_len(n - 1L)]))
    }
    ii <- rep.int(j - 1L, j - 1L)
    jj <- 1L:(j - 1L)
    if (j < n) {
        ii <- c(ii, j:(n - 1L))
        jj <- c(jj, rep.int(j, n - j))
    }
    kk <- ii + ((2L * (n - 1L) - jj) * (jj - 1L)) %/% 2L
    res <- double(n)
    res[-j] <- x[kk]
    res
}
fruits <- c("apple", "banana", "ananas", "apple", "ananas", "apple", "ananas")
x <- stringdist::stringdistmatrix(fruits)
##   1 2 3 4 5 6
## 2 5          
## 3 5 2        
## 4 0 5 5      
## 5 5 2 0 5    
## 6 0 5 5 0 5  
## 7 5 2 0 5 0 5

getDistCol(x, 1L)
## [1] 0 5 5 0 5 0 5

lapply(1:7, getDistCol, x = x)
## [[1]]
## [1] 0 5 5 0 5 0 5
## 
## [[2]]
## [1] 5 0 2 5 2 5 2
## 
## [[3]]
## [1] 5 2 0 5 0 5 0
## 
## [[4]]
## [1] 0 5 5 0 5 0 5
## 
## [[5]]
## [1] 5 2 0 5 0 5 0
## 
## [[6]]
## [1] 0 5 5 0 5 0 5
## 
## [[7]]
## [1] 5 2 0 5 0 5 0

如果你坚持要一个dspMatrix对象,那么你可以用这个方法强制从dist:

library("Matrix")
asDspMatrix <- function(x) {
    n <- attr(x, "Size")
    i <- 1L + c(0L, cumsum(n:2L))
    xx <- double(length(x) + n)
    xx[-i] <- x
    new("dspMatrix", uplo = "L", x = xx, Dim = c(n, n))
}
asDspMatrix(x)
## 7 x 7 Matrix of class "dspMatrix"
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7]
## [1,]    0    5    5    0    5    0    5
## [2,]    5    0    2    5    2    5    2
## [3,]    5    2    0    5    0    5    0
## [4,]    0    5    5    0    5    0    5
## [5,]    5    2    0    5    0    5    0
## [6,]    0    5    5    0    5    0    5
## [7,]    5    2    0    5    0    5    0