Sorting categories by explicit values.
[pspp] / src / language / stats / ctables.c
index 47d2c9bce0978f31bb19d6c976f556c94b1379d6..8277a702b28a165150f1191c0795aa763b1b761c 100644 (file)
@@ -144,27 +144,29 @@ enum {
 #undef S
 };
 
-struct ctables_subtable
+enum ctables_domain_type
   {
-    /* In struct ctables's 'subtables' hmap.  Indexed by all the values in all
-       the axes except the innermost row and column variable and the scalar
-       variable, if any.  (If the scalar variable is the innermost row or
-       column variable, then the second-to-innermost variable is also omitted
-       along that axis.) */
-    struct hmap_node node;
-
-    const struct ctables_freq *example;
-
-    double valid;
-    double missing;
+    /* Within a section, where stacked variables divide one section from
+       another. */
+    CTDT_TABLE,                  /* All layers of a whole section. */
+    CTDT_LAYER,                  /* One layer within a section. */
+    CTDT_LAYERROW,               /* Row in one layer within a section. */
+    CTDT_LAYERCOL,               /* Column in one layer within a section. */
+
+    /* Within a subtable, where a subtable pairs an innermost row variable with
+       an innermost column variable within a single layer.  */
+    CTDT_SUBTABLE,               /* Whole subtable. */
+    CTDT_ROW,                    /* Row within a subtable. */
+    CTDT_COL,                    /* Column within a subtable. */
+#define N_CTDTS 7
   };
 
-struct ctables_layer
+struct ctables_domain
   {
-    /* In struct ctables's 'layers' hmap.  Indexed by all the values in the
-       layer axis, except the scalar variable, if any. */
     struct hmap_node node;
 
+    const struct ctables_freq *example;
+
     double valid;
     double missing;
   };
@@ -175,10 +177,8 @@ struct ctables_freq
        axes (except the scalar variable, if any). */
     struct hmap_node node;
 
-    /* The subtable that contains this cell. */
-    struct ctables_subtable *subtable;
-
-    /* The layer that contains this cell. */
+    /* The domains that contains this cell. */
+    struct ctables_domain *domains[N_CTDTS];
 
     struct
       {
@@ -274,7 +274,8 @@ struct var_array
     struct variable **vars;
     size_t n;
     size_t scale_idx;
-    size_t subtable_idx;
+    size_t *domains[N_CTDTS];
+    size_t n_domains[N_CTDTS];
 
     struct ctables_summary_spec *summaries;
     size_t n_summaries;
@@ -293,7 +294,7 @@ struct ctables_table
     struct var_array2 vaas[PIVOT_N_AXES];
     enum pivot_axis_type summary_axis;
     struct hmap ft;
-    struct hmap subtables;
+    struct hmap domains[N_CTDTS];
 
     enum pivot_axis_type slabels_position;
     bool slabels_visible;
@@ -313,7 +314,6 @@ struct ctables_table
 
     struct ctables_chisq *chisq;
     struct ctables_pairwise *pairwise;
-
   };
 
 struct ctables_var
@@ -388,6 +388,10 @@ struct ctables_cat_value
       };
   };
 
