From 0891406f7c1039af8c769b8069691246566fd894 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 13 Aug 2022 15:28:49 -0700 Subject: [PATCH] TABLEID, ROWID, COLID, LAYERROWID, LAYERCOLID are OK for ROWLABELS=OPPOSITE --- src/language/stats/ctables.c | 153 ++++++++++++++++++++++---------- tests/language/stats/ctables.at | 36 +++++++- 2 files changed, 137 insertions(+), 52 deletions(-) diff --git a/src/language/stats/ctables.c b/src/language/stats/ctables.c index 1abbf8a667..836a974d82 100644 --- a/src/language/stats/ctables.c +++ b/src/language/stats/ctables.c @@ -735,12 +735,32 @@ static void ctables_axis_destroy (struct ctables_axis *); struct ctables_summary_spec { + /* The calculation to be performed. + + 'function' is the function to calculate. 'weighted' specifies whether + to use weighted or unweighted data (for functions that do not support a + choice, it must be true). 'calc_area' is the area over which the + calculation takes place (for functions that target only an individual + cell, it must be 0). For CTSF_PTILE only, 'percentile' is the + percentile between 0 and 100 (for other functions it must be 0). */ enum ctables_summary_function function; bool weighted; - enum ctables_area_type area; + enum ctables_area_type calc_area; double percentile; /* CTSF_PTILE only. */ - char *label; + /* How to display the result of the calculation. + + 'label' is a user-specified label, NULL if the user didn't specify + one. + + 'user_area' is usually the same as 'calc_area', but when category labels + are rotated from one axis to another it swaps rows and columns. + + 'format' is the format for displaying the output. If + 'is_ctables_format' is true, then 'format.type' is one of the special + CTEF_* formats instead of the standard ones. */ + char *label; + enum ctables_area_type user_area; struct fmt_spec format; bool is_ctables_format; /* Is 'format' one of CTEF_*? */ @@ -965,7 +985,7 @@ static const char * ctables_summary_label__ (const struct ctables_summary_spec *spec) { bool w = spec->weighted; - enum ctables_area_type a = spec->area; + enum ctables_area_type a = spec->user_area; switch (spec->function) { case CTSF_COUNT: @@ -1160,7 +1180,8 @@ add_summary_spec (struct ctables_axis *axis, *dst = (struct ctables_summary_spec) { .function = function, .weighted = weighted, - .area = area, + .calc_area = area, + .user_area = area, .percentile = percentile, .label = xstrdup_if_nonnull (label), .format = (format ? *format @@ -2726,25 +2747,25 @@ ctables_summary_value (const struct ctables_cell *cell, return s->count; case CTSF_areaID: - return cell->areas[ss->area]->sequence; + return cell->areas[ss->calc_area]->sequence; case CTSF_areaPCT_COUNT: { - const struct ctables_area *a = cell->areas[ss->area]; + const struct ctables_area *a = cell->areas[ss->calc_area]; double a_count = ss->weighted ? a->e_count : a->u_count; return a_count ? s->count / a_count * 100 : SYSMIS; } case CTSF_areaPCT_VALIDN: { - const struct ctables_area *a = cell->areas[ss->area]; + const struct ctables_area *a = cell->areas[ss->calc_area]; double a_valid = ss->weighted ? a->e_valid : a->u_valid; return a_valid ? s->count / a_valid * 100 : SYSMIS; } case CTSF_areaPCT_TOTALN: { - const struct ctables_area *a = cell->areas[ss->area]; + const struct ctables_area *a = cell->areas[ss->calc_area]; double a_total = ss->weighted ? a->e_total : a->u_total; return a_total ? s->count / a_total * 100 : SYSMIS; } @@ -2807,7 +2828,7 @@ ctables_summary_value (const struct ctables_cell *cell, if (weight == SYSMIS || mean == SYSMIS) return SYSMIS; - const struct ctables_area *a = cell->areas[ss->area]; + const struct ctables_area *a = cell->areas[ss->calc_area]; const struct ctables_sum *sum = &a->sums[ss->sum_var_idx]; double denom = ss->weighted ? sum->e_sum : sum->u_sum; return denom != 0 ? weight * mean / denom * 100 : SYSMIS; @@ -3499,8 +3520,8 @@ merge_item_compare_3way (const struct merge_item *a, const struct merge_item *b) return as->function > bs->function ? 1 : -1; else if (as->weighted != bs->weighted) return as->weighted > bs->weighted ? 1 : -1; - else if (as->area != bs->area) - return as->area > bs->area ? 1 : -1; + else if (as->calc_area != bs->calc_area) + return as->calc_area > bs->calc_area ? 1 : -1; else if (as->percentile != bs->percentile) return as->percentile < bs->percentile ? 1 : -1; @@ -3996,7 +4017,7 @@ ctables_cell_calculate_postcompute (const struct ctables_section *s, const struct ctables_summary_spec *ss2 = &pc->specs->specs[i]; if (ss->function == ss2->function && ss->weighted == ss2->weighted - && ss->area == ss2->area + && ss->calc_area == ss2->calc_area && ss->percentile == ss2->percentile) { *format = ss2->format; @@ -4575,6 +4596,33 @@ add_sum_var (struct variable *var, return (*n)++; } +static enum ctables_area_type +rotate_area (enum ctables_area_type area) +{ + return area; + switch (area) + { + case CTAT_TABLE: + case CTAT_LAYER: + case CTAT_SUBTABLE: + return area; + + case CTAT_LAYERROW: + return CTAT_LAYERCOL; + + case CTAT_LAYERCOL: + return CTAT_LAYERROW; + + case CTAT_ROW: + return CTAT_COL; + + case CTAT_COL: + return CTAT_ROW; + } + + NOT_REACHED (); +} + static void enumerate_sum_vars (const struct ctables_axis *a, struct variable ***sum_vars, size_t *n, size_t *allocated) @@ -4618,48 +4666,40 @@ ctables_prepare_table (struct ctables_table *t) nest->areas[at] = xmalloc (nest->n * sizeof *nest->areas[at]); nest->n_areas[at] = 0; - for (size_t k = 0; k < nest->n; k++) + bool add_vars = (at == CTAT_LAYER ? a == PIVOT_AXIS_LAYER + : at == CTAT_LAYERROW ? a != PIVOT_AXIS_COLUMN + : at == CTAT_LAYERCOL ? a != PIVOT_AXIS_ROW + : at == CTAT_TABLE ? false + : true); + if (add_vars) + for (size_t k = 0; k < nest->n; k++) + { + if (k == nest->scale_idx) + continue; + nest->areas[at][nest->n_areas[at]++] = k; + } + else if ((at == CTAT_COL || at == CTAT_LAYERCOL) && a == PIVOT_AXIS_ROW && t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN) { - if (k == nest->scale_idx) - continue; - - switch (at) + for (size_t k = nest->n - 1; k < nest->n; k--) { - case CTAT_TABLE: - continue; - - case CTAT_LAYER: - if (a != PIVOT_AXIS_LAYER) - continue; - break; - - case CTAT_SUBTABLE: - case CTAT_ROW: - case CTAT_COL: - if (at == CTAT_SUBTABLE ? a != PIVOT_AXIS_LAYER - : at == CTAT_ROW ? a == PIVOT_AXIS_COLUMN - : a == PIVOT_AXIS_ROW) - { - if (k == nest->n - 1 - || (nest->scale_idx == nest->n - 1 - && k == nest->n - 2)) - continue; - } - break; - - case CTAT_LAYERROW: - if (a == PIVOT_AXIS_COLUMN) - continue; - break; - - case CTAT_LAYERCOL: - if (a == PIVOT_AXIS_ROW) + if (k == nest->scale_idx) continue; + nest->areas[at][nest->n_areas[at]++] = k; break; } - - nest->areas[at][nest->n_areas[at]++] = k; } + + bool drop_last = (at == CTAT_SUBTABLE ? a != PIVOT_AXIS_LAYER + : at == CTAT_ROW ? a == PIVOT_AXIS_COLUMN + : at == CTAT_COL ? a == PIVOT_AXIS_ROW + : false); + if (drop_last && nest->n_areas[at] > 0) + nest->n_areas[at]--; + + bool drop_additional + = (((at == CTAT_ROW || at == CTAT_LAYERROW || at == CTAT_COL) && a == PIVOT_AXIS_ROW && t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN)); + if (drop_additional && nest->n_areas[at] > 0) + nest->n_areas[at]--; } } } @@ -4702,6 +4742,20 @@ ctables_prepare_table (struct ctables_table *t) ctables_summary_spec_set_clone (&nest->specs[CSV_TOTAL], &nest->specs[CSV_CELL]); + if (t->label_axis[PIVOT_AXIS_ROW] == PIVOT_AXIS_COLUMN + || t->label_axis[PIVOT_AXIS_COLUMN] == PIVOT_AXIS_ROW) + { + for (enum ctables_summary_variant sv = 0; sv < N_CSVS; sv++) + for (size_t i = 0; i < nest->specs[sv].n; i++) + { + struct ctables_summary_spec *ss = &nest->specs[sv].specs[i]; + const struct ctables_function_info *cfi = + &ctables_function_info[ss->function]; + if (cfi->is_area) + ss->calc_area = rotate_area (ss->calc_area); + } + } + if (t->ctables->smissing_listwise) { struct variable **listwise_vars = NULL; @@ -5691,7 +5745,8 @@ ctables_parse_pproperties_format (struct lexer *lexer, sss->specs[sss->n++] = (struct ctables_summary_spec) { .function = function, .weighted = weighted, - .area = area, + .calc_area = area, + .user_area = area, .percentile = percentile, .format = format, .is_ctables_format = is_ctables_format, diff --git a/tests/language/stats/ctables.at b/tests/language/stats/ctables.at index fd1000b853..dbc70830dc 100644 --- a/tests/language/stats/ctables.at +++ b/tests/language/stats/ctables.at @@ -2055,9 +2055,10 @@ 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 qns3a > (qn26 + qn27 + qn28 + qn29) [COLPCT]. -CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [COLPCT] /CLABELS ROWLABELS=OPPOSITE. -CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [COLPCT] /CLABELS ROWLABELS=LAYER. +CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT]. +CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT] /CLABELS ROWLABELS=OPPOSITE. +CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT] /CLABELS ROWLABELS=OPPOSITE. +CTABLES /TABLE qns3a > (qn26 + qn27 + qn28 + qn29) [ROWPCT, COLPCT] /CLABELS ROWLABELS=LAYER. ]]) AT_CHECK([pspp ctables.sps --table-look="$builddir"/all-layers.stt -O box=unicode], [0], [dnl ]) @@ -3200,6 +3201,35 @@ Female ]) AT_CLEANUP +AT_SETUP([CTABLES area definitions with CLABELS OPPOSITE]) +AT_CHECK([ln $top_srcdir/examples/nhtsa.sav . || cp $top_srcdir/examples/nhtsa.sav .]) +AT_DATA([ctables.sps], +[[GET 'nhtsa.sav'. +CTABLES + /VLABELS VARIABLES=ALL DISPLAY=NAME + /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[TABLEID] + /SLABELS POSITION=ROW + /CLABELS ROWLABELS=OPPOSITE + /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[SUBTABLEID] + /SLABELS POSITION=ROW + /CLABELS ROWLABELS=OPPOSITE + /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[ROWID] + /SLABELS POSITION=ROW + /CLABELS ROWLABELS=OPPOSITE + /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[COLID] + /SLABELS POSITION=ROW + /CLABELS ROWLABELS=OPPOSITE + /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[LAYERROWID] + /SLABELS POSITION=ROW + /CLABELS ROWLABELS=OPPOSITE + /TABLE qn26 > qn61 > qn57 BY qn27 > qnd7a > qn86 BY qns3a[LAYERCOLID] + /SLABELS POSITION=ROW + /CLABELS ROWLABELS=OPPOSITE +]]) +AT_CHECK([pspp ctables.sps --table-look="$builddir"/all-layers.stt -O box=unicode -O width=120], [0], [dnl +]) +AT_CLEANUP + AT_SETUP([CTABLES categorical summary functions]) AT_CHECK([ln $top_srcdir/examples/nhtsa.sav . || cp $top_srcdir/examples/nhtsa.sav .]) AT_DATA([ctables.sps], -- 2.30.2