Applying an R method only to *matrices* of a certain class

Viewed 92

A package I am developing contains an R method, and have a function that I wish to apply to numeric matrices only.

My function will call a C function that expects a numeric matrix as its input. I want to make it easy for authors of other packages to write separate handlers for (say) numeric vectors or character matrices without having to edit the function in my package.

For the sake of a simple example, let's say I want the function to add one to matrices:

AddOne <- function (x) UseMethod('AddOne')
AddOne.[numeric-AND-matrix] <- function (x) add_one_in_c(x)

AddOne(1) # Should report "No applicable method"
AddOne(matrix("one")) # Should report "No applicable method"
AddOne(matrix(1)) # Should send the matrix to add_one_in_c()

Just looking to see whether x is a numeric, or a matrix, is too general:

AddOne <- function (x) UseMethod('AddOne')
AddOne.numeric <- function (x) message("X is numeric, but may not be a matrix")
AddOne.matrix <- function (x) message("X is a matrix, but may not be numeric")

And I've been discouraged from using inherits for this sort of purpose:

AddOne <- function (x) UseMethod('AddOne')
AddOne.matrix <- function (x) {
  if (inherits(x, 'numeric')) {
    add_one_in_c(x)
  } else {
    NextMethod(x)
  }
}

This last solution would also be difficult to extend. Perhaps someone else could handle character matrices by writing AddOne.character(), but this function would also have to handle character vectors.

Is there a way to do this that takes full advantage of the Methods protocols?

2 Answers

You can define a default method that handles everything except matrices. Then within the matrix method, if it is anything other than numeric, you simply call NextMethod, which will invoke the default method. Therefore you only need to write a single error message and a single matrix method.

# Here's a stand in for your C function:
add_one_in_c   <- function(x) x + 1

AddOne         <- function(x) UseMethod('AddOne')
AddOne.default <- function(x) stop("No applicable method")
AddOne.matrix  <- function(x) if (is.numeric(x)) add_one_in_c(x) else NextMethod()

So, testing it we get:

char_matrix <- matrix(LETTERS[1:9], nrow = 3)
num_matrix  <- matrix(1:9, nrow = 3)

AddOne(num_matrix)
#>      [,1] [,2] [,3]
#> [1,]    2    5    8
#> [2,]    3    6    9
#> [3,]    4    7   10

AddOne(char_matrix)
#>  Error in AddOne.default(char_matrix) : No applicable method

AddOne(1:9)
#>  Error in AddOne.default(1:9) : No applicable method

Addendum

Another way to do this that makes it easier to add methods for further classes as needed is to have the function AddOne as a "gatekeeper" that ensures only matrices are handled, and which then dispatches an S3 method according to the class of the underlying data:

add_one_in_c   <- function(x) x + 1

AddOne  <- function(x) 
{
  if(class(x) != "matrix") stop("No applicable method")
  return(AddOne_basic(as.vector(x), nrow(x), ncol(x)))
}

AddOne_basic <- function(x, r, c) UseMethod("AddOne_basic")

AddOne_basic.default <- function(x, r, c) stop("No appicable method")

AddOne_basic.numeric <- function(x, r, c) {
  dim(x) <- c(r, c); 
  add_one_in_c(x)
}

So to expand the handling of AddOne to other basic types, you add the appropriate AddOne_basic method. Obviously when writing a package you may decide not to export the "back end" AddOne_basic methods.

I don't know if that would also be discouraged, but couldn't you just test like this:

AddOne.matrix <- function (x) {
  all(methods::is(x[1], 'numeric'), methods::is(x, 'matrix')) {
    add_one_in_c(x)
  } else {
    NextMethod(x)
  }
}
Related