TABLEID, ROWID, COLID, LAYERROWID, LAYERCOLID are OK for ROWLABELS=OPPOSITE
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 13 Aug 2022 22:28:49 +0000 (15:28 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 13 Aug 2022 22:28:49 +0000 (15:28 -0700)
src/language/stats/ctables.c
tests/language/stats/ctables.at

index 1abbf8a667cb31134125a1d87b4f0c6b26dc7e49..836a974d824dda2f6f00c7cffa96df4e4b97c559 100644 (file)
@@ -735,12 +735,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 +985,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 +1180,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 +2747,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 +2828,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 +3520,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 +4017,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;
@@ -4575,6 +4596,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 +4666,40 @@ 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_COL || at == CTAT_LAYERCOL) && a == PIVOT_AXIS_ROW && t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN)
                   {
-                    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)
-                          continue;
-                        break;
-
-                      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;
-
-                      case CTAT_LAYERCOL:
-                        if (a == PIVOT_AXIS_ROW)
+                        if (k == nest->scale_idx)
                           continue;
+                        nest->areas[at][nest->n_areas[at]++] = k;
                         break;
                       }
-
-                    nest->areas[at][nest->n_areas[at]++] = k;
                   }
+
+                bool drop_last = (at == CTAT_SUBTABLE ? a != PIVOT_AXIS_LAYER
+                                  : at == CTAT_ROW ? a == PIVOT_AXIS_COLUMN
+                                  : at == CTAT_COL ? a == PIVOT_AXIS_ROW
+                                  : false);
+                if (drop_last && nest->n_areas[at] > 0)
+                  nest->n_areas[at]--;
+
+                bool drop_additional
+                  = (((at == CTAT_ROW || at == CTAT_LAYERROW || at == CTAT_COL) && a == PIVOT_AXIS_ROW && t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN));
+                if (drop_additional && nest->n_areas[at] > 0)
+                  nest->n_areas[at]--;
               }
           }
       }
@@ -4702,6 +4742,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 +5745,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,
index fd1000b853734325834f237971e4f03b38502543..dbc70830dc1464a1b7e4c746cdda1933286a52d4 100644 (file)
@@ -2055,9 +2055,10 @@ AT_SETUP([CTABLES CLABELS])
 AT_CHECK([ln $top_srcdir/examples/nhtsa.sav . || cp $top_srcdir/examples/nhtsa.sav .])
 AT_DATA([ctables.sps],
 [[GET 'nhtsa.sav'.
-CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [COLPCT].
-CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [COLPCT] /CLABELS ROWLABELS=OPPOSITE.
-CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [COLPCT] /CLABELS ROWLABELS=LAYER.
+CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT].
+CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT] /CLABELS ROWLABELS=OPPOSITE.
+CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT] /CLABELS ROWLABELS=OPPOSITE.
+CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT] /CLABELS ROWLABELS=LAYER.
 ]])
 AT_CHECK([pspp ctables.sps --table-look="$builddir"/all-layers.stt -O box=unicode], [0], [dnl
 ])
@@ -3200,6 +3201,35 @@ Female
 ])
 AT_CLEANUP
 
+AT_SETUP([CTABLES area definitions with CLABELS OPPOSITE])
+AT_CHECK([ln $top_srcdir/examples/nhtsa.sav . || cp $top_srcdir/examples/nhtsa.sav .])
+AT_DATA([ctables.sps],
+[[GET 'nhtsa.sav'.
+CTABLES
+    /VLABELS VARIABLES=ALL DISPLAY=NAME
+    /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[TABLEID]
+    /SLABELS POSITION=ROW
+    /CLABELS ROWLABELS=OPPOSITE
+    /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[SUBTABLEID]
+    /SLABELS POSITION=ROW
+    /CLABELS ROWLABELS=OPPOSITE
+    /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[ROWID]
+    /SLABELS POSITION=ROW
+    /CLABELS ROWLABELS=OPPOSITE
+    /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[COLID]
+    /SLABELS POSITION=ROW
+    /CLABELS ROWLABELS=OPPOSITE
+    /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[LAYERROWID]
+    /SLABELS POSITION=ROW
+    /CLABELS ROWLABELS=OPPOSITE
+    /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[LAYERCOLID]
+    /SLABELS POSITION=ROW
+    /CLABELS ROWLABELS=OPPOSITE
+]])
+AT_CHECK([pspp ctables.sps --table-look="$builddir"/all-layers.stt -O box=unicode -O width=120], [0], [dnl
+])
+AT_CLEANUP
+
 AT_SETUP([CTABLES categorical summary functions])
 AT_CHECK([ln $top_srcdir/examples/nhtsa.sav . || cp $top_srcdir/examples/nhtsa.sav .])
 AT_DATA([ctables.sps],