CTABLES missing values start to make some minimal amount of sense
[pspp] / src / language / stats / ctables.c
index 5eb4bb4a3d1394c9028b1bfadf187b8556db737e..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;
 
+    /* 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,
-    .var = src->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;
         }
     }
@@ -2037,6 +2061,7 @@ enumerate_fts (enum pivot_axis_type axis_type, const struct ctables_axis *a)
           {
             ctables_summary_spec_set_clone (&nest->specs[sv], &a->specs[sv]);
             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:
@@ -2229,20 +2250,41 @@ static void
 ctables_summary_add (union ctables_summary *s,
                      const struct ctables_summary_spec *ss,
                      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
+     kind of summary, consider the following charts for each variable in the
+     table.  Only if "yes" appears for every variable for the summary is the
+     case counted.
+
+     Categorical variables:                    VALIDN   COUNT   TOTALN
+       Valid values in included categories       yes     yes      yes
+       Missing values in included categories     ---     yes      yes
+       Missing values in excluded categories     ---     ---      yes
+       Valid values in excluded categories       ---     ---      ---
+
+     Scale variables:                          VALIDN   COUNT   TOTALN
+       Valid value                               yes     yes      yes
+       Missing value                             ---     yes      yes
+
+     Missing values include both user- and system-missing.  (The system-missing
+     value is always in an excluded category.)
+  */
   switch (ss->function)
     {
-    case CTSF_COUNT:
     case CSTF_TOTALN:
-    case CTSF_VALIDN:
-      if (var_is_value_missing (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:
@@ -2250,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:
@@ -2257,20 +2304,31 @@ 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 (var_is_value_missing (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:
@@ -2398,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:
@@ -2409,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);
       }
 
@@ -2421,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:
@@ -2428,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;
@@ -2562,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;
 
@@ -2668,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; )
     {
@@ -2710,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;
         }
     }
 
@@ -2786,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;
@@ -2801,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));
         }
     }
 
@@ -2820,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);
@@ -2827,14 +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++)
-    ctables_summary_add (&cell->summaries[i], &specs->specs[i], specs->var,
-                         case_data (c, specs->var), 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;
+            }
         }
     }
 }
@@ -2842,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)
 {
@@ -2861,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;
             }
         }
@@ -2873,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)
 {
@@ -2888,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;
             }
         }
@@ -2922,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];
@@ -2933,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
@@ -3787,7 +3913,7 @@ ctables_prepare_table (struct ctables_table *t)
           specs->n = 1;
 
           enum ctables_summary_function function
-            = specs->var ? CTSF_MEAN : CTSF_COUNT;
+            = specs->is_scale ? CTSF_MEAN : CTSF_COUNT;
           struct ctables_var var = { .is_mrset = false, .var = specs->var };
 
           *specs->specs = (struct ctables_summary_spec) {
@@ -3987,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;
         }
     }
 }