Mercurial > hg > octave-lyh
diff scripts/polynomial/polyfit.m @ 7500:2df882e69f13
use QR decomposition and normalization for polyfit; normalization for polyval
author | Ben Abbott <bpabbott@mac.com> |
---|---|
date | Tue, 19 Feb 2008 07:28:40 -0500 |
parents | 83a8781b529d |
children | 83cce070104f |
line wrap: on
line diff
--- a/scripts/polynomial/polyfit.m +++ b/scripts/polynomial/polyfit.m @@ -18,39 +18,38 @@ ## <http://www.gnu.org/licenses/>. ## -*- texinfo -*- -## @deftypefn {Function File} {[@var{p}, @var{s}] =} polyfit (@var{x}, @var{y}, @var{n}) +## @deftypefn {Function File} {[@var{p}, @var{s}, @var{mu}] =} polyfit (@var{x}, @var{y}, @var{n}) ## Return the coefficients of a polynomial @var{p}(@var{x}) of degree -## @var{n} that minimizes -## @iftex -## @tex -## $$ -## \sum_{i=1}^N (p(x_i) - y_i)^2 -## $$ -## @end tex -## @end iftex -## @ifinfo -## @code{sumsq (p(x(i)) - y(i))}, -## @end ifinfo -## to best fit the data in the least squares sense. +## @var{n} that minimizes the least-squares-error of the fit. ## ## The polynomial coefficients are returned in a row vector. ## -## If two output arguments are requested, the second is a structure +## The second output is a structured variable, @var{s}, ## containing the following fields: ## -## @table @code +## @table @samp ## @item R -## The Cholesky factor of the Vandermonde matrix used to compute the -## polynomial coefficients. +## Triangular factor R from the QR decomposition. ## @item X -## The Vandermonde matrix used to compute the polynomial coefficients. +## The Vandermonde matrix used to compute the polynomial coefficients. ## @item df -## The degrees of freedom. +## The degrees of freedom. ## @item normr -## The norm of the residuals. +## The norm of the residuals. ## @item yf -## The values of the polynomial for each value of @var{x}. +## The values of the polynomial for each value of @var{x}. ## @end table +## +## The second output may be used by @code{polyval} to calculate the +## statistical error limits of the predicted values. +## +## When the third output, @var{mu}, is present the +## coefficients, @var{p}, are associated with a polynomial in +## @var{xhat} = (@var{x}-@var{mu}(1))/@var{mu}(2). +## Where @var{mu}(1) = mean (@var{x}), and @var{mu}(2) = std (@var{x}). +## This linear transformation of @var{x} improves the numerical +## stability of the fit. +## @seealso{polyval, polyconf, residue} ## @end deftypefn ## Author: KH <Kurt.Hornik@wu-wien.ac.at> @@ -59,12 +58,17 @@ function [p, s, mu] = polyfit (x, y, n) - - if (nargin != 3) + if (nargin < 3 || nargin > 4) print_usage (); endif - if (! (isvector (x) && isvector (y) && size_equal (x, y))) + if (nargout > 2) + ## Normalized the x values. + mu = [mean(x), std(x)]; + x = (x - mu(1)) / mu(2); + endif + + if (! size_equal (x, y)) error ("polyfit: x and y must be vectors of the same size"); endif @@ -74,17 +78,21 @@ y_is_row_vector = (rows (y) == 1); - l = length (x); + ## Reshape x & y into column vectors. + l = numel (x); x = reshape (x, l, 1); y = reshape (y, l, 1); - X = (x * ones (1, n+1)) .^ (ones (l, 1) * (n : -1 : 0)); + ## Construct the Vandermonde matrix. + v = (x * ones (1, n+1)) .^ (ones (l, 1) * (n : -1 : 0)); - p = X \ y; + ## Solve by QR decomposition. + [q, r, k] = qr (v, 0); + p = r \ (y' * q)'; + p(k) = p; if (nargout > 1) - - yf = X*p; + yf = v*p; if (y_is_row_vector) s.yf = yf.'; @@ -92,15 +100,13 @@ s.yf = yf; endif - [s.R, dummy] = chol (X'*X); - s.X = X; + s.R = r; + s.X = v; s.df = l - n - 1; s.normr = norm (yf - y); - endif - ## Return value should be a row vector. - + ## Return a row vector. p = p.'; endfunction @@ -109,12 +115,12 @@ %! x = [-2, -1, 0, 1, 2]; %! assert(all (all (abs (polyfit (x, x.^2+x+1, 2) - [1, 1, 1]) < sqrt (eps)))); +%!error(polyfit ([1, 2; 3, 4], [1, 2, 3, 4], 2)) + %!test %! x = [-2, -1, 0, 1, 2]; %! assert(all (all (abs (polyfit (x, x.^2+x+1, 3) - [0, 1, 1, 1]) < sqrt (eps)))); -%!error polyfit ([1, 2; 3, 4], [1, 2; 3, 4], 4); - %!test %! x = [-2, -1, 0, 1, 2]; %! fail("polyfit (x, x.^2+x+1)"); @@ -123,3 +129,45 @@ %! x = [-2, -1, 0, 1, 2]; %! fail("polyfit (x, x.^2+x+1, [])"); +## Test difficult case where scaling is really needed. This example +## demonstrates the rather poor result which occurs when the dependent +## variable is not normalized properly. +## Also check the usage of 2nd & 3rd output arguments. +%!test +%! x = [ -1196.4, -1195.2, -1194, -1192.8, -1191.6, -1190.4, -1189.2, -1188, \ +%! -1186.8, -1185.6, -1184.4, -1183.2, -1182]; +%! y = [ 315571.7086, 315575.9618, 315579.4195, 315582.6206, 315585.4966, \ +%! 315588.3172, 315590.9326, 315593.5934, 315596.0455, 315598.4201, \ +%! 315600.7143, 315602.9508, 315605.1765 ]; +%! [p1, s1] = polyfit (x, y, 10); +%! [p2, s2, mu] = polyfit (x, y, 10); +%! assert (s1.normr, 0.11264, 0.1) +%! assert (s2.normr < s1.normr) + +%!test +%! x = 1:4; +%! p0 = [1i, 0, 2i, 4]; +%! y0 = polyval (p0, x); +%! p = polyfit (x, y0, numel(p0)-1); +%! assert (p, p0, 1000*eps) + +%!test +%! x = 1000 + (-5:5); +%! xn = (x - mean (x)) / std (x); +%! pn = ones (1,5); +%! y = polyval (pn, xn); +%! [p, s, mu] = polyfit (x, y, numel(pn)-1); +%! [p2, s2] = polyfit (x, y, numel(pn)-1); +%! assert (p, pn, s.normr) +%! assert (s.yf, y, s.normr) +%! assert (mu, [mean(x), std(x)]) +%! assert (s.normr/s2.normr < 1e-9) + +%!test +%! x = [1, 2, 3; 4, 5, 6]; +%! y = [0, 0, 1; 1, 0, 0]; +%! p = polyfit (x, y, 5); +%! expected = [0, 1, -14, 65, -112, 60]/12; +%! assert (p, expected, sqrt(eps)) + +