begin refactoring how to do grouping
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 16 Jan 2022 03:07:07 +0000 (19:07 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 13 Mar 2022 23:56:02 +0000 (16:56 -0700)
src/language/stats/ctables.c

index 2eed4be1bf39071e2208df9587771bee5c8a4f32..51a4a102a644ebe33005d3fd4697f00cebfbc582 100644 (file)
@@ -328,6 +328,7 @@ struct ctables_table
     const struct variable *clabels_example;
     struct hmap clabels_values_map;
     union value *clabels_values;
+    size_t n_clabels_values;
 
     enum pivot_axis_type slabels_axis;
     bool slabels_visible;
@@ -342,6 +343,7 @@ struct ctables_table
        opposite axis or PIVOT_AXIS_LAYER.  Only one of them will differ.
     */
     enum pivot_axis_type label_axis[PIVOT_N_AXES];
+    enum pivot_axis_type clabels_from_axis;
 
     /* Indexed by variable dictionary index. */
     struct ctables_categories **categories;
@@ -2697,6 +2699,16 @@ ctables_table_output_same_axis (struct ctables *ct, struct ctables_table *t)
   pivot_table_submit (pt);
 }
 
+static struct pivot_value *
+ctables_category_create_label (const struct ctables_category *cat,
+                               const struct variable *var,
+                               const union value *value)
+{
+  return (cat->type == CCT_TOTAL || cat->type == CCT_SUBTOTAL || cat->type == CCT_HSUBTOTAL
+          ? pivot_value_new_user_text (cat->total_label, SIZE_MAX)
+          : pivot_value_new_var_value (var, value));
+}
+
 static void
 ctables_table_output_different_axis (struct ctables *ct, struct ctables_table *t)
 {
@@ -2712,6 +2724,39 @@ ctables_table_output_different_axis (struct ctables *ct, struct ctables_table *t
     pivot_table_set_caption (
       pt, pivot_value_new_user_text (t->corner, SIZE_MAX));
 
+  if (t->summary_axis != t->slabels_axis)
+    {
+      struct pivot_dimension *d = pivot_dimension_create (
+        pt, t->slabels_axis, N_("Summaries"));
+      const struct ctables_summary_spec_set *specs = &t->summary_specs;
+      for (size_t i = 0; i < specs->n; i++)
+        pivot_category_create_leaf (
+          d->root, pivot_value_new_text (specs->specs[i].label));
+    }
+
+  if (t->clabels_example)
+    {
+      struct pivot_dimension *d = pivot_dimension_create (
+        pt, t->label_axis[t->clabels_from_axis],
+        t->clabels_from_axis == PIVOT_AXIS_ROW
+        ? N_("Row Categories")
+        : N_("Column Categories"));
+      const struct variable *var = t->clabels_example;
+      const struct ctables_categories *c = t->categories[var_get_dict_index (var)];
+      for (size_t i = 0; i < t->n_clabels_values; i++)
+        {
+          const union value *value = &t->clabels_values[i];
+          const struct ctables_category *cat = ctables_categories_match (c, value, var);
+          if (!cat)
+            {
+              /* XXX probably missing */
+              continue;
+            }
+          pivot_category_create_leaf (d->root, ctables_category_create_label (
+                                        cat, t->clabels_example, value));
+        }
+    }
+
   pivot_table_set_look (pt, ct->look);
   struct pivot_dimension *d[PIVOT_N_AXES];
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
@@ -2730,49 +2775,63 @@ ctables_table_output_different_axis (struct ctables *ct, struct ctables_table *t
       assert (t->axes[a]);
 
       struct ctables_cell **sorted = xnmalloc (t->cells.count, sizeof *sorted);
+      size_t n_sorted = 0;
 
       struct ctables_cell *cell;
-      size_t n = 0;
       HMAP_FOR_EACH (cell, struct ctables_cell, node, &t->cells)
         if (!cell->hide)
-          sorted[n++] = cell;
-      assert (n <= t->cells.count);
+          sorted[n_sorted++] = cell;
+      assert (n_sorted <= t->cells.count);
 
       struct ctables_cell_sort_aux aux = { .t = t, .a = a };
-      sort (sorted, n, sizeof *sorted, ctables_cell_compare_3way, &aux);
+      sort (sorted, n_sorted, sizeof *sorted, ctables_cell_compare_3way, &aux);
 
       size_t max_depth = 0;
       for (size_t j = 0; j < t->stacks[a].n; j++)
         if (t->stacks[a].nests[j].n > max_depth)
           max_depth = t->stacks[a].nests[j].n;
 
-      struct pivot_category **groups = xnmalloc (max_depth, sizeof *groups);
-      struct pivot_category *top = NULL;
-      int prev_leaf = 0;
-      for (size_t j = 0; j < n; j++)
-        {
-          struct ctables_cell *cell = sorted[j];
-          const struct ctables_nest *nest = &t->stacks[a].nests[cell->axes[a].stack_idx];
+      /* Pivot categories:
+
+         - variable label for nest->vars[0], if vlabel != CTVL_NONE
+         - category for nest->vars[0], if nest->scale_idx != 0
+         - variable label for nest->vars[1], if vlabel != CTVL_NONE
+         - category for nest->vars[1], if nest->scale_idx != 1
+         ...
+         - variable label for nest->vars[n - 1], if vlabel != CTVL_NONE
+         - category for nest->vars[n - 1], if t->label_axis[a] == a && nest->scale_idx != n - 1.
+         - summary function, if 'a == t->slabels_axis && a ==
+         t->summary_axis'.
+
+         Additional dimensions:
 
-          /* Pivot categories:
+         - If 'a == t->slabels_axis && a != t->summary_axis', add a summary
+         dimension.
+         - If 't->label_axis[b] == a' for some 'b != a', add a category
+         dimension to 'a'.
+      */
 
-             - variable label for nest->vars[0], if vlabel != CTVL_NONE
-             - category for nest->vars[0]
-             - variable label for nest->vars[1], if vlabel != CTVL_NONE
-             - category for nest->vars[1]
-             ...
-             - variable label for nest->vars[nest->n - 1], if vlabel != CTVL_NONE
-             - category for nest->vars[nest->n - 1], unless t->label_axis[a] != a.
-             - summary function, if 'a == t->slabels_axis && a ==
-               t->summary_axis'.
+      struct ctables_level
+        {
+          enum ctables_level_type
+            {
+              CTL_VAR,          /* Variable label for nest->vars[var_idx]. */
+              CTL_CATEGORY,     /* Category for nest->vars[var_idx]. */
+              CTL_SUMMARY,      /* Summary functions. */
+            }
+          type;
 
-             Additional dimensions:
+          size_t var_idx;
+        };
+      struct ctables_level *levels = xnmalloc (1 + 2 * max_depth, sizeof *levels);
+      size_t n_levels = 0;
 
-             - If 'a == t->slabels_axis && a != t->summary_axis', add a summary
-               dimension.
-             - If 't->label_axis[b] == a' for some 'b != a', add a category
-               dimension to 'a'.
-           */
+      struct pivot_category **groups = xnmalloc (1 + 2 * max_depth, sizeof *groups);
+      int prev_leaf = 0;
+      for (size_t j = 0; j < n_sorted; j++)
+        {
+          struct ctables_cell *cell = sorted[j];
+          const struct ctables_nest *nest = &t->stacks[a].nests[cell->axes[a].stack_idx];
 
           size_t n_common = 0;
           bool new_subtable = false;
@@ -2789,6 +2848,8 @@ ctables_table_output_different_axis (struct ctables *ct, struct ctables_table *t
                                              &cell->axes[a].cvs[n_common].value,
                                              var_get_type (nest->vars[n_common]))))
                       break;
+                  if (a == PIVOT_AXIS_ROW)
+                    printf ("n_common=%zu\n", n_common);
                 }
               else
                 new_subtable = true;
@@ -2798,61 +2859,83 @@ ctables_table_output_different_axis (struct ctables *ct, struct ctables_table *t
 
           if (new_subtable)
             {
-              enum ctables_vlabel vlabel = ct->vlabels[var_get_dict_index (nest->vars[0])];
-              top = d[a]->root;
-              if (vlabel != CTVL_NONE)
-                top = pivot_category_create_group__ (
-                  top, pivot_value_new_variable (nest->vars[0]));
+              n_levels = 0;
+              printf ("%s levels:", pivot_axis_type_to_string (a));
+              for (size_t k = 0; k < nest->n; k++)
+                {
+                  enum ctables_vlabel vlabel = ct->vlabels[var_get_dict_index (nest->vars[k])];
+                  if (vlabel != CTVL_NONE)
+                    {
+                      printf (" var(%s)", var_get_name (nest->vars[k]));
+                      levels[n_levels++] = (struct ctables_level) {
+                        .type = CTL_VAR,
+                        .var_idx = k,
+                      };
+                    }
+
+                  if (nest->scale_idx != k
+                      && (k != nest->n - 1 || t->label_axis[a] == a))
+                    {
+                      printf (" category(%s)", var_get_name (nest->vars[k]));
+                      levels[n_levels++] = (struct ctables_level) {
+                        .type = CTL_CATEGORY,
+                        .var_idx = k,
+                      };
+                    }
+                }
+
+              if (a == t->slabels_axis && a == t->summary_axis)
+                {
+                  printf (" summary");
+                  levels[n_levels++] = (struct ctables_level) {
+                    .type = CTL_SUMMARY,
+                    .var_idx = SIZE_MAX,
+                  };
+                }
+              printf ("\n");
             }
+          if (a == PIVOT_AXIS_ROW)
+            printf ("n_common=%zu\n", n_common);
           if (n_common == nest->n)
             {
               cell->axes[a].leaf = prev_leaf;
               continue;
             }
 
-          for (size_t k = n_common; k < nest->n; k++)
+          for (size_t k = 0; k < n_levels; k++)
             {
-              struct pivot_category *parent = k > 0 ? groups[k - 1] : top;
+              const struct ctables_level *level = &levels[k];
+              if (n_common > level->var_idx)
+                continue;
 
-              struct pivot_value *label
-                = (k == nest->scale_idx ? NULL
-                   : (cell->axes[a].cvs[k].category->type == CCT_TOTAL
-                      || cell->axes[a].cvs[k].category->type == CCT_SUBTOTAL
-                      || cell->axes[a].cvs[k].category->type == CCT_HSUBTOTAL)
-                   ? pivot_value_new_user_text (cell->axes[a].cvs[k].category->total_label,
-                                                SIZE_MAX)
-                   : pivot_value_new_var_value (nest->vars[k],
-                                                &cell->axes[a].cvs[k].value));
-              if (k == nest->n - 1)
+              struct pivot_category *parent = k ? groups[k - 1] : d[a]->root;
+              if (level->type == CTL_SUMMARY)
+                {
+                  const struct ctables_summary_spec_set *specs = &t->summary_specs;
+                  for (size_t m = 0; m < specs->n; m++)
+                    pivot_category_create_leaf (
+                      parent, pivot_value_new_text (specs->specs[m].label));
+                }
+              else
                 {
-                  if (a == t->slabels_axis)
+                  const struct variable *var = nest->vars[level->var_idx];
+                  struct pivot_value *label;
+                  if (level->type == CTL_VAR)
+                    label = pivot_value_new_variable (var);
+                  else if (level->type == CTL_CATEGORY)
                     {
-                      if (label)
-                        parent = pivot_category_create_group__ (parent, label);
-                      const struct ctables_summary_spec_set *specs = &t->summary_specs;
-                      for (size_t m = 0; m < specs->n; m++)
-                        {
-                          int leaf = pivot_category_create_leaf (
-                            parent, pivot_value_new_text (specs->specs[m].label));
-                          if (m == 0)
-                            prev_leaf = leaf;
-                        }
+                      const struct ctables_cell_value *cv = &cell->axes[a].cvs[level->var_idx];
+                      label = ctables_category_create_label (cv->category,
+                                                             var, &cv->value);
                     }
                   else
-                    {
-                      prev_leaf = pivot_category_create_leaf (parent, label ? label : pivot_value_new_user_text ("text", SIZE_MAX));
-                    }
-                  break;
-                }
-
-              if (label)
-                parent = pivot_category_create_group__ (parent, label);
+                    NOT_REACHED ();
 
-              enum ctables_vlabel vlabel = ct->vlabels[var_get_dict_index (nest->vars[k + 1])];
-              if (vlabel != CTVL_NONE)
-                parent = pivot_category_create_group__ (
-                  parent, pivot_value_new_variable (nest->vars[k + 1]));
-              groups[k] = parent;
+                  if (k == n_levels - 1)
+                    pivot_category_create_leaf (parent, label);
+                  else
+                    groups[k] = pivot_category_create_group__ (parent, label);
+                }
             }
 
           cell->axes[a].leaf = prev_leaf;
@@ -3068,6 +3151,7 @@ ctables_sort_clabels_values (struct ctables_table *t)
   size_t i = 0;
   HMAP_FOR_EACH (clv, struct ctables_value, node, &t->clabels_values_map)
     t->clabels_values[i++] = clv->value;
+  t->n_clabels_values = n;
   assert (i == n);
 
   sort (t->clabels_values, n, sizeof *t->clabels_values,
@@ -3127,6 +3211,8 @@ ctables_check_label_position (struct ctables_table *t, enum pivot_axis_type a)
   if (label_pos == a)
     return true;
 
+  t->clabels_from_axis = a;
+
   const char *subcommand_name = a == PIVOT_AXIS_ROW ? "ROWLABELS" : "COLLABELS";
   const char *pos_name = label_pos == PIVOT_AXIS_LAYER ? "LAYER" : "OPPOSITE";
 
@@ -3148,6 +3234,13 @@ ctables_check_label_position (struct ctables_table *t, enum pivot_axis_type a)
              subcommand_name, pos_name);
         return false;
       }
+  if (n0->n - 1 == n0->scale_idx)
+    {
+      msg (SE, _("%s=%s requires the variables to be moved to be categorical, "
+                 "but %s is a scale variable."),
+           subcommand_name, pos_name, var_get_name (v0));
+      return false;
+    }
 
   for (size_t i = 1; i < stack->n; i++)
     {
@@ -3156,6 +3249,13 @@ ctables_check_label_position (struct ctables_table *t, enum pivot_axis_type a)
       const struct variable *vi = ni->vars[ni->n - 1];
       struct ctables_categories *ci = t->categories[var_get_dict_index (vi)];
 
+      if (ni->n - 1 == ni->scale_idx)
+        {
+          msg (SE, _("%s=%s requires the variables to be moved to be "
+                     "categorical, but %s is a scale variable."),
+               subcommand_name, pos_name, var_get_name (vi));
+          return false;
+        }
       if (var_get_width (v0) != var_get_width (vi))
         {
           msg (SE, _("%s=%s requires the variables to be "
@@ -3434,6 +3534,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
           [PIVOT_AXIS_COLUMN] = PIVOT_AXIS_COLUMN,
           [PIVOT_AXIS_LAYER] = PIVOT_AXIS_LAYER,
         },
+        .clabels_from_axis = PIVOT_AXIS_LAYER, 
         .categories = categories,
         .n_categories = n_vars,
         .cilevel = 95,