Handle multiple postcomputes.
[pspp] / src / language / stats / ctables.c
index f611af5100014c93fe13a61a94a3dbd915a7ae5b..59c97ecdc20e8714de04ae1e1858b8bfad71c0df 100644 (file)
@@ -234,6 +234,13 @@ struct ctables_domain
     double u_valid;             /* Unweighted. */
     double u_count;
     double u_total;
+    struct ctables_sum *sums;
+  };
+
+struct ctables_sum
+  {
+    double e_sum;
+    double u_sum;
   };
 
 enum ctables_summary_variant
@@ -467,6 +474,8 @@ struct ctables_table
     size_t n_sections;
     enum pivot_axis_type summary_axis;
     struct ctables_summary_spec_set summary_specs;
+    struct variable **sum_vars;
+    size_t n_sum_vars;
 
     const struct variable *clabels_example;
     struct hmap clabels_values_map;
@@ -777,6 +786,7 @@ struct ctables_summary_spec
     bool is_ctables_format;       /* Is 'format' one of CTEF_*? */
 
     size_t axis_idx;
+    size_t sum_var_idx;
   };
 
 static void
@@ -1073,19 +1083,40 @@ ctables_summary_default_format (enum ctables_summary_function function,
     }
 }
 
-static char *
-ctables_summary_default_label (enum ctables_summary_function function,
-                               double percentile)
+static struct pivot_value *
+ctables_summary_label (const struct ctables_summary_spec *spec, double cilevel)
 {
-  static const char *default_labels[] = {
+  if (!spec->label)
+    {
+      static const char *default_labels[] = {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = LABEL,
-    SUMMARIES
+        SUMMARIES
 #undef S
-  };
+      };
+
+      return (spec->function == CTSF_PTILE
+              ? pivot_value_new_text_format (N_("Percentile %.2f"),
+                                             spec->percentile)
+              : pivot_value_new_text (default_labels[spec->function]));
+    }
+  else
+    {
+      struct substring in = ss_cstr (spec->label);
+      struct substring target = ss_cstr (")CILEVEL");
 
-  return (function == CTSF_PTILE
-          ? xasprintf (_("Percentile %.2f"), percentile)
-          : xstrdup (gettext (default_labels[function])));
+      struct string out = DS_EMPTY_INITIALIZER;
+      for (;;)
+        {
+          size_t chunk = ss_find_substring (in, target);
+          ds_put_substring (&out, ss_head (in, chunk));
+          ss_advance (&in, chunk);
+          if (!in.length)
+            return pivot_value_new_user_text_nocopy (ds_steal_cstr (&out));
+          
+          ss_advance (&in, target.length);
+          ds_put_format (&out, "%g", cilevel);
+        }
+    }
 }
 
 static const char *
@@ -1146,7 +1177,7 @@ add_summary_spec (struct ctables_axis *axis,
       *dst = (struct ctables_summary_spec) {
         .function = function,
         .percentile = percentile,
-        .label = xstrdup (label),
+        .label = xstrdup_if_nonnull (label),
         .format = (format ? *format
                    : ctables_summary_default_format (function, axis->var)),
         .is_ctables_format = is_ctables_format,
@@ -1286,14 +1317,12 @@ ctables_axis_parse_postfix (struct ctables_axis_parse_ctx *ctx)
         }
 
       /* Parse label. */
-      char *label;
+      char *label = NULL;
       if (lex_is_string (ctx->lexer))
         {
           label = ss_xstrdup (lex_tokss (ctx->lexer));
           lex_get (ctx->lexer);
         }
-      else
-        label = ctables_summary_default_label (function, percentile);
 
       /* Parse format. */
       struct fmt_spec format;
@@ -2883,6 +2912,105 @@ ctables_function_domain (enum ctables_summary_function function)
   NOT_REACHED ();
 }
 
