Matrices

violahs edited this page Mar 22, 2014 · 22 revisions

Creating Matrices

The matrix function returns an instance of an incanter.Matrix, which is an
extension of clatrix.core.Matrix (and no longer
cern.colt.matrix.tdouble.impl.DenseColDoubleMatrix2D). It implements the Clojure
interface clojure.lang.ISeq. Therefore Clojure sequence operations can be applied to
matrices. A matrix consists of a sequence of rows, where each row is a
one-dimensional row matrix. One-dimensional matrices are, in turn, sequences of
numbers. Equivalent to R’s matrix function.

To create a matrix, either pass a Clojure sequence (e.g. vector or list) of
sequences to the matrix function:

(def A (matrix [[1 2 3] [4 5 6] [7 8 9]])) ; produces a 3x3 matrix

Or pass a ‘one-dimensional’ sequence along with the number of desired columns:

(def A2 (matrix [1 2 3 4 5 6 7 8 9] 3)) ; produces the same 3x3 matrix

Passing a sequence without the ncol argument will produce a column vector:

(def B (matrix [1 2 3 4 5 6 7 8 9])) ; produces a 9x1 column vector

To create a matrix initialized with a single value, pass the initial value, the number of rows,
and the number of columns to the matrix function:

(matrix 0 3 4) ; produces 3x4 matrix of zeros

To create an identity matrix, use the identity-matrix function:

(identity-matrix 4) ; produces 4x4 identity matrix

To create a diagonal matrix, pass a sequence of values for the diagonal to the diag function:

(diag [1 2 3 4]) ; produces 4x4 matrix with [1 2 3 4] along the diagonal

And to extract the diagonal elements of a matrix, pass it to the diag function:
(diag A) ; produces the sequence (1.0 5.0 9.0)

To create a symmetric matrix, pass a sequence of values representing the lower-triangular elements
of the matrix (in row-major order) to the symmetric-matrix function:


(symmetric-matrix 
  [1
   2 3
   4 5 6
   7 8 9 10])

Matrix Operations

Incanter includes ‘vectorized’ versions of standard math operations that operate on numbers,
Clojure sequences, and Incanter matrices, including:
plus minus mult div abs exp sqrt pow cos acos sin asin tan atan sum prod.

To add two (or more) matrices together element-by-element use the plus function:

(plus A A2)

To add a scalar to a matrix:

(plus 3 A)

Incanter also includes standard matrix operations, including mmult (matrix multiplication),
solve, trans, det, trace and matrix decomposition:
decomp-cholesky decomp-eigenvalue decomp-lu decomp-qr decomp-svd

Manipulating Matrices

The incanter.Matrix class implements Clojure’s ISeq interface, so operations that work on built-in
sequences also work on matrices, include first, rest, map, reduce, filter, etc.
A two-dimensional matrix is a sequence of rows, which are in turn a sequence of doubles.

(first A) ; produces a row matrix [1 2 3]
(rest A) ; produces a sub matrix [[4 5 6] [7 8 9]]
(first (first A)) ; produces 1.0
(rest (first A)) ; produces a row matrix [2 3]

Since (plus row1 row2) adds the two rows element-by-element,
(reduce plus A) produces the sums of the columns.

And since (sum row1) sums the elements of the row,
(map sum A) produces the sums of the rows.

Clojure’s filter function can be used to filter rows:

(filter #(> (nth % 1) 4) A) ; returns the rows where the second column is greater than 4.

Selecting Subsets of Matrices

Start by loading one of the included data sets from the datasets library:

(use 'incanter.datasets)
(def speed (to-matrix (get-dataset :speed)))

Select the element in the first column, first row:

(sel speed 0 0)

or
(sel speed :rows 0 :cols 0)

Select the first column of data:

(sel speed :cols 0)

Select multiple columns or rows:

(sel speed :cols [0 2]) ; first and third column of all rows
(sel speed :rows (range 10) :cols (range 2)) ; first two rows of the first 2 columns
(sel speed :rows (range 10)) ; all columns of the first 10 rows

Exclude one or more columns or rows:

(sel speed :except-cols 1) ; all columns except the second
(sel speed :except-rows (range 10)) ; all but the first 10 rows

Use the filter option to return the rows where the distance (third column) is greater than 50:

(sel speed :filter #(> (nth % 2) 50))

Or only the first 10 even rows:

(sel speed :rows (range 10) :filter #(even? (int (nth % 0))))

Destructuring Matrices

Matrices can be destructured by rows. The following returns a hashmap with entries for each row:

(let '[[row1 row2 row3] A]
  {:r1 row1 :r2 row2 :r3 row3})

To destructure by columns, transpose the matrix:

(let '[[col1 col2 col3] (trans A)]
  {:x1 col1 :x2 col2 :x3 col3})

Destructuring Matrices by Group

The group-on function will take a matrix and one or more column indices and return a collection of sub-matrices based on the value(s) of those columns. For instance the following code will load the sample plant-growth data set, and take the mean of the first column (using the :cols option in group-on) for each of the three sub-group defined by the value of column two (the group variable).

(use '(incanter core stats datasets))
(def plant-growth (to-matrix (get-dataset :plant-growth)))
(map mean (group-on plant-growth 1 :cols 0)) ;; this returns (5.032 4.661 5.526) 

Multiple columns can be used to define the groups. For instance if we use dummy-variables to encode the treatment groups in the plant-growth data set, we will need to use the last two indicator columns to define the groupings.

(def plant-growth-dummies (to-matrix (get-dataset :plant-growth) :dummies true))
(map mean (group-on plant-growth-dummies [1 2] :cols 0)) 
;; this returns (5.032 5.526 4.661)

Notice that the order of the means is not the same as before. The groups are sorted based on the value of the grouping variable ONLY when a single column value is used to define them, otherwise the order of the returned groups cannot be relied upon.

We can also use group-on in a destructuring bind (this is best done when using a single column to define the groupings so that the order of the groups will match the binding variables).

;; plot the plant groups
(use 'incanter.charts)
(let [[ctrl trt1 trt2] (group-on plant-growth 1 :cols 0)]
  (doto (box-plot ctrl)
    (add-box-plot trt1)
    (add-box-plot trt2)
    view))

References

For further information on using matrices in Incanter see: