Ben Bolker
Ben Bolker

Reputation: 226522

convert from sparse Matrix to dense matrix without losing dimnames?

Converting a sparse Matrix object (from the Matrix package) to a plain old base-R dense matrix object appears to lose the row/column names.

m <- matrix(0, 3,3, dimnames = list(LETTERS[1:3], LETTERS[1:3]))
dimnames(m)
## [[1]]
## [1] "A" "B" "C"
## 
## [[2]]
## [1] "A" "B" "C"

Converting to a Matrix is fine:

dimnames(M <- Matrix::Matrix(m))
## [[1]]
## [1] "A" "B" "C"
## [[2]]
## [1] "A" "B" "C"

But converting back appears to lose the row/column names:

dimnames(as.matrix(M))
## NULL
dimnames(as(M, "matrix"))
## NULL

I know I can work around it by storing the dimnames and then attaching them to the new object (see below), but I don't feel like I should have to ... am I missing either a transparent/better way of making the conversion, or a logical reason why dimnames should not be preserved when making this conversion??

## workaround/hack
dn <- dimnames(m)
m2 <- as.matrix(M)
dimnames(m2) <- dn

To clarify, I want to handle the case where M already exists has been defined but m hasn't (i.e. the old m[] <- trick to replace the contents of m while leaving its attributes unchanged doesn't seem to work ...)

Upvotes: 4

Views: 743

Answers (2)

Mikael Jagan
Mikael Jagan

Reputation: 11326

[Update 1: bug report] [Update 2: patch]

The loss of [dD]imnames here appears to be a bug in Matrix, at least in the implementation of the specific subclass of "Matrix" in question, namely "ddiMatrix" (see ?`ddiMatrix-class`).

library("Matrix")
M <- Matrix(0, 3L, 3L, dimnames = list(LETTERS[1:3], LETTERS[1:3]))
M
3 x 3 diagonal matrix of class "ddiMatrix"
  A B C
A 0 . .
B . 0 .
C . . 0

There is a method for coercing from "ddiMatrix" to "matrix", called when you do as(M, "matrix"), but it does not preserve [dD]imnames, as you observed.

selectMethod("coerce", signature(from = "ddiMatrix", to = "matrix"))
Method Definition:

function (from, to = "matrix", strict = TRUE) 
base::diag(if (from@diag == "U") as1(from@x) else from@x, nrow = from@Dim[1])
<bytecode: 0x11e7dc068>
<environment: namespace:Matrix>

Signatures:
        from        to      
target  "ddiMatrix" "matrix"
defined "ddiMatrix" "matrix"

You can work around the bug with an intermediate coercion from "ddiMatrix" to "dgCMatrix", which is a more general and more carefully implemented class of sparse numeric matrices (see ?`dgCMatrix-class`). The coercions from "ddiMatrix" to "dgCMatrix" to "matrix" do preserve [dD]imnames:

MM <- as(M, "dgCMatrix")
MM
3 x 3 sparse Matrix of class "dgCMatrix"
  A B C
A . . .
B . . .
C . . .
m <- as(MM, "matrix")
m
  A B C
A 0 0 0
B 0 0 0
C 0 0 0

or simply m <- as(as(M, "dgCMatrix"), "matrix").

To be clear, your approach is both faster and more transparent. I am presenting this one mainly to expose the fact that the bug affects "ddiMatrix" but not all classes of sparse numeric matrices implemented in Matrix. If your use case doesn't actually require "ddiMatrix", then you don't necessarily need to worry about preserving [dD]imnames yourself.

FWIW, when struggling with Matrix coercions, I have found it helpful to:

  1. Use showClass("Matrix") to remind myself of the inheritance structure of the "Matrix" subclasses.
  2. Consult ?`<classname>-class` for human-readable descriptions of the classes reported in (1) and their slots.
  3. Use selectMethod("coerce", signature(from=, to=)) to find the method actually called by as (though, as coercions are often implemented in C, the selectMethod result can be unhelpful apart from telling me what to search in Matrix/src).

Upvotes: 2

jared_mamrot
jared_mamrot

Reputation: 26690

It looks like the difference between "m" and "M" is the object type:

m <- matrix(0, 3,3, dimnames = list(LETTERS[1:3], LETTERS[1:3]))
M <- Matrix::Matrix(m)

typeof(m)
#> [1] "double"

typeof(M)
#> [1] "S4"

I don't know if there's an 'easy' way, but another alternative might be:

m <- matrix(0, 3,3, dimnames = list(LETTERS[1:3], LETTERS[1:3]))
dimnames(m)
#> [[1]]
#> [1] "A" "B" "C"
#> 
#> [[2]]
#> [1] "A" "B" "C"
M <- Matrix::Matrix(m)
m <- as.matrix(M)
dimnames(m) <- M@Dimnames
m
#>   A B C
#> A 0 0 0
#> B 0 0 0
#> C 0 0 0

Or maybe:

m <- matrix(M, nrow = M@Dim, dimnames = M@Dimnames)
m
#>   A B C
#> A 0 0 0
#> B 0 0 0
#> C 0 0 0

Upvotes: 1

Related Questions