+static enum ctables_domain_type
+ctables_function_is_pctsum (enum ctables_summary_function function)
+{
+  switch (function)
+    {
+    case CTSF_COUNT:
+    case CTSF_ECOUNT:
+    case CTSF_MISSING:
+    case CSTF_TOTALN:
+    case CTSF_ETOTALN:
+    case CTSF_VALIDN:
+    case CTSF_EVALIDN:
+    case CTSF_MAXIMUM:
+    case CTSF_MINIMUM:
+    case CTSF_RANGE:
+    case CTSF_MEAN:
+    case CTSF_SEMEAN:
+    case CTSF_STDDEV:
+    case CTSF_SUM:
+    case CTSF_VARIANCE:
+    case CTSF_MEDIAN:
+    case CTSF_PTILE:
+    case CTSF_MODE:
+    case CTSF_UCOUNT:
+    case CTSF_UMISSING:
+    case CSTF_UTOTALN:
+    case CTSF_UVALIDN:
+    case CTSF_UMEAN:
+    case CTSF_USEMEAN:
+    case CTSF_USTDDEV:
+    case CTSF_USUM:
+    case CTSF_UVARIANCE:
+    case CTSF_UMEDIAN:
+    case CTSF_UPTILE:
+    case CTSF_UMODE:
+    case CTSF_COLPCT_COUNT:
+    case CTSF_COLPCT_TOTALN:
+    case CTSF_COLPCT_VALIDN:
+    case CTSF_UCOLPCT_COUNT:
+    case CTSF_UCOLPCT_TOTALN:
+    case CTSF_UCOLPCT_VALIDN:
+    case CTSF_LAYERCOLPCT_COUNT:
+    case CTSF_LAYERCOLPCT_TOTALN:
+    case CTSF_LAYERCOLPCT_VALIDN:
+    case CTSF_ULAYERCOLPCT_COUNT:
+    case CTSF_ULAYERCOLPCT_TOTALN:
+    case CTSF_ULAYERCOLPCT_VALIDN:
+    case CTSF_LAYERPCT_COUNT:
+    case CTSF_LAYERPCT_TOTALN:
+    case CTSF_LAYERPCT_VALIDN:
+    case CTSF_ULAYERPCT_COUNT:
+    case CTSF_ULAYERPCT_TOTALN:
+    case CTSF_ULAYERPCT_VALIDN:
+    case CTSF_LAYERROWPCT_COUNT:
+    case CTSF_LAYERROWPCT_TOTALN:
+    case CTSF_LAYERROWPCT_VALIDN:
+    case CTSF_ULAYERROWPCT_COUNT:
+    case CTSF_ULAYERROWPCT_TOTALN:
+    case CTSF_ULAYERROWPCT_VALIDN:
+    case CTSF_ROWPCT_COUNT:
+    case CTSF_ROWPCT_TOTALN:
+    case CTSF_ROWPCT_VALIDN:
+    case CTSF_UROWPCT_COUNT:
+    case CTSF_UROWPCT_TOTALN:
+    case CTSF_UROWPCT_VALIDN:
+    case CTSF_SUBTABLEPCT_COUNT:
+    case CTSF_SUBTABLEPCT_TOTALN:
+    case CTSF_SUBTABLEPCT_VALIDN:
+    case CTSF_USUBTABLEPCT_COUNT:
+    case CTSF_USUBTABLEPCT_TOTALN:
+    case CTSF_USUBTABLEPCT_VALIDN:
+    case CTSF_TABLEPCT_COUNT:
+    case CTSF_TABLEPCT_TOTALN:
+    case CTSF_TABLEPCT_VALIDN:
+    case CTSF_UTABLEPCT_COUNT:
+    case CTSF_UTABLEPCT_TOTALN:
+    case CTSF_UTABLEPCT_VALIDN:
+      return false;
+
+    case CTSF_COLPCT_SUM:
+    case CTSF_UCOLPCT_SUM:
+    case CTSF_LAYERCOLPCT_SUM:
+    case CTSF_ULAYERCOLPCT_SUM:
+    case CTSF_LAYERPCT_SUM:
+    case CTSF_ULAYERPCT_SUM:
+    case CTSF_LAYERROWPCT_SUM:
+    case CTSF_ULAYERROWPCT_SUM:
+    case CTSF_ROWPCT_SUM:
+    case CTSF_UROWPCT_SUM:
+    case CTSF_SUBTABLEPCT_SUM:
+    case CTSF_USUBTABLEPCT_SUM:
+    case CTSF_TABLEPCT_SUM:
+    case CTSF_UTABLEPCT_SUM:
+      return true;
+    }
+
+  NOT_REACHED ();
+}
+
 static double
 ctables_summary_value (const struct ctables_cell *cell,
                        union ctables_summary *s,
@@ -3045,6 +3173,16 @@ ctables_summary_value (const struct ctables_cell *cell,
     case CTSF_LAYERPCT_SUM:
     case CTSF_LAYERROWPCT_SUM:
     case CTSF_LAYERCOLPCT_SUM:
+      {
+        double weight, mean;
+        moments1_calculate (s->moments, &weight, &mean, NULL, NULL, NULL);
+        if (weight == SYSMIS || mean == SYSMIS)
+          return SYSMIS;
+        enum ctables_domain_type d = ctables_function_domain (ss->function);
+        double num = weight * mean;
+        double denom = cell->domains[d]->sums[ss->sum_var_idx].e_sum;
+        return denom != 0 ? num / denom * 100 : SYSMIS;
+      }
     case CTSF_UROWPCT_SUM:
     case CTSF_UCOLPCT_SUM:
     case CTSF_UTABLEPCT_SUM:
@@ -3052,7 +3190,16 @@ ctables_summary_value (const struct ctables_cell *cell,
     case CTSF_ULAYERPCT_SUM:
     case CTSF_ULAYERROWPCT_SUM:
     case CTSF_ULAYERCOLPCT_SUM:
-      NOT_REACHED ();
+      {
+        double weight, mean;
+        moments1_calculate (s->moments, &weight, &mean, NULL, NULL, NULL);
+        if (weight == SYSMIS || mean == SYSMIS)
+          return SYSMIS;
+        enum ctables_domain_type d = ctables_function_domain (ss->function);
+        double num = weight * mean;
+        double denom = cell->domains[d]->sums[ss->sum_var_idx].u_sum;
+        return denom != 0 ? num / denom * 100 : SYSMIS;
+      }
 
     case CTSF_MEDIAN:
     case CTSF_PTILE:
@@ -3233,8 +3380,12 @@ ctables_domain_insert (struct ctables_section *s, struct ctables_cell *cell,
     not_equal: ;
     }
 
+  struct ctables_sum *sums = (s->table->n_sum_vars
+                              ? xzalloc (s->table->n_sum_vars * sizeof *sums)
+                              : NULL);
+
   d = xmalloc (sizeof *d);
-  *d = (struct ctables_domain) { .example = cell };
+  *d = (struct ctables_domain) { .example = cell, .sums = sums };
   hmap_insert (&s->domains[domain], &d->node, hash);
   return d;
 }
@@ -3524,6 +3675,19 @@ ctables_cell_add__ (struct ctables_section *s, const struct ccase *c,
             d->d_valid += d_weight;
             d->e_valid += e_weight;
             d->u_count += 1.0;
+
+            for (size_t i = 0; i < s->table->n_sum_vars; i++)
+              {
+                /* XXX listwise_missing??? */
+                const struct variable *var = s->table->sum_vars[i];
+                double addend = case_num (c, var);
+                if (!var_is_num_missing (var, addend))
+                  {
+                    struct ctables_sum *sum = &d->sums[i];
+                    sum->e_sum += addend * e_weight;
+                    sum->u_sum += addend;
+                  }
+              }
           }
       }
 }
