# HG changeset patch
# User Stefano Guidoni <ilguido@users.sf.net>
# Date 1615298294 -3600
#      Tue Mar 09 14:58:14 2021 +0100
# Node ID 79745516eea4b263dc0fceede0f98a281e2fd4b9
# Parent  41380a1cf9d8476b1278f9d7d8afee745a0b5f72
kmeans: display feature

diff -r 41380a1cf9d8 -r 79745516eea4 inst/kmeans.m
--- a/inst/kmeans.m	Tue Mar 09 14:56:51 2021 +0100
+++ b/inst/kmeans.m	Tue Mar 09 14:58:14 2021 +0100
@@ -121,6 +121,18 @@
 ##        are unchanged, with values for omitted centroids replaced by NA.
 ##        
 ##  @end table
+##
+## @item @var{Display}
+## Display a text summary.
+##  @table @code
+##  @item @qcode{off}
+##        (Default) Display no summary.
+##  @item @qcode{final}
+##        Display a summary for each clustering operation.
+##  @item @qcode{iter}
+##        Display a summary for each iteration of a clustering operation.
+##        
+##  @end table
 ## @end table
 ##
 ## Example:
@@ -139,6 +151,7 @@
   replicates  = 1;
   max_iter    = 100;
   distance    = "sqeuclidean";
+  display     = "off";
 
   replicates_set_explicitly = false;
 
@@ -173,8 +186,9 @@
 
       case "replicates"  replicates  = prop{2};
                          replicates_set_explicitly = true;
-
-      case {"display", "onlinephase", "options"}
+                         
+      case "display"     display = prop{2};
+      case {"onlinephase", "options"}
         warning ("kmeans: Ignoring unimplemented option '%s'", prop{1});
 
       otherwise
@@ -284,6 +298,19 @@
       error ("kmeans: unsupported distance parameter %s", distance);
   endswitch
 
+  ## check for the 'display' property
+  if (! strcmp (display, "off"))
+    display = lower (display);
+    switch (display)
+      case {"off", "final"} ;
+      case "iter"
+        printf ("%6s\t%6s\t%8s\t%12s\n", "iter", "phase", "num", "sum");
+      otherwise
+        error ("kmeans: invalid display parameter %s", display);
+    endswitch
+  endif
+  
+  
   ## Done processing options
   ########################################
 
@@ -291,13 +318,17 @@
   if (! isscalar (k))
     error ("kmeans: second input argument must be a scalar");
   endif
-
+  
   ## used to hold the distances from each sample to each class
   D = zeros (n_rows, k);
 
   best         = Inf;
   best_centers = [];
   for rep = 1:replicates
+    ## keep track of the number of data points that change class
+    old_classes = zeros (rows (data), 1);
+    n_changes = -1;
+    
     ## check for the 'start' property
     switch (lower (start))
       case "sample"
@@ -329,7 +360,7 @@
     ## Compute distances and classify
     [D, classes, sumd] = update_dist (data, centers, D, k, dist);
 
-    while (err > 0.001 && iter++ <= max_iter)
+    while (err > 0.001 && iter++ <= max_iter && n_changes != 0)
       ## Calculate new centroids
       replaced_centroids = [];        ## Used by "emptyaction = singleton"
       for i = 1:k
@@ -374,15 +405,40 @@
       err  = sum (sumd - new_sumd);
       ## update the current sum of distances
       sumd = new_sumd;
+      ## compute the number of class changes
+      n_changes = sum (old_classes != classes);
+      old_classes = classes;
+      
+      ## display iteration status
+      if (strcmp (display, "iter"))
+        printf ("%6d\t%6d\t%8d\t%12.3f\n", (iter - 1), 1, ...
+          n_changes, sum (sumd));
+      endif
     endwhile
+    ## throw a warning if the algorithm did not converge
+    if (iter > max_iter && err > 0.001 && n_changes != 0)
+      warning ("kmeans: failed to converge in %d iterations", max_iter);
+    endif
+    
     if (sum (sumd) < sum (best) || isinf (best))
       best = sumd;
       best_centers = centers;
     endif
+    
+    ## display final results
+    if (strcmp (display, "final"))
+      printf ("Replicate %d, %d iterations, total sum of distances = %.3f.\n", ...
+        rep, iter, sum (sumd));
+    endif
   endfor
   centers = best_centers;
   ## Compute final distances, classes and sums
   [D, classes, sumd] = update_dist (data, centers, D, k, dist);
+  
+  ## display final results
+  if (strcmp (display, "final") || strcmp (display, "iter"))
+    printf ("Best total sum of distances = %.3f\n", sum (sumd));
+  endif
 
   ## Return with equal size as inputs
   if (original_rows != rows (data))
