6

For tutorial purposes, I'd like to be able to print or display matrices and vectors side-by-side, often to illustrate the result of a matrix equation, like $A x = b$.

I could do this using SAS/IML, where the print statement takes an arbitrary collection of (space separated) expressions, evaluates them and prints the result, e.g.,

print A ' * ' x '=' (A * x) '=' b;

                    A                     X   #TEM1001       B
                    1     1    -4  *  0.733 =        2 =     2
                    1    -2     1     -0.33          1       1
                    1     1     1      -0.4          0       0

Note that quoted strings are printed as is.

I've searched, but can find nothing like this in R. I imagine something like this could be done by a function showObj(object, ...) taking its list of arguments, formatting each to a block of characters, and joining them side-by-side.

Another use of this would be a compact way of displaying a 3D array as the side-by-side collection of its slices.

Does this ring a bell or does anyone have a suggestion for getting started?

user101089
  • 3,756
  • 1
  • 26
  • 53

1 Answers1

3

I have created a very simple function that can print matrices and vectors with arbitrary character strings (typically operators) in between. It allows for matrices with different numbers of rows and treats vectors as column matrices. It is not very elaborate, so I fear there are many examples where it fails. But for an example as simple as the one in your question, it should be enough.

format() is used to convert the numbers to characters. This has the advantage that all the rows of the matrix have the same width and are thus nicely aligned when printed. If needed, you could add some of the arguments of format() also as arguments mat_op_print() to make the configurable. As an example, I have added the argument width that can be used to control the minimal width of the columns.

If the matrices and vectors are name in the function call, these names are printed as headers in the first line. Otherwise, only the numbers are printed.

So, this is the function:

mat_op_print <- function(..., width = 0) {

  # get arguments
  args <- list(...)
  chars <- sapply(args, is.character)

  # auxilliary function to create character of n spaces
  spaces <- function(n) paste(rep(" ", n), collapse = "")

  # convert vectors to row matrix
  vecs <- sapply(args, is.vector)
  args[vecs & !chars] <- lapply(args[vecs & !chars], function(v) matrix(v, ncol = 1))

  # convert all non-characters to character with format
  args[!chars] <- lapply(args[!chars], format, width = width)

  # print names as the first line, if present
  arg_names <- names(args)
  if (!is.null(arg_names)) {
    get_title <- function(x, name) {
      if (is.matrix(x)) {
        paste0(name, spaces(sum(nchar(x[1, ])) + ncol(x) - 1 - nchar(name)))
      } else {
        spaces(nchar(x))
      }
    }
  cat(mapply(get_title, args, arg_names), "\n")
  }

  # auxiliary function to create the lines
  get_line <- function(x, n) {
    if (is.matrix(x)) {
      if (nrow(x) < n) {
       spaces(sum(nchar(x[1, ])) + ncol(x) - 1)
      } else {
        paste(x[n, ], collapse = " ")
      }
    } else if (n == 1) {
      x
    } else {
      spaces(nchar(x))
    }
  }

  # print as many lines as needed for the matrix with most rows
  N <- max(sapply(args[!chars], nrow))
  for (n in 1:N) {
    cat(sapply(args, get_line, n), "\n")
  }
}

And this is an example of how it works:

A = matrix(c(0.5, 1, 3, 0.75, 2.8, 4), nrow = 2)
x = c(0.5, 3.7, 2.3)
y = c(0.7, -1.2)
b = A %*% x - y
mat_op_print(A = A, " * ", x = x, " - ", y = y, " = ", b = b, width = 6)
## A                        x          y          b      
##   0.50   3.00   2.80  *     0.5  -     0.7  =  17.090 
##   1.00   0.75   4.00        3.7       -1.2     13.675 
##                             2.3    

Also printing the slices of a 3-dimensional array side-by-side is possible:

A <- array(1:12, dim = c(2, 2, 3))
mat_op_print(A1 = A[, , 1], " | ", A2 = A[, , 2], " | ", A3 = A[, , 3])
## A1      A2      A3    
## 1 3  |  5 7  |   9 11 
## 2 4     6 8     10 12
Stibu
  • 15,166
  • 6
  • 57
  • 71
  • This gets me part way to where I want to go, but I can't quite see how to increase the spacing between matrices, other than by adding `" "` arguments and it would be nice to also display the names of those arguments that have names. – user101089 Sep 14 '16 at 14:02
  • I have extended the answer to make the width of columns configurable and to add a title with the matrix names. – Stibu Sep 14 '16 at 19:38
  • Nice work on this, @Stibu! I turned x into a character matrix with `x = as.matrix(c("x1", "x2", "x3"), ncol=1)` and that worked as well. – Ben Ogorek Oct 31 '20 at 16:02