+static const struct ctables_cat_value *ctables_categories_match (
+  const struct ctables_categories *, const union value *,
+  const struct variable *);
+
 static void
 ctables_cat_value_uninit (struct ctables_cat_value *cv)
 {
@@ -1747,14 +1751,26 @@ ctables_summary_value (const struct ctables_freq *f,
       return s->valid;
 
     case CTSF_SUBTABLEPCT_COUNT:
-      return f->subtable->valid ? s->valid / f->subtable->valid * 100 : SYSMIS;
+      return f->domains[CTDT_SUBTABLE]->valid ? s->valid / f->domains[CTDT_SUBTABLE]->valid * 100 : SYSMIS;
 
     case CTSF_ROWPCT_COUNT:
+      return f->domains[CTDT_ROW]->valid ? s->valid / f->domains[CTDT_ROW]->valid * 100 : SYSMIS;
+
     case CTSF_COLPCT_COUNT:
+      return f->domains[CTDT_COL]->valid ? s->valid / f->domains[CTDT_COL]->valid * 100 : SYSMIS;
+
     case CTSF_TABLEPCT_COUNT:
+      return f->domains[CTDT_TABLE]->valid ? s->valid / f->domains[CTDT_TABLE]->valid * 100 : SYSMIS;
+
     case CTSF_LAYERPCT_COUNT:
+      return f->domains[CTDT_LAYER]->valid ? s->valid / f->domains[CTDT_LAYER]->valid * 100 : SYSMIS;
+
     case CTSF_LAYERROWPCT_COUNT:
+      return f->domains[CTDT_LAYERROW]->valid ? s->valid / f->domains[CTDT_LAYERROW]->valid * 100 : SYSMIS;
+
     case CTSF_LAYERCOLPCT_COUNT:
+      return f->domains[CTDT_LAYERCOL]->valid ? s->valid / f->domains[CTDT_LAYERCOL]->valid * 100 : SYSMIS;
+
     case CTSF_ROWPCT_VALIDN:
     case CTSF_COLPCT_VALIDN:
     case CTSF_TABLEPCT_VALIDN:
@@ -1890,11 +1906,25 @@ ctables_freq_compare_3way (const void *a_, const void *b_, const void *aux_)
   for (size_t i = 0; i < va->n; i++)
     if (i != va->scale_idx)
       {
-        int cmp = value_compare_3way (&a->axes[aux->a].values[i],
-                                      &b->axes[aux->a].values[i],
-                                      var_get_width (va->vars[i]));
-        if (cmp)
-          return cmp;
+        const struct variable *var = va->vars[i];
+        const union value *val_a = &a->axes[aux->a].values[i];
+        const union value *val_b = &b->axes[aux->a].values[i];
+        int cmp = value_compare_3way (val_a, val_b, var_get_width (var));
+        if (!cmp)
+          continue;
+
+        const struct ctables_categories *cats = aux->t->categories[var_get_dict_index (var)];
+        if (cats && cats->n_values)
+          {
+            const struct ctables_cat_value *a_cv = ctables_categories_match (cats, val_a, var);
+            const struct ctables_cat_value *b_cv = ctables_categories_match (cats, val_b, var);
+            assert (a_cv && b_cv);
+            return (a_cv == b_cv ? cmp
+                    : a_cv > b_cv ? 1
+                    : -1);
+          }
+
+        return cmp;
       }
   return 0;
 }
@@ -1917,8 +1947,9 @@ ctables_freq_compare_3way (const void *a_, const void *b_, const void *aux_)
        Fill the table entry using the indexes from before.
  */
 
-static struct ctables_subtable *
-ctables_subtable_insert (struct ctables_table *t, struct ctables_freq *f)
+static struct ctables_domain *
+ctables_domain_insert (struct ctables_table *t, struct ctables_freq *f,
+                       enum ctables_domain_type domain)
 {
   size_t hash = 0;
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
@@ -1926,40 +1957,86 @@ ctables_subtable_insert (struct ctables_table *t, struct ctables_freq *f)
       size_t idx = f->axes[a].vaa_idx;
       const struct var_array *va = &t->vaas[a].vas[idx];
       hash = hash_int (idx, hash);
-      for (size_t i = 0; i < va->n; i++)
-        if (i != va->scale_idx && i != va->subtable_idx)
-          hash = value_hash (&f->axes[a].values[i],
-                             var_get_width (va->vars[i]), hash);
+      for (size_t i = 0; i < va->n_domains[domain]; i++)
+        {
+          size_t v_idx = va->domains[domain][i];
+          hash = value_hash (&f->axes[a].values[v_idx],
+                             var_get_width (va->vars[v_idx]), hash);
+        }
     }
 
-  struct ctables_subtable *st;
-  HMAP_FOR_EACH_WITH_HASH (st, struct ctables_subtable, node, hash, &t->subtables)
+  struct ctables_domain *d;
+  HMAP_FOR_EACH_WITH_HASH (d, struct ctables_domain, node, hash, &t->domains[domain])
     {
-      const struct ctables_freq *stf = st->example;
+      const struct ctables_freq *df = d->example;
       for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
         {
           size_t idx = f->axes[a].vaa_idx;
-          if (idx != stf->axes[a].vaa_idx)
+          if (idx != df->axes[a].vaa_idx)
             goto not_equal;
 
           const struct var_array *va = &t->vaas[a].vas[idx];
-          for (size_t i = 0; i < va->n; i++)
-            if (i != va->scale_idx && i != va->subtable_idx
-                && !value_equal (&stf->axes[a].values[i],
-                                 &f->axes[a].values[i],
-                                 var_get_width (va->vars[i])))
+          for (size_t i = 0; i < va->n_domains[domain]; i++)
+            {
+              size_t v_idx = va->domains[domain][i];
+              if (!value_equal (&df->axes[a].values[v_idx],
+                                &f->axes[a].values[v_idx],
+                                var_get_width (va->vars[v_idx])))
                 goto not_equal;
+            }
         }
-
-      return st;
+      return d;
 
     not_equal: ;
     }
 
-  st = xmalloc (sizeof *st);
-  *st = (struct ctables_subtable) { .example = f };
-  hmap_insert (&t->subtables, &st->node, hash);
-  return st;
+  d = xmalloc (sizeof *d);
+  *d = (struct ctables_domain) { .example = f };
+  hmap_insert (&t->domains[domain], &d->node, hash);
+  return d;
+}
+
+static const struct ctables_cat_value *
+ctables_categories_match (const struct ctables_categories *cats,
+                          const union value *v, const struct variable *var)
+{
+  const struct ctables_cat_value *othernm = NULL;
+  for (size_t i = cats->n_values; i-- > 0; )
+    {
+      const struct ctables_cat_value *cv = &cats->values[i];
+      switch (cv->type)
+        {
+        case CCVT_NUMBER:
+          if (cv->number == v->f)
+            return cv;
+          break;
+
+        case CCVT_STRING:
+          NOT_REACHED ();
+
+        case CCVT_RANGE:
+          if ((cv->range[0] == -DBL_MAX || v->f >= cv->range[0])
+              && (cv->range[1] == DBL_MAX || v->f <= cv->range[1]))
+            return cv;
+          break;
+
+        case CCVT_MISSING:
+          if (var_is_value_missing (var, v))
+            return cv;
+          break;
+
+        case CCVT_OTHERNM:
+          if (!othernm)
+            othernm = cv;
+          break;
+
+        case CCVT_SUBTOTAL:
+        case CCVT_HSUBTOTAL:
+          break;
+        }
+    }
+
+  return var_is_value_missing (var, v) ? NULL : othernm;
 }
 
 static void
