histogram - fixed missing cases which have the maximum value
[pspp] / src / math / histogram.c
index 9b89df689527996d1d4393f8f21fb56e3bba494f..e46986d2e0dff4ef6ba4a445cbc9c12aa34b7d0b 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2004, 2008, 2009, 2011 Free Software Foundation, Inc.
+   Copyright (C) 2004, 2008, 2009, 2011, 2012 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #include <gsl/gsl_histogram.h>
 #include <math.h>
 
+#include "data/settings.h"
+#include "libpspp/message.h"
 #include "libpspp/assertion.h"
 #include "libpspp/cast.h"
 #include "math/chart-geometry.h"
 
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+
 #include "gl/xalloc.h"
 
 void
@@ -34,17 +41,23 @@ histogram_add (struct histogram *h, double y, double c)
   stat->accumulate (stat, NULL, c, 0, y);
 }
 
-
-
 static void
 acc (struct statistic *s, const struct ccase *cx UNUSED, double c, double cc UNUSED, double y)
 {
   struct histogram *hist = UP_CAST (s, struct histogram, parent);
+  gsl_histogram *gslh = hist->gsl_hist;
 
-  gsl_histogram_accumulate (hist->gsl_hist, y, c);
+  /* Include cases which are just on the boundary */
+  if (y == gsl_histogram_max (gslh))
+    {
+      double lower, upper;
+      gsl_histogram_get_range (gslh, gsl_histogram_bins (gslh)-1, &lower, &upper);
+      gsl_histogram_accumulate (gslh, lower + (upper - lower)/2.0, c);
+    }
+  else
+    gsl_histogram_accumulate (hist->gsl_hist, y, c);
 }
 
-
 static void
 destroy (struct statistic *s)
 {
@@ -54,63 +67,96 @@ destroy (struct statistic *s)
 }
 
 
-struct histogram *
-histogram_create (double bin_width, double min, double max)
-{
-  int bins;
-  struct histogram *h = xmalloc (sizeof *h);
-  struct statistic *stat = &h->parent;
-  double upper_limit, lower_limit;
-  const double half_bin_width = bin_width / 2.0;
-
-  /* -1 if the lower end of the range contains more unused space
-     than the upper end.
-     +1 otherwise.  */
-  short sparse_end = 0;
+/* Find a bin width which is adapted to the scaling of the x axis
+In the example here, the binwidth is half of the tick interval.
 
-  assert (max >= min);
+        binwidth
+         >   <
+     |....+....+....+.      .+....|
+   LOWER  1    2    3     N_TICKS
+        ^LOWDBL                 ^HIGHDBL
 
-  if (max == min)
-    bin_width = 1;
+This only works, when the min and max value for the histogram are adapted
+such that (max-min) is a multiple of the binwidth. Then the location of the
+first bin has to be aligned to the ticks.
+*/
+static int
+hist_find_pretty_no_of_bins(double bin_width_in, double min, double max,
+                           double *adjusted_min, double *adjusted_max)
+{
+  double lower, interval;
+  int n_ticks;
+  double binwidth;
+  int nbins;
 
-  lower_limit = floor (min / half_bin_width) - 1;
-  upper_limit = floor (max / half_bin_width) + 1;
-  
-  if (remainder (min, half_bin_width > remainder (max, half_bin_width)))
-    sparse_end = -1;
-  else
-    sparse_end = +1;
+  chart_get_scale (max, min, &lower, &interval, &n_ticks);
 
-  /* The range must be an EVEN number of half bin_widths */
-  if ( (int)(upper_limit - lower_limit) % 2)
+  if (bin_width_in >= 2 * interval)
+    {
+      binwidth = floor(bin_width_in/interval) * interval;
+      *adjusted_min = lower;
+    }
+  else if (bin_width_in >= 1.5 * interval)
     {
-      /* Extend the range at the end which gives the least unused space */
-      if (sparse_end == +1)
-       lower_limit --;
+      binwidth = 1.5 * interval;
+      if (min < (lower + 0.5 * interval))
+       *adjusted_min = lower;
       else
-       upper_limit ++;
-      
-      /* Now the other end has more space */
-      sparse_end *= -1;
+       *adjusted_min = lower + 0.5 * interval;
     }
+  else if (bin_width_in >= interval)
+    {
+      binwidth = interval;
+      *adjusted_min = lower;
+    }
+  else if (bin_width_in >= (2.0/3.0 * interval))
+    {
+      binwidth = (2.0/3.0 * interval);
+      if (min >= lower + binwidth)
+       *adjusted_min = lower + binwidth;
+      else
+       *adjusted_min = lower;
+    }
+  else
+    {
+      int i;
+      for(i = 2; bin_width_in < interval/i; i++);
+      binwidth = interval/i;
+      *adjusted_min = floor((min - lower)/binwidth)*binwidth + lower;
+    }
+
+  nbins = ceil((max-*adjusted_min)/binwidth);
+  *adjusted_max = nbins*binwidth + *adjusted_min;
 
-  /* But the range should be aligned to an ODD number of
-     half bin widths, so that the labels are aesthetically pleasing ones. */
-  if ( (int)lower_limit % 2 == 0)
+  return nbins;
+}
+
+
+struct histogram *
+histogram_create (double bin_width_in, double min, double max)
+{
+  struct histogram *h;
+  struct statistic *stat;
+  int bins;
+  double adjusted_min, adjusted_max;
+
+  if (max == min)
     {
-      lower_limit += -sparse_end ;
-      upper_limit += -sparse_end ;
+      msg (MW, _("Not creating histogram because the data contains less than 2 distinct values"));
+      return NULL;
     }
 
-  bins = (upper_limit - lower_limit) / 2.0;
+  assert (bin_width_in > 0);
+
+  bins = hist_find_pretty_no_of_bins(bin_width_in, min, max, &adjusted_min, &adjusted_max);
 
-  upper_limit *= half_bin_width;
-  lower_limit *= half_bin_width;
+  h = xmalloc (sizeof *h);
 
   h->gsl_hist = gsl_histogram_alloc (bins);
 
-  gsl_histogram_set_ranges_uniform (h->gsl_hist, lower_limit, upper_limit);
+  gsl_histogram_set_ranges_uniform (h->gsl_hist, adjusted_min, adjusted_max);
 
+  stat = &h->parent;
   stat->accumulate = acc;
   stat->destroy = destroy;