From 4e9cac1017c866edad3e5573e4a181eeb69c2703 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 1 Jun 2022 22:56:33 -0700 Subject: [PATCH] Fix bug in CLABELS when a missing category was moved, and add test. --- doc/pspp-figures/ctables17.sps | 2 +- doc/statistics.texi | 14 ++++++--- src/language/stats/ctables.c | 49 +++++++++++++++++++----------- tests/language/stats/ctables.at | 53 +++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 22 deletions(-) diff --git a/doc/pspp-figures/ctables17.sps b/doc/pspp-figures/ctables17.sps index 590e600ff5..191703384a 100644 --- a/doc/pspp-figures/ctables17.sps +++ b/doc/pspp-figures/ctables17.sps @@ -1,4 +1,4 @@ GET FILE='nhtsa.sav'. -*CTABLES /TABLE AgeGroup BY qns3a /CLABELS ROWLABELS=OPPOSITE. +CTABLES /TABLE AgeGroup BY qns3a /CLABELS ROWLABELS=OPPOSITE. CTABLES /TABLE AgeGroup BY qns3a /CLABELS COLLABELS=OPPOSITE. diff --git a/doc/statistics.texi b/doc/statistics.texi index be57f1a735..f199069c94 100644 --- a/doc/statistics.texi +++ b/doc/statistics.texi @@ -1434,10 +1434,10 @@ CTABLES /TABLE AgeGroup BY qns3a. @end example @psppoutput {ctables16} -With @t{ROWLABELS=OPPOSITE} or @t{COLLABELS=OPPOSITE}, row or column -variable labels, respectively, move to the opposite axis. Only one -axis's labels may be moved, and the setting affects only the innermost -variable on the given axis. For example: +@t{ROWLABELS=OPPOSITE} or @t{COLLABELS=OPPOSITE} move row or column +variable category labels, respectively, to the opposite axis. The +setting affects only the innermost variable on the given axis. For +example: @example CTABLES /TABLE AgeGroup BY qns3a /CLABELS ROWLABELS=OPPOSITE. @@ -1445,6 +1445,12 @@ CTABLES /TABLE AgeGroup BY qns3a /CLABELS COLLABELS=OPPOSITE. @end example @psppoutput {ctables17} +@t{ROWLABELS=LAYER} or @t{COLLABELS=LAYER} move the innermost row or +column variable category labels, respectively, to the layer axis. + +Only one axis's labels may be moved, whether to the opposite axis or +to the layer axis. + @node CTABLES Per-Variable Category Options @subsection Per-Variable Category Options diff --git a/src/language/stats/ctables.c b/src/language/stats/ctables.c index 51493b0012..e34621e2fa 100644 --- a/src/language/stats/ctables.c +++ b/src/language/stats/ctables.c @@ -3002,6 +3002,20 @@ ctables_value_find__ (struct ctables_table *t, const union value *value, return NULL; } +static void +ctables_value_insert (struct ctables_table *t, const union value *value, + int width) +{ + unsigned int hash = value_hash (value, width, 0); + struct ctables_value *clv = ctables_value_find__ (t, value, width, hash); + if (!clv) + { + clv = xmalloc (sizeof *clv); + value_clone (&clv->value, value, width); + hmap_insert (&t->clabels_values_map, &clv->node, hash); + } +} + static struct ctables_value * ctables_value_find (struct ctables_table *t, const union value *value, int width) @@ -3547,7 +3561,8 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t) const struct variable *var = clabels_nest->vars[clabels_nest->n - 1]; const union value *value = &cell->axes[t->clabels_from_axis].cvs[clabels_nest->n - 1].value; const struct ctables_value *ctv = ctables_value_find (t, value, var_get_width (var)); - assert (ctv != NULL); + if (!ctv) + continue; dindexes[n_dindexes++] = ctv->leaf; } @@ -3828,25 +3843,14 @@ ctables_insert_clabels_values (struct ctables_table *t, const struct ccase *c, { const struct ctables_nest *nest = &stack->nests[i]; const struct variable *var = nest->vars[nest->n - 1]; - int width = var_get_width (var); const union value *value = case_data (c, var); if (var_is_numeric (var) && value->f == SYSMIS) continue; - if (!ctables_categories_match (t->categories [var_get_dict_index (var)], - value, var)) - continue; - - unsigned int hash = value_hash (value, width, 0); - - struct ctables_value *clv = ctables_value_find__ (t, value, width, hash); - if (!clv) - { - clv = xmalloc (sizeof *clv); - value_clone (&clv->value, value, width); - hmap_insert (&t->clabels_values_map, &clv->node, hash); - } + if (ctables_categories_match (t->categories [var_get_dict_index (var)], + value, var)) + ctables_value_insert (t, value, var_get_width (var)); } } @@ -3864,7 +3868,18 @@ compare_clabels_values_3way (const void *a_, const void *b_, const void *width_) static void ctables_sort_clabels_values (struct ctables_table *t) { - int width = var_get_width (t->clabels_example); + const struct variable *v0 = t->clabels_example; + int width = var_get_width (v0); + + struct ctables_categories *c0 = t->categories[var_get_dict_index (v0)]; + if (c0->show_empty) + { + const struct val_labs *val_labs = var_get_value_labels (v0); + for (const struct val_lab *vl = val_labs_first (val_labs); vl; + vl = val_labs_next (val_labs, vl)) + if (ctables_categories_match (c0, &vl->value, v0)) + ctables_value_insert (t, &vl->value, width); + } size_t n = hmap_count (&t->clabels_values_map); t->clabels_values = xnmalloc (n, sizeof *t->clabels_values); @@ -4847,7 +4862,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds) [PIVOT_AXIS_COLUMN] = PIVOT_AXIS_COLUMN, [PIVOT_AXIS_LAYER] = PIVOT_AXIS_LAYER, }, - .clabels_from_axis = PIVOT_AXIS_LAYER, + .clabels_from_axis = PIVOT_AXIS_LAYER, .categories = categories, .n_categories = n_vars, .cilevel = 95, diff --git a/tests/language/stats/ctables.at b/tests/language/stats/ctables.at index e19007e65e..ec89cab1a5 100644 --- a/tests/language/stats/ctables.at +++ b/tests/language/stats/ctables.at @@ -860,3 +860,56 @@ AT_CHECK([pspp ctables.sps -O box=unicode -O width=120], [0], [dnl ╰─────────────────────────────────────────────────────────┴───────┴───────┴─────────┴───────┴────────┴──────┴──────────╯ ]) AT_CLEANUP + +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 AgeGroup BY qns3a /CLABELS ROWLABELS=OPPOSITE. +CTABLES /TABLE AgeGroup BY qns3a /CLABELS COLLABELS=OPPOSITE. +]]) +AT_CHECK([pspp ctables.sps -O box=unicode -O width=120], [0], [dnl + Custom Tables +╭───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ S3a. GENDER: │ +│ ├──────────────────────────────────────────────────────┬───────────────────────────────────────────────────────┤ +│ │ Male │ Female │ +│ ├─────────┬───────┬──────┬──────┬──────┬───────┬───────┼──────────┬──────┬───────┬──────┬──────┬──────┬────────┤ +│ │ 15 or │ 16 to │ 26 to│ 36 to│ 46 to│ 56 to │ 66 or │ 15 or │ 16 to│ 26 to │ 36 to│ 46 to│ 56 to│ 66 or │ +│ │ younger │ 25 │ 35 │ 45 │ 55 │ 65 │ older │ younger │ 25 │ 35 │ 45 │ 55 │ 65 │ older │ +│ ├─────────┼───────┼──────┼──────┼──────┼───────┼───────┼──────────┼──────┼───────┼──────┼──────┼──────┼────────┤ +│ │ Count │ Count │ Count│ Count│ Count│ Count │ Count │ Count │ Count│ Count │ Count│ Count│ Count│ Count │ +├───────┼─────────┼───────┼──────┼──────┼──────┼───────┼───────┼──────────┼──────┼───────┼──────┼──────┼──────┼────────┤ +│Age │ 0│ 594│ 476│ 489│ 526│ 516│ 531│ 0│ 505│ 491│ 548│ 649│ 731│ 943│ +│group │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ +╰───────┴─────────┴───────┴──────┴──────┴──────┴───────┴───────┴──────────┴──────┴───────┴──────┴──────┴──────┴────────╯ + + Custom Tables +╭──────────────────────────────┬────────────╮ +│ │S3a. GENDER:│ +│ ├────────────┤ +│ │ Count │ +├──────────────────────────────┼────────────┤ +│Age group 15 or younger Male │ 0│ +│ Female│ 0│ +│ ╶────────────────────┼────────────┤ +│ 16 to 25 Male │ 594│ +│ Female│ 505│ +│ ╶────────────────────┼────────────┤ +│ 26 to 35 Male │ 476│ +│ Female│ 491│ +│ ╶────────────────────┼────────────┤ +│ 36 to 45 Male │ 489│ +│ Female│ 548│ +│ ╶────────────────────┼────────────┤ +│ 46 to 55 Male │ 526│ +│ Female│ 649│ +│ ╶────────────────────┼────────────┤ +│ 56 to 65 Male │ 516│ +│ Female│ 731│ +│ ╶────────────────────┼────────────┤ +│ 66 or older Male │ 531│ +│ Female│ 943│ +╰──────────────────────────────┴────────────╯ +]) +AT_CLEANUP \ No newline at end of file -- 2.30.2