reduce/apply/values corrections
[CommonLispStat.git] / nonlin.lsp
blob0dbeaf6c417b37799a0a5d36a7ede75a4baa242a
1 ;;; -*- mode: lisp -*-
2 ;;;
3 ;;; Copyright (c) 2005--2007, by A.J. Rossini <blindglobe@gmail.com>
4 ;;; See COPYRIGHT file for any additional restrictions (BSD license).
5 ;;; Since 1991, ANSI was finally finished. Modified to match ANSI
6 ;;; Common Lisp.
9 ;;;; XLISP-STAT 2.1 Copyright (c) 1990, by Luke Tierney
10 ;;;; Additions to Xlisp 2.1, Copyright (c) 1989 by David Michael Betz
11 ;;;; You may give out copies of this software; for conditions see the file
12 ;;;; COPYING included with this distribution.
14 (defpackage :lisp-stat-regression-nonlin
15 (:use :common-lisp
16 :lisp-stat-object-system
17 :lisp-stat-basics
18 :lisp-stat-compound-data
19 :lisp-stat-sequence
20 :lisp-stat-matrix
21 :lisp-stat-linalg
22 :lisp-stat-regression-linear)
23 (:shadowing-import-from :lisp-stat-object-system
24 slot-value call-method call-next-method)
25 (:export nreg-model nreg-model-proto mean-function theta-hat epsilon
26 count-limit verbose))
28 (in-package :lisp-stat-regression-nonlin)
30 ;;;;
31 ;;;; Nonlinear Regression Model Prototype
32 ;;;;
34 (defproto nreg-model-proto
35 '(mean-function theta-hat epsilon count-limit verbose)
36 '()
37 regression-model-proto)
39 (defun nreg-model (mean-function y theta
40 &key
41 (epsilon .0001)
42 (print t)
43 (count-limit 20)
44 parameter-names
45 response-name
46 case-labels
47 weights
48 included
49 (verbose print))
50 "Args: (mean-function y theta &key (epsilon .0001) (count-limit 20)
51 (print t) parameter-names response-name case-labels
52 weights included (vetbose print))
53 Fits nonlinear regression model with MEAN-FUNCTION and response Y using initial
54 parameter guess THETA. Returns model object."
55 (let ((m (send nreg-model-proto :new)))
56 (send m :mean-function mean-function)
57 (send m :y y)
58 (send m :new-initial-guess theta)
59 (send m :epsilon epsilon)
60 (send m :count-limit count-limit)
61 (send m :parameter-names parameter-names)
62 (send m :response-name response-name)
63 (send m :case-labels case-labels)
64 (send m :weights weights)
65 (send m :included included)
66 (send m :verbose verbose)
67 (if print (send m :display))
68 m))
70 (defmeth nreg-model-proto :save ()
71 "Message args: ()
72 Returns an expression that will reconstruct the regression model."
73 `(nreg-model ',(send self :mean-function)
74 ',(send self :y)
75 ',(send self :coef-estimates)
76 :epsilon ',(send self :epsilon)
77 :count-limit ',(send self :count-limit)
78 :predictor-names ',(send self :predictor-names)
79 :response-name ',(send self :response-name)
80 :case-labels ',(send self :case-labels)
81 :weights ',(send self :weights)
82 :included ',(send self :included)
83 :verbose ',(send self :verbose)))
85 ;;;
86 ;;; Computing Method
87 ;;;
89 (defmeth nreg-model-proto :compute ()
90 "Message args: ()
91 Recomputes the estimates. For internal use by other messages"
92 (let* ((y (send self :y))
93 (weights (send self :weights))
94 (inc (if-else (send self :included) 1 0))
95 (w (if weights (* inc weights) inc)))
96 (setf (slot-value 'theta-hat)
97 (nlreg (send self :mean-function)
99 (slot-value 'theta-hat)
100 (send self :epsilon)
101 (send self :count-limit)
103 (send self :verbose)))
104 (setf (slot-value 'x)
105 (funcall (make-jacobian (slot-value 'mean-function)
106 (length (slot-value 'theta-hat)))
107 (slot-value 'theta-hat)))
108 (setf (slot-value 'intercept) nil)
109 (call-next-method)
110 (let ((r (send self :residuals)))
111 (setf (slot-value 'residual-sum-of-squares)
112 (sum (* inc r r))))))
115 ;;; Slot Accessors and Mutators
118 (defmeth nreg-model-proto :new-initial-guess (guess)
119 "Message args: (guess)
120 Sets a new initial uess for parmeters."
121 (setf (slot-value 'theta-hat) guess)
122 (send self :needs-computing t))
124 (defmeth nreg-model-proto :theta-hat ()
125 "Message args: ()
126 Returns current parameter estimate."
127 (if (send self :needs-computing) (send self :compute))
128 (coerce (slot-value 'theta-hat) 'list))
130 (defmeth nreg-model-proto :mean-function (&optional f)
131 "Message args: (&optional f)
132 With no argument returns the mean function as supplied to m. With an
133 argument F sets the mean function of m to F and recomputes the
134 estimates."
135 (when (and f (functionp f))
136 (setf (slot-value 'mean-function) f)
137 (send self :needs-computing t))
138 (slot-value 'mean-function))
140 (defmeth nreg-model-proto :epsilon (&optional eps)
141 "Message args: (&optional eps)
142 With no argument returns the tolerance as supplied to m. With an argument
143 EPS sets the tolerance of m to EPS and recomputes the estimates."
144 (when (and eps (numberp eps))
145 (setf (slot-value 'epsilon) eps)
146 (send self :needs-computing t))
147 (slot-value 'epsilon))
149 (defmeth nreg-model-proto :count-limit (&optional count)
150 "Message args: (&optional new-count)
151 With no argument returns the iteration count limit as supplied to m. With
152 an argument COUNT sets the limit to COUNT and recomputes the
153 estimates."
154 (when (and count (numberp count))
155 (setf (slot-value 'count-limit) count)
156 (send self :needs-computing t))
157 (slot-value 'count-limit))
159 (defmeth nreg-model-proto :parameter-names (&optional (names nil set))
160 "Method args: (&optional names)
161 Sets or returns parameter names."
162 (if set (setf (slot-value 'predictor-names) names))
163 (let ((p-names (slot-value 'predictor-names))
164 (p (length (slot-value 'theta-hat))))
165 (if (not (and p-names (= p (length p-names))))
166 (setf (slot-value 'predictor-names)
167 (mapcar #'(lambda (a) (format nil "Parameter ~a" a))
168 (iseq 0 (- p 1))))))
169 (slot-value 'predictor-names))
171 (defmeth nreg-model-proto :verbose (&optional (val nil set))
172 "Method args: (&optional val)
173 Sets or retrieves verbose setting. If T iteration info is printed during
174 optimization."
175 (if set (setf (slot-value 'verbose) val))
176 (slot-value 'verbose))
179 ;;; Overrides for Linear Regression Methods
182 (defmeth nreg-model-proto :x ()
183 "Message args: ()
184 Returns the Jacobian matrix at theta-hat."
185 (call-next-method))
187 (defmeth nreg-model-proto :intercept (&rest args)
188 "Message args: ()
189 Always returns nil. (For compatibility with linear regression.)"
190 nil)
192 (defmeth nreg-model-proto :fit-values ()
193 "Message args: ()
194 Returns the fitted values for the model."
195 (coerce (funcall (send self :mean-function) (send self :theta-hat))
196 'list))
198 (defmeth nreg-model-proto :coef-estimates (&optional guess)
199 "Message args: (&optional guess)
200 With no argument returns the current parameter estimate. With an
201 argument GUESS takes it as a new initial guess and recomputes
202 the estimates."
203 (if guess (send self :new-initial-guess guess))
204 (send self :theta-hat))
206 (defmeth nreg-model-proto :predictor-names () (send self :parameter-names))
208 ;;;;
209 ;;;;
210 ;;;; Linear Regression Coefficients
211 ;;;;
212 ;;;;
214 (defun regression-coefficients (x y &key (intercept T) weights)
215 "Args: (x y &key (intercept T) weights)
216 Returns the coefficients of the regression of the sequence Y on the columns of
217 the matrix X."
218 (let* ((m (if weights
219 (make-sweep-matrix x y weights)
220 (make-sweep-matrix x y)))
221 (n (array-dimension x 1)))
222 (coerce (compound-data-seq
223 (if intercept
224 (select (car (sweep-operator m (iseq 1 n)))
225 (1+ n)
226 (iseq 0 n))
227 (select (car (sweep-operator m (iseq 0 n)))
228 (1+ n)
229 (iseq 1 n))))
230 'vector)))
232 ;;;;
233 ;;;;
234 ;;;; Nonlinear Regression Functions
235 ;;;;
236 ;;;;
237 (defun nlreg1 (f j y initial-beta epsilon count-limit weights verbose)
238 "Args: (mean-function jacobian y initial-beta
239 epsilon count-limit weights verbose)
240 MEAN-FUNCTION returns the mean response vector for a given parameter vector.
241 JACOBIAN returns the jacobian of MEAN-FUNCTION at a given parameter vector.
242 Y is the observed response vector. Returns the estimated parameter vector
243 obtained by a Gauss-Newton algorithm with backtracking that continues until
244 the COUNT-LIMIT is reached or no component of the parameter vector changes
245 by more than EPSILON."
246 (labels ((rss (beta) ; residual sum of squares
247 (let ((res (- y (funcall f beta))))
248 (sum (if weights (* res res weights) (* res res)))))
249 (next-beta (beta delta rss) ; next beta by backtracking
250 (do* ((lambda 1 (/ lambda 2))
251 (new-rss (rss (+ beta delta))
252 (rss (+ beta (* lambda delta)))))
253 ((or (< new-rss rss) (< lambda .0001))
254 (if (>= lambda .0001)
255 (+ beta (* lambda delta))
256 beta)))))
257 (do* ((delbeta (regression-coefficients
258 (funcall j initial-beta)
259 (- y (funcall f initial-beta))
260 :intercept nil
261 :weights weights)
262 (regression-coefficients
263 (funcall j beta)
264 (- y (funcall f beta))
265 :intercept nil
266 :weights weights))
267 (beta initial-beta (next-beta beta delbeta rss))
268 (rss (rss beta) (rss beta))
269 (count 0 (1+ count)))
270 ((or (> count count-limit) (> epsilon (max (abs delbeta))))
271 (if (and verbose (> count count-limit))
272 (format t "Iteration limit exceeded.~%"))
273 beta)
274 (if verbose (format t "Residual sum of squares: ~10g~%" rss)))))
276 (defun make-jacobian (f n)
277 "Args: (f n)
278 F is a function of an N-vector. Returns a function that approximates the
279 jacobian function iof F by a symmetric difference."
280 (let* ((h .0001)
281 (del (* h (values-list (column-list (identity-matrix n))))))
282 #'(lambda (b)
283 (let ((b+ (mapcar #'(lambda (x) (+ b x)) del))
284 (b- (mapcar #'(lambda (x) (- b x)) del)))
285 ;; FIXME:AJR BAD IDIOM FOLLOWS
286 (apply #'bind-columns (/ (- (mapcar f b+) (mapcar f b-)) (* 2 h)))))))
288 (defun nlreg (f y guess &optional
289 (epsilon .0001) (count-limit 20) weights verbose)
290 "Args: (mean-function y guess &optional
291 (epsilon .0001) (count-limit 20) weights verbose)
292 MEAN-FUNCTION returns the mean response vector for a given parameter vector.
293 Y is the observed response vector. Returns the estimated parameter vector
294 obtained by a Gauss-Newton algorithm that continues until the ITERATION-LIMIT
295 is reached or no component of the parameter vector changes by more than
296 EPSILON. The jacobian of MEAN-FUNCTION is approximated by a symmetric difference."
297 (nlreg1 f (make-jacobian f (length guess)) y guess
298 epsilon count-limit weights verbose))