From: Ben Pfaff Date: Sat, 25 Jun 2022 04:11:39 +0000 (-0700) Subject: work on missing value handling X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?p=pspp;a=commitdiff_plain;h=refs%2Fheads%2Fctables11 work on missing value handling --- diff --git a/doc/statistics.texi b/doc/statistics.texi index adf1f67d95..78b76bfbc4 100644 --- a/doc/statistics.texi +++ b/doc/statistics.texi @@ -1242,6 +1242,12 @@ for each function is listed in parentheses: @item @code{COUNT} (``Count'') The sum of weights in a cell. +If @code{CATEGORIES} for one or more of the variables in a table +include missing values (@pxref{CTABLES Per-Variable Category +Options}), then some or all of the categories for a cell might be +missing values. @code{COUNT} counts data included in a cell +regardless of whether its categories are missing. + @item @code{@i{area}PCT} or @code{@i{area}PCT.COUNT} (``@i{Area} %'') A percentage within the specified @var{area}. @@ -1252,7 +1258,11 @@ A percentage of valid values within the specified @var{area}. A percentage of total values within the specified @var{area}. @end table -The following summary functions apply only to scalar variables: +The following summary functions apply only to scalar variables or +totals and subtotals for categorical variables. Be cautious about +interpreting the summary value in the latter case, because it is not +necessarily meaningful; however, the mean of a Likert scale, etc.@: +may have a straightforward interpreation. @table @asis @item @code{MAXIMUM} (``Maximum'') @@ -1292,10 +1302,23 @@ The standard deviation. The sum. @item @code{TOTALN} (``Total N'') -The sum of total count weights. +The sum of weights in a cell. + +For scale data, @code{COUNT} and @code{TOTALN} are the same. + +For categorical data, @code{TOTALN} counts missing values in excluded +categories, that is, user-missing values not in an explicit category +list on @code{CATEGORIES} (@pxref{CTABLES Per-Variable Category +Options}), or user-missing values excluded because +@code{MISSING=EXCLUDE} is in effect on @code{CATEGORIES}, or +system-missing values. @code{COUNT} does not count these. @item @code{VALIDN} (``Valid N'') -The sum of valid count weights. +The sum of valid count weights in included categories. + +@code{VALIDN} does not count missing values regardless of whether they +are in included categories via @code{CATEGORIES}. @code{VALIDN} does +not count valid values that are in excluded categories. @item @code{VARIANCE} (``Variance'') The variance. diff --git a/src/language/stats/ctables.c b/src/language/stats/ctables.c index fe46f10c92..b64ea2b565 100644 --- a/src/language/stats/ctables.c +++ b/src/language/stats/ctables.c @@ -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,13 @@ struct ctables_cell struct ctables_domain *domains[N_CTDTS]; bool hide; + + /* Is at least one value missing, whether included or excluded? */ + bool is_missing; + + /* Is at least one value missing and excluded? */ + bool excluded_missing; + bool postcompute; enum ctables_summary_variant sv; @@ -337,7 +346,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 +498,9 @@ struct ctables_category CCT_VALUE, CCT_LABEL, CCT_FUNCTION, + + /* For contributing to TOTALN. */ + CCT_EXCLUDED_MISSING, } type; @@ -516,7 +536,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 +568,9 @@ ctables_category_uninit (struct ctables_category *cat) case CCT_LABEL: case CCT_FUNCTION: break; + + case CCT_EXCLUDED_MISSING: + break; } } @@ -588,6 +611,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 +756,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 +1034,7 @@ add_summary_spec (struct ctables_axis *axis, break; case CTFA_SCALE: +#if 0 if (!axis->scale) { msg_at (SE, loc, @@ -1016,6 +1044,7 @@ add_summary_spec (struct ctables_axis *axis, var_name); return false; } +#endif break; case CTFA_ALL: @@ -1924,6 +1953,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 +2067,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 +2086,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 +2143,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: @@ -2226,9 +2253,10 @@ ctables_summary_uninit (union ctables_summary *s, } static void -ctables_summary_add (union ctables_summary *s, +ctables_summary_add (const struct ctables_cell *cell, union ctables_summary *s, const struct ctables_summary_spec *ss, const struct variable *var, const union value *value, + bool is_scale, bool is_missing, double d_weight, double e_weight) { /* To determine whether a case is included in a given table for a particular @@ -2251,13 +2279,25 @@ ctables_summary_add (union ctables_summary *s, */ switch (ss->function) { - case CTSF_COUNT: case CSTF_TOTALN: + s->count += d_weight; + break; + + case CTSF_COUNT: + if (is_scale || !cell->excluded_missing) + s->count += d_weight; + break; + case CTSF_VALIDN: - if (var_is_value_missing (var, value)) - s->missing += d_weight; - else - s->valid += d_weight; + if (is_scale + ? !var_is_value_missing (var, value) + : !is_missing) + s->count += d_weight; + break; + + case CTSF_MISSING: + if (is_missing) + s->count += d_weight; break; case CTSF_ECOUNT: @@ -2282,13 +2322,18 @@ ctables_summary_add (union ctables_summary *s, case CTSF_LAYERPCT_TOTALN: case CTSF_LAYERROWPCT_TOTALN: case CTSF_LAYERCOLPCT_TOTALN: - case CTSF_MISSING: - case CTSF_ETOTALN: + s->count += d_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: @@ -2416,7 +2461,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: @@ -2428,7 +2473,7 @@ ctables_summary_value (const struct ctables_cell *cell, { enum ctables_domain_type d = ctables_function_domain (ss->function); return (cell->domains[d]->e_valid - ? s->valid / cell->domains[d]->e_valid * 100 + ? s->count / cell->domains[d]->e_valid * 100 : SYSMIS); } @@ -2449,15 +2494,17 @@ ctables_summary_value (const struct ctables_cell *cell, NOT_REACHED (); 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; @@ -2580,6 +2627,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; @@ -2686,6 +2734,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; ) { @@ -2728,6 +2779,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; } } @@ -2792,6 +2846,8 @@ ctables_cell_insert__ (struct ctables_section *s, const struct ccase *c, cell = xmalloc (sizeof *cell); cell->hide = false; + cell->is_missing = false; + cell->excluded_missing = false; cell->sv = sv; cell->contributes_to_domains = true; cell->postcompute = false; @@ -2804,6 +2860,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; @@ -2814,13 +2872,16 @@ ctables_cell_insert__ (struct ctables_section *s, const struct ccase *c, || cat->type == CCT_SUBTOTAL || cat->type == CCT_POSTCOMPUTE) cell->contributes_to_domains = false; + else if (var_is_value_missing (var, value)) + cell->is_missing = true; + if (cat->type == CCT_EXCLUDED_MISSING) + cell->excluded_missing = true; if (cat->type == CCT_POSTCOMPUTE) cell->postcompute = true; } 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)); } } @@ -2838,21 +2899,33 @@ 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], - double d_weight, double e_weight) + bool is_missing, double d_weight, double e_weight) { struct ctables_cell *cell = ctables_cell_insert__ (s, c, cats); const struct ctables_nest *ss = s->nests[s->table->summary_axis]; 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, &cell->summaries[i], &specs->specs[i], + specs->var, case_data (c, specs->var), specs->is_scale, + is_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 (!cell->excluded_missing) + { + d->d_count += d_weight; + d->e_count += e_weight; + } + if (!cell->is_missing) + { + d->d_valid += d_weight; + d->e_valid += e_weight; + } } } } @@ -2860,7 +2933,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], - double d_weight, double e_weight, + bool is_missing, double d_weight, double e_weight, enum pivot_axis_type start_axis, size_t start_nest) { for (enum pivot_axis_type a = start_axis; a < PIVOT_N_AXES; a++) @@ -2879,8 +2952,9 @@ 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, d_weight, e_weight); + recurse_totals (s, c, cats, is_missing, + d_weight, e_weight, a, i + 1); cats[a][i] = save; } } @@ -2891,7 +2965,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], - double d_weight, double e_weight, + bool is_missing, double d_weight, double e_weight, enum pivot_axis_type start_axis, size_t start_nest) { for (enum pivot_axis_type a = start_axis; a < PIVOT_N_AXES; a++) @@ -2906,8 +2980,9 @@ 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, d_weight, e_weight); + recurse_subtotals (s, c, cats, is_missing, + d_weight, e_weight, a, i + 1); cats[a][i] = save; } } @@ -2940,6 +3015,8 @@ ctables_cell_insert (struct ctables_section *s, double d_weight, double e_weight) { const struct ctables_category *cats[PIVOT_N_AXES][10]; /* XXX */ + bool is_missing = false; + bool excluded_missing = false; for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++) { const struct ctables_nest *nest = s->nests[a]; @@ -2951,32 +3028,47 @@ 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 (!is_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, 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, d_weight, e_weight, 0, 0); + recurse_subtotals (s, c, cats, is_missing, d_weight, e_weight, 0, 0); + } } struct merge_item @@ -3805,7 +3897,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) { @@ -4005,6 +4097,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; } } } diff --git a/tests/language/stats/ctables.at b/tests/language/stats/ctables.at index 9d58441d88..1b7021438e 100644 --- a/tests/language/stats/ctables.at +++ b/tests/language/stats/ctables.at @@ -12,6 +12,7 @@ dnl * Unimplemented ones. dnl * U-prefix for unweighted summaries. dnl * .LCL and .UCL suffixes. dnl * .SE suffixes. +dnl * Why are summary functions for scale variables also available for totals and subtotals? dnl - CATEGORIES: dnl * String values dnl * Date values @@ -131,17 +132,19 @@ AT_SETUP([CTABLES one scale variable]) AT_CHECK([ln $top_srcdir/examples/nhtsa.sav . || cp $top_srcdir/examples/nhtsa.sav .]) AT_DATA([ctables.sps], [[GET 'nhtsa.sav'. -CTABLES /TABLE qnd1[COUNT, MEAN, STDDEV, MINIMUM, MAXIMUM]. +CTABLES /TABLE qnd1[COUNT, VALIDN, TOTALN, MEAN, STDDEV, MINIMUM, MAXIMUM]. CTABLES /TABLE BY qnd1. CTABLES /TABLE BY BY qnd1. ]]) AT_CHECK([pspp ctables.sps -O box=unicode -O width=80], [0], [dnl - Custom Tables -╭──────────────────────────┬─────┬────┬─────────────┬───────┬───────╮ -│ │Count│Mean│Std Deviation│Minimum│Maximum│ -├──────────────────────────┼─────┼────┼─────────────┼───────┼───────┤ -│D1. AGE: What is your age?│ 6930│ 48│ 19│ 16│ 86│ -╰──────────────────────────┴─────┴────┴─────────────┴───────┴───────╯ + Custom Tables +╭──────────────────────┬─────┬───────┬───────┬────┬────────────┬───────┬───────╮ +│ │ │ │ │ │ Std │ │ │ +│ │Count│Valid N│Total N│Mean│ Deviation │Minimum│Maximum│ +├──────────────────────┼─────┼───────┼───────┼────┼────────────┼───────┼───────┤ +│D1. AGE: What is your │ 6999│ 6930│ 6999│ 48│ 19│ 16│ 86│ +│age? │ │ │ │ │ │ │ │ +╰──────────────────────┴─────┴───────┴───────┴────┴────────────┴───────┴───────╯ Custom Tables ╭──────────────────────────╮ @@ -708,7 +711,8 @@ AT_DATA([ctables.sps], [[GET 'nhtsa.sav'. CTABLES /TABLE=qn17 /CATEGORIES VARIABLES=qn17 TOTAL=YES LABEL='Number responding'. -CTABLES /TABLE=region > qn18 [MEAN, COUNT] +DESCRIPTIVES qn18/STATISTICS=MEAN. +CTABLES /TABLE=region > qn18 [MEAN, COUNT, VALIDN, TOTALN] /CATEGORIES VARIABLES=region TOTAL=YES LABEL='All regions'. ]]) AT_CHECK([pspp ctables.sps -O box=unicode -O width=80], [0], [dnl @@ -728,30 +732,41 @@ AT_CHECK([pspp ctables.sps -O box=unicode -O width=80], [0], [dnl │ Number responding │ 4221│ ╰────────────────────────────────────────────────────────────────────────┴─────╯ + Descriptive Statistics +╭────────────────────────────────────────────────────────────────────┬────┬────╮ +│ │ N │Mean│ +├────────────────────────────────────────────────────────────────────┼────┼────┤ +│18. When you drink ANSWERFROM(QN17R1), about how many │4218│4.62│ +│ANSWERFROM(QN17R2) do you usually drink per sitting? │ │ │ +│Valid N (listwise) │6999│ │ +│Missing N (listwise) │2781│ │ +╰────────────────────────────────────────────────────────────────────┴────┴────╯ + Custom Tables -╭───────────────────────────────────────────────────────────────────┬────┬─────╮ -│ │Mean│Count│ -├───────────────────────────────────────────────────────────────────┼────┼─────┤ -│Region NE 18. When you drink ANSWERFROM(QN17R1), about how │4.36│ 949│ -│ many ANSWERFROM(QN17R2) do you usually drink per │ │ │ -│ sitting? │ │ │ -│ ╶────────────────────────────────────────────────────────────┼────┼─────┤ -│ MW 18. When you drink ANSWERFROM(QN17R1), about how │4.67│ 1027│ -│ many ANSWERFROM(QN17R2) do you usually drink per │ │ │ -│ sitting? │ │ │ -│ ╶────────────────────────────────────────────────────────────┼────┼─────┤ -│ S 18. When you drink ANSWERFROM(QN17R1), about how │4.71│ 1287│ -│ many ANSWERFROM(QN17R2) do you usually drink per │ │ │ -│ sitting? │ │ │ -│ ╶────────────────────────────────────────────────────────────┼────┼─────┤ -│ W 18. When you drink ANSWERFROM(QN17R1), about how │4.69│ 955│ -│ many ANSWERFROM(QN17R2) do you usually drink per │ │ │ -│ sitting? │ │ │ -│ ╶────────────────────────────────────────────────────────────┼────┼─────┤ -│ All 18. When you drink ANSWERFROM(QN17R1), about how │4.62│ 4218│ -│ regions many ANSWERFROM(QN17R2) do you usually drink per │ │ │ -│ sitting? │ │ │ -╰───────────────────────────────────────────────────────────────────┴────┴─────╯ +╭──────────────────────────────────────────────────────┬────┬─────┬──────┬─────╮ +│ │ │ │ Valid│Total│ +│ │Mean│Count│ N │ N │ +├──────────────────────────────────────────────────────┼────┼─────┼──────┼─────┤ +│Region NE 18. When you drink ANSWERFROM(QN17R1),│4.36│ 1409│ 949│ 1409│ +│ about how many ANSWERFROM(QN17R2) do │ │ │ │ │ +│ you usually drink per sitting? │ │ │ │ │ +│ ╶───────────────────────────────────────────────┼────┼─────┼──────┼─────┤ +│ MW 18. When you drink ANSWERFROM(QN17R1),│4.67│ 1654│ 1027│ 1654│ +│ about how many ANSWERFROM(QN17R2) do │ │ │ │ │ +│ you usually drink per sitting? │ │ │ │ │ +│ ╶───────────────────────────────────────────────┼────┼─────┼──────┼─────┤ +│ S 18. When you drink ANSWERFROM(QN17R1),│4.71│ 2390│ 1287│ 2390│ +│ about how many ANSWERFROM(QN17R2) do │ │ │ │ │ +│ you usually drink per sitting? │ │ │ │ │ +│ ╶───────────────────────────────────────────────┼────┼─────┼──────┼─────┤ +│ W 18. When you drink ANSWERFROM(QN17R1),│4.69│ 1546│ 955│ 1546│ +│ about how many ANSWERFROM(QN17R2) do │ │ │ │ │ +│ you usually drink per sitting? │ │ │ │ │ +│ ╶───────────────────────────────────────────────┼────┼─────┼──────┼─────┤ +│ All 18. When you drink ANSWERFROM(QN17R1),│4.62│ 6999│ 4218│ 6999│ +│ regions about how many ANSWERFROM(QN17R2) do │ │ │ │ │ +│ you usually drink per sitting? │ │ │ │ │ +╰──────────────────────────────────────────────────────┴────┴─────┴──────┴─────╯ ]) AT_CLEANUP @@ -922,4 +937,58 @@ AT_CHECK([pspp ctables.sps -O box=unicode -O width=120], [0], [dnl │ Female│ 943│ ╰──────────────────────────────┴────────────╯ ]) +AT_CLEANUP + +AT_SETUP([CTABLES missing values]) +AT_DATA([ctables.sps], +[[DATA LIST LIST NOTABLE/x y. +BEGIN DATA. +1 1 +1 2 +1 3 +1 4 +1 5 +1 . +2 1 +2 2 +2 3 +2 4 +2 5 +2 . +3 1 +3 2 +3 3 +3 4 +3 5 +3 . +4 1 +4 2 +4 3 +4 4 +4 5 +4 . +5 1 +5 2 +5 3 +5 4 +5 5 +5 . +. 1 +. 2 +. 3 +. 4 +. 5 +END DATA. +MISSING VALUES x (1, 2) y (2, 3). +VARIABLE LEVEL ALL (NOMINAL). + +CTABLES /TABLE x[COUNT,TOTALS[COUNT, VALIDN, TOTALN]] + /CATEGORIES VARIABLES=ALL TOTAL=YES. +CTABLES /TABLE x[COUNT,TOTALS[COUNT, VALIDN, TOTALN]] + /CATEGORIES VARIABLES=ALL TOTAL=YES MISSING=INCLUDE. +CTABLES /TABLE x BY y. +CTABLES /TABLE x BY y /CATEGORIES VARIABLES=ALL MISSING=INCLUDE. +CTABLES /TABLE y BY x /CATEGORIES VARIABLES=ALL MISSING=INCLUDE. +]]) +AT_CHECK([pspp ctables.sps -O box=unicode -O width=120], [0], []) AT_CLEANUP \ No newline at end of file