CTABLES missing values start to make some minimal amount of sense
[pspp] / src / language / stats / ctables.c
index f7b2691ac0a2bc3a526c79cbbba2b7bb1668b619..92e7a55fa5cda8763f28762424b768a5da22656c 100644 (file)
@@ -180,9 +180,11 @@ struct ctables_domain
     const struct ctables_cell *example;
 
     double d_valid;             /* Dictionary weight. */
-    double d_missing;
+    double d_count;
+    double d_total;
     double e_valid;             /* Effective weight */
-    double e_missing;
+    double e_count;
+    double e_total;
   };
 
 enum ctables_summary_variant
@@ -203,6 +205,7 @@ struct ctables_cell
     struct ctables_domain *domains[N_CTDTS];
 
     bool hide;
+
     bool postcompute;
     enum ctables_summary_variant sv;
 
@@ -337,7 +340,15 @@ struct ctables_summary_spec_set
     size_t n;
     size_t allocated;
 
-    struct variable *scale_var;
+    /* The variable to which the summary specs are applied. */
+    struct variable *var;
+
+    /* Whether the variable to which the summary specs are applied is a scale
+       variable for the purpose of summarization.
+
+       (VALIDN and TOTALN act differently for summarizing scale and categorical
+       variables.) */
+    bool is_scale;
   };
 
 static void ctables_summary_spec_set_clone (struct ctables_summary_spec_set *,
@@ -481,6 +492,9 @@ struct ctables_category
         CCT_VALUE,
         CCT_LABEL,
         CCT_FUNCTION,
+
+        /* For contributing to TOTALN. */
+        CCT_EXCLUDED_MISSING,
       }
     type;
 
@@ -516,7 +530,7 @@ struct ctables_category
       };
 
     /* Source location.  This is null for CCT_TOTAL, CCT_VALUE, CCT_LABEL,
-       CCT_FUNCTION. */
+       CCT_FUNCTION, CCT_EXCLUDED_MISSING. */
     struct msg_location *location;
   };
 
@@ -548,6 +562,9 @@ ctables_category_uninit (struct ctables_category *cat)
     case CCT_LABEL:
     case CCT_FUNCTION:
       break;
+
+    case CCT_EXCLUDED_MISSING:
+      break;
     }
 }
 
@@ -588,6 +605,9 @@ ctables_category_equal (const struct ctables_category *a,
               && a->sort_function == b->sort_function
               && a->sort_var == b->sort_var
               && a->percentile == b->percentile);
+
+    case CCT_EXCLUDED_MISSING:
+      return true;
     }
 
   NOT_REACHED ();
@@ -730,7 +750,8 @@ ctables_summary_spec_set_clone (struct ctables_summary_spec_set *dst,
     .specs = specs,
     .n = src->n,
     .allocated = src->n,
-    .scale_var = src->scale_var
+    .var = src->var,
+    .is_scale = src->is_scale,
   };
 }
 
@@ -1007,6 +1028,7 @@ add_summary_spec (struct ctables_axis *axis,
           break;
 
         case CTFA_SCALE:
+#if 0
           if (!axis->scale)
             {
               msg_at (SE, loc,
@@ -1016,6 +1038,7 @@ add_summary_spec (struct ctables_axis *axis,
                       var_name);
               return false;
             }
+#endif
           break;
 
         case CTFA_ALL:
@@ -1924,6 +1947,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
         case CCT_VALUE:
         case CCT_LABEL:
         case CCT_FUNCTION:
+        case CCT_EXCLUDED_MISSING:
           break;
         }
     }
@@ -1975,9 +1999,9 @@ nest_fts (struct ctables_stack s0, struct ctables_stack s1)
         assert (n == allocate);
 
         const struct ctables_nest *summary_src;
-        if (!a->specs[CSV_CELL].n && !a->specs[CSV_CELL].scale_var)
+        if (!a->specs[CSV_CELL].var)
           summary_src = b;
-        else if (!b->specs[CSV_CELL].n && !b->specs[CSV_CELL].scale_var)
+        else if (!b->specs[CSV_CELL].var)
           summary_src = a;
         else
           NOT_REACHED ();
@@ -2036,7 +2060,8 @@ enumerate_fts (enum pivot_axis_type axis_type, const struct ctables_axis *a)
         for (enum ctables_summary_variant sv = 0; sv < N_CSVS; sv++)
           {
             ctables_summary_spec_set_clone (&nest->specs[sv], &a->specs[sv]);
-            nest->specs[sv].scale_var = a->var.var;
+            nest->specs[sv].var = a->var.var;
+            nest->specs[sv].is_scale = a->scale;
           }
       return (struct ctables_stack) { .nests = nest, .n = 1 };
 
