code for dataframe tests, MUST be migrated into real tests, not forms which are moral...
[CommonLispStat.git] / TODO.lisp
blob77f0b1b5a4f82e61ab3dac1994f0d38739dfe4d5
1 ;;; -*- mode: lisp -*-
3 ;;; Time-stamp: <2009-04-17 13:05:00 tony>
4 ;;; Creation: <2008-09-08 08:06:30 tony>
5 ;;; File: TODO.lisp
6 ;;; Author: AJ Rossini <blindglobe@gmail.com>
7 ;;; Copyright: (c) 2007-2008, AJ Rossini <blindglobe@gmail.com>. BSD.
8 ;;; Purpose: Stuff that needs to be made working sits inside the
9 ;;; progns... This file contains the current challenges to
10 ;;; solve, including a description of the setup and the work
11 ;;; to solve....
13 ;;; What is this talk of 'release'? Klingons do not make software
14 ;;; 'releases'. Our software 'escapes', leaving a bloody trail of
15 ;;; designers and quality assurance people in its wake.
17 ;;; SET UP
19 (in-package :cl-user)
20 ;;(asdf:oos 'asdf:load-op 'lisp-matrix)
21 ;;(asdf:oos 'asdf:compile-op 'lispstat)
22 ;;(asdf:oos 'asdf:load-op 'lispstat)
24 (in-package :lisp-stat-unittests)
26 ;; tests = 80, failures = 8, errors = 15
27 (run-tests :suite 'lisp-stat-ut)
28 (describe (run-tests :suite 'lisp-stat-ut))
30 ;; FIXME: Example: currently not relevant, yet
31 ;; (describe (lift::run-test :test-case 'lisp-stat-unittests::create-proto
32 ;; :suite 'lisp-stat-unittests::lisp-stat-ut-proto))
34 (describe (lift::run-tests :suite 'lisp-stat-ut-dataframe))
35 (lift::run-tests :suite 'lisp-stat-ut-dataframe)
37 (describe
38 (lift::run-test
39 :test-case 'lisp-stat-unittests::create-proto
40 :suite 'lisp-stat-unittests::lisp-stat-ut-proto))
42 (describe 'lisp-stat-ut)
44 (in-package :ls-user)
46 (progn ;; FIXME: Regression modeling (some data future-ish).
48 ;; TODO:
49 ;; - confirm estimates for multivariate case,
50 ;; - pretty-print output
51 ;; - fix up API -- what do we want this to look like?
53 (defparameter *m*
54 (regression-model (list->vector-like iron) ;; BROKEN
55 (list->vector-like absorbtion))
56 "holding variable.")
58 (defparameter *m-fit*
59 (fit-model *m*))
61 (princ *m*)
62 (princ *m-fit*)
64 (estimates *m-fit*)
65 (covariance-matrix *m-fit*)
67 (defparameter *m3*
68 (regression-model (transpose (listoflist->matrix-like (list iron aluminum)
69 :orientation :row-major))
70 (list->vector-like absorbtion) ))
71 (princ *m3*)
72 (defparameter *m3-fit*
73 (fit-model *m3*))
75 ;; Should the above look something like:
76 (defparameter *m3-fit*
77 (spec-and-fit-model '(absorbtion = iron aluminum)))
78 ;; in which case we split the list before/after the "=" character.
81 (estimates *m3-fit*)
82 (covariance-matrix *m3-fit*))
85 #+nil
86 (progn ;; FIXME: Need to clean up data examples, licenses, attributions, etc.
87 ;; The following breaks because we should use a package to hold
88 ;; configuration details, and this would be the only package outside
89 ;; of packages.lisp, as it holds the overall defsystem structure.
90 (load-data "iris.lsp") ;; (the above partially fixed).
91 (variables)
92 diabetes )
95 (progn ;; Importing data from DSV text files.
97 (defparameter *my-df-2*
98 (make-instance 'dataframe-array
99 :storage
100 (listoflist->array
101 (cybertiggyr-dsv::load-escaped
102 "/media/disk/Desktop/sandbox/CLS.git/Data/example-mixed.csv"))
103 :doc "This is an interesting dataframe-array"))
104 #| :case-labels (list "x" "y")
105 :var-labels (list "a" "b" "c" "d" "e")
108 (asdf:oos 'asdf:load-op 'rsm-string)
109 (rsm.string:file->string-table
110 "/media/disk/Desktop/sandbox/CLS.git/Data/example-mixed.csv")
112 (rsm.string:file->number-table
113 "/media/disk/Desktop/sandbox/CLS.git/Data/example-numeric.csv")
115 (defparameter *my-df-2*
116 (make-instance 'dataframe-array
117 :storage
118 (listoflist->array
119 (transpose-listoflist
120 (rsm.string:file->string-table
121 "/media/disk/Desktop/sandbox/CLS.git/Data/example-mixed.csv")))
122 :doc "This is an interesting dataframe-array"))
123 *my-df-2*
127 (progn ;; Data setup
129 (describe 'make-matrix)
131 (defparameter *indep-vars-2-matrix*
132 (make-matrix (length iron) 2
133 :initial-contents
134 (mapcar #'(lambda (x y)
135 (list (coerce x 'double-float)
136 (coerce y 'double-float)))
137 iron aluminum)))
140 (defparameter *dep-var*
141 (make-vector (length absorbtion)
142 :type :row
143 :initial-contents
144 (list
145 (mapcar #'(lambda (x) (coerce x 'double-float))
146 absorbtion))))
148 (make-dataframe *dep-var*)
149 (make-dataframe (transpose *dep-var*))
151 (defparameter *dep-var-int*
152 (make-vector (length absorbtion)
153 :type :row
154 :element-type 'integer
155 :initial-contents (list absorbtion)))
158 (defparameter *xv+1a*
159 (make-matrix
161 :initial-contents #2A((1d0 1d0)
162 (1d0 3d0)
163 (1d0 2d0)
164 (1d0 4d0)
165 (1d0 3d0)
166 (1d0 5d0)
167 (1d0 4d0)
168 (1d0 6d0))))
170 (defparameter *xv+1b*
171 (bind2
172 (ones 8 1)
173 (make-matrix
175 :initial-contents '((1d0)
176 (3d0)
177 (2d0)
178 (4d0)
179 (3d0)
180 (5d0)
181 (4d0)
182 (6d0)))
183 :by :column))
185 (m= *xv+1a* *xv+1b*) ; => T
187 (princ "Data Set up"))
192 (progn
193 ;; REVIEW: general Lisp use guidance
195 (fdefinition 'make-matrix)
196 (documentation 'make-matrix 'function)
198 #| Examples from CLHS, a bit of guidance.
200 ;; This function assumes its callers have checked the types of the
201 ;; arguments, and authorizes the compiler to build in that assumption.
202 (defun discriminant (a b c)
203 (declare (number a b c))
204 "Compute the discriminant for a quadratic equation."
205 (- (* b b) (* 4 a c))) => DISCRIMINANT
206 (discriminant 1 2/3 -2) => 76/9
208 ;; This function assumes its callers have not checked the types of the
209 ;; arguments, and performs explicit type checks before making any assumptions.
210 (defun careful-discriminant (a b c)
211 "Compute the discriminant for a quadratic equation."
212 (check-type a number)
213 (check-type b number)
214 (check-type c number)
215 (locally (declare (number a b c))
216 (- (* b b) (* 4 a c)))) => CAREFUL-DISCRIMINANT
217 (careful-discriminant 1 2/3 -2) => 76/9
225 (progn ;; FIXME: read data from CSV file. To do.
228 ;; challenge is to ensure that we get mixed arrays when we want them,
229 ;; and single-type (simple) arrays in other cases.
232 (defparameter *csv-num*
233 (cybertiggyr-dsv::load-escaped
234 #p"/media/disk/Desktop/sandbox/CLS.git/Data/example-numeric.csv"
235 :field-separator #\,
236 :trace T))
238 (nth 0 (nth 0 *csv-num*))
240 (defparameter *csv-num*
241 (cybertiggyr-dsv::load-escaped
242 #p"/media/disk/Desktop/sandbox/CLS.git/Data/example-numeric2.dsv"
243 :field-separator #\:))
245 (nth 0 (nth 0 *csv-num*))
248 ;; The handling of these types should be compariable to what we do for
249 ;; matrices, but without the numerical processing. i.e. mref, bind2,
250 ;; make-dataframe, and the class structure should be similar.
252 ;; With numerical data, there should be a straightforward mapping from
253 ;; the data.frame to a matrix. With categorical data (including
254 ;; dense categories such as doc-strings, as well as sparse categories
255 ;; such as binary data), we need to include metadata about ordering,
256 ;; coding, and such. So the structures should probably consider
258 ;; Using the CSV file:
260 (defun parse-number (s)
261 (let* ((*read-eval* nil)
262 (n (read-from-string s)))
263 (if (numberp n) n)))
265 (parse-number "34")
266 (parse-number "34 ")
267 (parse-number " 34")
268 (parse-number " 34 ")
270 (+ (parse-number "3.4") 3)
271 (parse-number "3.4 ")
272 (parse-number " 3.4")
273 (+ (parse-number " 3.4 ") 3)
275 (parse-number "a")
277 ;; (coerce "2.3" 'number) => ERROR
278 ;; (coerce "2" 'float) => ERROR
280 (defparameter *csv-num*
281 (cybertiggyr-dsv::load-escaped
282 #p"/media/disk/Desktop/sandbox/CLS.git/Data/example-numeric.csv"
283 :field-separator #\,
284 :filter #'parse-number
285 :trace T))
287 (nth 0 (nth 0 *csv-num*))
289 (defparameter *csv-num*
290 (cybertiggyr-dsv::load-escaped
291 #p"/media/disk/Desktop/sandbox/CLS.git/Data/example-numeric2.dsv"
292 :field-separator #\:
293 :filter #'parse-number))
295 (nth 0 (nth 0 *csv-num*))
297 ;; now we've got the DSV code in the codebase, auto-loaded I hope:
298 cybertiggyr-dsv:*field-separator*
299 (defparameter *example-numeric.csv*
300 (cybertiggyr-dsv:load-escaped "Data/example-numeric.csv"
301 :field-separator #\,))
302 *example-numeric.csv*
304 ;; the following fails because we've got a bit of string conversion
305 ;; to do. 2 thoughts: #1 modify dsv package, but mucking with
306 ;; encapsulation. #2 add a coercion tool (better, but potentially
307 ;; inefficient).
308 #+nil(coerce (nth 3 (nth 3 *example-numeric.csv*)) 'double-float)
310 ;; cases, simple to not so
311 (defparameter *test-string1* "1.2")
312 (defparameter *test-string2* " 1.2")
313 (defparameter *test-string3* " 1.2 ")
317 #+nil
318 (progn ;; experiments with GSL and the Lisp interface.
319 (asdf:oos 'asdf:load-op 'gsll)
320 (asdf:oos 'asdf:load-op 'gsll-tests)
322 ;; the following should be equivalent
323 (setf *t1* (LIST 6.18d0 6.647777777777779d0 6.18d0))
324 (setf *t2* (MULTIPLE-VALUE-LIST
325 (LET ((VEC
326 (gsll:make-marray 'DOUBLE-FLOAT
327 :INITIAL-CONTENTS '(-3.21d0 1.0d0 12.8d0)))
328 (WEIGHTS
329 (gsll:MAKE-MARRAY 'DOUBLE-FLOAT
330 :INITIAL-CONTENTS '(3.0d0 1.0d0 2.0d0))))
331 (LET ((MEAN (gsll:MEAN VEC)))
332 (LIST (gsll:ABSOLUTE-DEVIATION VEC)
333 (gsll:WEIGHTED-ABSOLUTE-DEVIATION VEC WEIGHTS)
334 (gsll:ABSOLUTE-DEVIATION VEC MEAN))))))
335 (eql *t1* *t2*)
337 ;; from (gsll:examples 'gsll::numerical-integration) ...
338 (gsll:integration-qng gsll::one-sine 0.0d0 PI)
340 (gsll:defun-single axpb (x) (+ (* 2 x) 3)) ;; a<-2, b<-3
341 (gsll:integration-qng axpb 1d0 2d0)
343 (let ((a 2)
344 (b 3))
345 (defun-single axpb2 (x) (+ (* a x) b)))
346 (gsll:integration-qng axpb2 1d0 2d0)
348 ;; BAD
349 ;; (gsll:integration-qng
350 ;; (let ((a 2)
351 ;; (b 3))
352 ;; (defun-single axpb2 (x) (+ (* a x) b)))
353 ;; 1d0 2d0)
355 ;; right, but weird expansion...
356 (gsll:integration-qng
357 (let ((a 2)
358 (b 3))
359 (defun axpb2 (x) (+ (* a x) b))
360 (gsll:def-single-function axpb2)
361 axpb2)
362 1d0 2d0)
364 ;; Linear least squares
366 (gsll:gsl-lookup "gsl_linalg_LU_decomp") ; => gsll:lu-decomposition
367 (gsll:gsl-lookup "gsl_linalg_LU_solve") ; => gsll:lu-solve
372 #+nil
373 (progn ;; philosophy time
375 (setf my-model (model :name "ex1"
376 :data-slots (list w x y z)
377 :param-slots (list alpha beta gamma)
378 :math-form (regression-model :formula '(= w (+ (* beta x)
379 (* alpha y)
380 (* gamma z)
381 normal-error))
382 :centrality 'median ; 'mean
385 #| or:
386 #R"W ~ x+ y + z "
389 (setf my-dataset (statistical-table :table data-frame-contents
390 :metadata (list (:case-names (list ))
391 (:var-names (list ))
392 (:documentation "string of doc"))))
394 (setf my-analysis (analysis
395 :model my-model
396 :data my-dataset
397 :parameter-map (pairing (model-param-slots my-model)
398 (data-var-names my-dataset))))
400 ;; ontological implications -- the analysis is an abstract class of
401 ;; data, model, and mapping between the model and data. The fit is
402 ;; the instantiation of such. This provides a statistical object
403 ;; computation theory which can be realized as "executable
404 ;; statistics" or "computable statistics".
405 (setf my-analysis (analyze my-fit
406 :estimation-method 'linear-least-squares-regression))
408 ;; one of the tricks here is that one needs to provide the structure
409 ;; from which to consider estimation, and more importantly, the
410 ;; validity of the estimation.
413 (setf linear-least-squares-regression
414 (estimation-method-definition
415 :variable-defintions ((list
416 ;; from MachLearn: supervised,
417 ;; unsupervised
418 :data-response-vars list-drv ; nil if unsup
420 :param-vars list-pv
421 :data-predictor-vars list-dpv
422 ;; nil in this case. these
423 ;; describe "out-of-box" specs
424 :hyper-vars list-hv))
425 :form '(regression-additive-error
426 :central-form (linear-form drv pv dpv)
427 :error-form 'normal-error)
428 :resulting-decision '(point-estimation interval-estimation)
429 :philosophy 'frequentist
430 :documentation "use least squares to fit a linear regression
431 model to data."))
433 (defparameter *statistical-philosophies*
434 '(frequentist bayesian fiducial decision-analysis)
435 "can be combined to build decision-making approaches and
436 characterizations")
438 (defparameter *decisions*
439 '(estimation selection testing)
440 "possible results from a...")
441 ;; is this really true? One can embedded hypothesis testing within
442 ;; estimation, as the hypothesis estimated to select. And
443 ;; categorical/continuous rear their ugly heads, but not really in
444 ;; an essential way.
446 (defparameter *ontology-of-decision-procedures*
447 (list :decisions
448 (list :estimation
449 (list :point
450 (list :maximum-likelihood
451 :minimum-entropy
452 :least-squares
453 :method-of-moments)
454 :interval
455 (list :maximum-likelihood
457 :testing
458 (list :fisherian
459 :neyman-pearson
460 (list :traditional
461 :bioequivalence-inversion)
462 :selection
463 (list :ranking
464 :top-k-of-n-select))
465 :parametric
466 :partially-parametric))
467 "start of ontology"))
470 ;;;; LM
472 (progn
474 (defparameter *y*
475 (make-vector
477 :type :row
478 :initial-contents '((1d0 2d0 3d0 4d0 5d0 6d0 7d0 8d0))))
481 (defparameter *xv+1*
482 (make-matrix
484 :initial-contents '((1d0 1d0)
485 (1d0 3d0)
486 (1d0 2d0)
487 (1d0 4d0)
488 (1d0 3d0)
489 (1d0 5d0)
490 (1d0 4d0)
491 (1d0 6d0))))
494 ;; so something like (NOTE: matrices are transposed to begin with, hence the incongruety)
495 (defparameter *xtx-2* (m* (transpose *xv+1*) *xv+1*))
496 ;; #<LA-SIMPLE-MATRIX-DOUBLE 2 x 2
497 ;; 8.0d0 28.0d0
498 ;; 28.0d0 116.0d0>
500 (defparameter *xty-2* (m* (transpose *xv+1*) (transpose *y*)))
501 ;; #<LA-SIMPLE-VECTOR-DOUBLE (2 x 1)
502 ;; 36.0d0
503 ;; 150.0d0>
505 (defparameter *rcond-2* 0.000001)
506 (defparameter *betahat-2* (gelsy *xtx-2* *xty-2* *rcond-2*))
507 ;; *xtx-2* => "details of complete orthogonal factorization"
508 ;; according to man page:
509 ;; #<LA-SIMPLE-MATRIX-DOUBLE 2 x 2
510 ;; -119.33147112141039d0 -29.095426104883202d0
511 ;; 0.7873402682880205d0 -1.20672274167718d0>
513 ;; *xty-2* => output becomes solution:
514 ;; #<LA-SIMPLE-VECTOR-DOUBLE (2 x 1)
515 ;; -0.16666666666668312d0
516 ;; 1.333333333333337d0>
518 *betahat-2* ; which matches R, see below
520 (documentation 'gelsy 'function)
523 ;; (#<LA-SIMPLE-VECTOR-DOUBLE (2 x 1)
524 ;; -0.16666666666668312 1.333333333333337>
525 ;; 2)
527 ;; ## Test case in R:
528 ;; x <- c( 1.0, 3.0, 2.0, 4.0, 3.0, 5.0, 4.0, 6.0)
529 ;; y <- c( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0)
530 ;; lm(y~x)
531 ;; ## => Call: lm(formula = y ~ x)
533 ;; Coefficients: (Intercept) x
534 ;; -0.1667 1.3333
536 ;; summary(lm(y~x))
537 ;; ## =>
539 ;; Call:
540 ;; lm(formula = y ~ x)
542 ;; Residuals:
543 ;; Min 1Q Median 3Q Max
544 ;; -1.833e+00 -6.667e-01 -3.886e-16 6.667e-01 1.833e+00
546 ;; Coefficients:
547 ;; Estimate Std. Error t value Pr(>|t|)
548 ;; (Intercept) -0.1667 1.1587 -0.144 0.89034
549 ;; x 1.3333 0.3043 4.382 0.00466 **
550 ;; ---
551 ;; Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
553 ;; Residual standard error: 1.291 on 6 degrees of freedom
554 ;; Multiple R-squared: 0.7619, Adjusted R-squared: 0.7222
555 ;; F-statistic: 19.2 on 1 and 6 DF, p-value: 0.004659
559 ;; which suggests one might do (modulo ensuring correct
560 ;; orientations). When this is finalized, it should migrate to
561 ;; CLS.
565 (defparameter *n* 20) ; # rows = # obsns
566 (defparameter *p* 10) ; # cols = # vars
567 (defparameter *x-temp* (rand *n* *p*))
568 (defparameter *b-temp* (rand *p* 1))
569 (defparameter *y-temp* (m* *x-temp* *b-temp*))
570 ;; so Y=Xb + \eps
571 (defparameter *rcond* (* (coerce (expt 2 -52) 'double-float)
572 (max (nrows *x-temp*) (ncols *y-temp*))))
573 (defparameter *orig-x* (copy *x-temp*))
574 (defparameter *orig-b* (copy *b-temp*))
575 (defparameter *orig-y* (copy *y-temp*))
577 (defparameter *lm-result* (lm *x-temp* *y-temp*))
578 (princ (first *lm-result*))
579 (princ (second *lm-result*))
580 (princ (third *lm-result*))
581 (v= (third *lm-result*)
582 (v- (first (first *lm-result*))
583 (first (second *lm-result*))))
588 ;; Some issues exist in the LAPACK vs. LINPACK variants, hence R
589 ;; uses LINPACK primarily, rather than LAPACK. See comments in R
590 ;; source for issues.
593 ;; Goal is to start from X, Y and then realize that if
594 ;; Y = X \beta, then, i.e. 8x1 = 8xp px1 + 8x1
595 ;; XtX \hat\beta = Xt Y
596 ;; so that we can solve the equation W \beta = Z where W and Z
597 ;; are known, to estimate \beta.
599 ;; the above is known to be numerically instable -- some processing
600 ;; of X is preferred and should be done prior. And most of the
601 ;; transformation-based work does precisely that.
603 ;; recall: Var[Y] = E[(Y - E[Y])(Y-E[Y])t]
604 ;; = E[Y Yt] - 2 \mu \mut + \mu \mut
605 ;; = E[Y Yt] - \mu \mut
607 ;; Var Y = E[Y^2] - \mu^2
610 ;; For initial estimates of covariance of \hat\beta:
612 ;; \hat\beta = (Xt X)^-1 Xt Y
613 ;; with E[ \hat\beta ]
614 ;; = E[ (Xt X)^-1 Xt Y ]
615 ;; = E[(Xt X)^-1 Xt (X\beta)]
616 ;; = \beta
618 ;; So Var[\hat\beta] = ...
619 ;; (Xt X)
620 ;; and this gives SE(\beta_i) = (* (sqrt (mref Var i i)) adjustment)
623 ;; from docs:
625 (setf *temp-result*
626 (let ((*default-implementation* :foreign-array))
627 (let* ((m 10)
628 (n 10)
629 (a (rand m n))
630 (x (rand n 1))
631 (b (m* a x))
632 (rcond (* (coerce (expt 2 -52) 'double-float)
633 (max (nrows a) (ncols a))))
634 (orig-a (copy a))
635 (orig-b (copy b))
636 (orig-x (copy x)))
637 (list x (gelsy a b rcond))
638 ;; no applicable conversion?
639 ;; (m- (#<FA-SIMPLE-VECTOR-DOUBLE (10 x 1))
640 ;; (#<FA-SIMPLE-VECTOR-DOUBLE (10 x 1)) )
641 (v- x (first (gelsy a b rcond))))))
644 (princ *temp-result*)
646 (setf *temp-result*
647 (let ((*default-implementation* :lisp-array))
648 (let* ((m 10)
649 (n 10)
650 (a (rand m n))
651 (x (rand n 1))
652 (b (m* a x))
653 (rcond (* (coerce (expt 2 -52) 'double-float)
654 (max (nrows a) (ncols a))))
655 (orig-a (copy a))
656 (orig-b (copy b))
657 (orig-x (copy x)))
658 (list x (gelsy a b rcond))
659 (m- x (first (gelsy a b rcond)))
661 (princ *temp-result*)
664 (defparameter *xv*
665 (make-vector
667 :type :row ;; default, not usually needed!
668 :initial-contents '((1d0 3d0 2d0 4d0 3d0 5d0 4d0 6d0))))
670 (defparameter *y*
671 (make-vector
673 :type :row
674 :initial-contents '((1d0 2d0 3d0 4d0 5d0 6d0 7d0 8d0))))
676 ;; so something like (NOTE: matrices are transposed to begin with, hence the incongruety)
677 (defparameter *xtx-1* (m* *xv* (transpose *xv*)))
678 (defparameter *xty-1* (m* *xv* (transpose *y*)))
679 (defparameter *rcond-in* (* (coerce (expt 2 -52) 'double-float)
680 (max (nrows *xtx-1*)
681 (ncols *xty-1*))))
683 (defparameter *betahat* (gelsy *xtx-1* *xty-1* *rcond-in*))
685 ;; (#<LA-SIMPLE-VECTOR-DOUBLE (1 x 1)
686 ;; 1.293103448275862>
687 ;; 1)
689 ;; ## Test case in R:
690 ;; x <- c( 1.0, 3.0, 2.0, 4.0, 3.0, 5.0, 4.0, 6.0)
691 ;; y <- c( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0)
692 ;; lm(y~x-1)
693 ;; ## =>
694 ;; Call:
695 ;; lm(formula = y ~ x - 1)
697 ;; Coefficients:
698 ;; x
699 ;; 1.293
701 (first *betahat*))
705 #+nil
706 (progn
708 (asdf:oos 'asdf:load-op 'cl-plplot)
710 (plot-ex))
714 (type-of #2A((1 2 3 4 5)
715 (10 20 30 40 50)))
717 (type-of (rand 10 20))
719 (typep #2A((1 2 3 4 5)
720 (10 20 30 40 50))
721 'matrix-like)
723 (typep (rand 10 20) 'matrix-like)
725 (typep #2A((1 2 3 4 5)
726 (10 20 30 40 50))
727 'array)
729 (typep (rand 10 20) 'array)