1.3.5 Matrices from math/matrix🔗ℹ

glmnet/data/math converts the matrices of Racket’s math/matrix library to design matrices and back. A matrix’s rows are the observations. A response can be a column matrix, a row matrix or a one-dimensional math/array array. (require glmnet) does not load the module, so a program that does not use math/matrix does not wait for it to load.

See Matrices from math/matrix in the Reference for the three procedures.

The Quick Start of R’s glmnet fits glmnet(x, y) to the 100 × 20 matrix x and the response vector y of QuickStartExample, then predicts at new rows nx drawn at random. Here is the same fit, with x a math/matrix matrix and y a column matrix:

> (require math/matrix math/distributions glmnet/datasets glmnet/data/math)
> (define-values (qs-x qs-y) (quick-start-example))
> (define x (design-matrix->matrix qs-x))
> (define y (->col-matrix qs-y))
> (matrix-shape x)

100

20

> (define fit (elnet-path (matrix->design-matrix x) (array->response y)))
> (coef fit #:lambda 0.1)

'#(0.15092807247412926

   1.3205971945479142

   0.0

   0.6751102344847403

   0.0

   -0.8174115175489668

   0.5214366714587341

   0.004829335148616431

   0.3194159167155697

   0.0

   0.0

   0.14249851856673404

   0.0

   0.0

   -1.0599787019192302

   0.0

   0.0

   0.0

   0.0

   0.0

   -1.0218737036830785)

> (random-seed 29)
> (define nx (build-matrix 5 20 (lambda (i j) (sample (normal-dist)))))
> (predict fit (matrix->design-matrix nx) #:lambda '(0.1 0.05))

'((1.3062999979031626

   2.0460482043995163

   -0.3251741946880151

   -1.9745626856083445

   1.6552393883406682)

  (1.387664042746005

   2.126839326881185

   -0.26250673336767805

   -2.1506980670436624

   1.8246177456688972))

matrix->design-matrix copies and checks a matrix as rows->design-matrix does a list of rows, and array->response gives the response as a list, so the fit is the one from the same numbers as lists. design-matrix->matrix converts back, to a matrix of flonums:

> (define X (matrix->design-matrix x))
> (equal? (elnet-path X (array->response y))
          (elnet-path (matrix->list* x) (matrix->list y)))

#t

> (equal? (design-matrix->matrix X) x)

#t

> (design-matrix->matrix (rows->design-matrix '((1 2) (3 4))))

(flarray #[#[1.0 2.0] #[3.0 4.0]])

A flonum array, from array->flarray or design-matrix->matrix, or a mutable array, from vector->matrix, is copied straight from the vector that holds its elements. Any other array is read one element at a time, through the contract that the Typed Racket library puts on each array it returns to untyped code, which makes a large matrix several times slower to convert, and a lazy one, made while array-strictness is #f or returned by array-broadcast or array-lazy, slower still. Converting once and passing the design matrix to every fit pays that cost once.