implement MEDIAN (untested)
[pspp] / src / language / stats / ctables.c
index b57f33f5d7ef4a35203433e231d49057820fffb9..5f9d2584c809649d527cf0bd59a0f879cd38eefb 100644 (file)
 #include <math.h>
 
 #include "data/casereader.h"
+#include "data/casewriter.h"
 #include "data/dataset.h"
 #include "data/dictionary.h"
 #include "data/mrset.h"
+#include "data/subcase.h"
 #include "data/value-labels.h"
 #include "language/command.h"
 #include "language/lexer/format-parser.h"
@@ -34,6 +36,8 @@
 #include "libpspp/message.h"
 #include "libpspp/string-array.h"
 #include "math/moments.h"
+#include "math/percentiles.h"
+#include "math/sort.h"
 #include "output/pivot-table.h"
 
 #include "gl/minmax.h"
@@ -186,6 +190,7 @@ struct ctables_cell
     struct hmap_node node;
 
     /* The domains that contain this cell. */
+    bool contributes_to_domains;
     struct ctables_domain *domains[N_CTDTS];
 
     bool hide;
@@ -1680,7 +1685,14 @@ union ctables_summary
     /* MEAN, SEMEAN, STDDEV, SUM, VARIANCE, *.SUM. */
     struct moments1 *moments;
 
-    /* XXX percentiles, median, mode, multiple response */
+    struct
+      {
+        struct casewriter *writer;
+        double mvalid;
+        double median;
+      };
+
+    /* XXX percentiles, mode, multiple response */
   };
 
 static void