@@ -3698,7 +3862,10 @@ merge_item_compare_3way (const struct merge_item *a, const struct merge_item *b)
     return as->function > bs->function ? 1 : -1;
   else if (as->percentile != bs->percentile)
     return as->percentile < bs->percentile ? 1 : -1;
-  return strcmp (as->label, bs->label);
+
+  const char *as_label = as->label ? as->label : "";
+  const char *bs_label = bs->label ? bs->label : "";
+  return strcmp (as_label, bs_label);
 }
 
 static struct pivot_value *
@@ -4046,24 +4213,30 @@ ctables_cell_postcompute (const struct ctables_section *s,
                           size_t *pc_a_idx_p)
 {
   assert (cell->postcompute);
-  for (enum pivot_axis_type pc_a = 0; ; pc_a++)
-    {
-      assert (pc_a < PIVOT_N_AXES);
-      for (size_t pc_a_idx = 0; pc_a_idx < s->nests[pc_a]->n; pc_a_idx++)
-        {
-          const struct ctables_cell_value *cv = &cell->axes[pc_a].cvs[pc_a_idx];
-          if (cv->category->type == CCT_POSTCOMPUTE)
-            {
-              if (pc_a_p)
-                *pc_a_p = pc_a;
-              if (pc_a_idx_p)
-                *pc_a_idx_p = pc_a_idx;
-              return cv->category->pc;
-            }
-        }
-    }
+  const struct ctables_postcompute *pc = NULL;
+  for (enum pivot_axis_type pc_a = 0; pc_a < PIVOT_N_AXES; pc_a++)
+    for (size_t pc_a_idx = 0; pc_a_idx < s->nests[pc_a]->n; pc_a_idx++)
+      {
+        const struct ctables_cell_value *cv = &cell->axes[pc_a].cvs[pc_a_idx];
+        if (cv->category->type == CCT_POSTCOMPUTE)
+          {
+            if (pc)
+              {
+                /* Multiple postcomputes cross each other.  The value is
+                   undefined. */
+                return NULL;
+              }
 
-  NOT_REACHED ();
+            pc = cv->category->pc;
+            if (pc_a_p)
+              *pc_a_p = pc_a;
+            if (pc_a_idx_p)
+              *pc_a_idx_p = pc_a_idx;
+          }
+      }
+
+  assert (pc != NULL);
+  return pc;
 }
 
 static double