@@ -1975,6 +2052,23 @@ ctables_freqtab_insert (struct ctables_table *t,
   };
   const struct var_array *ss = &t->vaas[t->summary_axis].vas[ix[t->summary_axis]];
 
+  for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+    {
+      const struct var_array *va = &t->vaas[a].vas[ix[a]];
+      for (size_t i = 0; i < va->n; i++)
+        {
+          if (i == va->scale_idx)
+            continue;
+
+          const struct ctables_categories *cats = t->categories[var_get_dict_index (va->vars[i])];
+          if (!cats || !cats->n_values)
+            continue;
+
+          if (!ctables_categories_match (cats, case_data (c, va->vars[i]), va->vars[i]))
+            return;
+        }
+    }
+
   size_t hash = 0;
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
     {
@@ -2022,14 +2116,16 @@ ctables_freqtab_insert (struct ctables_table *t,
   f->summaries = xmalloc (ss->n_summaries * sizeof *f->summaries);
   for (size_t i = 0; i < ss->n_summaries; i++)
     ctables_summary_init (&f->summaries[i], &ss->summaries[i]);
-  f->subtable = ctables_subtable_insert (t, f);
+  for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+    f->domains[dt] = ctables_domain_insert (t, f, dt);
   hmap_insert (&t->ft, &f->node, hash);
 
 summarize:
   for (size_t i = 0; i < ss->n_summaries; i++)
     ctables_summary_add (&f->summaries[i], &ss->summaries[i], ss->summary_var,
                          case_data (c, ss->summary_var), weight);
-  f->subtable->valid += weight;
+  for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+    f->domains[dt]->valid += weight;
 }
 
 static bool
@@ -2042,15 +2138,58 @@ ctables_execute (struct dataset *ds, struct ctables *ct)
         if (t->axes[a])
           {
             t->vaas[a] = enumerate_fts (a, t->axes[a]);
+
             for (size_t j = 0; j < t->vaas[a].n; j++)
               {
                 struct var_array *va = &t->vaas[a].vas[j];
-                va->subtable_idx = (
-                  a == PIVOT_AXIS_LAYER ? SIZE_MAX
-                  : va->n == 0 ? SIZE_MAX
-                  : va->scale_idx != va->n - 1 ? va->n - 1
-                  : va->n == 1 ? SIZE_MAX
-                  : va->n - 2);
+                for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+                  {
+                    va->domains[dt] = xmalloc (va->n * sizeof *va->domains[dt]);
+                    va->n_domains[dt] = 0;
+
+                    for (size_t k = 0; k < va->n; k++)
+                      {
+                        if (k == va->scale_idx)
+                          continue;
+
+                        switch (dt)
+                          {
+                          case CTDT_TABLE:
+                            continue;
+
+                          case CTDT_LAYER:
+                            if (a != PIVOT_AXIS_LAYER)
+                              continue;
+                            break;
+
+                          case CTDT_SUBTABLE:
+                          case CTDT_ROW:
+                          case CTDT_COL:
+                            if (dt == CTDT_SUBTABLE ? a != PIVOT_AXIS_LAYER
+                                : dt == CTDT_ROW ? a == PIVOT_AXIS_COLUMN
+                                : a == PIVOT_AXIS_ROW)
+                              {
+                                if (k == va->n - 1
+                                    || (va->scale_idx == va->n - 1
+                                        && k == va->n - 2))
+                                  continue;
+                              }
+                            break;
+
+                          case CTDT_LAYERROW:
+                            if (a == PIVOT_AXIS_COLUMN)
+                              continue;
+                            break;
+
+                          case CTDT_LAYERCOL:
+                            if (a == PIVOT_AXIS_ROW)
+                              continue;
+                            break;
+                          }
+
+                        va->domains[dt][va->n_domains[dt]++] = k;
+                      }
+                  }
               }
           }
         else
@@ -2523,7 +2662,6 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
       struct ctables_table *t = xmalloc (sizeof *t);
       *t = (struct ctables_table) {
         .ft = HMAP_INITIALIZER (t->ft),
-        .subtables = HMAP_INITIALIZER (t->subtables),
         .slabels_position = PIVOT_AXIS_COLUMN,
         .slabels_visible = true,
         .row_labels = CTLP_NORMAL,
@@ -2533,6 +2671,8 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
         .n_categories = dict_get_n_vars (dataset_dict (ds)),
         .cilevel = 95,
       };
+      for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+        hmap_init (&t->domains[dt]);
       ct->tables[ct->n_tables++] = t;
 
       lex_match (lexer, T_EQUALS);