@@ -1712,6 +1724,7 @@ ctables_summary_init (union ctables_summary *s,
     case CTSF_LAYERPCT_TOTALN:
     case CTSF_LAYERROWPCT_TOTALN:
     case CTSF_LAYERCOLPCT_TOTALN:
+    case CTSF_MISSING:
     case CSTF_TOTALN:
     case CTSF_ETOTALN:
     case CTSF_VALIDN:
@@ -1741,7 +1754,22 @@ ctables_summary_init (union ctables_summary *s,
       break;
 
     case CTSF_MEDIAN:
-    case CTSF_MISSING:
+      {
+        struct caseproto *proto = caseproto_create ();
+        proto = caseproto_add_width (proto, 0);
+        proto = caseproto_add_width (proto, 0);
+
+        struct subcase ordering;
+        subcase_init (&ordering, 0, 0, SC_ASCEND);
+        s->writer = sort_create_writer (&ordering, proto);
+        subcase_uninit (&ordering);
+        caseproto_unref (proto);
+
+        s->mvalid = 0;
+        s->median = SYSMIS;
+      }
+      break;
+
     case CTSF_MODE:
     case CTSF_PTILE:
       NOT_REACHED ();
@@ -1801,6 +1829,7 @@ ctables_summary_uninit (union ctables_summary *s,
     case CTSF_LAYERPCT_TOTALN:
     case CTSF_LAYERROWPCT_TOTALN:
     case CTSF_LAYERCOLPCT_TOTALN:
+    case CTSF_MISSING:
     case CSTF_TOTALN:
     case CTSF_ETOTALN:
     case CTSF_VALIDN:
@@ -1828,7 +1857,9 @@ ctables_summary_uninit (union ctables_summary *s,
       break;
 
     case CTSF_MEDIAN:
-    case CTSF_MISSING:
+      casewriter_destroy (s->writer);
+      break;
+
     case CTSF_MODE:
     case CTSF_PTILE:
       NOT_REACHED ();
@@ -1890,6 +1921,7 @@ ctables_summary_add (union ctables_summary *s,
     case CTSF_LAYERPCT_TOTALN:
     case CTSF_LAYERROWPCT_TOTALN:
     case CTSF_LAYERCOLPCT_TOTALN:
+    case CTSF_MISSING:
     case CSTF_TOTALN:
     case CTSF_ETOTALN:
     case CTSF_VALIDN:
@@ -1930,7 +1962,17 @@ ctables_summary_add (union ctables_summary *s,
       break;
 
     case CTSF_MEDIAN:
-    case CTSF_MISSING:
+      if (var_is_value_missing (var, value))
+        {
+          s->mvalid += weight;
+
+          struct ccase *c = case_create (casewriter_get_proto (s->writer));
+          *case_num_rw_idx (c, 0) = value->f;
+          *case_num_rw_idx (c, 1) = weight;
+          casewriter_write (s->writer, c);
+        }
+      break;
+
     case CTSF_MODE:
     case CTSF_PTILE:
       NOT_REACHED ();
@@ -2009,6 +2051,9 @@ ctables_summary_value (const struct ctables_cell *cell,
     case CTSF_LAYERCOLPCT_TOTALN:
       NOT_REACHED ();
 
+    case CTSF_MISSING:
+      return s->missing;
+
     case CSTF_TOTALN:
     case CTSF_ETOTALN:
       return s->valid + s->missing;
@@ -2071,7 +2116,19 @@ ctables_summary_value (const struct ctables_cell *cell,
       NOT_REACHED ();
 
     case CTSF_MEDIAN:
-    case CTSF_MISSING:
+      if (s->writer)
+        {
+          struct casereader *reader = casewriter_make_reader (s->writer);
+          s->writer = NULL;
+
+          struct percentile *median = percentile_create (0.5, s->mvalid);
+          struct order_stats *os = &median->parent;
+          order_stats_accumulate_idx (&os, 1, reader, 1, 0);
+          s->median = percentile_calculate (median, PC_HAVERAGE);
+          statistic_destroy (&median->parent.parent);
+        }
+      return s->median;
+
     case CTSF_MODE:
     case CTSF_PTILE:
       NOT_REACHED ();
@@ -2366,6 +2423,7 @@ ctables_cell_insert__ (struct ctables_table *t, const struct ccase *c,
   cell = xmalloc (sizeof *cell);
   cell->hide = false;
   cell->sv = sv;
+  cell->contributes_to_domains = true;
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
     {
       const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
@@ -2375,14 +2433,19 @@ ctables_cell_insert__ (struct ctables_table *t, const struct ccase *c,
                         : NULL);
       for (size_t i = 0; i < nest->n; i++)
         {
+          const struct ctables_category *cat = cats[a][i];
+
           if (i != nest->scale_idx)
             {
-              const struct ctables_category *subtotal = cats[a][i]->subtotal;
+              const struct ctables_category *subtotal = cat->subtotal;
               if (subtotal && subtotal->type == CCT_HSUBTOTAL)
                 cell->hide = true;
+
+              if (cat->type == CCT_TOTAL || cat->type == CCT_SUBTOTAL || cat->type == CCT_HSUBTOTAL)
+                cell->contributes_to_domains = false;
             }
 
-          cell->axes[a].cvs[i].category = cats[a][i];
+          cell->axes[a].cvs[i].category = cat;
           value_clone (&cell->axes[a].cvs[i].value, case_data (c, nest->vars[i]),
                        var_get_width (nest->vars[i]));
         }
@@ -2411,8 +2474,9 @@ ctables_cell_add__ (struct ctables_table *t, const struct ccase *c,
   for (size_t i = 0; i < specs->n; i++)
     ctables_summary_add (&cell->summaries[i], &specs->specs[i], specs->var,
                          case_data (c, specs->var), weight);
-  for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
-    cell->domains[dt]->valid += weight;
+  if (cell->contributes_to_domains)
+    for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+      cell->domains[dt]->valid += weight;
 }
 
 static void
@@ -2447,6 +2511,34 @@ recurse_totals (struct ctables_table *t, const struct ccase *c,
     }
 }
 
+static void
+recurse_subtotals (struct ctables_table *t, const struct ccase *c,
+                   size_t ix[PIVOT_N_AXES],
+                   const struct ctables_category *cats[PIVOT_N_AXES][10],
+                   double weight,
+                   enum pivot_axis_type start_axis, size_t start_nest)
+{
+  for (enum pivot_axis_type a = start_axis; a < PIVOT_N_AXES; a++)
+    {
+      const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+      for (size_t i = start_nest; i < nest->n; i++)
+        {
+          if (i == nest->scale_idx)
+            continue;
+
+          const struct ctables_category *save = cats[a][i];
+          if (save->subtotal)
+            {
+              cats[a][i] = save->subtotal;
+              ctables_cell_add__ (t, c, ix, cats, weight);
+              recurse_subtotals (t, c, ix, cats, weight, a, i + 1);
+              cats[a][i] = save;
+            }
+        }
+      start_nest = 0;
+    }
+}
+
 static void
 ctables_cell_insert (struct ctables_table *t,
                      const struct ccase *c,
@@ -2484,24 +2576,7 @@ ctables_cell_insert (struct ctables_table *t,
   ctables_cell_add__ (t, c, ix, cats, weight);
 
   recurse_totals (t, c, ix, cats, weight, 0, 0);
-
-  for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
-    {
-      const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
-      for (size_t i = 0; i < nest->n; i++)
-        {
-          if (i == nest->scale_idx)
-            continue;
-
-          const struct ctables_category *save = cats[a][i];
-          if (save->subtotal)
-            {
-              cats[a][i] = save->subtotal;
-              ctables_cell_add__ (t, c, ix, cats, weight);
-              cats[a][i] = save;
-            }
-        }
-    }
+  recurse_subtotals (t, c, ix, cats, weight, 0, 0);
 }
 
 struct merge_item