大きめのSparse行列をnormalizeしたい時の書き方

掲題の件、あると思います。

私は今まで行列っぽいデータを normalize するとき*1は以下のように書いていました。

最後の結果を見たらわかるように行ごとに和をとると1になるようになっています。

> x <- matrix(1:4,2)
> x
     [,1] [,2]
[1,]    1    3
[2,]    2    4
> x/rowSums(x)
          [,1]      [,2]
[1,] 0.2500000 0.7500000
[2,] 0.3333333 0.6666667
> x <- matrix(1:4,2)
> x
     [,1] [,2]
[1,]    1    3
[2,]    2    4
> x/rowSums(x)
          [,1]      [,2]
[1,] 0.2500000 0.7500000
[2,] 0.3333333 0.6666667

適当に作ったSparse行列でもこれで動くと言えば動いている。

> library("Matrix")
> x <- as(matrix(c(1,2,0,4,0,0,7,8,9),3),"sparseMatrix")
> x
3 x 3 sparse Matrix of class "dgCMatrix"
          
[1,] 1 4 7
[2,] 2 . 8
[3,] . . 9
> x/Matrix::rowSums(x)
3 x 3 sparse Matrix of class "dgCMatrix"
                                   
[1,] 0.08333333 0.3333333 0.5833333
[2,] 0.20000000 .         0.8000000
[3,] .          .         1.0000000

でも、行列のサイズがでかくなるとアウト、なんか一回Denseな行列にしてるんだろうと思われる。

ここの例は恣意的 データに見えるがもともと俺が使ってハマったデータがこんなんだったのでこうなっている。

> size <- 10^5
> i <- c(sample(1:size, 3*10^7, replace=TRUE), size)
> j <- c(sample(1:size, 3*10^7, replace=TRUE), size)
> x <- sparseMatrix(i,j,x=rnorm(3*10^7 + 1), dims=rep(size + 1, 2))
> x <- as(x + t(x), "symmetricMatrix")
> str(x)
Formal class 'dsCMatrix' [package "Matrix"] with 7 slots
  ..@ i       : int [1:29910604] 2 23 14 6 13 28 18 24 0 19 ...
  ..@ p       : int [1:100002] 0 0 0 0 0 0 0 0 0 0 ...
  ..@ Dim     : int [1:2] 100001 100001
  ..@ Dimnames:List of 2
  .. ..$ : NULL
  .. ..$ : NULL
  ..@ x       : num [1:29910604] -1.511 -0.878 0.777 1.258 1.185 ...
  ..@ uplo    : chr "U"
  ..@ factors : list()
> x/Matrix::rowSums(x)
 エラー:  ベクトルのメモリを使い切りました (上限に達した?) 

こういうときは等価な計算のこれを使おう、これならいける

> Matrix::Diagonal(x=1 / Matrix::rowSums(x)) %*% x
100001 x 100001 sparse Matrix of class "dgCMatrix"
                                                                                                                              
 [1,] . . . . . . . . . . . . . . . . . . . . . .  .          . . . . . . . . . . . . . . . . .  .        . . . . . . . ......
 [2,] . . . . . . . . . . . . . . . . . . . . . .  .          . . . . . . . . . . . . . . . . .  .        . . . . . . . ......
 [3,] . . . . . . . . . . . . . . . . . . . . . . -0.08169018 . . . . . . . . . . . . . . . . .  .        . . . . . . . ......

ちゃんと計算も合う(確認)

> x <- as(matrix(c(1,2,0,4,0,0,7,8,9),3),"sparseMatrix")
> x/Matrix::rowSums(x)
3 x 3 sparse Matrix of class "dgCMatrix"
                                   
[1,] 0.08333333 0.3333333 0.5833333
[2,] 0.20000000 .         0.8000000
[3,] .          .         1.0000000
> Matrix::Diagonal(x=1 / Matrix::rowSums(x)) %*% x
3 x 3 sparse Matrix of class "dgCMatrix"
                                   
[1,] 0.08333333 0.3333333 0.5833333
[2,] 0.20000000 .         0.8000000
[3,] .          .         1.0000000

元ネタ

*1:各行ごとに足したら1になる、でnormalizeを定義。他L2ノルムなどによるnormalizeも同様