# HG changeset patch
# User Stefano Guidoni <ilguido@users.sf.net>
# Date 1635676944 -3600
#      Sun Oct 31 11:42:24 2021 +0100
# Node ID 2f1e773dae5dcd414d293854bbe7e67181452ddc
# Parent  48d897408527292f3535fd3463e1f187b6926a77
var.m: add keyword 'all' (bug #58116) and vectors as dimension input

diff -r 48d897408527 -r 2f1e773dae5d NEWS
--- a/NEWS	Sat Oct 23 17:18:36 2021 +0200
+++ b/NEWS	Sun Oct 31 11:42:24 2021 +0100
@@ -245,7 +245,7 @@
 to true.
 
 - The function `var` now accepts a weight vector as input and computes the
-weigthed variance.
+weigthed variance.  Dimension input now allows a vector and the keyword "all".
 
 ### Alphabetical list of new functions added in Octave 7
 
diff -r 48d897408527 -r 2f1e773dae5d scripts/statistics/var.m
--- a/scripts/statistics/var.m	Sat Oct 23 17:18:36 2021 +0200
+++ b/scripts/statistics/var.m	Sun Oct 31 11:42:24 2021 +0100
@@ -27,6 +27,7 @@
 ## @deftypefn  {} {} var (@var{x})
 ## @deftypefnx {} {} var (@var{x}, @var{w})
 ## @deftypefnx {} {} var (@var{x}, @var{w}, @var{dim})
+## @deftypefnx {} {} var (@var{x}, @var{w}, @qcode{"ALL"})
 ## Compute the variance of the elements of the vector @var{x}.
 ##
 ## The variance is defined as
@@ -74,7 +75,15 @@
 ## If @math{N} is equal to 1 the value of @var{opt} is ignored and
 ## normalization by @math{N} is used.
 ##
-## If the optional argument @var{dim} is given, operate along this dimension.
+## The optional variable @var{dim} can be used to force @code{var} to operate
+## over the specified dimension.  @var{dim} can either be a scalar dimension or
+## a vector of non-repeating dimensions over which to operate.  Dimensions are
+## positive integers.  When @var{dim} is a vector, @var{w} must be either 0 or
+## 1, and the variance is calculated over the array slice defined by @var{dim}.
+##
+## Specifying dimension @qcode{"ALL"} will force @code{var} to operate
+## on all elements of @var{x}, when @var{w} is either 0 or 1, and is equivalent
+## to @code{var (@var{x}(:))}.
 ## @seealso{cov, std, skewness, kurtosis, moment}
 ## @end deftypefn
 
@@ -82,6 +91,8 @@
 
   if (nargin < 1)
     print_usage ();
+  elseif (nargin < 3)
+    dim = [];
   endif
 
   if (! (isnumeric (x) || islogical (x)))
@@ -94,17 +105,70 @@
           ! isnumeric (w) ||
           (isvector (w) && any (w < 0)) ||
           (isscalar (w) && (w != 0 && w != 1)))
-    error ("var: W must be 0 or 1 or a vector of nonnegative scalars");
+    error ("var: W must be 0 or 1 or a vector of positive integers");
   endif
 
   nd = ndims (x);
   sz = size (x);
-  if (nargin < 3)
+  if (isempty (dim))
     ## Find the first non-singleton dimension.
     (dim = find (sz > 1, 1)) || (dim = 1);
   else
     if (! (isscalar (dim) && dim == fix (dim) && dim > 0))
-      error ("var: DIM must be an integer and a valid dimension");
+      if (isvector (dim) &&
+          isnumeric (dim) &&
+          all (dim > 0) &&
+          all (rem (dim, 1) == 0))
+        if (dim != unique (dim, "stable"))
+          error (["var: vector DIM must contain non-repeating positive"...
+                  "integers"]);
+        endif
+        ## Check W
+        if (! isscalar (w))
+          error ("var: W must be either 0 or 1 when DIM is a vector");
+        endif
+
+        ## Reshape X to compute the variance over an array slice
+        if (iscolumn (dim))
+          dim = transpose (dim);
+        endif
+
+        collapsed_dims = dim;
+        dim = dim(end);
+
+        ## Permute X to cluster the dimensions to collapse
+        highest_dim = max ([nd, collapsed_dims]);
+        perm_start = perm_end = [1:highest_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);
+
+        ## Collapse the given dimensions
+        newshape = ones (1, highest_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) &&
+              strcmp (tolower (dim), "all"))
+        ## Check W
+        if (! isscalar (w))
+          error ("var: W must be either 0 or 1 when using 'ALL' as dimension");
+        endif
+
+        ## "ALL" equals 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'");
+      endif
     endif
   endif
 
@@ -156,14 +220,18 @@
 %!assert (var ([1:7], [1:7]), 3)
 %!assert (var ([(eye (3))], [1:3]), [.1389 .2222 .25], 1e-4)
 %!assert (var (ones (2,2,2), [1:2], 3), [(zeros (2,2))])
+%!assert (var ([1 2; 3 4], 0, 'all'), var ([1:4]))
+%!assert (var (reshape ([1:8], 2, 2, 2), 0, [1 3]), [5.6667 5.6667], 1e-4)
 
 ## Test input validation
 %!error <Invalid call> var ()
 %!error <X must be a numeric> var (['A'; 'B'])
 %!error <W must be 0 or 1> var (1, 2)
-%!error <W must be .* a vector of nonnegative scalars> var ([1 2], [-1 0])
-%!error <W must be .* a vector of nonnegative scalars> var ([1 2], eye (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 <DIM must be an integer> var (1, [], ones (2,2))
-%!error <DIM must be an integer> var (1, [], 1.5)
-%!error <DIM must be .* a valid dimension> var (1, [], 0)
+%!error <DIM must be a positive integer> var (1, [], ones (2,2))
+%!error <DIM must be a positive integer> var (1, [], 1.5)
+%!error <DIM must be a positive integer> var (1, [], 0)