@@ -4078,6 +4251,8 @@ ctables_cell_calculate_postcompute (const struct ctables_section *s,
   size_t pc_a_idx;
   const struct ctables_postcompute *pc = ctables_cell_postcompute (
     s, cell, &pc_a, &pc_a_idx);
+  if (!pc)
+    return SYSMIS;
 
   if (pc->specs)
     {
@@ -4135,7 +4310,7 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
         d->hide_all_labels = true;
       for (size_t i = 0; i < specs->n; i++)
         pivot_category_create_leaf (
-          d->root, pivot_value_new_text (specs->specs[i].label));
+          d->root, ctables_summary_label (&specs->specs[i], t->cilevel));
     }
 
   bool categories_dimension = t->clabels_example != NULL;
@@ -4330,7 +4505,8 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
                       for (size_t m = 0; m < specs->n; m++)
                         {
                           int leaf = pivot_category_create_leaf (
-                            parent, pivot_value_new_text (specs->specs[m].label));
+                            parent, ctables_summary_label (&specs->specs[m],
+                                                           t->cilevel));
                           if (!m)
                             prev_leaf = leaf;
                         }
@@ -4537,6 +4713,47 @@ ctables_check_label_position (struct ctables_table *t, enum pivot_axis_type a)
   return true;
 }
 
+static size_t
+add_sum_var (struct variable *var,
+             struct variable ***sum_vars, size_t *n, size_t *allocated)
+{
+  for (size_t i = 0; i < *n; i++)
+    if (var == (*sum_vars)[i])
+      return i;
+
+  if (*n >= *allocated)
+    *sum_vars = x2nrealloc (*sum_vars, allocated, sizeof **sum_vars);
+  (*sum_vars)[*n] = var;
+  return (*n)++;
+}
+
+static void
+enumerate_sum_vars (const struct ctables_axis *a,
+                    struct variable ***sum_vars, size_t *n, size_t *allocated)
+{
+  if (!a)
+    return;
+
+  switch (a->op)
+    {
+    case CTAO_VAR:
+      for (size_t i = 0; i < N_CSVS; i++)
+        for (size_t j = 0; j < a->specs[i].n; j++)
+          {
+            struct ctables_summary_spec *spec = &a->specs[i].specs[j];
+            if (ctables_function_is_pctsum (spec->function))
+              spec->sum_var_idx = add_sum_var (a->var, sum_vars, n, allocated);
+          }
+      break;
+
+    case CTAO_STACK:
+    case CTAO_NEST:
+      for (size_t i = 0; i < 2; i++)
+        enumerate_sum_vars (a->subs[i], sum_vars, n, allocated);
+      break;
+    }
+}
+
 static bool
 ctables_prepare_table (struct ctables_table *t)
 {
@@ -4621,7 +4838,6 @@ ctables_prepare_table (struct ctables_table *t)
           *specs->specs = (struct ctables_summary_spec) {
             .function = function,
             .format = ctables_summary_default_format (function, specs->var),
-            .label = ctables_summary_default_label (function, 0),
           };
           if (!specs->var)
             specs->var = nest->vars[0];
@@ -4662,7 +4878,7 @@ ctables_prepare_table (struct ctables_table *t)
     }
 
   struct ctables_summary_spec_set *merged = &t->summary_specs;
-  struct merge_item *items = xnmalloc (2 * stack->n, sizeof *items);
+  struct merge_item *items = xnmalloc (N_CSVS * stack->n, sizeof *items);
   size_t n_left = 0;
   for (size_t j = 0; j < stack->n; j++)
     {
@@ -4718,6 +4934,10 @@ ctables_prepare_table (struct ctables_table *t)
     }
 #endif
 
+  size_t allocated_sum_vars = 0;
+  enumerate_sum_vars (t->axes[t->summary_axis],
+                      &t->sum_vars, &t->n_sum_vars, &allocated_sum_vars);
+
   return (ctables_check_label_position (t, PIVOT_AXIS_ROW)
           && ctables_check_label_position (t, PIVOT_AXIS_COLUMN));
 }