refactoring
[pspp] / src / language / stats / ctables.c
index 1abbf8a667cb31134125a1d87b4f0c6b26dc7e49..ff8f1abbc0a6f0f95b7f8a3b40f620507b0f8a37 100644 (file)
@@ -455,6 +455,7 @@ struct ctables_table
     */
     enum pivot_axis_type label_axis[PIVOT_N_AXES];
     enum pivot_axis_type clabels_from_axis;
+    enum pivot_axis_type clabels_to_axis;
     const struct variable *clabels_example;
     struct hmap clabels_values_map;
     struct ctables_value **clabels_values;
@@ -735,12 +736,32 @@ static void ctables_axis_destroy (struct ctables_axis *);
 
 struct ctables_summary_spec
   {
+    /* The calculation to be performed.
+
+       'function' is the function to calculate.  'weighted' specifies whether
+       to use weighted or unweighted data (for functions that do not support a
+       choice, it must be true).  'calc_area' is the area over which the
+       calculation takes place (for functions that target only an individual
+       cell, it must be 0).  For CTSF_PTILE only, 'percentile' is the
+       percentile between 0 and 100 (for other functions it must be 0). */
     enum ctables_summary_function function;
     bool weighted;
-    enum ctables_area_type area;
+    enum ctables_area_type calc_area;
     double percentile;          /* CTSF_PTILE only. */
-    char *label;
 
+    /* How to display the result of the calculation.
+
+       'label' is a user-specified label, NULL if the user didn't specify
+       one.
+
+       'user_area' is usually the same as 'calc_area', but when category labels
+       are rotated from one axis to another it swaps rows and columns.
+
+       'format' is the format for displaying the output.  If
+       'is_ctables_format' is true, then 'format.type' is one of the special
+       CTEF_* formats instead of the standard ones. */
+    char *label;
+    enum ctables_area_type user_area;
     struct fmt_spec format;
     bool is_ctables_format;       /* Is 'format' one of CTEF_*? */
 
@@ -965,7 +986,7 @@ static const char *
 ctables_summary_label__ (const struct ctables_summary_spec *spec)
 {
   bool w = spec->weighted;
-  enum ctables_area_type a = spec->area;
+  enum ctables_area_type a = spec->user_area;
   switch (spec->function)
     {
     case CTSF_COUNT:
@@ -1160,7 +1181,8 @@ add_summary_spec (struct ctables_axis *axis,
       *dst = (struct ctables_summary_spec) {
         .function = function,
         .weighted = weighted,
-        .area = area,
+        .calc_area = area,
+        .user_area = area,
         .percentile = percentile,
         .label = xstrdup_if_nonnull (label),
         .format = (format ? *format
@@ -2726,25 +2748,25 @@ ctables_summary_value (const struct ctables_cell *cell,
       return s->count;
 
     case CTSF_areaID:
-      return cell->areas[ss->area]->sequence;
+      return cell->areas[ss->calc_area]->sequence;
 
     case CTSF_areaPCT_COUNT:
       {
-        const struct ctables_area *a = cell->areas[ss->area];
+        const struct ctables_area *a = cell->areas[ss->calc_area];
         double a_count = ss->weighted ? a->e_count : a->u_count;
         return a_count ? s->count / a_count * 100 : SYSMIS;
       }
 
     case CTSF_areaPCT_VALIDN:
       {
-        const struct ctables_area *a = cell->areas[ss->area];
+        const struct ctables_area *a = cell->areas[ss->calc_area];
         double a_valid = ss->weighted ? a->e_valid : a->u_valid;
         return a_valid ? s->count / a_valid * 100 : SYSMIS;
       }
 
     case CTSF_areaPCT_TOTALN:
       {
-        const struct ctables_area *a = cell->areas[ss->area];
+        const struct ctables_area *a = cell->areas[ss->calc_area];
         double a_total = ss->weighted ? a->e_total : a->u_total;
         return a_total ? s->count / a_total * 100 : SYSMIS;
       }
@@ -2807,7 +2829,7 @@ ctables_summary_value (const struct ctables_cell *cell,
         if (weight == SYSMIS || mean == SYSMIS)
           return SYSMIS;
 
-        const struct ctables_area *a = cell->areas[ss->area];
+        const struct ctables_area *a = cell->areas[ss->calc_area];
         const struct ctables_sum *sum = &a->sums[ss->sum_var_idx];
         double denom = ss->weighted ? sum->e_sum : sum->u_sum;
         return denom != 0 ? weight * mean / denom * 100 : SYSMIS;
@@ -3499,8 +3521,8 @@ merge_item_compare_3way (const struct merge_item *a, const struct merge_item *b)
     return as->function > bs->function ? 1 : -1;
   else if (as->weighted != bs->weighted)
     return as->weighted > bs->weighted ? 1 : -1;
-  else if (as->area != bs->area)
-    return as->area > bs->area ? 1 : -1;
+  else if (as->calc_area != bs->calc_area)
+    return as->calc_area > bs->calc_area ? 1 : -1;
   else if (as->percentile != bs->percentile)
     return as->percentile < bs->percentile ? 1 : -1;
 
@@ -3996,7 +4018,7 @@ ctables_cell_calculate_postcompute (const struct ctables_section *s,
           const struct ctables_summary_spec *ss2 = &pc->specs->specs[i];
           if (ss->function == ss2->function
               && ss->weighted == ss2->weighted
-              && ss->area == ss2->area
+              && ss->calc_area == ss2->calc_area
               && ss->percentile == ss2->percentile)
             {
               *format = ss2->format;
@@ -4476,8 +4498,6 @@ 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";
 
@@ -4575,6 +4595,33 @@ add_sum_var (struct variable *var,
   return (*n)++;
 }
 
+static enum ctables_area_type
+rotate_area (enum ctables_area_type area)
+{
+  return area;
+  switch (area)
+    {
+    case CTAT_TABLE:
+    case CTAT_LAYER:
+    case CTAT_SUBTABLE:
+      return area;
+
+    case CTAT_LAYERROW:
+      return CTAT_LAYERCOL;
+
+    case CTAT_LAYERCOL:
+      return CTAT_LAYERROW;
+
+    case CTAT_ROW:
+      return CTAT_COL;
+
+    case CTAT_COL:
+      return CTAT_ROW;
+    }
+
+  NOT_REACHED ();
+}
+
 static void
 enumerate_sum_vars (const struct ctables_axis *a,
                     struct variable ***sum_vars, size_t *n, size_t *allocated)
@@ -4618,48 +4665,94 @@ ctables_prepare_table (struct ctables_table *t)
                 nest->areas[at] = xmalloc (nest->n * sizeof *nest->areas[at]);
                 nest->n_areas[at] = 0;
 
-                for (size_t k = 0; k < nest->n; k++)
+                bool add_vars = (at == CTAT_LAYER ? a == PIVOT_AXIS_LAYER
+                                 : at == CTAT_LAYERROW ? a != PIVOT_AXIS_COLUMN
+                                 : at == CTAT_LAYERCOL ? a != PIVOT_AXIS_ROW
+                                 : at == CTAT_TABLE ? false
+                                 : true);
+                if (add_vars)
+                  for (size_t k = 0; k < nest->n; k++)
+                    {
+                      if (k == nest->scale_idx)
+                        continue;
+                      nest->areas[at][nest->n_areas[at]++] = k;
+                    }
+                else if ((at == CTAT_LAYERCOL && a == PIVOT_AXIS_ROW && t->label_axis[a] != a)
+                         || (at == CTAT_LAYERROW && a == PIVOT_AXIS_COLUMN && t->label_axis[a] != a)
+                         || (at == CTAT_LAYER && t->label_axis[a] == PIVOT_AXIS_LAYER))
                   {
-                    if (k == nest->scale_idx)
-                      continue;
-
-                    switch (at)
+                    for (size_t k = nest->n - 1; k < nest->n; k--)
                       {
-                      case CTAT_TABLE:
-                        continue;
-
-                      case CTAT_LAYER:
-                        if (a != PIVOT_AXIS_LAYER)
+                        if (k == nest->scale_idx)
                           continue;
+                        nest->areas[at][nest->n_areas[at]++] = k;
                         break;
+                      }
+                    continue;
+                  }
 
-                      case CTAT_SUBTABLE:
-                      case CTAT_ROW:
-                      case CTAT_COL:
-                        if (at == CTAT_SUBTABLE ? a != PIVOT_AXIS_LAYER
-                            : at == CTAT_ROW ? a == PIVOT_AXIS_COLUMN
-                            : a == PIVOT_AXIS_ROW)
-                          {
-                            if (k == nest->n - 1
-                                || (nest->scale_idx == nest->n - 1
-                                    && k == nest->n - 2))
-                              continue;
-                          }
-                        break;
-
-                      case CTAT_LAYERROW:
-                        if (a == PIVOT_AXIS_COLUMN)
-                          continue;
-                        break;
+                size_t n_drop;
+                bool drop_inner = false;
+                if (at == CTAT_SUBTABLE
+                    && t->clabels_from_axis != PIVOT_AXIS_LAYER
+                    && t->clabels_to_axis != PIVOT_AXIS_LAYER)
+                  n_drop = a == t->clabels_from_axis ? 2 : 0;
+                else if (at == CTAT_SUBTABLE
+                         && t->clabels_from_axis != PIVOT_AXIS_LAYER
+                         && t->clabels_to_axis == PIVOT_AXIS_LAYER)
+                  {
+                    drop_inner = a == t->clabels_from_axis;
+                    n_drop = a != t->clabels_from_axis && a != PIVOT_AXIS_LAYER;
+                  }
+                else if (at == CTAT_ROW && a == PIVOT_AXIS_COLUMN && t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN)
+                  n_drop = 0;
+                else if (at == CTAT_COL && a == PIVOT_AXIS_ROW && t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN)
+                  {
+                    drop_inner = true;
+                    n_drop = 0;
+                  }
+                else if (at == CTAT_COL && a == PIVOT_AXIS_ROW && t->label_axis[PIVOT_AXIS_COLUMN] == PIVOT_AXIS_ROW)
+                  n_drop = 0;
+                else if (at == CTAT_ROW && a == PIVOT_AXIS_COLUMN && t->label_axis[PIVOT_AXIS_COLUMN] == PIVOT_AXIS_ROW)
+                  {
+                    drop_inner = true;
+                    n_drop = 0;
+                  }
+                else if (at == CTAT_COL && a == PIVOT_AXIS_ROW && t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_LAYER)
+                  {
+                    drop_inner = true;
+                    n_drop = 0;
+                  }
+                else if (at == CTAT_ROW && a == PIVOT_AXIS_COLUMN && t->label_axis[PIVOT_AXIS_COLUMN] == PIVOT_AXIS_LAYER)
+                  {
+                    drop_inner = true;
+                    n_drop = 0;
+                  }
+                else
+                  {
+                    n_drop = 0;
+                    if (at == CTAT_SUBTABLE ? a != PIVOT_AXIS_LAYER
+                        : at == CTAT_ROW ? a == PIVOT_AXIS_COLUMN
+                        : at == CTAT_COL ? a == PIVOT_AXIS_ROW
+                        : false)
+                      n_drop++;
+                    if ((t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN && (at == CTAT_ROW || at == CTAT_LAYERROW) && a == PIVOT_AXIS_ROW)
+                        || (t->label_axis[PIVOT_AXIS_COLUMN] == PIVOT_AXIS_ROW && (at == CTAT_COL || at == CTAT_LAYERCOL) && a == PIVOT_AXIS_COLUMN))
+                    n_drop++;
+                  }
 
-                      case CTAT_LAYERCOL:
-                        if (a == PIVOT_AXIS_ROW)
-                          continue;
-                        break;
+                if (drop_inner)
+                  {
+                    size_t n = nest->n_areas[at];
+                    if (n > 1)
+                      {
+                        nest->areas[at][n - 2] = nest->areas[at][n - 1];
+                        nest->n_areas[at]--;
                       }
-
-                    nest->areas[at][nest->n_areas[at]++] = k;
                   }
+                for (size_t i = 0; i < n_drop; i++)
+                  if (nest->n_areas[at] > 0)
+                    nest->n_areas[at]--;
               }
           }
       }
@@ -4702,6 +4795,20 @@ ctables_prepare_table (struct ctables_table *t)
         ctables_summary_spec_set_clone (&nest->specs[CSV_TOTAL],
                                         &nest->specs[CSV_CELL]);
 
+      if (t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN
+          || t->label_axis[PIVOT_AXIS_COLUMN] == PIVOT_AXIS_ROW)
+        {
+          for (enum ctables_summary_variant sv = 0; sv < N_CSVS; sv++)
+            for (size_t i = 0; i < nest->specs[sv].n; i++)
+              {
+                struct ctables_summary_spec *ss = &nest->specs[sv].specs[i];
+                const struct ctables_function_info *cfi =
+                  &ctables_function_info[ss->function];
+                if (cfi->is_area)
+                  ss->calc_area = rotate_area (ss->calc_area);
+              }
+        }
+
       if (t->ctables->smissing_listwise)
         {
           struct variable **listwise_vars = NULL;
@@ -5691,7 +5798,8 @@ ctables_parse_pproperties_format (struct lexer *lexer,
       sss->specs[sss->n++] = (struct ctables_summary_spec) {
         .function = function,
         .weighted = weighted,
-        .area = area,
+        .calc_area = area,
+        .user_area = area,
         .percentile = percentile,
         .format = format,
         .is_ctables_format = is_ctables_format,
@@ -6166,6 +6274,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
           [PIVOT_AXIS_LAYER] = PIVOT_AXIS_LAYER,
         },
         .clabels_from_axis = PIVOT_AXIS_LAYER,
+        .clabels_to_axis = PIVOT_AXIS_LAYER,
         .categories = categories,
         .n_categories = n_vars,
         .cilevel = 95,
@@ -6604,12 +6713,18 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
             break;
         }
 
-      if (t->label_axis[PIVOT_AXIS_ROW] != PIVOT_AXIS_ROW
-          && t->label_axis[PIVOT_AXIS_COLUMN] != PIVOT_AXIS_COLUMN)
+      if (t->label_axis[PIVOT_AXIS_ROW] != PIVOT_AXIS_ROW)
         {
-          msg (SE, _("ROWLABELS and COLLABELS may not both be specified."));
-          goto error;
+          t->clabels_from_axis = PIVOT_AXIS_ROW;
+          if (t->label_axis[PIVOT_AXIS_COLUMN] != PIVOT_AXIS_COLUMN)
+            {
+              msg (SE, _("ROWLABELS and COLLABELS may not both be specified."));
+              goto error;
+            }
         }
+      else if (t->label_axis[PIVOT_AXIS_COLUMN] != PIVOT_AXIS_COLUMN)
+        t->clabels_from_axis = PIVOT_AXIS_COLUMN;
+      t->clabels_to_axis = t->label_axis[t->clabels_from_axis];
 
       if (!ctables_prepare_table (t))
         goto error;