From 64241d62de48196e4ae2fd25541a5036d3de3cf9 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 5 Aug 2022 16:20:04 -0700 Subject: [PATCH] work on docs --- doc/automake.mk | 8 ++ doc/pspp-figures/ctables22.sps | 5 + doc/statistics.texi | 55 ++++++----- doc/tutorial.stt | 2 +- src/language/stats/ctables.c | 88 ++++++++++++++--- src/language/stats/ctables.inc | 14 +-- tests/language/stats/ctables.at | 169 ++++++++++++++++++++++++++++++++ 7 files changed, 291 insertions(+), 50 deletions(-) create mode 100644 doc/pspp-figures/ctables22.sps diff --git a/doc/automake.mk b/doc/automake.mk index 4ecd6ff9c0..e99b82c6ee 100644 --- a/doc/automake.mk +++ b/doc/automake.mk @@ -138,6 +138,7 @@ FIGURE_SYNTAX = \ doc/pspp-figures/ctables19.sps \ doc/pspp-figures/ctables20.sps \ doc/pspp-figures/ctables21.sps \ + doc/pspp-figures/ctables22.sps \ doc/pspp-figures/crosstabs.sps \ doc/pspp-figures/descriptives.sps \ doc/pspp-figures/flip.sps \ @@ -267,6 +268,13 @@ EXTRA_DIST += doc/tutorial.stt .spv.html: $(convert) -O format=html -O bare=true +# Make sure that tutorial.stt outputs all layers, because a few of the +# examples in the manual rely on that and it would be easy to replace +# it with a style that didn't. +ALL_LOCAL += tutorial-stt-must-print-all-layers +tutorial-stt-must-print-all-layers: + $(AM_V_GEN)grep 'printAllLayers="true"' $(srcdir)/doc/tutorial.stt >/dev/null 2>&1 && touch $@ + # Convert a text file into a Texinfo file. .txt.texi: $(AM_V_GEN)$(SED) -e 's/@/@@/g' $< > $@ diff --git a/doc/pspp-figures/ctables22.sps b/doc/pspp-figures/ctables22.sps new file mode 100644 index 0000000000..396fbd3939 --- /dev/null +++ b/doc/pspp-figures/ctables22.sps @@ -0,0 +1,5 @@ +GET FILE='nhtsa.sav'. +CTABLES + /VLABELS VARIABLES=ALL DISPLAY=NAME + /TABLE qn61 > qn57 BY qnd7a > qn86 + qn64b BY qns3a[TABLE.ID, LAYER.ID, SUBTABLE.ID] + /SLABELS POSITION=ROW. diff --git a/doc/statistics.texi b/doc/statistics.texi index 497d3fa5a6..6025323654 100644 --- a/doc/statistics.texi +++ b/doc/statistics.texi @@ -1196,46 +1196,47 @@ The following section lists the available summary functions. This section lists the summary functions that can be applied to cells in @code{CTABLES}. Many of these functions have an @var{area} in -their names. The supported areas are: - -@itemize @bullet -@item -Areas that correspond to parts of @dfn{sections}, where stacked -variables divide each section from another: +their names, indicating that they summarize over multiple cells within +an area of the output chosen by the user. The following basic +@var{area}s are supported, in decreasing order of size: @table @code @item TABLE -An entire section. +A @dfn{section}. Stacked variables divide sections of the output from +each other. sections may span multiple layers. @item LAYER -A layer within a section. - -@item LAYERROW -A row in one layer within a section. +A section within a single layer. -@item LAYERCOL -A column in one layer within a section. +@item SUBTABLE +A @dfn{subtable}, whose contents are the cells that pair an innermost +row variable and an innermost column variable within a single layer. @end table -@item -Areas that correspond to parts of @dfn{subtables}, whose contents are -the cells that pair an innermost row variable and an innermost column -variable within a single layer. A section can contain multiple -subtables and a subtable is always within a single section: +The following shows how the output for the table expression @code{qn61 +> qn57 BY qnd7a > qn86 + qn64b BY qns3a}@footnote{This is not +necessarily a meaningful table, so for clarity variable labels are +omitted.} is divided up into @code{TABLE}, @code{LAYER}, and +@code{SUBTABLE} areas. Each unique value for Table ID is one section, +and similarly for Layer ID and Subtable ID. Thus, this output has two +@code{TABLE} areas (one for @code{qnd7a} and one for @code{qn64b}), +four @code{LAYER} areas (for those two variables, per layer), and 12 +@code{SUBTABLE} areas. +@psppoutput {ctables22} -@table @code -@item ROW -A row within a subtable. +@code{CTABLES} also supports the following @var{area}s that further +divide a subtable or a layer within a section: -@item COL -A column within a subtable. +@table @code +@item LAYERROW +@itemx LAYERCOL +A row or column, respectively, in one layer of a section. -@item SUBTABLE -All the cells in a subtable. +@item ROW +@itemx ROW +A row or column, respectively, in a subtable. @end table -@end itemize - The following summary functions may be applied to any variable regardless of whether it is categorical or scalar. The default label for each function is listed in parentheses: diff --git a/doc/tutorial.stt b/doc/tutorial.stt index 130ae9b23b..5c15a7130d 100644 --- a/doc/tutorial.stt +++ b/doc/tutorial.stt @@ -49,5 +49,5 @@ - + diff --git a/src/language/stats/ctables.c b/src/language/stats/ctables.c index 15c45b3042..a573afaad1 100644 --- a/src/language/stats/ctables.c +++ b/src/language/stats/ctables.c @@ -109,7 +109,7 @@ struct ctables_domain const struct ctables_cell *example; - int sequence; + size_t sequence; double d_valid; /* Dictionary weight. */ double d_count; double d_total; @@ -3311,6 +3311,25 @@ ctables_cell_compare_3way (const void *a_, const void *b_, const void *aux_) return 0; } +static int +ctables_cell_compare_leaf_3way (const void *a_, const void *b_, + const void *aux UNUSED) +{ + struct ctables_cell *const *ap = a_; + struct ctables_cell *const *bp = b_; + const struct ctables_cell *a = *ap; + const struct ctables_cell *b = *bp; + + for (enum pivot_axis_type axis = 0; axis < PIVOT_N_AXES; axis++) + { + int al = a->axes[axis].leaf; + int bl = b->axes[axis].leaf; + if (al != bl) + return al > bl ? 1 : -1; + } + return 0; +} + /* Algorithm: For each row: @@ -4412,7 +4431,7 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t) { struct ctables_section *s = &t->sections[j]; sections[n_sections++] = s; - n_total_cells += s->cells.count; + n_total_cells += hmap_count (&s->cells); size_t depth = s->nests[a]->n; max_depth = MAX (depth, max_depth); @@ -4436,6 +4455,24 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t) struct ctables_cell_sort_aux aux = { .nest = nest, .a = a }; sort (sorted, n_sorted, sizeof *sorted, ctables_cell_compare_3way, &aux); +#if 0 + if (a == PIVOT_AXIS_ROW) + { + size_t ids[N_CTDTS]; + memset (ids, 0, sizeof ids); + for (size_t j = 0; j < n_sorted; j++) + { + struct ctables_cell *cell = sorted[j]; + for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++) + { + struct ctables_domain *domain = cell->domains[dt]; + if (!domain->sequence) + domain->sequence = ++ids[dt]; + } + } + } +#endif + #if 0 for (size_t j = 0; j < n_sorted; j++) { @@ -4517,19 +4554,6 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t) struct ctables_cell *cell = sorted[j]; struct ctables_cell *prev = j > 0 ? sorted[j - 1] : NULL; - struct ctables_domain *domain = cell->domains[CTDT_SUBTABLE]; - if (!domain->sequence) - { - static int x; - domain->sequence = ++x; - } - domain = cell->domains[CTDT_TABLE]; - if (!domain->sequence) - { - static int x; - domain->sequence = ++x; - } - size_t n_common = 0; if (j > 0) { @@ -4606,6 +4630,40 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t) } } + { + size_t n_total_cells = 0; + for (size_t j = 0; j < t->n_sections; j++) + n_total_cells += hmap_count (&t->sections[j].cells); + + struct ctables_cell **sorted = xnmalloc (n_total_cells, sizeof *sorted); + size_t n_sorted = 0; + for (size_t j = 0; j < t->n_sections; j++) + { + const struct ctables_section *s = &t->sections[j]; + struct ctables_cell *cell; + HMAP_FOR_EACH (cell, struct ctables_cell, node, &s->cells) + if (!cell->hide) + sorted[n_sorted++] = cell; + } + assert (n_sorted <= n_total_cells); + sort (sorted, n_sorted, sizeof *sorted, ctables_cell_compare_leaf_3way, + NULL); + size_t ids[N_CTDTS]; + memset (ids, 0, sizeof ids); + for (size_t j = 0; j < n_sorted; j++) + { + struct ctables_cell *cell = sorted[j]; + for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++) + { + struct ctables_domain *domain = cell->domains[dt]; + if (!domain->sequence) + domain->sequence = ++ids[dt]; + } + } + + free (sorted); + } + for (size_t i = 0; i < t->n_sections; i++) { struct ctables_section *s = &t->sections[i]; diff --git a/src/language/stats/ctables.inc b/src/language/stats/ctables.inc index 2cf5f9658a..e039cfd3ce 100644 --- a/src/language/stats/ctables.inc +++ b/src/language/stats/ctables.inc @@ -95,13 +95,13 @@ S(CTSF_ULAYERROWPCT_SUM, "ULAYERROWPCT.SUM", N_("Unweighted Layer Row Sum %"), C S(CTSF_ULAYERCOLPCT_SUM, "ULAYERCOLPCT.SUM", N_("Unweighted Layer Column Sum %"), CTF_PERCENT, CTFA_SCALE) /* Debugging and troubleshooting. */ -S(CTSF_ROW_ID, "ROW.ID", N_("Row ID"), CTF_GENERAL, CTFA_ALL) -S(CTSF_COL_ID, "COL.ID", N_("Column ID"), CTF_GENERAL, CTFA_ALL) -S(CTSF_TABLE_ID, "TABLE.ID", N_("Table ID"), CTF_GENERAL, CTFA_ALL) -S(CTSF_SUBTABLE_ID, "SUBTABLE.ID", N_("Subtable ID"), CTF_GENERAL, CTFA_ALL) -S(CTSF_LAYER_ID, "LAYER.ID", N_("Layer ID"), CTF_GENERAL, CTFA_ALL) -S(CTSF_LAYERROW_ID, "LAYERROW.ID", N_("Layer Row ID"), CTF_GENERAL, CTFA_ALL) -S(CTSF_LAYERCOL_ID, "LAYERCOL.ID", N_("Layer Column ID"), CTF_GENERAL, CTFA_ALL) +S(CTSF_ROW_ID, "ROW.ID", N_("Row ID"), CTF_COUNT, CTFA_ALL) +S(CTSF_COL_ID, "COL.ID", N_("Column ID"), CTF_COUNT, CTFA_ALL) +S(CTSF_TABLE_ID, "TABLE.ID", N_("Table ID"), CTF_COUNT, CTFA_ALL) +S(CTSF_SUBTABLE_ID, "SUBTABLE.ID", N_("Subtable ID"), CTF_COUNT, CTFA_ALL) +S(CTSF_LAYER_ID, "LAYER.ID", N_("Layer ID"), CTF_COUNT, CTFA_ALL) +S(CTSF_LAYERROW_ID, "LAYERROW.ID", N_("Layer Row ID"), CTF_COUNT, CTFA_ALL) +S(CTSF_LAYERCOL_ID, "LAYERCOL.ID", N_("Layer Column ID"), CTF_COUNT, CTFA_ALL) #if 0 /* Multiple response sets not yet implemented. */ S(CTSF_RESPONSES, "RESPONSES", N_("Responses"), CTF_COUNT, CTFA_MRSETS) diff --git a/tests/language/stats/ctables.at b/tests/language/stats/ctables.at index e9ab6d2187..efb59ca23a 100644 --- a/tests/language/stats/ctables.at +++ b/tests/language/stats/ctables.at @@ -2535,6 +2535,174 @@ Generated HH:MM:SS on MM/DD/YY ]) AT_CLEANUP +AT_SETUP([CTABLES area definitions]) +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 qn61 > qn57 BY qnd7a > qn86 + qn64b BY qns3a[TABLE.ID, LAYER.ID, SUBTABLE.ID] + /SLABELS POSITION=ROW + /TABLE qn61 > qn57 BY qnd7a > qn86 + qn64b BY qns3a[ROW.ID, LAYERROW.ID] + /SLABELS POSITION=ROW + /TABLE qn61 > qn57 BY qnd7a > qn86 + qn64b BY qns3a[COL.ID, LAYERCOL.ID] + /SLABELS POSITION=ROW. +]]) +AT_CHECK([pspp ctables.sps --table-look="$builddir"/all-layers.stt -O box=unicode -O width=80], [0], [dnl + Custom Tables +Male +╭─────────────────────────────┬─────────────┬──────╮ +│ │ QND7A │ QN64B│ +│ ├──────┬──────┼───┬──┤ +│ │ Yes │ No │ │ │ +│ ├──────┼──────┤ │ │ +│ │ QN86 │ QN86 │ │ │ +│ ├───┬──┼───┬──┤ │ │ +│ │Yes│No│Yes│No│Yes│No│ +├─────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│QN61 Yes QN57 Yes Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Subtable ID│ 1│ 1│ 2│ 2│ 3│ 3│ +│ ╶───────────────┼───┼──┼───┼──┼───┼──┤ +│ No Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Subtable ID│ 1│ 1│ 2│ 2│ 3│ 3│ +│ ╶────────────────────────┼───┼──┼───┼──┼───┼──┤ +│ No QN57 Yes Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Subtable ID│ 4│ 4│ 5│ 5│ 6│ 6│ +│ ╶───────────────┼───┼──┼───┼──┼───┼──┤ +│ No Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Subtable ID│ 4│ 4│ 5│ 5│ 6│ 6│ +╰─────────────────────────────┴───┴──┴───┴──┴───┴──╯ + + Custom Tables +Female +╭─────────────────────────────┬─────────────┬──────╮ +│ │ QND7A │ QN64B│ +│ ├──────┬──────┼───┬──┤ +│ │ Yes │ No │ │ │ +│ ├──────┼──────┤ │ │ +│ │ QN86 │ QN86 │ │ │ +│ ├───┬──┼───┬──┤ │ │ +│ │Yes│No│Yes│No│Yes│No│ +├─────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│QN61 Yes QN57 Yes Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 3│ 3│ 3│ 3│ 4│ 4│ +│ Subtable ID│ 7│ 7│ 8│ 8│ 9│ 9│ +│ ╶───────────────┼───┼──┼───┼──┼───┼──┤ +│ No Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 3│ 3│ 3│ 3│ 4│ 4│ +│ Subtable ID│ 7│ 7│ 8│ 8│ 9│ 9│ +│ ╶────────────────────────┼───┼──┼───┼──┼───┼──┤ +│ No QN57 Yes Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 3│ 3│ 3│ 3│ 4│ 4│ +│ Subtable ID│ 10│10│ 11│11│ 12│12│ +│ ╶───────────────┼───┼──┼───┼──┼───┼──┤ +│ No Table ID │ 1│ 1│ 1│ 1│ 2│ 2│ +│ Layer ID │ 3│ 3│ 3│ 3│ 4│ 4│ +│ Subtable ID│ 10│10│ 11│11│ 12│12│ +╰─────────────────────────────┴───┴──┴───┴──┴───┴──╯ + + Custom Tables +Male +╭──────────────────────────────┬─────────────┬──────╮ +│ │ QND7A │ QN64B│ +│ ├──────┬──────┼───┬──┤ +│ │ Yes │ No │ │ │ +│ ├──────┼──────┤ │ │ +│ │ QN86 │ QN86 │ │ │ +│ ├───┬──┼───┬──┤ │ │ +│ │Yes│No│Yes│No│Yes│No│ +├──────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│QN61 Yes QN57 Yes Row ID │ 1│ 1│ 2│ 2│ 3│ 3│ +│ Layer Row ID│ 1│ 1│ 1│ 1│ 2│ 2│ +│ ╶────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Row ID │ 4│ 4│ 5│ 5│ 6│ 6│ +│ Layer Row ID│ 3│ 3│ 3│ 3│ 4│ 4│ +│ ╶─────────────────────────┼───┼──┼───┼──┼───┼──┤ +│ No QN57 Yes Row ID │ 7│ 7│ 8│ 8│ 9│ 9│ +│ Layer Row ID│ 5│ 5│ 5│ 5│ 6│ 6│ +│ ╶────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Row ID │ 10│10│ 11│11│ 12│12│ +│ Layer Row ID│ 7│ 7│ 7│ 7│ 8│ 8│ +╰──────────────────────────────┴───┴──┴───┴──┴───┴──╯ + + Custom Tables +Female +╭──────────────────────────────┬─────────────┬──────╮ +│ │ QND7A │ QN64B│ +│ ├──────┬──────┼───┬──┤ +│ │ Yes │ No │ │ │ +│ ├──────┼──────┤ │ │ +│ │ QN86 │ QN86 │ │ │ +│ ├───┬──┼───┬──┤ │ │ +│ │Yes│No│Yes│No│Yes│No│ +├──────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│QN61 Yes QN57 Yes Row ID │ 13│13│ 14│14│ 15│15│ +│ Layer Row ID│ 9│ 9│ 9│ 9│ 10│10│ +│ ╶────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Row ID │ 16│16│ 17│17│ 18│18│ +│ Layer Row ID│ 11│11│ 11│11│ 12│12│ +│ ╶─────────────────────────┼───┼──┼───┼──┼───┼──┤ +│ No QN57 Yes Row ID │ 19│19│ 20│20│ 21│21│ +│ Layer Row ID│ 13│13│ 13│13│ 14│14│ +│ ╶────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Row ID │ 22│22│ 23│23│ 24│24│ +│ Layer Row ID│ 15│15│ 15│15│ 16│16│ +╰──────────────────────────────┴───┴──┴───┴──┴───┴──╯ + + Custom Tables +Male +╭─────────────────────────────────┬─────────────┬──────╮ +│ │ QND7A │ QN64B│ +│ ├──────┬──────┼───┬──┤ +│ │ Yes │ No │ │ │ +│ ├──────┼──────┤ │ │ +│ │ QN86 │ QN86 │ │ │ +│ ├───┬──┼───┬──┤ │ │ +│ │Yes│No│Yes│No│Yes│No│ +├─────────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│QN61 Yes QN57 Yes Column ID │ 1│ 2│ 3│ 4│ 5│ 6│ +│ Layer Column ID│ 1│ 2│ 3│ 4│ 5│ 6│ +│ ╶───────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Column ID │ 1│ 2│ 3│ 4│ 5│ 6│ +│ Layer Column ID│ 1│ 2│ 3│ 4│ 5│ 6│ +│ ╶────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│ No QN57 Yes Column ID │ 7│ 8│ 9│10│ 11│12│ +│ Layer Column ID│ 1│ 2│ 3│ 4│ 5│ 6│ +│ ╶───────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Column ID │ 7│ 8│ 9│10│ 11│12│ +│ Layer Column ID│ 1│ 2│ 3│ 4│ 5│ 6│ +╰─────────────────────────────────┴───┴──┴───┴──┴───┴──╯ + + Custom Tables +Female +╭─────────────────────────────────┬─────────────┬──────╮ +│ │ QND7A │ QN64B│ +│ ├──────┬──────┼───┬──┤ +│ │ Yes │ No │ │ │ +│ ├──────┼──────┤ │ │ +│ │ QN86 │ QN86 │ │ │ +│ ├───┬──┼───┬──┤ │ │ +│ │Yes│No│Yes│No│Yes│No│ +├─────────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│QN61 Yes QN57 Yes Column ID │ 13│14│ 15│16│ 17│18│ +│ Layer Column ID│ 7│ 8│ 9│10│ 11│12│ +│ ╶───────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Column ID │ 13│14│ 15│16│ 17│18│ +│ Layer Column ID│ 7│ 8│ 9│10│ 11│12│ +│ ╶────────────────────────────┼───┼──┼───┼──┼───┼──┤ +│ No QN57 Yes Column ID │ 19│20│ 21│22│ 23│24│ +│ Layer Column ID│ 7│ 8│ 9│10│ 11│12│ +│ ╶───────────────────┼───┼──┼───┼──┼───┼──┤ +│ No Column ID │ 19│20│ 21│22│ 23│24│ +│ Layer Column ID│ 7│ 8│ 9│10│ 11│12│ +╰─────────────────────────────────┴───┴──┴───┴──┴───┴──╯ +]) +AT_CLEANUP + AT_SETUP([CTABLES summary functions]) AT_CHECK([ln $top_srcdir/examples/nhtsa.sav . || cp $top_srcdir/examples/nhtsa.sav .]) AT_DATA([ctables.sps], @@ -2545,3 +2713,4 @@ CTABLES ]]) AT_CHECK([pspp ctables.sps --table-look="$builddir"/all-layers.stt -O box=unicode -O width=120], [0], []) AT_CLEANUP + -- 2.30.2