Artem Sokolov
Artem Sokolov

Reputation: 13701

Prevent rbind from creating empty rows when handling numeric(0)

I find the behavior of rbind somewhat non-intuitive when it's provided with empty vectors. For example,

rbind(numeric(0), numeric(0), numeric(0))
# [1,]
# [2,]
# [3,]

produces a 3-by-0 matrix, while I would expect the output to be a consistent numeric(0) instead. While it may be fun to debate whether you get "one nothing" or "three nothings" when you add a "nothing" to itself three times, my question is whether it's possible to suppress this behavior? Alternatively, I am looking for another function that can "rbind" matrices, while also producing an object with no rows when none of the inputs have rows.

My approach so far has been to cast numeric(0) to a matrix(0,0,0), which appears to work more consistently with rbind:

rbind(matrix(0,0,0), matrix(0,0,0), matrix(0,0,0))
# <0 x 0 matrix>

High-level picture: I have a function that accepts one or more matrices representing linear constraints, rbinds them as the first step and performs some downstream operations. The number of rows in the rbind output is taken to be the total number of input constraints. I discovered the above behavior when I was removing the last row in an input matrix and accidentally left off drop=FALSE, thus calling my function with a numeric(0) instead of a proper matrix(0,0,0) and getting the wrong total number of constraints, because nrow(rbind(numeric(0))) was returning 1.

Upvotes: 4

Views: 279

Answers (3)

Artem Sokolov
Artem Sokolov

Reputation: 13701

Thanks very much to @Onyambu and @MikaelJagan for an interesting discussion in the comments.

The discussion brought my attention to as.matrix(numeric(0)), which appears to be a great intermediate between numeric(0) and matrix(0,0,0) as it represents a 0-by-1 matrix and its number of rows is preserved by rbind:

rbind(as.matrix(numeric(0)), as.matrix(numeric(0)))
#     [,1]

A great solution for my high-level scenario is then to simply wrap each argument inside as.matrix() before passing all arguments to rbind.

# Row-count-preserving (rcp) rbind for use with matrix-like arguments
rcp_rbind <- function(...) do.call(rbind, lapply(list(...), as.matrix))

The new function behaves identically to rbind() for matrix arguments:

identical(    rbind(diag(2), diag(2)),
          rcp_rbind(diag(2), diag(2)))                 # TRUE

identical(    rbind(matrix(0,0,0), matrix(0,0,0)),
          rcp_rbind(matrix(0,0,0), matrix(0,0,0)))     # TRUE

while also solving my issue of extra rows being generated from empty vectors

rcp_rbind(numeric(0), numeric(0), numeric(0))
#      [,1]

(Note that additional modification are required to properly handle deparse.level if the function is to be extended beyond matrix-like objects, which is outside the scope here.)

Upvotes: 0

Mikael Jagan
Mikael Jagan

Reputation: 11336

From ?rbind:

For cbind (rbind), vectors of zero length (including NULL) are ignored unless the result would have zero rows (columns), for S compatibility.

I think it is reasonable, if your use case demands it, to write your own rbind analogue that handles zero-length vectors like 0-by-0 matrices instead of 1-by-0 matrices. Maybe something like this?

rbind0 <- function(..., deparse.level = 1) {
  f <- function(x) {
    if (length(dim(x)) != 2L && (is.atomic(x) || is.list(x)) && length(x) == 0L) {
      x <- vector(typeof(x), 0L)
      dim(x) <- c(0L, 0L)
    }
    x
  }
  args <- c(lapply(list(...), f), list(deparse.level = deparse.level))
  do.call(rbind, args)
}

Errors are thrown in more cases, because these 0-by-0 matrices are subject to rbind's checks for dimensional compatibility. For example:

x <- matrix(rnorm(9L), 3L, 3L)
identical(rbind(x, numeric(0L)), x)
## [1] TRUE
rbind0(x, numeric(0L))
## Error in (function (..., deparse.level = 1)  : 
##  number of columns of matrices must match (see arg 2)

You might consider that desirable behaviour, apart from the error message incorrectly referring to integer(0L) as a matrix. You can add more tests to avoid these errors (or throw different errors) in specific cases, but I don't know that it's worth it. Again, it depends on your use case.

Upvotes: 1

Brian Syzdek
Brian Syzdek

Reputation: 948

I don't think you should expect there to be no rows. From rdocumentation, "numeric is identical to double (and real). It creates a double-precision vector of the specified length with each element equal to 0." So, I don't think it's accurate to say they are "nothings."

If you rbind NULL, which I think is closer to "nothing," you get no rows:

rbind(NULL, NULL, NULL)

returns

NULL

So, to answer the question, no I don't know how it would be possible to bind numeric and get no rows.

Upvotes: 2

Related Questions