CTABLES: implement some simple features
[pspp] / src / language / stats / ctables.c
index e34621e2fa4ad270ede00e0d80d39059e35edf58..ce49e591020f73fa9bd2e587b6f5439ae1855731 100644 (file)
@@ -152,6 +152,8 @@ enum {
 #undef S
 };
 
+static bool ctables_summary_function_is_count (enum ctables_summary_function);
+
 enum ctables_domain_type
   {
     /* Within a section, where stacked variables divide one section from
@@ -771,6 +773,38 @@ ctables_function_availability (enum ctables_summary_function f)
   return availability[f];
 }
 
+static bool
+ctables_summary_function_is_count (enum ctables_summary_function f)
+{
+  static const bool is_count[N_CTSF_FUNCTIONS] = {
+    [CTSF_COUNT] = true,
+    [CTSF_ECOUNT] = true,
+    [CTSF_ROWPCT_COUNT] = true,
+    [CTSF_COLPCT_COUNT] = true,
+    [CTSF_TABLEPCT_COUNT] = true,
+    [CTSF_SUBTABLEPCT_COUNT] = true,
+    [CTSF_LAYERPCT_COUNT] = true,
+    [CTSF_LAYERROWPCT_COUNT] = true,
+    [CTSF_LAYERCOLPCT_COUNT] = true,
+    [CTSF_ROWPCT_RESPONSES_COUNT] = true,
+    [CTSF_COLPCT_RESPONSES_COUNT] = true,
+    [CTSF_TABLEPCT_RESPONSES_COUNT] = true,
+    [CTSF_SUBTABLEPCT_RESPONSES_COUNT] = true,
+    [CTSF_LAYERPCT_RESPONSES_COUNT] = true,
+    [CTSF_LAYERROWPCT_RESPONSES_COUNT] = true,
+    [CTSF_LAYERCOLPCT_RESPONSES_COUNT] = true,
+    [CTSF_ROWPCT_COUNT_RESPONSES] = true,
+    [CTSF_COLPCT_COUNT_RESPONSES] = true,
+    [CTSF_TABLEPCT_COUNT_RESPONSES] = true,
+    [CTSF_SUBTABLEPCT_COUNT_RESPONSES] = true,
+    [CTSF_LAYERPCT_COUNT_RESPONSES] = true,
+    [CTSF_LAYERROWPCT_COUNT_RESPONSES] = true,
+    [CTSF_LAYERCOLPCT_COUNT_RESPONSES] = true,
+  };
+  return is_count[f];
+}
+
+
 static bool
 parse_ctables_summary_function (struct lexer *lexer,
                                 enum ctables_summary_function *f)
@@ -3405,6 +3439,7 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
                 }
                 type;
 
+              enum settings_value_show vlabel; /* CTL_VAR only. */
               size_t var_idx;
             };
           struct ctables_level *levels = xnmalloc (1 + 2 * max_depth, sizeof *levels);
@@ -3416,6 +3451,7 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
                 {
                   levels[n_levels++] = (struct ctables_level) {
                     .type = CTL_VAR,
+                    .vlabel = (enum settings_value_show) vlabel,
                     .var_idx = k,
                   };
                 }
@@ -3511,7 +3547,10 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
                       const struct variable *var = nest->vars[level->var_idx];
                       struct pivot_value *label;
                       if (level->type == CTL_VAR)
-                        label = pivot_value_new_variable (var);
+                        {
+                          label = pivot_value_new_variable (var);
+                          label->variable.show = level->vlabel;
+                        }
                       else if (level->type == CTL_CATEGORY)
                         {
                           const struct ctables_cell_value *cv = &cell->axes[a].cvs[level->var_idx];
@@ -3575,11 +3614,30 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
                     dindexes[n_dindexes++] = leaf;
                   }
 
+              const struct ctables_summary_spec *ss = &specs->specs[j];
+
               double d = (cell->postcompute
                           ? ctables_cell_calculate_postcompute (s, cell)
-                          : ctables_summary_value (cell, &cell->summaries[j], &specs->specs[j]));
-              struct pivot_value *value = pivot_value_new_number (d);
-              value->numeric.format = specs->specs[j].format;
+                          : ctables_summary_value (cell, &cell->summaries[j], ss));
+              struct pivot_value *value;
+              if (ct->hide_threshold != 0
+                  && d < ct->hide_threshold
+                  && (cell->postcompute
+                      ? false /* XXX */
+                      : ctables_summary_function_is_count (ss->function)))
+                {
+                  value = pivot_value_new_user_text_nocopy (
+                    xasprintf ("<%d", ct->hide_threshold));
+                }
+              else if (d == 0 && ct->zero)
+                value = pivot_value_new_user_text (ct->zero, SIZE_MAX);
+              else if (d == SYSMIS && ct->missing)
+                value = pivot_value_new_user_text (ct->missing, SIZE_MAX);
+              else
+                {
+                  value = pivot_value_new_number (d);
+                  value->numeric.format = specs->specs[j].format;
+                }
               pivot_table_put (pt, dindexes, n_dindexes, value);
             }
         }
@@ -4623,7 +4681,6 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
                                         pivot_table_look_get_default ())),
     .vlabels = vlabels,
     .postcomputes = HMAP_INITIALIZER (ct->postcomputes),
-    .hide_threshold = 5,
   };
   ct->look->omit_empty = false;
 
@@ -4801,15 +4858,19 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
           if (!ct->e_weight)
             goto error;
         }
-      else if (lex_match_id (lexer, "HIDESMALLCOUNTS"))
+      else if (lex_match_id (lexer, " HIDESMALLCOUNTS"))
         {
-          if (!lex_force_match_id (lexer, "COUNT"))
-            goto error;
-          lex_match (lexer, T_EQUALS);
-          if (!lex_force_int_range (lexer, "HIDESMALLCOUNTS COUNT", 2, INT_MAX))
-            goto error;
-          ct->hide_threshold = lex_integer (lexer);
-          lex_get (lexer);
+          if (lex_match_id (lexer, "COUNT"))
+            {
+              lex_match (lexer, T_EQUALS);
+              if (!lex_force_int_range (lexer, "HIDESMALLCOUNTS COUNT",
+                                        2, INT_MAX))
+                goto error;
+              ct->hide_threshold = lex_integer (lexer);
+              lex_get (lexer);
+            }
+          else if (ct->hide_threshold == 0)
+            ct->hide_threshold = 5;
         }
       else
         {