@@ -2055,11 +2080,7 @@ enumerate_fts (enum pivot_axis_type axis_type, const struct ctables_axis *a)
 union ctables_summary
   {
     /* COUNT, VALIDN, TOTALN. */
-    struct
-      {
-        double valid;
-        double missing;
-      };
+    double count;
 
     /* MINIMUM, MAXIMUM, RANGE. */
     struct
@@ -2116,7 +2137,7 @@ ctables_summary_init (union ctables_summary *s,
     case CTSF_ETOTALN:
     case CTSF_VALIDN:
     case CTSF_EVALIDN:
-      s->missing = s->valid = 0;
+      s->count = 0;
       break;
 
     case CTSF_MAXIMUM:
@@ -2228,7 +2249,8 @@ ctables_summary_uninit (union ctables_summary *s,
 static void
 ctables_summary_add (union ctables_summary *s,
                      const struct ctables_summary_spec *ss,
-                     const struct variable *scale_var, const union value *value,
+                     const struct variable *var, const union value *value,
+                     bool is_scale, bool is_missing, bool excluded_missing,
                      double d_weight, double e_weight)
 {
   /* To determine whether a case is included in a given table for a particular
@@ -2251,17 +2273,18 @@ ctables_summary_add (union ctables_summary *s,
   */
   switch (ss->function)
     {
-    case CTSF_COUNT:
-
     case CSTF_TOTALN:
-    case CTSF_VALIDN:
-      if (scale_var && var_is_value_missing (scale_var, value))
-        s->missing += d_weight;
-      else
-        s->valid += d_weight;
+    case CTSF_ROWPCT_TOTALN:
+    case CTSF_COLPCT_TOTALN:
+    case CTSF_TABLEPCT_TOTALN:
+    case CTSF_SUBTABLEPCT_TOTALN:
+    case CTSF_LAYERPCT_TOTALN:
+    case CTSF_LAYERROWPCT_TOTALN:
+    case CTSF_LAYERCOLPCT_TOTALN:
+      s->count += d_weight;
       break;
 
-    case CTSF_ECOUNT:
+    case CTSF_COUNT:
     case CTSF_ROWPCT_COUNT:
     case CTSF_COLPCT_COUNT:
     case CTSF_TABLEPCT_COUNT:
@@ -2269,6 +2292,11 @@ ctables_summary_add (union ctables_summary *s,
     case CTSF_LAYERPCT_COUNT:
     case CTSF_LAYERROWPCT_COUNT:
     case CTSF_LAYERCOLPCT_COUNT:
+      if (is_scale || !excluded_missing)
+        s->count += d_weight;
+      break;
+
+    case CTSF_VALIDN:
     case CTSF_ROWPCT_VALIDN:
     case CTSF_COLPCT_VALIDN:
     case CTSF_TABLEPCT_VALIDN:
@@ -2276,28 +2304,39 @@ ctables_summary_add (union ctables_summary *s,
     case CTSF_LAYERPCT_VALIDN:
     case CTSF_LAYERROWPCT_VALIDN:
     case CTSF_LAYERCOLPCT_VALIDN:
-    case CTSF_ROWPCT_TOTALN:
-    case CTSF_COLPCT_TOTALN:
-    case CTSF_TABLEPCT_TOTALN:
-    case CTSF_SUBTABLEPCT_TOTALN:
-    case CTSF_LAYERPCT_TOTALN:
-    case CTSF_LAYERROWPCT_TOTALN:
-    case CTSF_LAYERCOLPCT_TOTALN:
+      if (is_scale
+          ? !var_is_value_missing (var, value)
+          : !is_missing)
+        s->count += d_weight;
+      break;
+
     case CTSF_MISSING:
-    case CTSF_ETOTALN:
+      if (is_missing)
+        s->count += d_weight;
+      break;
+
+    case CTSF_ECOUNT:
+      if (is_scale || !excluded_missing)
+        s->count += e_weight;
+      break;
+
     case CTSF_EVALIDN:
-      if (scale_var && var_is_value_missing (scale_var, value))
-        s->missing += e_weight;
-      else
-        s->valid += e_weight;
+      if (is_scale
+          ? !var_is_value_missing (var, value)
+          : !is_missing)
+        s->count += e_weight;
+      break;
+
+    case CTSF_ETOTALN:
+      s->count += e_weight;
       break;
 
     case CTSF_MAXIMUM:
     case CTSF_MINIMUM:
     case CTSF_RANGE:
-      if (!var_is_value_missing (scale_var, value))
+      if (!var_is_value_missing (var, value))
         {
-          assert (!var_is_alpha (scale_var)); /* XXX? */
+          assert (!var_is_alpha (var)); /* XXX? */
           if (s->min == SYSMIS || value->f < s->min)
             s->min = value->f;
           if (s->max == SYSMIS || value->f > s->max)
@@ -2317,14 +2356,14 @@ ctables_summary_add (union ctables_summary *s,
     case CTSF_LAYERPCT_SUM:
     case CTSF_LAYERROWPCT_SUM:
     case CTSF_LAYERCOLPCT_SUM:
-      if (!var_is_value_missing (scale_var, value))
+      if (!var_is_value_missing (var, value))
         moments1_add (s->moments, value->f, e_weight);
       break;
 
     case CTSF_MEDIAN:
     case CTSF_MODE:
     case CTSF_PTILE:
-      if (var_is_value_missing (scale_var, value))
+      if (var_is_value_missing (var, value))
         {
           s->ovalid += e_weight;
 
@@ -2417,7 +2456,7 @@ ctables_summary_value (const struct ctables_cell *cell,
     {
     case CTSF_COUNT:
     case CTSF_ECOUNT:
-      return s->valid;
+      return s->count;
 
     case CTSF_ROWPCT_COUNT:
     case CTSF_COLPCT_COUNT:
@@ -2428,8 +2467,8 @@ ctables_summary_value (const struct ctables_cell *cell,
     case CTSF_LAYERCOLPCT_COUNT:
       {
         enum ctables_domain_type d = ctables_function_domain (ss->function);
-        return (cell->domains[d]->e_valid
-                ? s->valid / cell->domains[d]->e_valid * 100
+        return (cell->domains[d]->e_count
+                ? s->count / cell->domains[d]->e_count * 100
                 : SYSMIS);
       }
 
@@ -2440,6 +2479,13 @@ ctables_summary_value (const struct ctables_cell *cell,
     case CTSF_LAYERPCT_VALIDN:
     case CTSF_LAYERROWPCT_VALIDN:
     case CTSF_LAYERCOLPCT_VALIDN:
+      {
+        enum ctables_domain_type d = ctables_function_domain (ss->function);
+        return (cell->domains[d]->e_valid
+                ? s->count / cell->domains[d]->e_valid * 100
+                : SYSMIS);
+      }
+
     case CTSF_ROWPCT_TOTALN:
     case CTSF_COLPCT_TOTALN:
     case CTSF_TABLEPCT_TOTALN:
@@ -2447,18 +2493,25 @@ ctables_summary_value (const struct ctables_cell *cell,
     case CTSF_LAYERPCT_TOTALN:
     case CTSF_LAYERROWPCT_TOTALN:
     case CTSF_LAYERCOLPCT_TOTALN:
-      NOT_REACHED ();
+      {
+        enum ctables_domain_type d = ctables_function_domain (ss->function);
+        return (cell->domains[d]->e_total
+                ? s->count / cell->domains[d]->e_total * 100
+                : SYSMIS);
+      }
 
     case CTSF_MISSING:
-      return s->missing;
+      return s->count;
 
     case CSTF_TOTALN:
     case CTSF_ETOTALN:
-      return s->valid + s->missing;
+      return s->count;
 
     case CTSF_VALIDN:
+      return s->count;
+
     case CTSF_EVALIDN:
-      return s->valid;
+      return s->count;
 
     case CTSF_MAXIMUM:
       return s->max;
@@ -2581,6 +2634,7 @@ ctables_cell_compare_3way (const void *a_, const void *b_, const void *aux_)
           case CCT_SUBTOTAL:
           case CCT_TOTAL:
           case CCT_POSTCOMPUTE:
+          case CCT_EXCLUDED_MISSING:
             /* Must be equal. */
             continue;
 
@@ -2687,6 +2741,9 @@ static const struct ctables_category *
 ctables_categories_match (const struct ctables_categories *c,
                           const union value *v, const struct variable *var)
 {
+  if (var_is_numeric (var) && v->f == SYSMIS)
+    return NULL;
+
   const struct ctables_category *othernm = NULL;
   for (size_t i = c->n_cats; i-- > 0; )
     {
@@ -2729,6 +2786,9 @@ ctables_categories_match (const struct ctables_categories *c,
         case CCT_FUNCTION:
           return (cat->include_missing || !var_is_value_missing (var, v) ? cat
                   : NULL);
+
+        case CCT_EXCLUDED_MISSING:
+          break;
         }
     }
 
@@ -2805,6 +2865,8 @@ ctables_cell_insert__ (struct ctables_section *s, const struct ccase *c,
       for (size_t i = 0; i < nest->n; i++)
         {
           const struct ctables_category *cat = cats[a][i];
+          const struct variable *var = nest->vars[i];
+          const union value *value = case_data (c, var);
           if (i != nest->scale_idx)
             {
               const struct ctables_category *subtotal = cat->subtotal;
@@ -2820,8 +2882,7 @@ ctables_cell_insert__ (struct ctables_section *s, const struct ccase *c,
             }
 
           cell->axes[a].cvs[i].category = cat;
-          value_clone (&cell->axes[a].cvs[i].value, case_data (c, nest->vars[i]),
-                       var_get_width (nest->vars[i]));
+          value_clone (&cell->axes[a].cvs[i].value, value, var_get_width (var));
         }
     }
 
@@ -2839,6 +2900,7 @@ ctables_cell_insert__ (struct ctables_section *s, const struct ccase *c,
 static void
 ctables_cell_add__ (struct ctables_section *s, const struct ccase *c,
                     const struct ctables_category *cats[PIVOT_N_AXES][10],
+                    bool is_missing, bool excluded_missing,
                     double d_weight, double e_weight)
 {
   struct ctables_cell *cell = ctables_cell_insert__ (s, c, cats);
@@ -2846,18 +2908,26 @@ ctables_cell_add__ (struct ctables_section *s, const struct ccase *c,
 
   const struct ctables_summary_spec_set *specs = &ss->specs[cell->sv];
   for (size_t i = 0; i < specs->n; i++)
-    {
-      const struct variable *scale_var = specs->scale_var;
-      const union value *value = scale_var ? case_data (c, scale_var) : NULL;
-      ctables_summary_add (&cell->summaries[i], &specs->specs[i],
-                           scale_var, value, d_weight, e_weight);
-    }
+    ctables_summary_add (&cell->summaries[i], &specs->specs[i],
+                         specs->var, case_data (c, specs->var), specs->is_scale,
+                         is_missing, excluded_missing, d_weight, e_weight);
   if (cell->contributes_to_domains)
     {
       for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
         {
-          cell->domains[dt]->d_valid += d_weight;
-          cell->domains[dt]->e_valid += e_weight;
+          struct ctables_domain *d = cell->domains[dt];
+          d->d_total += d_weight;
+          d->e_total += e_weight;
+          if (!excluded_missing)
+            {
+              d->d_count += d_weight;
+              d->e_count += e_weight;
+            }
+          if (!is_missing)
+            {
+              d->d_valid += d_weight;
+              d->e_valid += e_weight;
+            }
         }
     }
 }
@@ -2865,6 +2935,7 @@ ctables_cell_add__ (struct ctables_section *s, const struct ccase *c,
 static void
 recurse_totals (struct ctables_section *s, const struct ccase *c,
                 const struct ctables_category *cats[PIVOT_N_AXES][10],
+                bool is_missing, bool excluded_missing,
                 double d_weight, double e_weight,
                 enum pivot_axis_type start_axis, size_t start_nest)
 {
@@ -2884,8 +2955,10 @@ recurse_totals (struct ctables_section *s, const struct ccase *c,
             {
               const struct ctables_category *save = cats[a][i];
               cats[a][i] = total;
-              ctables_cell_add__ (s, c, cats, d_weight, e_weight);
-              recurse_totals (s, c, cats, d_weight, e_weight, a, i + 1);
+              ctables_cell_add__ (s, c, cats, is_missing, excluded_missing,
+                                  d_weight, e_weight);
+              recurse_totals (s, c, cats, is_missing, excluded_missing,
+                              d_weight, e_weight, a, i + 1);
               cats[a][i] = save;
             }
         }
@@ -2896,6 +2969,7 @@ recurse_totals (struct ctables_section *s, const struct ccase *c,
 static void
 recurse_subtotals (struct ctables_section *s, const struct ccase *c,
                    const struct ctables_category *cats[PIVOT_N_AXES][10],
+                   bool is_missing, bool excluded_missing,
                    double d_weight, double e_weight,
                    enum pivot_axis_type start_axis, size_t start_nest)
 {
@@ -2911,8 +2985,10 @@ recurse_subtotals (struct ctables_section *s, const struct ccase *c,
           if (save->subtotal)
             {
               cats[a][i] = save->subtotal;
-              ctables_cell_add__ (s, c, cats, d_weight, e_weight);
-              recurse_subtotals (s, c, cats, d_weight, e_weight, a, i + 1);
+              ctables_cell_add__ (s, c, cats, is_missing, excluded_missing,
+                                  d_weight, e_weight);
+              recurse_subtotals (s, c, cats, is_missing, excluded_missing,
+                                 d_weight, e_weight, a, i + 1);
               cats[a][i] = save;
             }
         }
@@ -2945,6 +3021,15 @@ ctables_cell_insert (struct ctables_section *s,
                      double d_weight, double e_weight)
 {
   const struct ctables_category *cats[PIVOT_N_AXES][10]; /* XXX */
+
+  /* Does at least one categorical variable have a missing value in an included
+     or excluded category? */
+  bool is_missing = false;
+
+  /* Does at least one categorical variable have a missing value in an excluded
+     category? */
+  bool excluded_missing = false;
+
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
     {
       const struct ctables_nest *nest = s->nests[a];
@@ -2956,32 +3041,50 @@ ctables_cell_insert (struct ctables_section *s,
           const struct variable *var = nest->vars[i];
           const union value *value = case_data (c, var);
 
-          if (var_is_numeric (var) && value->f == SYSMIS)
-            return;
+          bool var_missing = var_is_value_missing (var, value) != 0;
+          if (var_missing)
+            is_missing = true;
 
           cats[a][i] = ctables_categories_match (
             s->table->categories[var_get_dict_index (var)], value, var);
           if (!cats[a][i])
-            return;
+            {
+              if (!var_missing)
+                return;
+
+              static const struct ctables_category cct_excluded_missing = {
+                .type = CCT_EXCLUDED_MISSING,
+                .hide = true,
+              };
+              cats[a][i] = &cct_excluded_missing;
+              excluded_missing = true;
+            }
         }
     }
 
-  for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
-    {
-      const struct ctables_nest *nest = s->nests[a];
-      for (size_t i = 0; i < nest->n; i++)
-        if (i != nest->scale_idx)
-          {
-            const struct variable *var = nest->vars[i];
-            const union value *value = case_data (c, var);
-            ctables_add_occurrence (var, value, &s->occurrences[a][i]);
-          }
-    }
+  if (!excluded_missing)
+    for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+      {
+        const struct ctables_nest *nest = s->nests[a];
+        for (size_t i = 0; i < nest->n; i++)
+          if (i != nest->scale_idx)
+            {
+              const struct variable *var = nest->vars[i];
+              const union value *value = case_data (c, var);
+              ctables_add_occurrence (var, value, &s->occurrences[a][i]);
+            }
+      }
 
-  ctables_cell_add__ (s, c, cats, d_weight, e_weight);
+  ctables_cell_add__ (s, c, cats, is_missing, excluded_missing,
+                      d_weight, e_weight);
 
-  recurse_totals (s, c, cats, d_weight, e_weight, 0, 0);
-  recurse_subtotals (s, c, cats, d_weight, e_weight, 0, 0);
+  //if (!excluded_missing)
+    {
+      recurse_totals (s, c, cats, is_missing, excluded_missing,
+                      d_weight, e_weight, 0, 0);
+      recurse_subtotals (s, c, cats, is_missing, excluded_missing,
+                         d_weight, e_weight, 0, 0);
+    }
 }
 
 struct merge_item
@@ -3810,14 +3913,16 @@ ctables_prepare_table (struct ctables_table *t)
           specs->n = 1;
 
           enum ctables_summary_function function
-            = specs->scale_var ? CTSF_MEAN : CTSF_COUNT;
-          struct ctables_var var = { .var = specs->scale_var };
+            = specs->is_scale ? CTSF_MEAN : CTSF_COUNT;
+          struct ctables_var var = { .is_mrset = false, .var = specs->var };
 
           *specs->specs = (struct ctables_summary_spec) {
             .function = function,
             .format = ctables_summary_default_format (function, &var),
             .label = ctables_summary_default_label (function, 0),
           };
+          if (!specs->var)
+            specs->var = nest->vars[0];
 
           ctables_summary_spec_set_clone (&nest->specs[CSV_TOTAL],
                                           &nest->specs[CSV_CELL]);
@@ -4008,6 +4113,9 @@ ctables_add_category_occurrences (const struct variable *var,
             if (c->include_missing || !var_is_value_missing (var, &vl->value))
               ctables_add_occurrence (var, &vl->value, occurrences);
           break;
+
+        case CCT_EXCLUDED_MISSING:
+          break;
         }
     }
 }