Mercurial > octave
changeset 31868:c6eeb8b44c28
Port statistics pkg mean, median, var, & std into core (patch #10314)
* mean.m: Overhaul function to vectorize 'nanflag' codepath, improve 'vecdim'
handling for arbitrary dimensions (bug #63460), ensure proper outputs for
empty inputs (bug #50583), and avoid overflow when summing large int values or
precision loss for trying to handle large int64/uint64 values as doubles
(bug #54567). Add BISTs for items above and to address corner case inputs.
* median.m: Overhaul function to implement 'nanflag' option to optionally
ignore NaN values (bug #50571), implement 'all' (bug #58116) and 'VECDIM'
(bug #58089) dimension input options, ensure proper outputs for empty inputs
(bug #50583), and avoid overflow when summing large int values and precision
loss for trying to handle large int64/uint64 values as doubles (bug #54567).
Add BISTs for items above and to address corner case inputs.
* var.m: Overhaul function to implement 'nanflag' option to optionally ignore
NaN values (bug #50571), implement 'all' (bug #58116) and 'VECDIM' (bug #58089)
dimension input options, avoid broadcasting operations from erroring on sparse
or diagonal type inputs (bug #63291), ensure proper outputs for empty inputs
(bug #50583). Add BISTs for items above and to address corner case inputs.
* std.m: Update docstring to match changes in var.
* NEWS.9.md: Note changes to above functions under Matlab Compatibility.
author | Nicholas R. Jankowski <jankowski.nicholas@gmail.com> |
---|---|
date | Wed, 01 Mar 2023 00:38:54 -0500 |
parents | a9c8b1f8fb32 |
children | 0149a2f4ac98 |
files | etc/NEWS.9.md scripts/statistics/mean.m scripts/statistics/median.m scripts/statistics/std.m scripts/statistics/var.m |
diffstat | 5 files changed, 1774 insertions(+), 414 deletions(-) [+] |
line wrap: on
line diff
--- a/etc/NEWS.9.md Tue Feb 28 15:45:03 2023 -0500 +++ b/etc/NEWS.9.md Wed Mar 01 00:38:54 2023 -0500 @@ -29,6 +29,14 @@ ### Matlab compatibility +- Overhauled `mean`, `median`, `var`, and `std` functions have been imported +from statistics package v1.5.4 to compatibly implement 'nanflag' (bug #50571), +'all' (bug #58116), and 'vecdim' (bug #58089) options, preserve output class, +and match expected output behavior for empty (bug #50583) and sparse inputs +(bug #63291). Both `median` and `mean` can handle large int values without +overflow or precision concerns (bug #54567). `median` also adopts the +'outtype' option from `mean`. + ### Alphabetical list of new functions added in Octave 9 * `tensorprod`
--- a/scripts/statistics/mean.m Tue Feb 28 15:45:03 2023 -0500 +++ b/scripts/statistics/mean.m Wed Mar 01 00:38:54 2023 -0500 @@ -1,6 +1,6 @@ ######################################################################## ## -## Copyright (C) 1995-2023 The Octave Project Developers +## Copyright (C) 1996-2023 The Octave Project Developers ## ## See the file COPYRIGHT.md in the top-level directory of this ## distribution or <https://octave.org/copyright/>. @@ -24,21 +24,19 @@ ######################################################################## ## -*- texinfo -*- -## @deftypefn {} {@var{y} =} mean (@var{x}) -## @deftypefnx {} {@var{y} =} mean (@var{x}, 'all') -## @deftypefnx {} {@var{y} =} mean (@var{x}, @var{dim}) -## @deftypefnx {} {@var{y} =} mean (@dots{}, '@var{outtype}') -## @deftypefnx {} {@var{y} =} mean (@dots{}, '@var{nanflag}') +## @deftypefn {} {@var{m} =} mean (@var{x}) +## @deftypefnx {} {@var{m} =} mean (@var{x}, @var{dim}) +## @deftypefnx {} {@var{m} =} mean (@var{x}, @var{vecdim}) +## @deftypefnx {} {@var{m} =} mean (@var{x}, "all") +## @deftypefnx {} {@var{m} =} mean (@dots{}, @var{nanflag}) +## @deftypefnx {} {@var{m} =} mean (@dots{}, @var{outtype}) ## Compute the mean of the elements of @var{x}. ## -## @itemize -## @item ## If @var{x} is a vector, then @code{mean (@var{x})} returns the ## mean of the elements in @var{x} defined as ## @tex ## $$ {\rm mean}(x) = \bar{x} = {1\over N} \sum_{i=1}^N x_i $$ ## where $N$ is the number of elements of @var{x}. -## ## @end tex ## @ifnottex ## @@ -47,29 +45,29 @@ ## @end example ## ## @noindent -## where @math{N} is the number of elements in the @var{x} vector. +## where @math{N} is the number of elements in @var{x}. ## ## @end ifnottex ## -## @item -## If @var{x} is a matrix, then @code{mean} returns a row vector with the mean -## of each column in @var{x}. +## If @var{x} is an array, then @code{mean(@var{x})} computes the mean along the +## first nonsingleton dimension of @var{x}. +## +## The optional variable @var{dim} forces @code{mean} to operate over the +## specified dimension, which must be a positive integer-valued number. +## Specifying any singleton dimension in @var{x}, including any dimension +## exceeding @code{ndims (@var{x})}, will result in a mean equal to @var{x}. ## -## @item -## If @var{x} is a multi-dimensional array, then @code{mean} operates along the -## first non-singleton dimension of @var{x}. -## @end itemize +## Specifying the dimensions as @var{vecdim}, a vector of non-repeating +## dimensions, will return the mean over the array slice defined by +## @var{vecdim}. If @var{vecdim} indexes all dimensions of @var{x}, then it is +## equivalent to the option @qcode{"all"}. Any dimension in @var{vecdim} greater +## than @code{ndims (@var{x})} is ignored. ## -## The optional input @var{dim} forces @code{mean} to operate over the -## specified dimension(s). @var{dim} can either be a scalar dimension or a -## vector of non-repeating dimensions. Dimensions must be positive integers, -## and the mean is calculated over the array slice defined by @var{dim}. -## -## Specifying dimension @qcode{"all"} will force @code{mean} to operate on all -## elements of @var{x}, and is equivalent to @code{mean (@var{x}(:))}. +## Specifying the dimension as @qcode{"all"} will force @code{mean} to operate +## on all elements of @var{x}, and is equivalent to @code{mean (@var{x}(:))}. ## ## The optional input @var{outtype} specifies the data type that is returned. -## Valid values are: +## @var{outtype} can take the following values: ## ## @table @asis ## @item @qcode{'default'} : Output is of type double, unless the input is @@ -77,174 +75,306 @@ ## ## @item @qcode{'double'} : Output is of type double. ## -## @item @qcode{'native'} : Output is of the same type as the input -## (@code{class (@var{x})}), unless the input is logical in which case the +## @item @qcode{'native'} : Output is of the same type as the input as reported +## by (@code{class (@var{x})}), unless the input is logical in which case the ## output is of type double. -## ## @end table ## -## The optional input @var{nanflag} specifies whether to include/exclude NaN -## values from the calculation. By default, NaN values are included in the -## calculation (@var{nanflag} has the value @qcode{'includenan'}). To exclude -## NaN values, set the value of @var{nanflag} to @qcode{'omitnan'}. +## The optional variable @var{nanflag} specifies whether to include or exclude +## NaN values from the calculation using any of the previously specified input +## argument combinations. The default value for @var{nanflag} is "includenan" +## which keeps NaN values in the calculation. To exclude NaN values set the +## value of @var{nanflag} to "omitnan". The output will still contain NaN +## values if @var{x} consists of all NaN values in the operating dimension. ## -## @seealso{median, mode} +## @seealso{median, mode, movmean} ## @end deftypefn -function y = mean (x, varargin) +function m = mean (x, varargin) + + if (nargin < 1 || nargin > 4) + print_usage (); + endif - if (nargin < 1 || nargin > 4 || ! all (cellfun (@ischar, varargin(2:end)))) + ## Set initial conditions + all_flag = 0; + omitnan = 0; + out_flag = 0; + + nvarg = numel (varargin); + varg_chars = cellfun ("ischar", varargin); + outtype = "default"; + szx = size (x); + ndx = ndims (x); + + if (nvarg > 1 && ! varg_chars(2:end)) + ## Only first varargin can be numeric print_usage (); endif - ## Check all char arguments. - all_flag = false; - omitnan = false; - outtype = "default"; - - for i = 1:numel (varargin) - if (ischar (varargin{i})) - switch (varargin{i}) + ## Process any other char arguments. + if (any (varg_chars)) + for i = varargin(varg_chars) + switch (lower (i{:})) case "all" all_flag = true; + + case "omitnan" + omitnan = true; + case "includenan" omitnan = false; - case "omitnan" - omitnan = true; - case {"default", "double", "native"} - outtype = varargin{i}; + + case "default" + if (out_flag) + error ("mean: only one OUTTYPE can be specified.") + endif + if (isa (x, "single")) + outtype = "single"; + else + outtype = "double"; + endif + out_flag = 1; + + case "native" + outtype = class (x); + if (out_flag) + error ("mean: only one OUTTYPE can be specified.") + elseif (strcmp (outtype, "logical")) + outtype = "double"; + elseif (strcmp (outtype, "char")) + error ("mean: OUTTYPE 'native' cannot be used with char inputs."); + endif + out_flag = 1; + + case "double" + if (out_flag) + error ("mean: only one OUTTYPE can be specified.") + endif + outtype = "double"; + out_flag = 1; + otherwise print_usage (); endswitch + endfor + varargin(varg_chars) = []; + nvarg = numel (varargin); + endif + + if strcmp (outtype, "default") + if (isa (x, "single")) + outtype = "single"; + else + outtype = "double"; endif - endfor - varargin(cellfun (@ischar, varargin)) = []; + endif - if (((numel (varargin) == 1) && ! (isnumeric (varargin{1}))) ... - || (numel (varargin) > 1)) + if ((nvarg > 1) || ((nvarg == 1) && ! (isnumeric (varargin{1})))) + ## After trimming char inputs can only be one varargin left, must be numeric print_usage (); endif - if (! (isnumeric (x) || islogical (x))) - error ("mean: X must be either a numeric or logical vector or matrix"); + if (! (isnumeric (x) || islogical (x) || ischar (x))) + error ("mean: X must be either a numeric, boolean, or character array."); endif - if (numel (varargin) == 0) - + ## Process special cases for in/out size + if (nvarg == 0) ## Single numeric input argument, no dimensions given. + if (all_flag) - n = numel (x(:)); + x = x(:); + if (omitnan) - idx = isnan (x); - n -= sum (idx(:)); - x(idx) = 0; + x = x(! isnan (x)); endif - y = sum (x(:), 1) ./ n; + + if (any (isa (x, {"int64", "uint64"}))) + m = int64_mean (x, 1, numel (x), outtype); + else + m = sum (x) ./ numel (x); + endif + else - sz = size (x); ## Find the first non-singleton dimension. - (dim = find (sz != 1, 1)) || (dim = 1); - n = size (x, dim); + (dim = find (szx != 1, 1)) || (dim = 1); + n = szx(dim); if (omitnan) idx = isnan (x); n = sum (! idx, dim); x(idx) = 0; endif - y = sum (x, dim) ./ n; + + if (any (isa (x, {"int64", "uint64"}))) + m = int64_mean (x, dim, n, outtype); + else + m = sum (x, dim) ./ n; + endif + endif else ## Two numeric input arguments, dimensions given. Note scalar is vector! - dim = varargin{1}; - if (! (isvector (dim) && all (dim > 0) && all (rem (dim, 1) == 0))) - error ("mean: DIM must be a positive integer scalar or vector"); + vecdim = varargin{1}; + if (isempty (vecdim) || ! (isvector (vecdim) && all (vecdim > 0)) ... + || any (rem (vecdim, 1))) + error ("mean: DIM must be a positive integer scalar or vector."); endif - if (isscalar (dim)) - - n = size (x, dim); - if (omitnan) - idx = isnan (x); - n = sum (! idx, dim); - x(idx) = 0; - endif - y = sum (x, dim) ./ n; - + if (ndx == 2 && isempty (x) && szx == [0,0]) + ## FIXME: this special case handling could be removed once sum + ## compatably handles all sizes of empty inputs + sz_out = szx; + sz_out (vecdim(vecdim <= ndx)) = 1; + m = NaN (sz_out); else - sz = size (x); - ndims = numel (sz); - misdim = [1:ndims]; - - dim(dim > ndims) = []; # weed out dimensions larger than array - misdim(dim) = []; # remove dims asked for leaving missing dims - - switch (numel (misdim)) - ## if all dimensions are given, compute x(:) - case 0 - n = numel (x(:)); + if (isscalar (vecdim)) + if (vecdim > ndx) + m = x; + else + n = szx(vecdim); if (omitnan) - idx = isnan (x); - n -= sum (idx(:)); - x(idx) = 0; + nanx = isnan (x); + n = sum (! nanx, vecdim); + x(nanx) = 0; endif - y = sum (x(:), 1) ./ n; + + if (any (isa (x, {"int64", "uint64"}))) + m = int64_mean (x, vecdim, n, outtype); + else + m = sum (x, vecdim) ./ n; + endif + + endif - ## for 1 dimension left, return column vector - case 1 - x = permute (x, [misdim, dim]); - y = zeros (size (x, 1), 1, "like", x); - for i = 1:size (x, 1) - x_vec = x(i,:)(:); + else + vecdim = sort (vecdim); + if (! all (diff (vecdim))) + error ("mean: VECDIM must contain non-repeating positive integers."); + endif + ## Ignore exceeding dimensions in VECDIM + vecdim(find (vecdim > ndims (x))) = []; + + if (isempty (vecdim)) + m = x; + else + ## Move vecdims to dim 1. + + ## Calculate permutation vector + remdims = 1 : ndx; # All dimensions + remdims(vecdim) = []; # Delete dimensions specified by vecdim + nremd = numel (remdims); + + ## If all dimensions are given, it is similar to all flag + if (nremd == 0) + x = x(:); if (omitnan) - x_vec = x_vec(! isnan (x_vec)); + x = x(! isnan (x)); + endif + + if (any (isa (x, {"int64", "uint64"}))) + m = int64_mean (x, 1, numel (x), outtype); + else + m = sum (x) ./ numel (x); endif - y(i) = sum (x_vec, 1) ./ numel (x_vec); - endfor - y = ipermute (y, [misdim, dim]); + + else + ## Permute to bring vecdims to front + perm = [vecdim, remdims]; + x = permute (x, perm); + + ## Reshape to squash all vecdims in dim1 + num_dim = prod (szx(vecdim)); + szx(vecdim) = []; + szx = [ones(1, numel(vecdim)), szx]; + szx(1) = num_dim; + x = reshape (x, szx); - ## for 2 dimensions left, return matrix - case 2 - x = permute (x, [misdim, dim]); - y = zeros (size (x, 1), size (x, 2), "like", x); - for i = 1:size (x, 1) - for j = 1:size (x, 2) - x_vec = x(i,j,:)(:); - if (omitnan) - x_vec = x_vec(! isnan (x_vec)); - endif - y(i,j) = sum (x_vec, 1) ./ numel (x_vec); - endfor - endfor - y = ipermute (y, [misdim, dim]); + ## Calculate mean on dim1 + if (omitnan) + nanx = isnan (x); + x(nanx) = 0; + n = sum (! nanx, 1); + else + n = szx(1); + endif - ## for more than 2 dimensions left, throw error - otherwise - error ("DIM must index at least N-2 dimensions of X"); - endswitch + if (any (isa (x, {"int64", "uint64"}))) + m = int64_mean (x, 1, n, outtype); + else + m = sum (x, 1) ./ n; + endif + + ## Inverse permute back to correct dimensions + m = ipermute (m, perm); + endif + endif + endif endif - endif - ## Convert output if requested - switch (outtype) - case "default" - ## do nothing, the operators already do the right thing. - case "double" - y = double (y); - case "native" - if (! islogical (x)) - y = cast (y, class (x)); - endif - otherwise - ## FIXME: This is unreachable code. Valid values already - ## checked in input validation. - error ("mean: OUTTYPE '%s' not recognized", outtype); - endswitch - + ## Convert output as requested + if (! strcmp (class (m), outtype)) + switch (outtype) + case "double" + m = double (m); + case "single" + m = single (m); + otherwise + if (! islogical (x)) + m = cast (m, outtype); + endif + endswitch + endif endfunction +function m = int64_mean (x, dim, n, outtype) + ## Avoid int overflow in large ints. Smaller ints processed as double + ## avoids overflow but large int64 values have floating pt error as double. + ## Use integer math and manual remainder correction to avoid this. + if (any (abs (x(:)) >= flintmax / n)) + rmdr = double (rem (x, n)) / n; + rmdr_hilo = logical (int8 (rmdr)); # Integer rounding direction indicator + + ## Do 'native' int summation to prevent double precision error, + ## then add back in lost round-up/down remainders. + + m = sum (x/n, dim, "native"); + + ## rmdr.*!rmdr_hilo = remainders that were rounded down in abs val + ## signs retained, can be summed and added back. + ## rmdr.*rmdr_hilo = remainders that were rounded up in abs val. + ## need to add back difference between 1 and rmdr, retaining sign. + + rmdr = sum (rmdr .* !rmdr_hilo, dim) - ... + sum ((1 - abs (rmdr)) .* rmdr_hilo .* sign (rmdr), dim); + + if (any (abs (m(:)) >= flintmax)) + ## Avoid float errors when combining for large m. + ## FIXME - may also need to include checking rmdir for large numel (x), + ## as its value could be on the order of numel (x). + if (any (strcmp (outtype, {"int64", "uint64"}))) + m = m + rmdr; + else + m = double (m) + rmdr; + endif + + else + m = double(m) + rmdr; + switch (outtype) + case "int64" + m = int64 (m); + case "uint64" + m = uint64 (m); + endswitch + endif + else + m = double (sum (x, dim, "native")) ./ n; + endif +endfunction %!test %! x = -10:10; @@ -260,34 +390,121 @@ %!assert (mean (single ([1 0 1 1])), single (0.75)) %!assert (mean ([1 2], 3), [1 2]) -## Test outtype option +#### Test outtype option %!test %! in = [1 2 3]; %! out = 2; %! assert (mean (in, "default"), mean (in)); %! assert (mean (in, "default"), out); -%! +%! assert (mean (in, "double"), out); +%! assert (mean (in, "native"), out); + +%!test %! in = single ([1 2 3]); %! out = 2; %! assert (mean (in, "default"), mean (in)); %! assert (mean (in, "default"), single (out)); %! assert (mean (in, "double"), out); %! assert (mean (in, "native"), single (out)); -%! + +%!test +%! in = logical ([1 0 1]); +%! out = 2/3; +%! assert (mean (in, "default"), mean (in), eps); +%! assert (mean (in, "default"), out, eps); +%! assert (mean (in, "double"), out, eps); +%! assert (mean (in, "native"), out, eps); + +%!test +%! in = char ("ab"); +%! out = 97.5; +%! assert (mean (in, "default"), mean (in), eps); +%! assert (mean (in, "default"), out, eps); +%! assert (mean (in, "double"), out, eps); + +%!test %! in = uint8 ([1 2 3]); %! out = 2; %! assert (mean (in, "default"), mean (in)); %! assert (mean (in, "default"), out); %! assert (mean (in, "double"), out); %! assert (mean (in, "native"), uint8 (out)); -%! -%! in = logical ([1 0 1]); -%! out = 2/3; + +%!test +%! in = uint8 ([0 1 2 3]); +%! out = 1.5; +%! out_u8 = 2; +%! assert (mean (in, "default"), mean (in), eps); +%! assert (mean (in, "default"), out, eps); +%! assert (mean (in, "double"), out, eps); +%! assert (mean (in, "native"), uint8 (out_u8)); +%! assert (class (mean (in, "native")), "uint8"); + +%!test ## internal sum exceeding intmax +%! in = uint8 ([3 141 141 255]); +%! out = 135; +%! assert (mean (in, "default"), mean (in)); +%! assert (mean (in, "default"), out); +%! assert (mean (in, "double"), out); +%! assert (mean (in, "native"), uint8 (out)); +%! assert (class (mean (in, "native")), "uint8"); + +%!test ## fractional answer with interal sum exceeding intmax +%! in = uint8 ([1 141 141 255]); +%! out = 134.5; +%! out_u8 = 135; %! assert (mean (in, "default"), mean (in)); %! assert (mean (in, "default"), out); -%! assert (mean (in, "native"), out); # logical ignores native option +%! assert (mean (in, "double"), out); +%! assert (mean (in, "native"), uint8 (out_u8)); +%! assert (class (mean (in, "native")), "uint8"); -## Test single input and optional arguments "all", DIM, "omitnan") +%!test <54567> ## large int64 sum exceeding intmax and double precision limit +%! in_same = uint64 ([intmax("uint64") intmax("uint64")-2]); +%! out_same = intmax ("uint64")-1; +%! in_opp = int64 ([intmin("int64"), intmax("int64")-1]); +%! out_opp = -1; +%! in_neg = int64 ([intmin("int64") intmin("int64")+2]); +%! out_neg = intmin ("int64")+1; +%! +%! ## both positive +%! assert (mean (in_same, "default"), mean (in_same)); +%! assert (mean (in_same, "default"), double (out_same)); +%! assert (mean (in_same, "double"), double (out_same)); +%! assert (mean (in_same, "native"), uint64 (out_same)); +%! assert (class (mean (in_same, "native")), "uint64"); +%! +%! ## opposite signs +%! assert (mean (in_opp, "default"), mean (in_opp)); +%! assert (mean (in_opp, "default"), double (out_opp)); +%! assert (mean (in_opp, "double"), double (out_opp)); +%! assert (mean (in_opp, "native"), int64 (out_opp)); +%! assert (class (mean (in_opp, "native")), "int64"); +%! +%! ## both negative +%! assert (mean (in_neg, "default"), mean (in_neg)); +%! assert (mean (in_neg, "default"), double(out_neg)); +%! assert (mean (in_neg, "double"), double(out_neg)); +%! assert (mean (in_neg, "native"), int64(out_neg)); +%! assert (class (mean (in_neg, "native")), "int64"); + +## Additional tests int64 and double precision limits +%!test <54567> +%! in = [(intmin('int64')+5), (intmax('int64'))-5]; +%! assert (mean (in, "native"), int64(-1)); +%! assert (class (mean (in, "native")), "int64"); +%! assert (mean (double(in)), double(0) ); +%! assert (mean (in), double(-0.5) ); +%! assert (mean (in, "default"), double(-0.5) ); +%! assert (mean (in, "double"), double(-0.5) ); +%! assert (mean (in, "all", "native"), int64(-1)); +%! assert (mean (in, 2, "native"), int64(-1)); +%! assert (mean (in, [1 2], "native"), int64(-1)); +%! assert (mean (in, [2 3], "native"), int64(-1)); +%! assert (mean ([intmin("int64"), in, intmax("int64")]), double(-0.5)) +%! assert (mean ([in; int64([1 3])], 2, "native"), int64([-1; 2])); + +## Test input and optional arguments "all", DIM, "omitnan") %!test %! x = [-10:10]; %! y = [x;x+5;x-5]; @@ -298,6 +515,8 @@ %! assert (mean (y', "omitnan"), [0 5.35 -5]); %! z = y + 20; %! assert (mean (z, "all"), NaN); +%! assert (mean (z, "all", "includenan"), NaN); +%! assert (mean (z, "all", "omitnan"), 20.03225806451613, 4e-14); %! m = [20 NaN 15]; %! assert (mean (z'), m); %! assert (mean (z', "includenan"), m); @@ -307,7 +526,7 @@ %! assert (mean (z, 2, "native", "omitnan"), m'); %! assert (mean (z, 2, "omitnan", "native"), m'); -## Test boolean input +# Test boolean input %!test %! assert (mean (true, "all"), 1); %! assert (mean (false), 0); @@ -318,7 +537,49 @@ %! assert (mean ([true false NaN], 2, "omitnan"), 0.5); %! assert (mean ([true false NaN], 2, "omitnan", "native"), 0.5); -## Test dimension indexing with vecdim in N-dimensional arrays +## Test char inputs +%!assert (mean ("abc"), double (98)) +%!assert (mean ("ab"), double (97.5), eps) +%!assert (mean ("abc", "double"), double (98)) +%!assert (mean ("abc", "default"), double (98)) + +## Test NaN inputs +%!test +%! x = magic (4); +%! x([2, 9:12]) = NaN; +%! assert (mean (x), [NaN 8.5, NaN, 8.5], eps); +%! assert (mean (x,1), [NaN 8.5, NaN, 8.5], eps); +%! assert (mean (x,2), NaN(4,1), eps); +%! assert (mean (x,3), x, eps); +%! assert (mean (x, 'omitnan'), [29/3, 8.5, NaN, 8.5], eps); +%! assert (mean (x, 1, 'omitnan'), [29/3, 8.5, NaN, 8.5], eps); +%! assert (mean (x, 2, 'omitnan'), [31/3; 9.5; 28/3; 19/3], eps); +%! assert (mean (x, 3, 'omitnan'), x, eps); + +## Test empty inputs +%!assert (mean ([]), NaN(1,1)) +%!assert (mean (single([])), NaN(1,1,"single")) +%!assert (mean ([], 1), NaN(1,0)) +%!assert (mean ([], 2), NaN(0,1)) +%!assert (mean ([], 3), NaN(0,0)) +%!assert (mean (ones(1,0)), NaN(1,1)) +%!assert (mean (ones(1,0), 1), NaN(1,0)) +%!assert (mean (ones(1,0), 2), NaN(1,1)) +%!assert (mean (ones(1,0), 3), NaN(1,0)) +%!assert (mean (ones(0,1)), NaN(1,1)) +%!assert (mean (ones(0,1), 1), NaN(1,1)) +%!assert (mean (ones(0,1), 2), NaN(0,1)) +%!assert (mean (ones(0,1), 3), NaN(0,1)) +%!assert (mean (ones(0,1,0)), NaN(1,1,0)) +%!assert (mean (ones(0,1,0), 1), NaN(1,1,0)) +%!assert (mean (ones(0,1,0), 2), NaN(0,1,0)) +%!assert (mean (ones(0,1,0), 3), NaN(0,1,1)) +%!assert (mean (ones(0,0,1,0)), NaN(1,0,1,0)) +%!assert (mean (ones(0,0,1,0), 1), NaN(1,0,1,0)) +%!assert (mean (ones(0,0,1,0), 2), NaN(0,1,1,0)) +%!assert (mean (ones(0,0,1,0), 3), NaN(0,0,1,0)) + +## Test dimension indexing with vecdim in n-dimensional arrays %!test %! x = repmat ([1:20;6:25], [5 2 6 3]); %! assert (size (mean (x, [3 2])), [10 1 1 3]); @@ -327,33 +588,43 @@ %! assert (size (mean (x, [1 4 3])), [1 40]); %! assert (size (mean (x, [1 2 3 4])), [1 1]); -## Test results with vecdim in N-dimensional arrays and "omitnan" +## Test exceeding dimensions +%!assert (mean (ones (2,2), 3), ones (2,2)); +%!assert (mean (ones (2,2,2), 99), ones (2,2,2)); +%!assert (mean (magic (3), 3), magic (3)); +%!assert (mean (magic (3), [1 3]), [5, 5, 5]); +%!assert (mean (magic (3), [1 99]), [5, 5, 5]); + +## Test results with vecdim in n-dimensional arrays and "omitnan" %!test %! x = repmat ([1:20;6:25], [5 2 6 3]); %! m = repmat ([10.5;15.5], [5 1 1 3]); %! assert (mean (x, [3 2]), m, 4e-14); %! x(2,5,6,3) = NaN; -%! m(2,3) = NaN; +%! m(2,1,1,3) = NaN; %! assert (mean (x, [3 2]), m, 4e-14); -%! m(2,3) = 15.52301255230125; +%! m(2,1,1,3) = 15.52301255230125; %! assert (mean (x, [3 2], "omitnan"), m, 4e-14); +## Test input case insensitivity +%!assert (mean ([1 2 3], "aLL"), 2); +%!assert (mean ([1 2 3], "OmitNan"), 2); +%!assert (mean ([1 2 3], "DOUBle"), 2); + ## Test input validation -%!error <Invalid call> mean () -%!error <Invalid call> mean (1, 2, 3) -%!error <Invalid call> mean (1, 2, 3, 4, 5) -%!error <Invalid call> mean (1, "all", 3) -%!error <Invalid call> mean (1, "b") -%!error <Invalid call> mean (1, 1, "foo") -%!error <X must be either a numeric or logical> mean ({1:5}) -%!error <X must be either a numeric or logical> mean ("char") +%!error <Invalid call to mean. Correct usage is> mean () +%!error <Invalid call to mean. Correct usage is> mean (1, 2, 3) +%!error <Invalid call to mean. Correct usage is> mean (1, 2, 3, 4) +%!error <Invalid call to mean. Correct usage is> mean (1, "all", 3) +%!error <Invalid call to mean. Correct usage is> mean (1, "b") +%!error <Invalid call to mean. Correct usage is> mean (1, 1, "foo") +%!error <OUTTYPE 'native' cannot be used with char> mean ("abc", "native") +%!error <X must be either a numeric, boolean, or character> mean ({1:5}) %!error <DIM must be a positive integer> mean (1, ones (2,2)) %!error <DIM must be a positive integer> mean (1, 1.5) -%!error <DIM must be a positive integer> mean (1, -1) -%!error <DIM must be a positive integer> mean (1, -1.5) %!error <DIM must be a positive integer> mean (1, 0) -%!error <DIM must be a positive integer> mean (1, NaN) -%!error <DIM must be a positive integer> mean (1, Inf) -%!error <DIM must index at least N-2 dimensions of X> -%! mean (repmat ([1:20;6:25], [5 2 6 3 5]), [1 2]) - +%!error <DIM must be a positive integer> mean (1, []) +%!error <DIM must be a positive integer> mean (repmat ([1:20;6:25], [5 2]), -1) +%!error <DIM must be a positive integer> mean (repmat ([1:5;5:9], [5 2]), [1 -1]) +%!error <DIM must be a positive integer> mean (1, ones(1,0)) +%!error <VECDIM must contain non-repeating> mean (1, [2 2])
--- a/scripts/statistics/median.m Tue Feb 28 15:45:03 2023 -0500 +++ b/scripts/statistics/median.m Wed Mar 01 00:38:54 2023 -0500 @@ -24,9 +24,13 @@ ######################################################################## ## -*- texinfo -*- -## @deftypefn {} {@var{y} =} median (@var{x}) -## @deftypefnx {} {@var{y} =} median (@var{x}, @var{dim}) -## Compute the median value of the elements of the vector @var{x}. +## @deftypefn {} {@var{m} =} median (@var{x}) +## @deftypefnx {} {@var{m} =} median (@var{x}, @var{dim}) +## @deftypefnx {} {@var{m} =} median (@var{x}, @var{vecdim}) +## @deftypefnx {} {@var{m} =} median (@var{x}, "all") +## @deftypefnx {} {@var{m} =} median (@dots{}, @var{nanflag}) +## @deftypefnx {} {@var{m} =} median (@dots{}, @var{outtype}) +## Compute the median value of the elements of @var{x}. ## ## When the elements of @var{x} are sorted, say ## @code{@var{s} = sort (@var{x})}, the median is defined as @@ -43,65 +47,414 @@ ## ## @example ## @group -## | @var{s}(ceil(N/2)) N odd +## | @var{s}(ceil (N/2)) N odd ## median (@var{x}) = | ## | (@var{s}(N/2) + @var{s}(N/2+1))/2 N even ## @end group ## @end example ## ## @end ifnottex -## If @var{x} is of a discrete type such as integer or logical, then -## the case of even @math{N} rounds up (or toward @code{true}). +## +## If @var{x} is a array, then @code{median (@var{x})} operates along the first +## non-singleton dimension of @var{x}. +## +## The optional variable @var{dim} forces @code{median} to operate over the +## specified dimension, which must be a positive integer-valued number. +## Specifying any singleton dimension in @var{x}, including any dimension +## exceeding @code{ndims (@var{x})}, will result in a median equal to @var{x}. +## +## Specifying the dimensions as @var{vecdim}, a vector of non-repeating +## dimensions, will return the median over the array slice defined by +## @var{vecdim}. If @var{vecdim} indexes all dimensions of @var{x}, then it is +## equivalent to the option @qcode{"all"}. Any dimension in @var{vecdim} greater +## than @code{ndims (@var{x})} is ignored. +## +## Specifying the dimension as @qcode{"all"} will force @code{median} to operate +## on all elements of @var{x}, and is equivalent to @code{median (@var{x}(:))}. +## +## @code{median (@dots{}, @var{outtype})} returns the median with a specified +## data type, using any of the input arguments in the previous syntaxes. +## @var{outtype} can take the following values: ## -## If @var{x} is a matrix, compute the median value for each column and -## return them in a row vector. +## @table @asis +## @item "default" +## Output is of type double, unless the input is single in which case the output +## is of type single. +## +## @item "double" +## Output is of type double. ## -## If the optional @var{dim} argument is given, operate along this dimension. -## @seealso{mean, mode} +## @item "native". +## Output is of the same type as the input (@code{class (@var{x})}), unless the +## input is logical in which case the output is of type double. +## @end table +## +## The optional variable @var{nanflag} specifies whether to include or exclude +## NaN values from the calculation using any of the previously specified input +## argument combinations. The default value for @var{nanflag} is "includenan" +## which keeps NaN values in the calculation. To exclude NaN values set the +## value of @var{nanflag} to "omitnan". The output will still contain NaN +## values if @var{x} consists of all NaN values in the operating dimension. +## +## @seealso{mean, mode, movmedian} ## @end deftypefn -function y = median (x, dim) +function m = median (x, varargin) - if (nargin < 1) + if (nargin < 1 || nargin > 4) print_usage (); endif if (! (isnumeric (x) || islogical (x))) - error ("median: X must be a numeric vector or matrix"); + error ("median: X must be either numeric or logical."); + endif + + ## Set initial conditions + all_flag = 0; + omitnan = 0; + perm_flag = 0; + out_flag = 0; + vecdim_flag = 0; + dim = []; + + nvarg = numel (varargin); + varg_chars = cellfun ("ischar", varargin); + szx = sz_out = size (x); + ndx = ndims (x); + outtype = class (x); + + if (nvarg > 1 && ! varg_chars(2:end)) + ## Only first varargin can be numeric + print_usage (); + endif + + ## Process any other char arguments. + if (any (varg_chars)) + for idx = varargin(varg_chars) + switch (tolower (idx{:})) + case "all" + all_flag = 1; + + case "omitnan" + omitnan = 1; + + case "includenan" + omitnan = 0; + + case "native" + if (out_flag) + error ("median: only one OUTTYPE can be specified.") + endif + if (strcmp (outtype, "logical")) + outtype = "double"; + endif + out_flag = 1; + + case "default" + if (out_flag) + error ("median: only one OUTTYPE can be specified.") + endif + if (! strcmp (outtype, "single")) + outtype = "double"; + endif + out_flag = 1; + + case "double" + if (out_flag) + error ("median: only one OUTTYPE can be specified.") + endif + outtype = "double"; + out_flag = 1; + + otherwise + print_usage (); + endswitch + endfor + + varargin(varg_chars) = []; + nvarg = numel (varargin); + endif + + if (((nvarg == 1) && ! (isnumeric (varargin{1}))) || (nvarg > 1)) + ## After trimming char inputs should only be one numeric varargin left + print_usage (); + endif + + ## Process special cases for in/out size + if (nvarg > 0) + ## dim or vecdim provided + if (all_flag) + error ("median: 'all' cannot be used with DIM or VECDIM options."); + endif + + dim = varargin{1}; + vecdim_flag = ! isscalar (dim); + + if (! (isvector (dim) && (dim > 0)) || any (rem (dim, 1))) + error ("median: DIM must be a positive integer scalar or vector."); + endif + + ## Adjust sz_out, account for possible dim > ndx by appending singletons + sz_out(ndx + 1 : max (dim)) = 1; + sz_out(dim(dim <= ndx)) = 1; + szx(ndx + 1 : max (dim)) = 1; + + if (vecdim_flag) + ## vecdim - try to simplify first + dim = sort (dim); + if (! all (diff (dim))) + error ("median: VECDIM must contain non-repeating positive integers."); + endif + + ## dims > ndims(x) and dims only one element long don't affect median + sing_dim_x = find (szx != 1); + dim(dim > ndx | szx(dim) == 1) = []; + + if (isempty (dim)) + ## No dims left to process, return input as output + if (! strcmp (class (x), outtype)) + m = outtype_convert (x, outtype); + else + m = x; + endif + return; + elseif ((numel(dim) == numel(sing_dim_x)) ... + && unique ([dim, sing_dim_x]) == dim) + ## If DIMs cover all nonsingleton ndims(x) it's equivalent to "all" + ## (check lengths first to reduce unique overhead if not covered) + all_flag = true; + endif + endif + + else + ## Dim not provided. Determine scalar dimension + if (all_flag) + ## Special case 'all': Recast input as dim1 vector, process as normal + x = x(:); + szx = [numel(x), 1]; + dim = 1; + sz_out = [1 1]; + + elseif (isrow (x)) + ## Special case row vector: Avoid setting dim to 1. + dim = 2; + sz_out = [1, 1]; + + elseif (ndx == 2 && szx == [0, 0]) + ## Special case []: Do not apply sz_out(dim)=1 change + dim = 1; + sz_out = [1, 1]; + + else + ## General case: Set dim to first non-singleton, contract sz_out along dim + (dim = find (szx != 1, 1)) || (dim = 1); + sz_out(dim) = 1; + endif endif if (isempty (x)) - error ("median: X cannot be an empty matrix"); + ## Empty input - output NaN or class equivalent in pre-determined size + switch (outtype) + case {"double", "single"} + m = NaN (sz_out, outtype); + case ("logical") + m = false (sz_out); + otherwise + m = cast (NaN (sz_out), outtype); + endswitch + return; + endif + + if (szx(dim) == 1) + ## Operation along singleton dimension - nothing to do + if (! strcmp (class (x), outtype)) + m = outtype_convert (x, outtype); + else + m = x; + endif + return; + endif + + ## Permute dim to simplify all operations along dim1. At func. end ipermute. + + ## FIXME: for very large data sets, flattening all vecdim dimensions into dim1 + ## could hit index type limits + + if ((numel (dim) > 1) || (dim != 1 && ! isvector (x))) + perm_vect = 1 : ndx; + + if (! vecdim_flag) + ## Move dim to dim 1 + perm_vect([1, dim]) = [dim, 1]; + x = permute (x, perm_vect); + szx([1, dim]) = szx([dim, 1]); + dim = 1; + + else + ## Move vecdims to front + perm_vect(dim) = []; + perm_vect = [dim, perm_vect]; + x = permute (x, perm_vect); + + ## Reshape all vecdims into dim1 + num_dim = prod (szx(dim)); + szx(dim) = []; + szx = [ones(1, numel(dim)), szx]; + szx(1) = num_dim; + x = reshape (x, szx); + dim = 1; + endif + + perm_flag = true; + endif + + ## Find column locations of NaNs + nanfree = ! any (isnan (x), dim); + if (omitnan && nanfree(:)) + ## Don't use omitnan path if no NaNs are present. Prevents any data types + ## without a defined NaN from following slower omitnan codepath. + omitnan = 0; endif - nd = ndims (x); - sz = size (x); - if (nargin < 2) - ## Find the first non-singleton dimension. - (dim = find (sz > 1, 1)) || (dim = 1); + x = sort (x, dim); # Note: pushes any NaN's to end for omitnan compatability + + if (omitnan) + ## Ignore any NaN's in data. Each operating vector might have a + ## different number of non-NaN data points. + + if (isvector (x)) + ## Checks above ensure either dim1 or dim2 vector + x = x(! isnan (x)); + n = numel (x); + k = floor ((n + 1) / 2); + if (mod (n, 2)) + ## odd + m = x(k); + else + ## even + m = (x(k) + x(k + 1)) / 2; + endif + + else + n = sum (! isnan (x), 1); + k = floor ((n + 1) ./ 2); + m_idx_odd = mod (n, 2) & n; + m_idx_even = (! m_idx_odd) & n; + + m = NaN ([1, szx(2 : end)]); + + if (ndims (x) > 2) + szx = [szx(1), prod(szx(2 : end))]; + endif + + ## Grab kth value, k possibly different for each column + if (any (m_idx_odd(:))) + x_idx_odd = sub2ind (szx, k(m_idx_odd), find (m_idx_odd)); + m(m_idx_odd) = x(x_idx_odd); + endif + if (any (m_idx_even(:))) + k_even = k(m_idx_even)(:); + x_idx_even = sub2ind (szx, [k_even, k_even + 1], ... + (find (m_idx_even))(:, [1, 1])); + m(m_idx_even) = sum (x(x_idx_even), 2) / 2; + endif + endif + else - if (! (isscalar (dim) && dim == fix (dim) && dim > 0)) - error ("median: DIM must be an integer and a valid dimension"); + ## No "omitnan". All 'vectors' uniform length. + ## All types without a NaN value will use this path. + if (all (! nanfree)) + m = NaN (sz_out); + + else + if (isvector (x)) + n = numel (x); + k = floor ((n + 1) / 2); + + m = x(k); + if (! mod (n, 2)) + ## Even + if (any (isa (x, "integer"))) + ## avoid int overflow issues + m2 = x(k + 1); + if (sign (m) != sign (m2)) + m += m2; + m /= 2; + else + m += (m2 - m) / 2; + endif + else + m += (x(k + 1) - m) / 2; + endif + endif + + else + ## Nonvector, all operations were permuted to be along dim 1 + n = szx(1); + k = floor ((n + 1) / 2); + + if (isfloat (x)) + m = NaN ([1, szx(2 : end)]); + else + m = zeros ([1, szx(2 : end)], outtype); + endif + + if (! mod (n, 2)) + ## Even + if (any (isa (x, "integer"))) + ## avoid int overflow issues + + ## Use flattened index to simplify n-D operations + m(1, :) = x(k, :); + m2 = x(k + 1, :); + + samesign = prod (sign ([m(1, :); m2]), 1) == 1; + m(1, :) = samesign .* m(1, :) + ... + (m2 + !samesign .* m(1, :) - samesign .* m(1, :)) / 2; + + else + m(nanfree) = (x(k, nanfree) + x(k + 1, nanfree)) / 2; + endif + else + ## Odd. Use flattened index to simplify n-D operations + m(nanfree) = x(k, nanfree); + endif + endif endif endif - n = size (x, dim); - k = floor ((n+1) / 2); - if (mod (n, 2) == 1) - y = nth_element (x, k, dim); - else - y = sum (nth_element (x, k:k+1, dim), dim, "native") / 2; - if (islogical (x)) - y = logical (y); - endif + if (perm_flag) + ## Inverse permute back to correct dimensions + m = ipermute (m, perm_vect); endif - ## Inject NaNs where needed, to be consistent with Matlab. - if (isfloat (x)) - y(any (isnan (x), dim)) = NaN; + + ## Convert output type as requested + if (! strcmp (class (m), outtype)) + m = outtype_convert (m, outtype); endif endfunction +function m = outtype_convert (m, outtype) + switch (outtype) + case "single" + m = single (m); + case "double" + m = double (m); + otherwise + m = cast (m, outtype); + endswitch +endfunction + +%!assert (median (1), 1) +%!assert (median ([1,2,3]), 2) +%!assert (median ([1,2,3]'), 2) +%!assert (median (cat(3,3,1,2)), 2) +%!assert (median ([3,1,2]), 2) +%!assert (median ([2,4,6,8]), 5) +%!assert (median ([8,2,6,4]), 5) +%!assert (median (single ([1,2,3])), single (2)) +%!assert (median ([1,2], 3), [1,2]) %!test %! x = [1, 2, 3, 4, 5, 6]; @@ -111,12 +464,159 @@ %! %! assert (median (x) == median (x2) && median (x) == 3.5); %! assert (median (y) == median (y2) && median (y) == 4); -%! assert (median ([x2, 2*x2]), [3.5, 7]); -%! assert (median ([y2, 3*y2]), [4, 12]); +%! assert (median ([x2, 2 * x2]), [3.5, 7]); +%! assert (median ([y2, 3 * y2]), [4, 12]); + +## Test outtype option +%!test +%! in = [1 2 3]; +%! out = 2; +%! assert (median (in, "default"), median (in)); +%! assert (median (in, "default"), out); +%!test +%! in = single ([1 2 3]); +%! out = 2; +%! assert (median (in, "default"), single (median (in))); +%! assert (median (in, "default"), single (out)); +%! assert (median (in, "double"), double (out)); +%! assert (median (in, "native"), single (out)); +%!test +%! in = uint8 ([1 2 3]); +%! out = 2; +%! assert (median (in, "default"), double (median (in))); +%! assert (median (in, "default"), double (out)); +%! assert (median (in, "double"), out); +%! assert (median (in, "native"), uint8 (out)); +%!test +%! in = logical ([1 0 1]); +%! out = 1; +%! assert (median (in, "default"), double (median (in))); +%! assert (median (in, "default"), double (out)); +%! assert (median (in, "double"), double (out)); +%! assert (median (in, "native"), double (out)); + +## Test single input and optional arguments "all", DIM, "omitnan") +%!test +%! x = repmat ([2 2.1 2.2 2 NaN; 3 1 2 NaN 5; 1 1.1 1.4 5 3], [1, 1, 4]); +%! y = repmat ([2 1.1 2 NaN NaN], [1, 1, 4]); +%! assert (median (x), y); +%! assert (median (x, 1), y); +%! y = repmat ([2 1.1 2 3.5 4], [1, 1, 4]); +%! assert (median (x, "omitnan"), y); +%! assert (median (x, 1, "omitnan"), y); +%! y = repmat ([2.05; 2.5; 1.4], [1, 1, 4]); +%! assert (median (x, 2, "omitnan"), y); +%! y = repmat ([NaN; NaN; 1.4], [1, 1, 4]); +%! assert (median (x, 2), y); +%! assert (median (x, "all"), NaN); +%! assert (median (x, "all", "omitnan"), 2); +%!assert (median (cat (3, 3, 1, NaN, 2), "omitnan"), 2) +%!assert (median (cat (3, 3, 1, NaN, 2), 3, "omitnan"), 2) + +# Test boolean input +%!test +%! assert (median (true, "all"), logical (1)); +%! assert (median (false), logical (0)); +%! assert (median ([true false true]), true); +%! assert (median ([true false true], 2), true); +%! assert (median ([true false true], 1), logical ([1 0 1])); +%! assert (median ([true false NaN], 1), [1 0 NaN]); +%! assert (median ([true false NaN], 2), NaN); +%! assert (median ([true false NaN], 2, "omitnan"), 0.5); +%! assert (median ([true false NaN], 2, "omitnan", "native"), double(0.5)); + +## Test dimension indexing with vecdim in n-dimensional arrays +%!test +%! x = repmat ([1:20;6:25], [5 2 6 3]); +%! assert (size (median (x, [3 2])), [10 1 1 3]); +%! assert (size (median (x, [1 2])), [1 1 6 3]); +%! assert (size (median (x, [1 2 4])), [1 1 6]); +%! assert (size (median (x, [1 4 3])), [1 40]); +%! assert (size (median (x, [1 2 3 4])), [1 1]); + +## Test exceeding dimensions +%!assert (median (ones (2,2), 3), ones (2,2)); +%!assert (median (ones (2,2,2), 99), ones (2,2,2)); +%!assert (median (magic (3), 3), magic (3)); +%!assert (median (magic (3), [1 3]), [4, 5, 6]); +%!assert (median (magic (3), [1 99]), [4, 5, 6]); -%!assert (median (single ([1,2,3])), single (2)) +## Test results with vecdim in n-dimensional arrays and "omitnan" +%!test +%! x = repmat ([2 2.1 2.2 2 NaN; 3 1 2 NaN 5; 1 1.1 1.4 5 3], [1, 1, 4]); +%! assert (median (x, [3 2]), [NaN NaN 1.4]'); +%! assert (median (x, [3 2], "omitnan"), [2.05 2.5 1.4]'); +%! assert (median (x, [1 3]), [2 1.1 2 NaN NaN]); +%! assert (median (x, [1 3], "omitnan"), [2 1.1 2 3.5 4]); + +## Test empty, NaN, Inf inputs +%!assert (median (NaN), NaN) +%!assert (median (NaN, "omitnan"), NaN) +%!assert (median (NaN (2)), [NaN NaN]) +%!assert (median (NaN (2), "omitnan"), [NaN NaN]) +%!assert (median ([1 NaN 3]), NaN) +%!assert (median ([1 NaN 3], 1), [1 NaN 3]) +%!assert (median ([1 NaN 3], 2), NaN) +%!assert (median ([1 NaN 3]'), NaN) +%!assert (median ([1 NaN 3]', 1), NaN) +%!assert (median ([1 NaN 3]', 2), [1; NaN; 3]) +%!assert (median ([1 NaN 3], "omitnan"), 2) +%!assert (median ([1 NaN 3]', "omitnan"), 2) +%!assert (median ([1 NaN 3], 1, "omitnan"), [1 NaN 3]) +%!assert (median ([1 NaN 3], 2, "omitnan"), 2) +%!assert (median ([1 NaN 3]', 1, "omitnan"), 2) +%!assert (median ([1 NaN 3]', 2, "omitnan"), [1; NaN; 3]) +%!assert (median ([1 2 NaN 3]), NaN) +%!assert (median ([1 2 NaN 3], "omitnan"), 2) %!assert (median ([1,2,NaN;4,5,6;NaN,8,9]), [NaN, 5, NaN]) -%!assert (median ([1,2], 3), [1,2]) +%!assert (median ([1 2 ; NaN 4]), [NaN 3]) +%!assert (median ([1 2 ; NaN 4], "omitnan"), [1 3]) +%!assert (median ([1 2 ; NaN 4], 1, "omitnan"), [1 3]) +%!assert (median ([1 2 ; NaN 4], 2, "omitnan"), [1.5; 4], eps) +%!assert (median ([1 2 ; NaN 4], 3, "omitnan"), [1 2 ; NaN 4]) +%!assert (median ([NaN 2 ; NaN 4]), [NaN 3]) +%!assert (median ([NaN 2 ; NaN 4], "omitnan"), [NaN 3]) +%!assert (median (ones (1, 0, 3)), NaN (1, 1, 3)) + +%!assert (median (NaN("single")), NaN("single")); +%!assert (median (NaN("single"), "omitnan"), NaN("single")); +%!assert (median (NaN("single"), "double"), NaN("double")); +%!assert (median (single([1 2 ; NaN 4])), single([NaN 3])); +%!assert (median (single([1 2 ; NaN 4]), "double"), double([NaN 3])); +%!assert (median (single([1 2 ; NaN 4]), "omitnan"), single([1 3])); +%!assert (median (single([1 2 ; NaN 4]), "omitnan", "double"), double([1 3])); +%!assert (median (single([NaN 2 ; NaN 4]), "double"), double([NaN 3])); +%!assert (median (single([NaN 2 ; NaN 4]), "omitnan"), single([NaN 3])); +%!assert (median (single([NaN 2 ; NaN 4]), "omitnan", "double"), double([NaN 3])); + +%!assert (median (Inf), Inf); +%!assert (median (-Inf), -Inf); +%!assert (median ([-Inf Inf]), NaN); +%!assert (median ([3 Inf]), Inf); +%!assert (median ([3 4 Inf]), 4); +%!assert (median ([Inf 3 4]), 4); +%!assert (median ([Inf 3 Inf]), Inf); + +%!assert (median ([]), NaN); +%!assert (median (ones(1,0)), NaN); +%!assert (median (ones(0,1)), NaN); +%!assert (median ([], 1), NaN(1,0)); +%!assert (median ([], 2), NaN(0,1)); +%!assert (median ([], 3), NaN(0,0)); +%!assert (median (ones(1,0), 1), NaN(1,0)); +%!assert (median (ones(1,0), 2), NaN(1,1)); +%!assert (median (ones(1,0), 3), NaN(1,0)); +%!assert (median (ones(0,1), 1), NaN(1,1)); +%!assert (median (ones(0,1), 2), NaN(0,1)); +%!assert (median (ones(0,1), 3), NaN(0,1)); +%!assert (median (ones(0,1,0,1), 1), NaN(1,1,0)); +%!assert (median (ones(0,1,0,1), 2), NaN(0,1,0)); +%!assert (median (ones(0,1,0,1), 3), NaN(0,1,1)); +%!assert (median (ones(0,1,0,1), 4), NaN(0,1,0)); + +## Test complex inputs (should sort by abs(a)) +%!assert (median([1 3 3i 2 1i]), 2) +%!assert (median([1 2 4i; 3 2i 4]), [2, 1+1i, 2+2i]) ## Test multidimensional arrays %!shared a, b, x, y @@ -129,18 +629,80 @@ %! y = sort (b, 3); %!assert <*35679> (median (a, 4), x(:, :, :, 3)) %!assert <*35679> (median (b, 3), (y(:, :, 3, :) + y(:, :, 4, :))/2) +%!shared ## Clear shared to prevent variable echo for any later test failures ## Test non-floating point types %!assert (median ([true, false]), true) +%!assert (median (logical ([])), false) %!assert (median (uint8 ([1, 3])), uint8 (2)) +%!assert (median (uint8 ([])), uint8 (NaN)) +%!assert (median (uint8 ([NaN 10])), uint8 (5)) %!assert (median (int8 ([1, 3, 4])), int8 (3)) +%!assert (median (int8 ([])), int8 (NaN)) %!assert (median (single ([1, 3, 4])), single (3)) %!assert (median (single ([1, 3, NaN])), single (NaN)) +## Test same sign int overflow when getting mean of even number of values +%!assert <*54567> (median (uint8 ([253, 255])), uint8 (254)) +%!assert <*54567> (median (uint8 ([253, 254])), uint8 (254)) +%!assert <*54567> (median (int8 ([127, 126, 125, 124; 1 3 5 9])), ... +%! int8 ([64 65 65 67])) +%!assert <*54567> (median (int8 ([127, 126, 125, 124; 1 3 5 9]), 2), ... +%! int8 ([126; 4])) +%!assert <*54567> (median (int64 ([intmax("int64"), intmax("int64")-2])), ... +%! intmax ("int64") - 1) +%!assert <*54567> (median ( ... +%! int64 ([intmax("int64"), intmax("int64")-2; 1 2]), 2), ... +%! int64([intmax("int64") - 1; 2])) +%!assert <*54567> (median (uint64 ([intmax("uint64"), intmax("uint64")-2])), ... +%! intmax ("uint64") - 1) +%!assert <*54567> (median ( ... +%! uint64 ([intmax("uint64"), intmax("uint64")-2; 1 2]), 2), ... +%! uint64([intmax("uint64") - 1; 2])) + +## Test opposite sign int overflow when getting mean of even number of values +%!assert <*54567> (median (... +%! [intmin('int8') intmin('int8')+5 intmax('int8')-5 intmax('int8')]), ... +%! int8(-1)) +%!assert <*54567> (median ([int8([1 2 3 4]); ... +%! intmin('int8') intmin('int8')+5 intmax('int8')-5 intmax('int8')], 2), ... +%! int8([3;-1])) +%!assert <*54567> (median (... +%! [intmin('int64') intmin('int64')+5 intmax('int64')-5 intmax('int64')]), ... +%! int64(-1)) +%!assert <*54567> (median ([int64([1 2 3 4]); ... +%! intmin('int64') intmin('int64')+5 intmax('int64')-5 intmax('int64')], 2), ... +%! int64([3;-1])) + +## Test int accuracy loss doing mean of close int64/uint64 values as double +%!assert <*54567> (median ([intmax("uint64"), intmax("uint64")-2]), ... +%! intmax("uint64")-1) +%!assert <*54567> (median ([intmax("uint64"), intmax("uint64")-2], "default"), ... +%! double(intmax("uint64")-1)) +%!assert <*54567> (median ([intmax("uint64"), intmax("uint64")-2], "double"), ... +%! double(intmax("uint64")-1)) +%!assert <*54567> (median ([intmax("uint64"), intmax("uint64")-2], "native"), ... +%! intmax("uint64")-1) + +## Test input case insensitivity +%!assert (median ([1 2 3], "aLL"), 2); +%!assert (median ([1 2 3], "OmitNan"), 2); +%!assert (median ([1 2 3], "DOUBle"), 2); + ## Test input validation %!error <Invalid call> median () -%!error <X must be a numeric> median ({1:5}) -%!error <X cannot be an empty matrix> median ([]) -%!error <DIM must be an integer> median (1, ones (2,2)) -%!error <DIM must be an integer> median (1, 1.5) -%!error <DIM must be .* a valid dimension> median (1, 0) +%!error <Invalid call> median (1, 2, 3) +%!error <Invalid call> median (1, 2, 3, 4) +%!error <Invalid call> median (1, "all", 3) +%!error <Invalid call> median (1, "b") +%!error <Invalid call> median (1, 1, "foo") +%!error <'all' cannot be used with> median (1, 3, "all") +%!error <'all' cannot be used with> median (1, [2 3], "all") +%!error <X must be either numeric or logical> median ({1:5}) +%!error <X must be either numeric or logical> median ("char") +%!error <only one OUTTYPE can be specified> median(1, "double", "native") +%!error <DIM must be a positive integer> median (1, ones (2,2)) +%!error <DIM must be a positive integer> median (1, 1.5) +%!error <DIM must be a positive integer> median (1, 0) +%!error <DIM must be a positive integer> median ([1 2 3], [-1 1]) +%!error <VECDIM must contain non-repeating> median(1, [1 2 2])
--- a/scripts/statistics/std.m Tue Feb 28 15:45:03 2023 -0500 +++ b/scripts/statistics/std.m Wed Mar 01 00:38:54 2023 -0500 @@ -24,84 +24,104 @@ ######################################################################## ## -*- texinfo -*- -## @deftypefn {} {@var{y} =} std (@var{x}) -## @deftypefnx {} {@var{y} =} std (@var{x}, @var{w}) -## @deftypefnx {} {@var{y} =} std (@var{x}, @var{w}, @var{dim}) -## @deftypefnx {} {@var{y} =} std (@var{x}, @var{w}, @qcode{"ALL"}) -## @deftypefnx {} {[@var{y}, @var{mu}] =} std (@dots{}) +## @deftypefn {} {@var{s} =} std (@var{x}) +## @deftypefnx {} {@var{s} =} std (@var{x}, @var{w}) +## @deftypefnx {} {@var{s} =} std (@var{x}, @var{w}, @var{dim}) +## @deftypefnx {} {@var{s} =} std (@var{x}, @var{w}, @var{vecdim}) +## @deftypefnx {} {@var{s} =} std (@var{x}, @var{w}, @qcode{"ALL"}) +## @deftypefnx {} {@var{s} =} std (@dots{}, @var{nanflag}) +## @deftypefnx {} {[@var{s}, @var{m}] =} std (@dots{}) ## Compute the standard deviation of the elements of the vector @var{x}. ## ## The standard deviation is defined as ## @tex -## $$ -## {\rm std} (x) = \sigma = \sqrt{{\sum_{i=1}^N (x_i - \bar{x})^2 \over N - 1}} -## $$ -## where $\bar{x}$ is the mean value of @var{x} and $N$ is the number of elements of @var{x}. +## $$ {\rm std}(x) = \sqrt{{1\over N-1} \sum_{i=1}^N (x_i - \bar x )^2} $$ +## where $\bar{x}$ is the mean value of @var{x} and $N$ is the number of +## elements of @var{x}. ## @end tex ## @ifnottex ## ## @example ## @group -## std (@var{x}) = sqrt ( 1/(N-1) SUM_i (@var{x}(i) - mean(@var{x}))^2 ) +## std (@var{x}) = sqrt ((1 / (N-1)) * SUM_i ((@var{x}(i) - mean(@var{x}))^2)) ## @end group ## @end example ## ## @noindent -## where @math{N} is the number of elements of the @var{x} vector. +## where @math{N} is the number of elements of @var{x}. ## @end ifnottex ## -## If @var{x} is an array, compute the standard deviation for each column and -## return them in a row vector (or for an n-D array, the result is returned as -## an array of dimension 1 x n x m x @dots{}). +## If @var{x} is an array, compute the standard deviation along the first +## non-singleton dimensions of @var{x}. ## ## The optional argument @var{w} determines the weighting scheme to use. Valid ## values are: ## ## @table @asis ## @item 0 [default]: -## Normalize with @math{N-1}. This provides the square root of the best -## unbiased estimator of the variance. +## Normalize with @math{N-1} (population standard deviation). This provides the +## square root of the best unbiased estimator of the standard deviation. ## ## @item 1: -## Normalize with @math{N}@. This provides the square root of the second -## moment around the mean. +## Normalize with @math{N} (sample standard deviation). This provides the +## square root of the second moment around the mean. ## ## @item a vector: -## Compute the weighted standard deviation with non-negative scalar weights. -## The length of @var{w} must equal the size of @var{x} along dimension -## @var{dim}. +## Compute the weighted standard deviation with non-negative weights. +## The length of @var{w} must equal the size of @var{x} in the operating +## dimension. NaN values are permitted in @var{w}, will be multiplied with the +## associated values in @var{x}, and can be excluded by the @var{nanflag} +## option. +## +## @item an array: +## Similar to vector weights, but @var{w} must be the same size as @var{x}. If +## the operating dimension is supplied as @var{vecdim} or "all" and @var{w} is +## not a scalar, @var{w} must be an same-sized array. ## @end table ## -## If @math{N} is equal to 1 the value of @var{W} is ignored and normalization -## by @math{N} is used. +## Note: @var{w} must always be specified before specifying any of the following +## dimension options. To use the default value for @var{w} you may pass an empty +## input argument []. ## ## The optional variable @var{dim} forces @code{std} to operate over the -## specified dimension(s). @var{dim} can either be a scalar dimension or a -## vector of non-repeating dimensions. Dimensions must be positive integers, -## and the standard deviation is calculated over the array slice defined by -## @var{dim}. +## specified dimension, which must be a positive integer-valued number. +## Specifying any singleton dimension in @var{x}, including any dimension +## exceeding @code{ndims (@var{x})}, will result in a standard deviation of 0. +## +## Specifying the dimensions as @var{vecdim}, a vector of non-repeating +## dimensions, will return the standard deviation calculated over the array +## slice defined by @var{vecdim}. If @var{vecdim} indexes all dimensions of +## @var{x}, then it is equivalent to the option @qcode{"all"}. Any dimension in +## @var{vecdim} greater than @code{ndims (@var{x})} is ignored. ## -## Specifying dimension @qcode{"all"} will force @code{std} to operate on all -## elements of @var{x}, and is equivalent to @code{std (@var{x}(:))}. +## Specifying the dimension as @qcode{"all"} will force @code{std} to operate on +## all elements of @var{x}, and is equivalent to @code{std (@var{x}(:))}. ## -## When @var{dim} is a vector or @qcode{"all"}, @var{w} must be either 0 or 1. +## The optional variable @var{nanflag} specifies whether to include or exclude +## NaN values from the calculation using any of the previously specified input +## argument combinations. The default value for @var{nanflag} is "includenan" +## which keeps NaN values in the calculation. To exclude NaN values set the +## value of @var{nanflag} to "omitnan". The output will still contain NaN +## values if @var{x} consists of all NaN values in the operating dimension. ## -## The optional second output variable @var{mu} contains the mean or weighted -## mean used to calculate @var{y}, and will be the same size as @var{y}. +## The optional second output variable @var{mu} contains the mean of the +## elements of @var{x} used to calculate the standard deviation. If @var{v} is +## the weighted standard deviation, then @var{m} is also the weighted mean. +## ## @seealso{var, bounds, mad, range, iqr, mean, median} ## @end deftypefn -function [y, mu] = std (varargin) +function [s, m] = std (varargin) if (nargin < 1) print_usage (); endif if (nargout < 2) - y = sqrt (var (varargin{:})); + s = sqrt (var (varargin{:})); else - [y, mu] = var (varargin{:}); - y = sqrt (y); + [s, m] = var (varargin{:}); + s = sqrt (s); endif endfunction
--- a/scripts/statistics/var.m Tue Feb 28 15:45:03 2023 -0500 +++ b/scripts/statistics/var.m Wed Mar 01 00:38:54 2023 -0500 @@ -1,6 +1,6 @@ ######################################################################## ## -## Copyright (C) 1995-2023 The Octave Project Developers +## Copyright (C) 1996-2023 The Octave Project Developers ## ## See the file COPYRIGHT.md in the top-level directory of this ## distribution or <https://octave.org/copyright/>. @@ -27,214 +27,542 @@ ## @deftypefn {} {@var{v} =} var (@var{x}) ## @deftypefnx {} {@var{v} =} var (@var{x}, @var{w}) ## @deftypefnx {} {@var{v} =} var (@var{x}, @var{w}, @var{dim}) +## @deftypefnx {} {@var{v} =} var (@var{x}, @var{w}, @var{vecdim}) ## @deftypefnx {} {@var{v} =} var (@var{x}, @var{w}, @qcode{"ALL"}) +## @deftypefnx {} {@var{v} =} var (@dots{}, @var{nanflag}) ## @deftypefnx {} {[@var{v}, @var{m}] =} var (@dots{}) ## Compute the variance of the elements of the vector @var{x}. ## ## The variance is defined as ## @tex -## $$ -## {\rm var} (x) = \sigma^2 = {\sum_{i=1}^N (x_i - \bar{x})^2 \over N - 1} -## $$ +## $$ {\rm var}(x) = {1\over N-1} \sum_{i=1}^N (x_i - \bar x )^2 $$ ## where $\bar{x}$ is the mean value of @var{x} and $N$ is the number of ## elements of @var{x}. -## ## @end tex ## @ifnottex ## ## @example ## @group -## var (@var{x}) = 1/(N-1) SUM_i (@var{x}(i) - mean(@var{x}))^2 +## var (@var{x}) = (1 / (N-1)) * SUM_i ((@var{x}(i) - mean(@var{x}))^2) ## @end group ## @end example ## ## @noindent -## where @math{N} is the length of the @var{x} vector. -## +## where @math{N} is the number of elements of @var{x}. ## @end ifnottex -## If @var{x} is an array, compute the variance for each column and return them -## in a row vector (or for an n-D array, the result is returned as an array of -## dimension 1 x n x m x @dots{}). +## +## If @var{x} is an array, compute the variance along the first non-singleton +## dimensions of @var{x}. ## ## The optional argument @var{w} determines the weighting scheme to use. Valid -## values are +## values are: ## ## @table @asis ## @item 0 [default]: -## Normalize with @math{N-1}. This provides the square root of the best -## unbiased estimator of the variance. +## Normalize with @math{N-1} (population variance). This provides the square +## root of the best unbiased estimator of the variance. ## ## @item 1: -## Normalize with @math{N}@. This provides the square root of the second -## moment around the mean. +## Normalize with @math{N} (sample variance). This provides the square root of +## the second moment around the mean. ## ## @item a vector: -## Compute the weighted variance with non-negative scalar weights. The length -## of @var{w} must equal the size of @var{x} along dimension @var{dim}. +## Compute the weighted variance with non-negative weights. The length of +## @var{w} must equal the size of @var{x} in the operating dimension. NaN +## values are permitted in @var{w}, will be multiplied with the associated +## values in @var{x}, and can be excluded by the @var{nanflag} option. +## +## @item an array: +## Similar to vector weights, but @var{w} must be the same size as @var{x}. If +## the operating dimension is supplied as @var{vecdim} or "all" and @var{w} is +## not a scalar, @var{w} must be an same-sized array. ## @end table ## -## If @math{N} is equal to 1 the value of @var{W} is ignored and normalization -## by @math{N} is used. +## Note: @var{w} must always be specified before specifying any of the following +## dimension options. To use the default value for @var{w} you may pass an empty +## input argument []. ## ## The optional variable @var{dim} forces @code{var} to operate over the -## specified dimension(s). @var{dim} can either be a scalar dimension or a -## vector of non-repeating dimensions. Dimensions must be positive integers, -## and the variance is calculated over the array slice defined by @var{dim}. +## specified dimension, which must be a positive integer-valued number. +## Specifying any singleton dimension in @var{x}, including any dimension +## exceeding @code{ndims (@var{x})}, will result in a variance of 0. +## +## Specifying the dimensions as @var{vecdim}, a vector of non-repeating +## dimensions, will return the variance calculated over the array slice defined +## by @var{vecdim}. If @var{vecdim} indexes all dimensions of @var{x}, then it +## is equivalent to the option @qcode{"all"}. Any dimension in @var{vecdim} +## greater than @code{ndims (@var{x})} is ignored. +## +## Specifying the dimension as @qcode{"all"} will force @code{var} to operate on +## all elements of @var{x}, and is equivalent to @code{var (@var{x}(:))}. ## -## Specifying dimension @qcode{"all"} will force @code{var} to operate on all -## elements of @var{x}, and is equivalent to @code{var (@var{x}(:))}. +## The optional variable @var{nanflag} specifies whether to include or exclude +## NaN values from the calculation using any of the previously specified input +## argument combinations. The default value for @var{nanflag} is "includenan" +## which keeps NaN values in the calculation. To exclude NaN values set the +## value of @var{nanflag} to "omitnan". The output will still contain NaN +## values if @var{x} consists of all NaN values in the operating dimension. ## -## When @var{dim} is a vector or @qcode{"all"}, @var{w} must be either 0 or 1. +## The optional second output variable @var{mu} contains the mean of the +## elements of @var{x} used to calculate the variance. If @var{v} is the +## weighted variance, then @var{m} is also the weighted mean. ## -## The optional second output variable @var{mu} contains the mean or weighted -## mean used to calculate @var{v}, and will be the same size as @var{v}. -## @seealso{cov, std, skewness, kurtosis, moment} +## @seealso{std, mean, cov, skewness, kurtosis, moment} ## @end deftypefn -function [v, mu] = var (x, w = 0, dim = []) +function [v, m] = var (x, varargin) + + if (nargin < 1 || nargin > 4) + print_usage (); + endif - if (nargin < 1) + ## initialize variables + all_flag = false; + omitnan = false; + nvarg = numel (varargin); + varg_chars = cellfun ('ischar', varargin); + + ## Check all char arguments. + if (nvarg == 3 && ! varg_chars(3)) print_usage (); endif - if (! (isnumeric (x) || islogical (x))) - error ("var: X must be a numeric vector or matrix"); + if (any (varg_chars)) + for i = varargin(varg_chars) + switch (lower (i{:})) + case "all" + all_flag = true; + case "omitnan" + omitnan = true; + case "includenan" + omitnan = false; + otherwise + print_usage (); + endswitch + endfor + varargin(varg_chars) = []; + nvarg = numel (varargin); endif - if (isempty (w)) - w = 0; + # FIXME: when sparse can use broadcast ops, remove sparse checks and hacks + sprs_x = issparse (x); + w = 0; + weighted = false; # true if weight vector/array used + vecdim = []; + vecempty = true; + vecdim_scalar_vector = [false, false]; # [false, false] for empty vecdim + szx = size (x); + ndx = ndims (x); + + ## Check numeric arguments + if (! (isnumeric (x))) + error ("var: X must be a numeric vector or matrix."); + endif + if (isa (x, "single")) + outtype = "single"; + else + outtype = "double"; endif - nd = ndims (x); - sz = size (x); - emptydimflag = false; - - if (isempty (dim)) - emptydimflag = true; # Compatibility hack for empty x, ndims==2 - - ## Find the first non-singleton dimension. - (dim = find (sz != 1, 1)) || (dim = 1); + if (nvarg > 0) + if (nvarg > 2 || any (! cellfun ('isnumeric', varargin))) + print_usage (); + endif + ## Process weight input + if (any (varargin{1} < 0)) + error ("var: weights must not contain any negative values."); + endif + if (isscalar (varargin{1})) + w = varargin{1}; + if (! (w == 0 || w == 1) && ! isscalar (x)) + error ("var: normalization scalar must be either 0 or 1."); + endif + elseif (numel (varargin{1}) > 1) + weights = varargin{1}; + weighted = true; + endif + if (nvarg > 1) + ## Process dimension input + vecdim = varargin{2}; + if (! (vecempty = isempty (vecdim))) + ## Check for empty vecdim, won't change vsv if nonzero size empty + vecdim_scalar_vector = [isscalar(vecdim), isvector(vecdim)]; + endif + if (! (vecdim_scalar_vector(2) && all (vecdim > 0)) ... + || any (rem (vecdim, 1))) + error ("var: DIM must be a positive integer scalar or vector."); + endif + if (vecdim_scalar_vector(1) && vecdim > ndx && ! isempty (x)) + ## Scalar dimension larger than ndims(x), variance of any single number + ## is zero, except for inf, NaN, and empty values of x. + v = zeros (szx, outtype); + vn = ! isfinite (x); + v(vn) = NaN; + m = x; + return; + endif + if (vecdim_scalar_vector == [0 1] && (! all (diff (sort (vecdim))))) + error ("var: VECDIM must contain non-repeating positive integers."); + endif + endif + endif - else - if (isscalar (dim)) - if (dim < 1 || dim != fix (dim)) - error ("var: DIM must be a positive integer scalar, vector, or 'all'"); - endif - elseif (isnumeric (dim)) - if (! isvector (dim) && all (dim > 0) && all (rem (dim, 1) == 0)) - error ("var: DIM must be a positive integer scalar, vector, or 'all'"); - endif - if (dim != unique (dim, "stable")) - error ("var: vector DIM must contain non-repeating positive integers"); - endif - if (! isscalar (w)) - error ("var: W must be either 0 or 1 when DIM is a vector"); + ## Check for conflicting input arguments + if (all_flag && ! vecempty) + error ("var: 'all' flag cannot be used with DIM or VECDIM options."); + endif + if (weighted) + if (all_flag) + if (isvector (weights)) + if (numel (weights) != numel (x)) + error ("var: weight vector element count does not match X."); + endif + elseif (! (isequal (size (weights), szx))) + error ("var: weight matrix or array does not match X in size."); endif - ## Reshape X to compute the variance over an array slice - if (iscolumn (dim)) - dim = dim.'; + elseif (vecempty) + dim = find (szx > 1, 1); + if length (dim) == 0 + dim = 1; + endif + if (isvector (weights)) + if (numel (weights) != szx(dim)) + error (["var: weight vector length does not match operating ", ... + "dimension."]); + endif + elseif (! isequal (size (weights), szx)) + error ("var: weight matrix or array does not match X in size."); + endif + elseif (vecdim_scalar_vector(1)) + if (isvector (weights)) + if (numel (weights) != szx(vecdim)) + error (["var: weight vector length does not match operating ", ... + "dimension."]); + endif + elseif (! isequal (size (weights), szx)) + error ("var: weight matrix or array does not match X in size."); endif - collapsed_dims = dim; - dim = dim(end); - - ## Permute X to cluster the dimensions to collapse - max_dim = max ([nd, collapsed_dims]); - perm_start = perm_end = [1:max_dim]; - perm_start(dim:end) = []; - perm_start(ismember (perm_start, collapsed_dims)) = []; - perm_end(1:dim) = []; - perm_end(ismember (perm_end, collapsed_dims)) = []; - perm = [perm_start, collapsed_dims, perm_end]; - - x = permute (x, perm); + elseif (vecdim_scalar_vector(2) && ! (isequal (size (weights), szx))) + error ("var: weight matrix or array does not match X in size."); + endif + endif - ## Collapse the given dimensions - newshape = ones (1, max_dim); - newshape(1:nd) = sz; - newshape(collapsed_dims(1:(end-1))) = 1; - newshape(dim) = prod (sz(collapsed_dims)); - - ## New X with collapsed dimensions - x = reshape (x, newshape); - - elseif (ischar (dim) && strcmpi (dim, "all")) - if (! isscalar (w)) - error ("var: W must be either 0 or 1 when using 'all' as dimension"); + ## Force output for X being empty or scalar + if (isempty (x)) + if (vecempty && (ndx == 2 || all ((szx) == 0))) + v = NaN (outtype); + if (nargout > 1) + m = NaN (outtype); endif - - ## "all" equates to collapsing all elements to a single vector - x = x(:); - dim = 1; - sz = size (x); - else - error ("var: DIM must be a positive integer scalar, vector, or 'all'"); + return; + endif + if (vecdim_scalar_vector(1)) + szx(vecdim) = 1; + v = NaN (szx, outtype); + if (nargout > 1) + m = NaN (szx, outtype); + endif + return; endif endif - n = size (x, dim); - if (! isvector (w) || ! isnumeric (w) - || (isvector (w) && any (w < 0)) || - (isscalar (w) && ((w != 0 && w != 1) && (n != 1)))) - error ("var: W must be 0, 1, or a vector of positive integers"); + if (isscalar (x)) + if (isfinite (x)) + v = zeros (outtype); + else + v = NaN (outtype); + endif + if (nargout > 1) + m = x; + endif + return; endif - if (isempty (x)) - ## Empty matrix special case - if (emptydimflag && nd == 2 && all (sz == [0, 0])) - v = NaN; - mu = NaN; - else - sz(dim) = 1; - v = NaN (sz); - mu = NaN (sz); - endif - elseif (n == 1) - ## Scalar special case - if (! isscalar (w)) - error (["var: the length of W must be equal to the size of X " ... - "in the dimension along which variance is calculated"]); - endif - if (isa (x, "single")) - v = zeros (sz, "single"); - mu = x; - else - v = zeros (sz); - mu = x; - endif - v(isnan (x) | isinf (x)) = NaN; - else - ## Regular algorithm - if (isscalar (w)) - v = sumsq (center (x, dim), dim) / (n - 1 + w); - if (nargout == 2) - mu = mean (x, dim); + if (nvarg == 0) + ## Only numeric input argument, no dimensions or weights. + if (all_flag) + x = x(:); + if (omitnan) + x = x(! isnan (x)); + endif + n = length (x); + m = sum (x) ./ n; + v = sum (abs (x - m) .^ 2) ./ (n - 1 + w); + if (n == 1) + v = 0; endif else - ## Weighted variance - if (numel (w) != n) - error (["var: the length of W must be equal to the size of X " ... - "in the dimension along which variance is calculated"]); + dim = find (szx > 1, 1); + if length (dim) == 0 + dim = 1; + endif + n = szx(dim); + if (omitnan) + n = sum (! isnan (x), dim); + xn = isnan (x); + x(xn) = 0; + endif + m = sum (x, dim) ./ n; + dims = ones (1, ndx); + dims(dim) = szx(dim); + if (sprs_x) + m_exp = repmat (m, dims); + else + m_exp = m .* ones (dims); + endif + if (omitnan) + x(xn) = m_exp(xn); + endif + v = sumsq (x - m_exp, dim) ./ (n - 1 + w); + if (numel (n) == 1) + divby0 = n .* ones (size (v)) == 1; + else + divby0 = n == 1; + endif + v(divby0) = 0; + endif + + elseif (nvarg == 1) + ## Two numeric input arguments, w or weights given. + if (all_flag) + x = x(:); + if (weighted) + weights = weights(:); + wx = weights .* x; + else + weights = ones (length (x), 1); + wx = x; + endif + + if (omitnan) + xn = isnan (wx); + wx = wx(! xn); + weights = weights(! xn); + x = x(! xn); + endif + n = length (wx); + m = sum (wx) ./ sum (weights); + if (weighted) + v = sum (weights .* (abs (x - m) .^ 2)) ./ sum (weights); + else + v = sum (weights .* (abs (x - m) .^ 2)) ./ (n - 1 + w); + if (n == 1) + v = 0; + endif + endif + + else + dim = find (szx > 1, 1); + if length (dim) == 0 + dim = 1; + endif + if (! weighted) + weights = ones (szx); + wx = x; + else + if (isvector (weights)) + dims = 1:ndx; + dims([1, dim]) = [dim, 1]; + weights = zeros (szx) + permute (weights(:), dims); + endif + wx = weights .* x; + endif + n = size (wx, dim); + if (omitnan) + xn = isnan (wx); + n = sum (! xn, dim); + wx(xn) = 0; + weights(xn) = 0; + endif + m = sum (wx, dim) ./ sum (weights, dim); + dims = ones (1, ndims (wx)); + dims(dim) = size (wx, dim); + if (sprs_x) + m_exp = repmat (m, dims); + else + m_exp = m .* ones (dims); + endif + if (omitnan) + x(xn) = m_exp(xn); + endif + if (weighted) + v = sum (weights .* ((x - m_exp) .^ 2), dim) ./ sum (weights, dim); + else + v = sumsq (x - m_exp, dim) ./ (n - 1 + w); + if (numel (n) == 1) + divby0 = n .* ones (size (v)) == 1; + else + divby0 = n == 1; + endif + v(divby0) = 0; + endif + endif + + elseif (nvarg == 2) + ## Three numeric input arguments, both w or weights and dim or vecdim given. + if (vecdim_scalar_vector(1)) + if (!weighted) + weights = ones (szx); + wx = x; + else + if (isvector (weights)) + dims = 1:ndx; + dims([1, vecdim]) = [vecdim, 1]; + weights = zeros (szx) + permute (weights(:), dims); + endif + wx = weights .* x; endif - if ((dim == 1 && isrow (w)) || (dim == 2 && iscolumn (w))) - w = w.'; - elseif (dim > 2) - newdims = [ones(1, dim - 1), numel(w)]; - w = reshape (w, newdims); + n = size (wx, vecdim); + if (omitnan) + n = sum (! isnan (wx), vecdim); + xn = isnan (wx); + wx(xn) = 0; + weights(xn) = 0; + endif + m = sum (wx, vecdim) ./ sum (weights, vecdim); + dims = ones (1, ndims (wx)); + dims(vecdim) = size (wx, vecdim); + if (sprs_x) + m_exp = repmat (m, dims); + else + m_exp = m .* ones (dims); + endif + if (omitnan) + x(xn) = m_exp(xn); endif - den = sum (w); + if (weighted) + v = sum (weights .* ((x - m_exp) .^ 2), vecdim) ... + ./ sum (weights, vecdim); + else + v = sumsq (x - m_exp, vecdim); + vn = isnan (v); + v = v ./ (n - 1 + w); + if (numel (n) == 1) + divby0 = n .* ones (size (v)) == 1; + else + divby0 = n == 1; + endif + v(divby0) = 0; + v(vn) = NaN; + endif + + else + ## Weights and nonscalar vecdim specified + + ## Ignore exceeding dimensions in VECDIM + remdims = 1 : ndx; # all dimensions + vecdim(find (vecdim > ndx)) = []; + ## Calculate permutation vector + remdims(vecdim) = []; # delete dimensions specified by vecdim + nremd = numel (remdims); + + ## If all dimensions are given, it is similar to all flag + if (nremd == 0) + x = x(:); + if (weighted) + weights = weights(:); + wx = weights .* x; + else + weights = ones (length (x), 1); + wx = x; + endif - ## FIXME: Use bsxfun, rather than broadcasting, until broadcasting - ## supports diagonal and sparse matrices (Bugs #41441, #35787). - mu = sum (bsxfun (@times, w , x), dim) ./ den; - v = sum (bsxfun (@times, w, ... - bsxfun (@minus, x, mu) .^ 2), dim) ./ den; - ## mu = sum (w .* x, dim) ./ den; # automatic broadcasting - ## v = sum (w .* ((x - mu) .^ 2), dim) ./ den; + if (omitnan) + xn = isnan (wx); + wx = wx(! xn); + weights = weights(! xn); + x = x(! xn); + endif + n = length (wx); + m = sum (wx) ./ sum (weights); + if (weighted) + v = sum (weights .* (abs (x - m) .^ 2)) ./ sum (weights); + else + v = sum (weights .* (abs (x - m) .^ 2)) ./ (n - 1 + w); + if (n == 1) + v = 0; + endif + endif + + else + + ## FIXME: much of the reshaping can be skipped once octave's sum can + ## take a vecdim argument. + + ## Apply weights + if (weighted) + wx = weights .* x; + else + weights = ones (szx); + wx = x; + endif + + ## Permute to bring remaining dims forward + perm = [remdims, vecdim]; + wx = permute (wx, perm); + weights = permute (weights, perm); + x = permute (x, perm); + + ## Reshape to put all vecdims in final dimension + szwx = size (wx); + sznew = [szwx(1:nremd), prod(szwx(nremd+1:end))]; + wx = reshape (wx, sznew); + weights = reshape (weights, sznew); + x = reshape (x, sznew); + + ## Calculate var on single, squashed dimension + dim = nremd + 1; + n = size (wx, dim); + if (omitnan) + xn = isnan (wx); + n = sum (! xn, dim); + wx(xn) = 0; + weights(xn) = 0; + endif + m = sum (wx, dim) ./ sum (weights, dim); + m_exp = zeros (sznew) + m; + if (omitnan) + x(xn) = m_exp(xn); + endif + if (weighted) + v = sum (weights .* ((x - m_exp) .^ 2), dim) ./ sum (weights, dim); + else + v = sumsq (x - m_exp, dim) ./ (n - 1 + w); + if (numel (n) == 1) + divby0 = n .* ones (size (v)) == 1; + else + divby0 = n == 1; + endif + v(divby0) = 0; + endif + + ## Inverse permute back to correct dimensions + v = ipermute (v, perm); + if (nargout > 1) + m = ipermute (m, perm); + endif + endif endif endif + ## Preserve class type + if (nargout < 2) + if strcmp (outtype, "single") + v = single (v); + else + v = double (v); + endif + else + if strcmp (outtype, "single") + v = single (v); + m = single (m); + else + v = double (v); + m = double (m); + endif + endif endfunction @@ -247,6 +575,7 @@ %!assert (var (5, 99), 0) %!assert (var (5, 99, 1), 0) %!assert (var (5, 99, 2), 0) +%!assert (var ([5 3], [99 99], 2), 1) %!assert (var ([1:7], [1:7]), 3) %!assert (var ([eye(3)], [1:3]), [5/36, 2/9, 1/4], eps) %!assert (var (ones (2,2,2), [1:2], 3), [(zeros (2,2))]) @@ -254,16 +583,127 @@ %!assert (var (reshape ([1:8], 2, 2, 2), 0, [1 3]), [17/3 17/3], eps) %!assert (var ([1 2 3;1 2 3], [], [1 2]), 0.8, eps) +## Test single input and optional arguments "all", DIM, "omitnan") +%!test +%! x = [-10:10]; +%! y = [x;x+5;x-5]; +%! assert (var (x), 38.5); +%! assert (var (y, [], 2), [38.5; 38.5; 38.5]); +%! assert (var (y, 0, 2), [38.5; 38.5; 38.5]); +%! assert (var (y, 1, 2), ones (3,1) * 36.66666666666666, 1e-14); +%! assert (var (y, "all"), 54.19354838709678, 1e-14); +%! y(2,4) = NaN; +%! assert (var (y, "all"), NaN); +%! assert (var (y, "all", "includenan"), NaN); +%! assert (var (y, "all", "omitnan"), 55.01533580116342, 1e-14); +%! assert (var (y, 0, 2, "includenan"), [38.5; NaN; 38.5]); +%! assert (var (y, [], 2), [38.5; NaN; 38.5]); +%! assert (var (y, [], 2, "omitnan"), [38.5; 37.81842105263158; 38.5], 1e-14); + +## Tests for different weight and omitnan code paths +%!assert (var ([1 NaN 3], [1 2 3], "omitnan"), 0.75, eps) +%!assert (var ([1 2 3], [1 NaN 3], "omitnan"), 0.75, eps) +%!assert (var (magic(3), [1 NaN 3], "omitnan"), [3 12 3], eps) +%!assert (var ([1 NaN 3], [1 2 3], "omitnan", "all"), 0.75, eps) +%!assert (var ([1 NaN 3], [1 2 3], "all", "omitnan"), 0.75, eps) +%!assert (var ([1 2 3], [1 NaN 3], "omitnan", "all"), 0.75, eps) +%!assert (var ([1 NaN 3], [1 2 3], 2, "omitnan"), 0.75, eps) +%!assert (var ([1 2 3], [1 NaN 3], 2, "omitnan"), 0.75, eps) +%!assert (var (magic(3), [1 NaN 3], 1, "omitnan"), [3 12 3], eps) +%!assert (var (magic(3), [1 NaN 3], 2, "omitnan"), [0.75;3;0.75], eps) +%!assert (var ([4 4; 4 6; 6 6], [1 3], 2, 'omitnan'), [0;0.75;0], eps) +%!assert (var ([4 NaN; 4 6; 6 6], [1 2 3], 1, 'omitnan'), [1 0]) +%!assert (var ([4 NaN; 4 6; 6 6], [1 3], 2, 'omitnan'), [0;0.75;0], eps) +%!assert (var (3*reshape(1:18, [3 3 2]), [1 2 3], 1, 'omitnan'), ones(1,3,2)*5) +%!assert (var (reshape(1:18, [3 3 2]), [1 2 3], 2, 'omitnan'), 5*ones(3,1,2)) +%!assert (var (3*reshape(1:18, [3 3 2]), ones (3,3,2), [1 2], 'omitnan'), ... +%! 60 * ones(1,1,2)) +%!assert (var (3*reshape(1:18, [3 3 2]), ones (3,3,2), [1 4], 'omitnan'), ... +%! 6 * ones(1,3,2)) +%!assert (var (6*reshape(1:18, [3 3 2]), ones (3,3,2), [1:3], 'omitnan'), 969) +%!test +%! x = reshape(1:18, [3 3 2]); +%! x([2, 14]) = NaN; +%! w = ones (3,3,2); +%! assert (var (16*x, w, [1:3], 'omitnan'), 6519); +%!test +%! x = reshape(1:18, [3 3 2]); +%! w = ones (3,3,2); +%! w([2, 14]) = NaN; +%! assert (var (16*x, w, [1:3], 'omitnan'), 6519); + +## Test input case insensitivity +%!assert (var ([1 2 3], "aLl"), 1); +%!assert (var ([1 2 3], "OmitNan"), 1); +%!assert (var ([1 2 3], "IncludeNan"), 1); + +## Test dimension indexing with vecdim in n-dimensional arrays +%!test +%! x = repmat ([1:20;6:25], [5, 2, 6, 3]); +%! assert (size (var (x, 0, [3 2])), [10, 1, 1, 3]); +%! assert (size (var (x, 1, [1 2])), [1, 1, 6, 3]); +%! assert (size (var (x, [], [1 2 4])), [1, 1, 6]); +%! assert (size (var (x, 0, [1 4 3])), [1, 40]); +%! assert (size (var (x, [], [1 2 3 4])), [1, 1]); + +## Test matrix with vecdim, weighted, matrix weights, omitnan +%!assert (var (3*magic(3)), [63 144 63]) +%!assert (var (3*magic(3), 'omitnan'), [63 144 63]) +%!assert (var (3*magic(3), 1), [42 96 42]) +%!assert (var (3*magic(3), 1, 'omitnan'), [42 96 42]) +%!assert (var (3*magic(3), ones(1,3), 1), [42 96 42]) +%!assert (var (3*magic(3), ones(1,3), 1, 'omitnan'), [42 96 42]) +%!assert (var (2*magic(3), [1 1 NaN], 1, 'omitnan'), [25 16 1]) +%!assert (var (3*magic(3), ones(3,3)), [42 96 42]) +%!assert (var (3*magic(3), ones(3,3), 'omitnan'), [42 96 42]) +%!assert (var (3*magic(3), [1 1 1; 1 1 1; 1 NaN 1], 'omitnan'), [42 36 42]) +%!assert (var (3*magic(3), ones(3,3), 1), [42 96 42]) +%!assert (var (3*magic(3), ones(3,3), 1, 'omitnan'), [42 96 42]) +%!assert (var (3*magic(3), [1 1 1; 1 1 1; 1 NaN 1], 1, 'omitnan'), [42 36 42]) +%!assert (var (3*magic(3), ones(3,3), [1 4]), [42 96 42]) +%!assert (var (3*magic(3), ones(3,3), [1 4], 'omitnan'), [42 96 42]) +%!assert (var (3*magic(3), [1 1 1; 1 1 1; 1 NaN 1],[1 4],'omitnan'), [42 36 42]) + +## Test results with vecdim in n-dimensional arrays and "omitnan" +%!test +%! x = repmat ([1:20;6:25], [5, 2, 6, 3]); +%! v = repmat (33.38912133891213, [10, 1, 1, 3]); +%! assert (var (x, 0, [3, 2]), v, 1e-14); +%! v = repmat (33.250, [10, 1, 1, 3]); +%! assert (var (x, 1, [3, 2]), v, 1e-14); +%! x(2,5,6,3) = NaN; +%! v(2,1,1,3) = NaN; +%! assert (var (x, 1, [3, 2]), v, 4e-14); +%! v = repmat (33.38912133891213, [10 1 1 3]); +%! v(2,1,1,3) = NaN; +%! assert (var (x, [], [3, 2]), v, 4e-14); +%! v(2,1,1,3) = 33.40177912169048; +%! assert (var (x, [], [3, 2], "omitnan"), v, 4e-14); + +## Testing weights vector & arrays +%!assert (var (ones (2,2,2), [1:2], 3), [(zeros (2, 2))]); +%!assert (var (magic (3), [1:9], "all"), 6.666666666666667, 1e-14); + +## Test exceeding dimensions +%!assert (var (ones (2,2), [], 3), zeros (2,2)); +%!assert (var (ones (2,2,2), [], 99), zeros (2,2,2)); +%!assert (var (magic (3), [], 3), zeros (3,3)); +%!assert (var (magic (3), [], 1), [7, 16, 7]); +%!assert (var (magic (3), [], [1 3]), [7, 16, 7]); +%!assert (var (magic (3), [], [1 99]), [7, 16, 7]); + ## Test empty inputs %!assert (var ([]), NaN) +%!assert (class (var (single ([]))), "single") %!assert (var ([],[],1), NaN(1,0)) %!assert (var ([],[],2), NaN(0,1)) %!assert (var ([],[],3), []) -%!assert (var (ones (0,1)), NaN) +%!assert (class (var (single ([]), [], 1)), "single") %!assert (var (ones (1,0)), NaN) %!assert (var (ones (1,0), [], 1), NaN(1,0)) %!assert (var (ones (1,0), [], 2), NaN) %!assert (var (ones (1,0), [], 3), NaN(1,0)) +%!assert (class (var (ones (1, 0, "single"), [], 1)), "single") %!assert (var (ones (0,1)), NaN) %!assert (var (ones (0,1), [], 1), NaN) %!assert (var (ones (0,1), [], 2), NaN(0,1)) @@ -273,8 +713,11 @@ %!assert (var (ones (1,3,0,2), [], 2), NaN(1,1,0,2)) %!assert (var (ones (1,3,0,2), [], 3), NaN(1,3,1,2)) %!assert (var (ones (1,3,0,2), [], 4), NaN(1,3,0)) +%!test +%! [~, m] = var ([]); +%! assert (m, NaN); -## Test second output +## Test optional mean output %!test <*62395> %! [~, m] = var (13); %! assert (m, 13); @@ -289,9 +732,9 @@ %! [~, m] = var ([1, 2, 3; 3 2 1], [], 3); %! assert (m, [1 2 3; 3 2 1]); -## 2nd output, weighted inputs, vector dims +## Test mean output, weighted inputs, vector dims %!test <*62395> -%! [~, m] = var(5,99); +%! [~, m] = var (5,99); %! assert (m, 5); %! [~, m] = var ([1:7], [1:7]); %! assert (m, 5); @@ -303,19 +746,38 @@ %! assert (m, 2.5, eps); %! [~, m] = var (reshape ([1:8], 2, 2, 2), 0, [1 3]); %! assert (m, [3.5, 5.5], eps); +%!test +%! [v, m] = var (4 * eye (2), [1, 3]); +%! assert (v, [3, 3]); +%! assert (m, [1, 3]); -## 2nd output, empty inputs +## Test mean output, empty inputs, omitnan %!test <*62395> %! [~, m] = var ([]); %! assert (m, NaN); -%! [~, m] = var ([],[],1); -%! assert (m, NaN(1,0)); -%! [~, m] = var ([],[],2); -%! assert (m, NaN(0,1)); -%! [~, m] = var ([],[],3); -%! assert (m, []); -%! [~, m] = var (ones (1,3,0,2)); -%! assert (m, NaN(1,1,0,2)); +#%! [~, m] = var ([],[],1); +#%! assert (m, NaN(1,0)); +#%! [~, m] = var ([],[],2); +#%! assert (m, NaN(0,1)); +#%! [~, m] = var ([],[],3); +#%! assert (m, []); +#%! [~, m] = var (ones (1,3,0,2)); +#%! assert (m, NaN(1,1,0,2)); + +## Test mean output, nD array +%!test <*62395> +%! x = repmat ([1:20;6:25], [5, 2, 6, 3]); +%! [~, m] = var (x, 0, [3 2]); +%! assert (m, mean (x, [3 2])); +%! [~, m] = var (x, 0, [1 2]); +%! assert (m, mean (x, [1 2])); +%! [~, m] = var (x, 0, [1 3 4]); +%! assert (m, mean (x, [1 3 4])); +%!test +%! x = repmat ([1:20;6:25], [5, 2, 6, 3]); +%! x(2,5,6,3) = NaN; +%! [~, m] = var (x, 0, [3 2], "omitnan"); +%! assert (m, mean (x, [3 2], "omitnan")); ## Test Inf and NaN inputs %!test <*63203> @@ -440,27 +902,64 @@ %! [v, m] = var (sparse (4 * eye (2)), [1, 3]); %! assert (full (v), [3, 3]); %! assert (full (m), [1, 3]); - -%!test <63291> +%!test<*63291> %! [v, m] = var (sparse (eye (2))); %! assert (issparse (v)); %! assert (issparse (m)); -%!test <63291> +%!test<*63291> %! [v, m] = var (sparse (eye (2)), [1, 3]); %! assert (issparse (v)); %! assert (issparse (m)); + ## Test input validation %!error <Invalid call> var () -%!error <X must be a numeric> var (['A'; 'B']) -%!error <W must be 0> var ([1 2 3], 2) -%!error <W must be .* a vector of positive integers> var ([1 2], [-1 0]) -%!error <W must be .* a vector of positive integers> var ([1 2], eye (2)) -%!error <W must be either 0 or 1> var (ones (2, 2), [1 2], [1 2]) -%!error <W must be either 0 or 1> var ([1 2], [1 2], 'all') -%!error <the length of W must be> var ([1 2], [1 2 3]) -%!error <the length of W must be> var (1, [1 2]) -%!error <the length of W must be> var ([1 2], [1 2], 1) +%!error <Invalid call> var (1, 2, "omitnan", 3) +%!error <Invalid call> var (1, 2, 3, 4) +%!error <Invalid call> var (1, 2, 3, 4, 5) +%!error <Invalid call> var (1, "foo") +%!error <Invalid call> var (1, [], "foo") +%!error <normalization scalar must be either 0 or 1> var ([1 2 3], 2) +%!error <normalization scalar must be either 0 or 1> var ([1 2], 2, "all") +%!error <normalization scalar must be either 0 or 1> var ([1 2],0.5, "all") +%!error <weights must not contain any negative values> var (1, -1) +%!error <weights must not contain any negative values> var (1, [1 -1]) +%!error <weights must not contain any negative values> ... +%! var ([1 2 3], [1 -1 0]) +%!error <X must be a numeric vector or matrix> var ({1:5}) +%!error <X must be a numeric vector or matrix> var ("char") +%!error <X must be a numeric vector or matrix> var (['A'; 'B']) %!error <DIM must be a positive integer> var (1, [], ones (2,2)) +%!error <DIM must be a positive integer> var (1, 0, 1.5) +%!error <DIM must be a positive integer> var (1, [], 0) %!error <DIM must be a positive integer> var (1, [], 1.5) -%!error <DIM must be a positive integer> var (1, [], 0) +%!error <DIM must be a positive integer> var ([1 2 3], [], [-1 1]) +%!error <VECDIM must contain non-repeating positive integers> ... +%! var (repmat ([1:20;6:25], [5 2 6 3]), 0, [1 2 2 2]) +%!error <weight matrix or array does not match X in size> ... +%! var ([1 2], eye (2)) +%!error <weight matrix or array does not match X in size> ... +%! var ([1 2 3 4], [1 2; 3 4]) +%!error <weight matrix or array does not match X in size> ... +%! var ([1 2 3 4], [1 2; 3 4], 1) +%!error <weight matrix or array does not match X in size> ... +%! var ([1 2 3 4], [1 2; 3 4], [2 3]) +%!error <weight matrix or array does not match X in size> ... +%! var (ones (2, 2), [1 2], [1 2]) +%!error <weight matrix or array does not match X in size> ... +%! var ([1 2 3 4; 5 6 7 8], [1 2 1 2 1; 1 2 1 2 1], 1) +%!error <weight matrix or array does not match X in size> ... +%! var (repmat ([1:20;6:25], [5 2 6 3]), repmat ([1:20;6:25], [5 2 3]), [2 3]) +%!error <weight vector length does not match> var ([1 2 3; 2 3 4], [1 3 4]) +%!error <weight vector length does not match> var ([1 2], [1 2 3]) +%!error <weight vector length does not match> var (1, [1 2]) +%!error <weight vector length does not match> var ([1 2 3; 2 3 4], [1 3 4], 1) +%!error <weight vector length does not match> var ([1 2 3; 2 3 4], [1 3], 2) +%!error <weight vector length does not match> var ([1 2], [1 2], 1) +%!error <'all' flag cannot be used with DIM or VECDIM options> ... +%! var (1, [], 1, "all") +%!error <weight vector element count does not match X> ... +%! var ([1 2 3; 2 3 4], [1 3], "all") +%!error <weight matrix or array does not match X in size> ... +%! var (repmat ([1:20;6:25], [5 2 6 3]), repmat ([1:20;6:25], [5 2 3]), "all") +