Search code examples
c++rmatrixrcppsyntactic-sugar

How to scale a NumericMatrix in-place with Rcpp?


This is what I'm doing now

library(Rcpp)

A <- diag(c(1.0, 2.0, 3.0))
rownames(A) <- c('X', 'Y', 'Z')
colnames(A) <- c('A', 'B', 'C')

cppFunction('
void scaleMatrix(NumericMatrix& A, double x) {
    A = A * x;
}')

Unfortunately It doesn't work :(

> A
  A B C
X 1 0 0
Y 0 2 0
Z 0 0 3
> scaleMatrix(A, 2)
> A
  A B C
X 1 0 0
Y 0 2 0
Z 0 0 3

I learned from Rcpp FAQ, Question 5.1 that Rcpp should be able to change the object I passed by value. Stealing an example from Dirk's answer to my previous question:

> library(Rcpp)
> cppFunction("void inplaceMod(NumericVector x) { x = x * 2; }")
> x <- as.numeric(1:5)
> inplaceMod(x)
> x
[1]  2  4  6  8 10

I'm confused: it is possible to modify a NumericVector in-place, but not a NumericMatrix?


Solution

  • You can preserve the row and column names by using NumericVector instead of NumericMatrix, keeping in mind that a matrix in R is just a vector with attached dimensions. You can do this switch either when going from R to C++ (scaleVector below) or within C++ (scaleMatrix below taken from a now deleted answer by @Roland):

    library(Rcpp)
    cppFunction('
    NumericVector scaleVector(NumericVector& A, double x) {
        A = A * x;
        return A;
    }')
    
    cppFunction('
    NumericMatrix scaleMatrix(NumericMatrix& A, double x) {
        NumericVector B = A;
        B = B * x;
        return A;
    }')
    

    If one applies these two function to your matrix, the row and column names are preserved. However, the matrix is not changed in place:

    A <- diag(1:3)
    rownames(A) <- c('X', 'Y', 'Z')
    colnames(A) <- c('A', 'B', 'C')
    
    scaleMatrix(A, 2)
    #>   A B C
    #> X 2 0 0
    #> Y 0 4 0
    #> Z 0 0 6
    scaleVector(A, 2)
    #>   A B C
    #> X 2 0 0
    #> Y 0 4 0
    #> Z 0 0 6
    A
    #>   A B C
    #> X 1 0 0
    #> Y 0 2 0
    #> Z 0 0 3
    

    The reason for that is that diag(1:3) is actually an integer matrix, so a copy is made when you transfer it to a numeric matrix (or vector):

    is.integer(A)
    #> [1] TRUE
    

    If one uses a numeric matrix to begin with, modification is done in place:

    A <- diag(c(1.0, 2.0, 3.0))
    rownames(A) <- c('X', 'Y', 'Z')
    colnames(A) <- c('A', 'B', 'C')
    
    scaleMatrix(A, 2)
    #>   A B C
    #> X 2 0 0
    #> Y 0 4 0
    #> Z 0 0 6
    scaleVector(A, 2)
    #>   A B  C
    #> X 4 0  0
    #> Y 0 8  0
    #> Z 0 0 12
    A
    #>   A B  C
    #> X 4 0  0
    #> Y 0 8  0
    #> Z 0 0 12