Remove unneeded #includes.
[pspp] / src / language / stats / oneway.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014,
3    2020 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 #include <float.h>
21 #include <gsl/gsl_cdf.h>
22 #include <gsl/gsl_matrix.h>
23 #include <math.h>
24
25 #include "data/case.h"
26 #include "data/casegrouper.h"
27 #include "data/casereader.h"
28 #include "data/dataset.h"
29 #include "data/dictionary.h"
30 #include "data/format.h"
31 #include "data/value.h"
32 #include "language/command.h"
33 #include "language/dictionary/split-file.h"
34 #include "language/lexer/lexer.h"
35 #include "language/lexer/value-parser.h"
36 #include "language/lexer/variable-parser.h"
37 #include "libpspp/ll.h"
38 #include "libpspp/message.h"
39 #include "libpspp/misc.h"
40 #include "libpspp/taint.h"
41 #include "linreg/sweep.h"
42 #include "tukey/tukey.h"
43 #include "math/categoricals.h"
44 #include "math/interaction.h"
45 #include "math/covariance.h"
46 #include "math/levene.h"
47 #include "math/moments.h"
48 #include "output/pivot-table.h"
49
50 #include "gettext.h"
51 #define _(msgid) gettext (msgid)
52 #define N_(msgid) msgid
53
54 /* Workspace variable for each dependent variable */
55 struct per_var_ws
56   {
57     struct interaction *iact;
58     struct categoricals *cat;
59     struct covariance *cov;
60     struct levene *nl;
61
62     double n;
63
64     double sst;
65     double sse;
66     double ssa;
67
68     int n_groups;
69
70     double mse;
71   };
72
73 /* Per category data */
74 struct descriptive_data
75   {
76     const struct variable *var;
77     struct moments1 *mom;
78
79     double minimum;
80     double maximum;
81   };
82
83 enum missing_type
84   {
85     MISS_LISTWISE,
86     MISS_ANALYSIS,
87   };
88
89 struct coeff_node
90 {
91   struct ll ll;
92   double coeff;
93 };
94
95
96 struct contrasts_node
97 {
98   struct ll ll;
99   struct ll_list coefficient_list;
100 };
101
102
103 struct oneway_spec;
104
105 typedef double df_func (const struct per_var_ws *pvw, const struct moments1 *mom_i, const struct moments1 *mom_j);
106 typedef double ts_func (int k, const struct moments1 *mom_i, const struct moments1 *mom_j, double std_err);
107 typedef double p1tail_func (double ts, double df1, double df2);
108
109 typedef double pinv_func (double std_err, double alpha, double df, int k, const struct moments1 *mom_i, const struct moments1 *mom_j);
110
111
112 struct posthoc
113 {
114   const char *syntax;
115   const char *label;
116
117   df_func *dff;
118   ts_func *tsf;
119   p1tail_func *p1f;
120
121   pinv_func *pinv;
122 };
123
124 struct oneway_spec
125 {
126   size_t n_vars;
127   const struct variable **vars;
128
129   const struct variable *indep_var;
130
131   bool descriptive_stats;
132   bool homogeneity_stats;
133
134   enum missing_type missing_type;
135   enum mv_class exclude;
136
137   /* List of contrasts */
138   struct ll_list contrast_list;
139
140   /* The weight variable */
141   const struct variable *wv;
142   const struct fmt_spec *wfmt;
143
144   /* The confidence level for multiple comparisons */
145   double alpha;
146
147   int *posthoc;
148   int n_posthoc;
149 };
150
151 static double
152 df_common (const struct per_var_ws *pvw, const struct moments1 *mom_i UNUSED, const struct moments1 *mom_j UNUSED)
153 {
154   return  pvw->n - pvw->n_groups;
155 }
156
157 static double
158 df_individual (const struct per_var_ws *pvw UNUSED, const struct moments1 *mom_i, const struct moments1 *mom_j)
159 {
160   double n_i, var_i;
161   double n_j, var_j;
162   double nom,denom;
163
164   moments1_calculate (mom_i, &n_i, NULL, &var_i, 0, 0);
165   moments1_calculate (mom_j, &n_j, NULL, &var_j, 0, 0);
166
167   if (n_i <= 1.0 || n_j <= 1.0)
168     return SYSMIS;
169
170   nom = pow2 (var_i/n_i + var_j/n_j);
171   denom = pow2 (var_i/n_i) / (n_i - 1) + pow2 (var_j/n_j) / (n_j - 1);
172
173   return nom / denom;
174 }
175
176 static double lsd_pinv (double std_err, double alpha, double df, int k UNUSED, const struct moments1 *mom_i UNUSED, const struct moments1 *mom_j UNUSED)
177 {
178   return std_err * gsl_cdf_tdist_Pinv (1.0 - alpha / 2.0, df);
179 }
180
181 static double bonferroni_pinv (double std_err, double alpha, double df, int k, const struct moments1 *mom_i UNUSED, const struct moments1 *mom_j UNUSED)
182 {
183   const int m = k * (k - 1) / 2;
184   return std_err * gsl_cdf_tdist_Pinv (1.0 - alpha / (2.0 * m), df);
185 }
186
187 static double sidak_pinv (double std_err, double alpha, double df, int k, const struct moments1 *mom_i UNUSED, const struct moments1 *mom_j UNUSED)
188 {
189   const double m = k * (k - 1) / 2;
190   double lp = 1.0 - exp (log (1.0 - alpha) / m);
191   return std_err * gsl_cdf_tdist_Pinv (1.0 - lp / 2.0, df);
192 }
193
194 static double tukey_pinv (double std_err, double alpha, double df, int k, const struct moments1 *mom_i UNUSED, const struct moments1 *mom_j UNUSED)
195 {
196   if (k < 2 || df < 2)
197     return SYSMIS;
198
199   return std_err / sqrt (2.0)  * qtukey (1 - alpha, 1.0, k, df, 1, 0);
200 }
201
202 static double scheffe_pinv (double std_err, double alpha, double df, int k, const struct moments1 *mom_i UNUSED, const struct moments1 *mom_j UNUSED)
203 {
204   double x = (k - 1) * gsl_cdf_fdist_Pinv (1.0 - alpha, k - 1, df);
205   return std_err * sqrt (x);
206 }
207
208 static double gh_pinv (double std_err UNUSED, double alpha, double df, int k, const struct moments1 *mom_i, const struct moments1 *mom_j)
209 {
210   double n_i, mean_i, var_i;
211   double n_j, mean_j, var_j;
212   double m;
213
214   moments1_calculate (mom_i, &n_i, &mean_i, &var_i, 0, 0);
215   moments1_calculate (mom_j, &n_j, &mean_j, &var_j, 0, 0);
216
217   m = sqrt ((var_i/n_i + var_j/n_j) / 2.0);
218
219   if (k < 2 || df < 2)
220     return SYSMIS;
221
222   return m * qtukey (1 - alpha, 1.0, k, df, 1, 0);
223 }
224
225
226 static double
227 multiple_comparison_sig (double std_err,
228                                        const struct per_var_ws *pvw,
229                                        const struct descriptive_data *dd_i, const struct descriptive_data *dd_j,
230                                        const struct posthoc *ph)
231 {
232   int k = pvw->n_groups;
233   double df = ph->dff (pvw, dd_i->mom, dd_j->mom);
234   double ts = ph->tsf (k, dd_i->mom, dd_j->mom, std_err);
235   if (df == SYSMIS)
236     return SYSMIS;
237   return  ph->p1f (ts, k - 1, df);
238 }
239
240 static double
241 mc_half_range (const struct oneway_spec *cmd, const struct per_var_ws *pvw, double std_err, const struct descriptive_data *dd_i, const struct descriptive_data *dd_j, const struct posthoc *ph)
242 {
243   int k = pvw->n_groups;
244   double df = ph->dff (pvw, dd_i->mom, dd_j->mom);
245   if (df == SYSMIS)
246     return SYSMIS;
247
248   return ph->pinv (std_err, cmd->alpha, df, k, dd_i->mom, dd_j->mom);
249 }
250
251 static double tukey_1tailsig (double ts, double df1, double df2)
252 {
253   double twotailedsig;
254
255   if (df2 < 2 || df1 < 1)
256     return SYSMIS;
257
258   twotailedsig = 1.0 - ptukey (ts, 1.0, df1 + 1, df2, 1, 0);
259
260   return twotailedsig / 2.0;
261 }
262
263 static double lsd_1tailsig (double ts, double df1 UNUSED, double df2)
264 {
265   return ts < 0 ? gsl_cdf_tdist_P (ts, df2) : gsl_cdf_tdist_Q (ts, df2);
266 }
267
268 static double sidak_1tailsig (double ts, double df1, double df2)
269 {
270   double ex = (df1 + 1.0) * df1 / 2.0;
271   double lsd_sig = 2 * lsd_1tailsig (ts, df1, df2);
272
273   return 0.5 * (1.0 - pow (1.0 - lsd_sig, ex));
274 }
275
276 static double bonferroni_1tailsig (double ts, double df1, double df2)
277 {
278   const int m = (df1 + 1) * df1 / 2;
279
280   double p = ts < 0 ? gsl_cdf_tdist_P (ts, df2) : gsl_cdf_tdist_Q (ts, df2);
281   p *= m;
282
283   return p > 0.5 ? 0.5 : p;
284 }
285
286 static double scheffe_1tailsig (double ts, double df1, double df2)
287 {
288   return 0.5 * gsl_cdf_fdist_Q (ts, df1, df2);
289 }
290
291
292 static double tukey_test_stat (int k UNUSED, const struct moments1 *mom_i, const struct moments1 *mom_j, double std_err)
293 {
294   double ts;
295   double n_i, mean_i, var_i;
296   double n_j, mean_j, var_j;
297
298   moments1_calculate (mom_i, &n_i, &mean_i, &var_i, 0, 0);
299   moments1_calculate (mom_j, &n_j, &mean_j, &var_j, 0, 0);
300
301   ts =  (mean_i - mean_j) / std_err;
302   ts = fabs (ts) * sqrt (2.0);
303
304   return ts;
305 }
306
307 static double lsd_test_stat (int k UNUSED, const struct moments1 *mom_i, const struct moments1 *mom_j, double std_err)
308 {
309   double n_i, mean_i, var_i;
310   double n_j, mean_j, var_j;
311
312   moments1_calculate (mom_i, &n_i, &mean_i, &var_i, 0, 0);
313   moments1_calculate (mom_j, &n_j, &mean_j, &var_j, 0, 0);
314
315   return (mean_i - mean_j) / std_err;
316 }
317
318 static double scheffe_test_stat (int k, const struct moments1 *mom_i, const struct moments1 *mom_j, double std_err)
319 {
320   double t;
321   double n_i, mean_i, var_i;
322   double n_j, mean_j, var_j;
323
324   moments1_calculate (mom_i, &n_i, &mean_i, &var_i, 0, 0);
325   moments1_calculate (mom_j, &n_j, &mean_j, &var_j, 0, 0);
326
327   t = (mean_i - mean_j) / std_err;
328   t = pow2 (t);
329   t /= k - 1;
330
331   return t;
332 }
333
334 static double gh_test_stat (int k UNUSED, const struct moments1 *mom_i, const struct moments1 *mom_j, double std_err UNUSED)
335 {
336   double ts;
337   double thing;
338   double n_i, mean_i, var_i;
339   double n_j, mean_j, var_j;
340
341   moments1_calculate (mom_i, &n_i, &mean_i, &var_i, 0, 0);
342   moments1_calculate (mom_j, &n_j, &mean_j, &var_j, 0, 0);
343
344   thing = var_i / n_i + var_j / n_j;
345   thing /= 2.0;
346   thing = sqrt (thing);
347
348   ts = (mean_i - mean_j) / thing;
349
350   return fabs (ts);
351 }
352
353
354
355 static const struct posthoc ph_tests [] =
356   {
357     { "LSD",        N_("LSD"),          df_common, lsd_test_stat,     lsd_1tailsig,          lsd_pinv},
358     { "TUKEY",      N_("Tukey HSD"),    df_common, tukey_test_stat,   tukey_1tailsig,        tukey_pinv},
359     { "BONFERRONI", N_("Bonferroni"),   df_common, lsd_test_stat,     bonferroni_1tailsig,   bonferroni_pinv},
360     { "SCHEFFE",    N_("Scheffé"),      df_common, scheffe_test_stat, scheffe_1tailsig,      scheffe_pinv},
361     { "GH",         N_("Games-Howell"), df_individual, gh_test_stat,  tukey_1tailsig,        gh_pinv},
362     { "SIDAK",      N_("Å idák"),        df_common, lsd_test_stat,     sidak_1tailsig,        sidak_pinv}
363   };
364
365
366 struct oneway_workspace
367 {
368   /* The number of distinct values of the independent variable, when all
369      missing values are disregarded */
370   int actual_number_of_groups;
371
372   struct per_var_ws *vws;
373
374   /* An array of descriptive data.  One for each dependent variable */
375   struct descriptive_data **dd_total;
376 };
377
378 /* Routines to show the output tables */
379 static void show_anova_table (const struct oneway_spec *, const struct oneway_workspace *);
380 static void show_descriptives (const struct oneway_spec *, const struct oneway_workspace *);
381 static void show_homogeneity (const struct oneway_spec *, const struct oneway_workspace *);
382
383 static void output_oneway (const struct oneway_spec *, struct oneway_workspace *ws);
384 static void run_oneway (const struct oneway_spec *cmd, struct casereader *input, const struct dataset *ds);
385
386
387 static void
388 destroy_coeff_list (struct contrasts_node *coeff_list)
389 {
390   struct coeff_node *cn = NULL;
391   struct coeff_node *cnx = NULL;
392   struct ll_list *cl = &coeff_list->coefficient_list;
393
394   ll_for_each_safe (cn, cnx, struct coeff_node, ll, cl)
395     {
396       free (cn);
397     }
398
399   free (coeff_list);
400 }
401
402 static void
403 oneway_cleanup (struct oneway_spec *cmd)
404 {
405   struct contrasts_node *coeff_list  = NULL;
406   struct contrasts_node *coeff_next  = NULL;
407   ll_for_each_safe (coeff_list, coeff_next, struct contrasts_node, ll, &cmd->contrast_list)
408     {
409       destroy_coeff_list (coeff_list);
410     }
411
412   free (cmd->posthoc);
413 }
414
415
416
417 int
418 cmd_oneway (struct lexer *lexer, struct dataset *ds)
419 {
420   const struct dictionary *dict = dataset_dict (ds);
421   struct oneway_spec oneway = {
422     .missing_type = MISS_ANALYSIS,
423     .exclude = MV_ANY,
424     .wv = dict_get_weight (dict),
425     .wfmt = dict_get_weight_format (dict),
426     .alpha = 0.05,
427   };
428
429   ll_init (&oneway.contrast_list);
430   if (lex_match (lexer, T_SLASH))
431     {
432       if (!lex_force_match_id (lexer, "VARIABLES"))
433         goto error;
434       lex_match (lexer, T_EQUALS);
435     }
436
437   if (!parse_variables_const (lexer, dict,
438                               &oneway.vars, &oneway.n_vars,
439                               PV_NO_DUPLICATE | PV_NUMERIC))
440     goto error;
441
442   if (!lex_force_match (lexer, T_BY))
443     goto error;
444
445   oneway.indep_var = parse_variable_const (lexer, dict);
446   if (oneway.indep_var == NULL)
447     goto error;
448
449   while (lex_token (lexer) != T_ENDCMD)
450     {
451       lex_match (lexer, T_SLASH);
452
453       if (lex_match_id (lexer, "STATISTICS"))
454         {
455           lex_match (lexer, T_EQUALS);
456           while (lex_token (lexer) != T_ENDCMD && lex_token (lexer) != T_SLASH)
457             {
458               if (lex_match_id (lexer, "DESCRIPTIVES"))
459                 oneway.descriptive_stats = true;
460               else if (lex_match_id (lexer, "HOMOGENEITY"))
461                 oneway.homogeneity_stats = true;
462               else
463                 {
464                   lex_error_expecting (lexer, "DESCRIPTIVES", "HOMOGENEITY");
465                   goto error;
466                 }
467             }
468         }
469       else if (lex_match_id (lexer, "POSTHOC"))
470         {
471           lex_match (lexer, T_EQUALS);
472           while (lex_token (lexer) != T_ENDCMD && lex_token (lexer) != T_SLASH)
473             {
474               bool method = false;
475               for (size_t p = 0; p < sizeof ph_tests / sizeof *ph_tests; ++p)
476                 if (lex_match_id (lexer, ph_tests[p].syntax))
477                   {
478                     oneway.n_posthoc++;
479                     oneway.posthoc = xrealloc (oneway.posthoc, sizeof (*oneway.posthoc) * oneway.n_posthoc);
480                     oneway.posthoc[oneway.n_posthoc - 1] = p;
481                     method = true;
482                     break;
483                   }
484               if (method == false)
485                 {
486                   if (lex_match_id (lexer, "ALPHA"))
487                     {
488                       if (!lex_force_match (lexer, T_LPAREN)
489                           || !lex_force_num (lexer))
490                         goto error;
491                       oneway.alpha = lex_number (lexer);
492                       lex_get (lexer);
493                       if (!lex_force_match (lexer, T_RPAREN))
494                         goto error;
495                     }
496                   else
497                     {
498                       lex_error (lexer, _("Unknown post hoc analysis method."));
499                       goto error;
500                     }
501                 }
502             }
503         }
504       else if (lex_match_id (lexer, "CONTRAST"))
505         {
506           struct contrasts_node *cl = XZALLOC (struct contrasts_node);
507
508           struct ll_list *coefficient_list = &cl->coefficient_list;
509           lex_match (lexer, T_EQUALS);
510
511           ll_init (coefficient_list);
512
513           while (lex_token (lexer) != T_ENDCMD && lex_token (lexer) != T_SLASH)
514             {
515               if (!lex_force_num (lexer))
516                 {
517                   destroy_coeff_list (cl);
518                   goto error;
519                 }
520
521               struct coeff_node *cc = xmalloc (sizeof *cc);
522               cc->coeff = lex_number (lexer);
523
524               ll_push_tail (coefficient_list, &cc->ll);
525               lex_get (lexer);
526             }
527
528           if (ll_count (coefficient_list) <= 0)
529             {
530               destroy_coeff_list (cl);
531               goto error;
532             }
533
534           ll_push_tail (&oneway.contrast_list, &cl->ll);
535         }
536       else if (lex_match_id (lexer, "MISSING"))
537         {
538           lex_match (lexer, T_EQUALS);
539           while (lex_token (lexer) != T_ENDCMD && lex_token (lexer) != T_SLASH)
540             {
541               if (lex_match_id (lexer, "INCLUDE"))
542                 oneway.exclude = MV_SYSTEM;
543               else if (lex_match_id (lexer, "EXCLUDE"))
544                 oneway.exclude = MV_ANY;
545               else if (lex_match_id (lexer, "LISTWISE"))
546                 oneway.missing_type = MISS_LISTWISE;
547               else if (lex_match_id (lexer, "ANALYSIS"))
548                 oneway.missing_type = MISS_ANALYSIS;
549               else
550                 {
551                   lex_error_expecting (lexer, "INCLUDE", "EXCLUDE",
552                                        "LISTWISE", "ANALYSIS");
553                   goto error;
554                 }
555             }
556         }
557       else
558         {
559           lex_error_expecting (lexer, "STATISTICS", "POSTHOC", "CONTRAST",
560                                "MISSING");
561           goto error;
562         }
563     }
564
565   struct casegrouper *grouper = casegrouper_create_splits (proc_open (ds), dict);
566   struct casereader *group;
567   while (casegrouper_get_next_group (grouper, &group))
568     run_oneway (&oneway, group, ds);
569   bool ok = casegrouper_destroy (grouper);
570   ok = proc_commit (ds) && ok;
571
572   oneway_cleanup (&oneway);
573   free (oneway.vars);
574   return CMD_SUCCESS;
575
576  error:
577   oneway_cleanup (&oneway);
578   free (oneway.vars);
579   return CMD_FAILURE;
580 }
581 \f
582 static struct descriptive_data *
583 dd_create (const struct variable *var)
584 {
585   struct descriptive_data *dd = xmalloc (sizeof *dd);
586
587   dd->mom = moments1_create (MOMENT_VARIANCE);
588   dd->minimum = DBL_MAX;
589   dd->maximum = -DBL_MAX;
590   dd->var = var;
591
592   return dd;
593 }
594
595 static void
596 dd_destroy (struct descriptive_data *dd)
597 {
598   moments1_destroy (dd->mom);
599   free (dd);
600 }
601
602 static void *
603 makeit (const void *aux1, void *aux2 UNUSED)
604 {
605   const struct variable *var = aux1;
606
607   struct descriptive_data *dd = dd_create (var);
608
609   return dd;
610 }
611
612 static void
613 killit (const void *aux1 UNUSED, void *aux2 UNUSED, void *user_data)
614 {
615   struct descriptive_data *dd = user_data;
616
617   dd_destroy (dd);
618 }
619
620
621 static void
622 updateit (const void *aux1, void *aux2, void *user_data,
623           const struct ccase *c, double weight)
624 {
625   struct descriptive_data *dd = user_data;
626
627   const struct variable *varp = aux1;
628
629   const union value *valx = case_data (c, varp);
630
631   struct descriptive_data *dd_total = aux2;
632
633   moments1_add (dd->mom, valx->f, weight);
634   if (valx->f < dd->minimum)
635     dd->minimum = valx->f;
636
637   if (valx->f > dd->maximum)
638     dd->maximum = valx->f;
639
640   {
641     const struct variable *var = dd_total->var;
642     const union value *val = case_data (c, var);
643
644     moments1_add (dd_total->mom,
645                   val->f,
646                   weight);
647
648     if (val->f < dd_total->minimum)
649       dd_total->minimum = val->f;
650
651     if (val->f > dd_total->maximum)
652       dd_total->maximum = val->f;
653   }
654 }
655
656 static void
657 run_oneway (const struct oneway_spec *cmd, struct casereader *input,
658             const struct dataset *ds)
659 {
660   struct dictionary *dict = dataset_dict (ds);
661   struct oneway_workspace ws = {
662     .vws = xcalloc (cmd->n_vars, sizeof *ws.vws),
663     .dd_total = XCALLOC (cmd->n_vars, struct descriptive_data *),
664   };
665
666   for (size_t v = 0; v < cmd->n_vars; ++v)
667     ws.dd_total[v] = dd_create (cmd->vars[v]);
668
669   for (size_t v = 0; v < cmd->n_vars; ++v)
670     {
671       static const struct payload payload =
672         {
673           .create = makeit,
674           .update = updateit,
675           .destroy = killit,
676         };
677
678       ws.vws[v].iact = interaction_create (cmd->indep_var);
679       ws.vws[v].cat = categoricals_create (&ws.vws[v].iact, 1, cmd->wv,
680                                            cmd->exclude);
681
682       categoricals_set_payload (ws.vws[v].cat, &payload,
683                                 CONST_CAST (struct variable *, cmd->vars[v]),
684                                 ws.dd_total[v]);
685
686
687       ws.vws[v].cov = covariance_2pass_create (1, &cmd->vars[v],
688                                                ws.vws[v].cat,
689                                                cmd->wv, cmd->exclude, true);
690       ws.vws[v].nl = levene_create (var_get_width (cmd->indep_var), NULL);
691     }
692
693   output_split_file_values_peek (ds, input);
694
695   struct taint *taint = taint_clone (casereader_get_taint (input));
696   input = casereader_create_filter_missing (input, &cmd->indep_var, 1,
697                                             cmd->exclude, NULL, NULL);
698   if (cmd->missing_type == MISS_LISTWISE)
699     input = casereader_create_filter_missing (input, cmd->vars, cmd->n_vars,
700                                               cmd->exclude, NULL, NULL);
701   input = casereader_create_filter_weight (input, dict, NULL, NULL);
702
703   struct casereader *reader = casereader_clone (input);
704   struct ccase *c;
705   for (; (c = casereader_read (reader)) != NULL; case_unref (c))
706     {
707       double w = dict_get_case_weight (dict, c, NULL);
708
709       for (size_t i = 0; i < cmd->n_vars; ++i)
710         {
711           struct per_var_ws *pvw = &ws.vws[i];
712           const struct variable *v = cmd->vars[i];
713           const union value *val = case_data (c, v);
714
715           if (MISS_ANALYSIS == cmd->missing_type)
716             {
717               if (var_is_value_missing (v, val) & cmd->exclude)
718                 continue;
719             }
720
721           covariance_accumulate_pass1 (pvw->cov, c);
722           levene_pass_one (pvw->nl, val->f, w, case_data (c, cmd->indep_var));
723         }
724     }
725   casereader_destroy (reader);
726
727   reader = casereader_clone (input);
728   for (; (c = casereader_read (reader)); case_unref (c))
729     {
730       double w = dict_get_case_weight (dict, c, NULL);
731       for (size_t i = 0; i < cmd->n_vars; ++i)
732         {
733           struct per_var_ws *pvw = &ws.vws[i];
734           const struct variable *v = cmd->vars[i];
735           const union value *val = case_data (c, v);
736
737           if (MISS_ANALYSIS == cmd->missing_type)
738             {
739               if (var_is_value_missing (v, val) & cmd->exclude)
740                 continue;
741             }
742
743           covariance_accumulate_pass2 (pvw->cov, c);
744           levene_pass_two (pvw->nl, val->f, w, case_data (c, cmd->indep_var));
745         }
746     }
747   casereader_destroy (reader);
748
749   reader = casereader_clone (input);
750   for (; (c = casereader_read (reader)); case_unref (c))
751     {
752       double w = dict_get_case_weight (dict, c, NULL);
753
754       for (size_t i = 0; i < cmd->n_vars; ++i)
755         {
756           struct per_var_ws *pvw = &ws.vws[i];
757           const struct variable *v = cmd->vars[i];
758           const union value *val = case_data (c, v);
759
760           if (MISS_ANALYSIS == cmd->missing_type)
761             {
762               if (var_is_value_missing (v, val) & cmd->exclude)
763                 continue;
764             }
765
766           levene_pass_three (pvw->nl, val->f, w, case_data (c, cmd->indep_var));
767         }
768     }
769   casereader_destroy (reader);
770
771   for (size_t v = 0; v < cmd->n_vars; ++v)
772     {
773       struct per_var_ws *pvw = &ws.vws[v];
774
775       const struct categoricals *cats = covariance_get_categoricals (pvw->cov);
776       if (!categoricals_sane (cats))
777         {
778           msg (MW, _("Dependent variable %s has no non-missing values.  "
779                      "No analysis for this variable will be done."),
780                var_get_name (cmd->vars[v]));
781           continue;
782         }
783
784       const gsl_matrix *ucm = covariance_calculate_unnormalized (pvw->cov);
785
786       gsl_matrix *cm = gsl_matrix_alloc (ucm->size1, ucm->size2);
787       gsl_matrix_memcpy (cm, ucm);
788
789       moments1_calculate (ws.dd_total[v]->mom, &pvw->n, NULL, NULL, NULL, NULL);
790
791       pvw->sst = gsl_matrix_get (cm, 0, 0);
792
793       reg_sweep (cm, 0);
794
795       pvw->sse = gsl_matrix_get (cm, 0, 0);
796       gsl_matrix_free (cm);
797
798       pvw->ssa = pvw->sst - pvw->sse;
799
800       pvw->n_groups = categoricals_n_total (cats);
801
802       pvw->mse = (pvw->sst - pvw->ssa) / (pvw->n - pvw->n_groups);
803     }
804
805   for (size_t v = 0; v < cmd->n_vars; ++v)
806     {
807       const struct categoricals *cats = covariance_get_categoricals (ws.vws[v].cov);
808       if (categoricals_is_complete (cats))
809         {
810           if (categoricals_n_total (cats) > ws.actual_number_of_groups)
811             ws.actual_number_of_groups = categoricals_n_total (cats);
812         }
813     }
814   casereader_destroy (input);
815
816   if (!taint_has_tainted_successor (taint))
817     output_oneway (cmd, &ws);
818
819   taint_destroy (taint);
820
821   for (size_t v = 0; v < cmd->n_vars; ++v)
822     {
823       covariance_destroy (ws.vws[v].cov);
824       levene_destroy (ws.vws[v].nl);
825       dd_destroy (ws.dd_total[v]);
826       interaction_destroy (ws.vws[v].iact);
827     }
828
829   free (ws.vws);
830   free (ws.dd_total);
831 }
832
833 static void show_contrast_coeffs (const struct oneway_spec *, const struct oneway_workspace *);
834 static void show_contrast_tests (const struct oneway_spec *, const struct oneway_workspace *);
835 static void show_comparisons (const struct oneway_spec *, const struct oneway_workspace *, int depvar);
836
837 static void
838 output_oneway (const struct oneway_spec *cmd, struct oneway_workspace *ws)
839 {
840   size_t list_idx = 0;
841
842   /* Check the sanity of the given contrast values */
843   struct contrasts_node *coeff_list  = NULL;
844   struct contrasts_node *coeff_next  = NULL;
845   ll_for_each_safe (coeff_list, coeff_next, struct contrasts_node, ll, &cmd->contrast_list)
846     {
847       struct coeff_node *cn = NULL;
848       double sum = 0;
849       struct ll_list *cl = &coeff_list->coefficient_list;
850       ++list_idx;
851
852       if (ll_count (cl) != ws->actual_number_of_groups)
853         {
854           msg (SW,
855                _("In contrast list %zu, the number of coefficients (%zu) does not equal the number of groups (%d). This contrast list will be ignored."),
856                list_idx, ll_count (cl), ws->actual_number_of_groups);
857
858           ll_remove (&coeff_list->ll);
859           destroy_coeff_list (coeff_list);
860           continue;
861         }
862
863       ll_for_each (cn, struct coeff_node, ll, cl)
864         sum += cn->coeff;
865
866       if (sum != 0.0)
867         msg (SW, _("Coefficients for contrast %zu do not total zero"),
868              list_idx);
869     }
870
871   if (cmd->descriptive_stats)
872     show_descriptives (cmd, ws);
873
874   if (cmd->homogeneity_stats)
875     show_homogeneity (cmd, ws);
876
877   show_anova_table (cmd, ws);
878
879   if (ll_count (&cmd->contrast_list) > 0)
880     {
881       show_contrast_coeffs (cmd, ws);
882       show_contrast_tests (cmd, ws);
883     }
884
885   if (cmd->posthoc)
886     for (size_t v = 0; v < cmd->n_vars; ++v)
887       {
888         const struct categoricals *cats = covariance_get_categoricals (ws->vws[v].cov);
889
890         if (categoricals_is_complete (cats))
891           show_comparisons (cmd, ws, v);
892       }
893 }
894
895
896 /* Show the ANOVA table */
897 static void
898 show_anova_table (const struct oneway_spec *cmd, const struct oneway_workspace *ws)
899 {
900   struct pivot_table *table = pivot_table_create (N_("ANOVA"));
901
902   pivot_dimension_create (table, PIVOT_AXIS_COLUMN, N_("Statistics"),
903                           N_("Sum of Squares"), PIVOT_RC_OTHER,
904                           N_("df"), PIVOT_RC_INTEGER,
905                           N_("Mean Square"), PIVOT_RC_OTHER,
906                           N_("F"), PIVOT_RC_OTHER,
907                           N_("Sig."), PIVOT_RC_SIGNIFICANCE);
908
909   pivot_dimension_create (table, PIVOT_AXIS_ROW, N_("Type"),
910                           N_("Between Groups"), N_("Within Groups"),
911                           N_("Total"));
912
913   struct pivot_dimension *variables = pivot_dimension_create (
914     table, PIVOT_AXIS_ROW, N_("Variables"));
915
916   for (size_t i = 0; i < cmd->n_vars; ++i)
917     {
918       int var_idx = pivot_category_create_leaf (
919         variables->root, pivot_value_new_variable (cmd->vars[i]));
920
921       const struct per_var_ws *pvw = &ws->vws[i];
922
923       double n;
924       moments1_calculate (ws->dd_total[i]->mom, &n, NULL, NULL, NULL, NULL);
925
926       double df1 = pvw->n_groups - 1;
927       double df2 = n - pvw->n_groups;
928       double msa = pvw->ssa / df1;
929       double F = msa / pvw->mse;
930
931       struct entry
932         {
933           int stat_idx;
934           int type_idx;
935           double x;
936         }
937       entries[] = {
938         /* Sums of Squares. */
939         { 0, 0, pvw->ssa },
940         { 0, 1, pvw->sse },
941         { 0, 2, pvw->sst },
942         /* Degrees of Freedom. */
943         { 1, 0, df1 },
944         { 1, 1, df2 },
945         { 1, 2, n - 1 },
946         /* Mean Squares. */
947         { 2, 0, msa },
948         { 2, 1, pvw->mse },
949         /* F. */
950         { 3, 0, F },
951         /* Significance. */
952         { 4, 0, gsl_cdf_fdist_Q (F, df1, df2) },
953       };
954       for (size_t j = 0; j < sizeof entries / sizeof *entries; j++)
955         {
956           const struct entry *e = &entries[j];
957           pivot_table_put3 (table, e->stat_idx, e->type_idx, var_idx,
958                             pivot_value_new_number (e->x));
959         }
960     }
961
962   pivot_table_submit (table);
963 }
964
965 /* Show the descriptives table */
966 static void
967 show_descriptives (const struct oneway_spec *cmd, const struct oneway_workspace *ws)
968 {
969   if (!cmd->n_vars)
970     return;
971   const struct categoricals *cats = covariance_get_categoricals (
972     ws->vws[0].cov);
973
974   struct pivot_table *table = pivot_table_create (N_("Descriptives"));
975   pivot_table_set_weight_format (table, cmd->wfmt);
976
977   const double confidence = 0.95;
978
979   struct pivot_dimension *statistics = pivot_dimension_create (
980     table, PIVOT_AXIS_COLUMN, N_("Statistics"),
981     N_("N"), PIVOT_RC_COUNT, N_("Mean"), N_("Std. Deviation"),
982     N_("Std. Error"));
983   struct pivot_category *interval = pivot_category_create_group__ (
984     statistics->root,
985     pivot_value_new_text_format (N_("%g%% Confidence Interval for Mean"),
986                                  confidence * 100.0));
987   pivot_category_create_leaves (interval, N_("Lower Bound"),
988                                 N_("Upper Bound"));
989   pivot_category_create_leaves (statistics->root,
990                                 N_("Minimum"), N_("Maximum"));
991
992   struct pivot_dimension *indep_var = pivot_dimension_create__ (
993     table, PIVOT_AXIS_ROW, pivot_value_new_variable (cmd->indep_var));
994   indep_var->root->show_label = true;
995   size_t n;
996   union value *values = categoricals_get_var_values (cats, cmd->indep_var, &n);
997   for (size_t j = 0; j < n; j++)
998     pivot_category_create_leaf (
999       indep_var->root, pivot_value_new_var_value (cmd->indep_var, &values[j]));
1000   pivot_category_create_leaf (
1001     indep_var->root, pivot_value_new_text_format (N_("Total")));
1002
1003   struct pivot_dimension *dep_var = pivot_dimension_create (
1004     table, PIVOT_AXIS_ROW, N_("Dependent Variable"));
1005
1006   const double q = (1.0 - confidence) / 2.0;
1007   for (int v = 0; v < cmd->n_vars; ++v)
1008     {
1009       int dep_var_idx = pivot_category_create_leaf (
1010         dep_var->root, pivot_value_new_variable (cmd->vars[v]));
1011
1012       struct per_var_ws *pvw = &ws->vws[v];
1013       const struct categoricals *cats = covariance_get_categoricals (pvw->cov);
1014
1015       int count;
1016       for (count = 0; count < categoricals_n_total (cats); ++count)
1017         {
1018           const struct descriptive_data *dd
1019             = categoricals_get_user_data_by_category (cats, count);
1020
1021           double n, mean, variance;
1022           moments1_calculate (dd->mom, &n, &mean, &variance, NULL, NULL);
1023
1024           double std_dev = sqrt (variance);
1025           double std_error = std_dev / sqrt (n);
1026           double T = gsl_cdf_tdist_Qinv (q, n - 1);
1027
1028           double entries[] = {
1029             n,
1030             mean,
1031             std_dev,
1032             std_error,
1033             mean - T * std_error,
1034             mean + T * std_error,
1035             dd->minimum,
1036             dd->maximum,
1037           };
1038           for (size_t i = 0; i < sizeof entries / sizeof *entries; i++)
1039             pivot_table_put3 (table, i, count, dep_var_idx,
1040                               pivot_value_new_number (entries[i]));
1041         }
1042
1043       if (categoricals_is_complete (cats))
1044         {
1045           double n, mean, variance;
1046           moments1_calculate (ws->dd_total[v]->mom, &n, &mean, &variance,
1047                               NULL, NULL);
1048
1049           double std_dev = sqrt (variance);
1050           double std_error = std_dev / sqrt (n);
1051           double T = gsl_cdf_tdist_Qinv (q, n - 1);
1052
1053           double entries[] = {
1054             n,
1055             mean,
1056             std_dev,
1057             std_error,
1058             mean - T * std_error,
1059             mean + T * std_error,
1060             ws->dd_total[v]->minimum,
1061             ws->dd_total[v]->maximum,
1062           };
1063           for (size_t i = 0; i < sizeof entries / sizeof *entries; i++)
1064             pivot_table_put3 (table, i, count, dep_var_idx,
1065                               pivot_value_new_number (entries[i]));
1066         }
1067     }
1068
1069   pivot_table_submit (table);
1070 }
1071
1072 /* Show the homogeneity table */
1073 static void
1074 show_homogeneity (const struct oneway_spec *cmd, const struct oneway_workspace *ws)
1075 {
1076   struct pivot_table *table = pivot_table_create (
1077     N_("Test of Homogeneity of Variances"));
1078
1079   pivot_dimension_create (table, PIVOT_AXIS_COLUMN, N_("Statistics"),
1080                           N_("Levene Statistic"), PIVOT_RC_OTHER,
1081                           N_("df1"), PIVOT_RC_INTEGER,
1082                           N_("df2"), PIVOT_RC_INTEGER,
1083                           N_("Sig."), PIVOT_RC_SIGNIFICANCE);
1084
1085   struct pivot_dimension *variables = pivot_dimension_create (
1086     table, PIVOT_AXIS_ROW, N_("Variables"));
1087
1088   for (int v = 0; v < cmd->n_vars; ++v)
1089     {
1090       int var_idx = pivot_category_create_leaf (
1091         variables->root, pivot_value_new_variable (cmd->vars[v]));
1092
1093       double n;
1094       moments1_calculate (ws->dd_total[v]->mom, &n, NULL, NULL, NULL, NULL);
1095
1096       const struct per_var_ws *pvw = &ws->vws[v];
1097       double df1 = pvw->n_groups - 1;
1098       double df2 = n - pvw->n_groups;
1099       double F = levene_calculate (pvw->nl);
1100
1101       double entries[] =
1102         {
1103           F,
1104           df1,
1105           df2,
1106           gsl_cdf_fdist_Q (F, df1, df2),
1107         };
1108       for (size_t i = 0; i < sizeof entries / sizeof *entries; i++)
1109         pivot_table_put2 (table, i, var_idx,
1110                           pivot_value_new_number (entries[i]));
1111     }
1112
1113   pivot_table_submit (table);
1114 }
1115
1116
1117 /* Show the contrast coefficients table */
1118 static void
1119 show_contrast_coeffs (const struct oneway_spec *cmd, const struct oneway_workspace *ws)
1120 {
1121   struct pivot_table *table = pivot_table_create (N_("Contrast Coefficients"));
1122
1123   struct pivot_dimension *indep_var = pivot_dimension_create__ (
1124     table, PIVOT_AXIS_COLUMN, pivot_value_new_variable (cmd->indep_var));
1125   indep_var->root->show_label = true;
1126
1127   struct pivot_dimension *contrast = pivot_dimension_create (
1128     table, PIVOT_AXIS_ROW, N_("Contrast"));
1129   contrast->root->show_label = true;
1130
1131   const struct covariance *cov = ws->vws[0].cov;
1132
1133   const struct contrasts_node *cn;
1134   int c_num = 1;
1135   ll_for_each (cn, struct contrasts_node, ll, &cmd->contrast_list)
1136     {
1137       int contrast_idx = pivot_category_create_leaf (
1138         contrast->root, pivot_value_new_integer (c_num++));
1139
1140       const struct coeff_node *coeffn;
1141       int indep_idx = 0;
1142       ll_for_each (coeffn, struct coeff_node, ll, &cn->coefficient_list)
1143         {
1144           const struct categoricals *cats = covariance_get_categoricals (cov);
1145           const struct ccase *gcc = categoricals_get_case_by_category (
1146             cats, indep_idx);
1147
1148           if (!contrast_idx)
1149             pivot_category_create_leaf (
1150               indep_var->root, pivot_value_new_var_value (
1151                 cmd->indep_var, case_data (gcc, cmd->indep_var)));
1152
1153           pivot_table_put2 (table, indep_idx++, contrast_idx,
1154                             pivot_value_new_integer (coeffn->coeff));
1155         }
1156     }
1157
1158   pivot_table_submit (table);
1159 }
1160
1161 /* Show the results of the contrast tests */
1162 static void
1163 show_contrast_tests (const struct oneway_spec *cmd, const struct oneway_workspace *ws)
1164 {
1165   struct pivot_table *table = pivot_table_create (N_("Contrast Tests"));
1166
1167   pivot_dimension_create (table, PIVOT_AXIS_COLUMN, N_("Statistics"),
1168                           N_("Value of Contrast"), PIVOT_RC_OTHER,
1169                           N_("Std. Error"), PIVOT_RC_OTHER,
1170                           N_("t"), PIVOT_RC_OTHER,
1171                           N_("df"), PIVOT_RC_OTHER,
1172                           N_("Sig. (2-tailed)"), PIVOT_RC_SIGNIFICANCE);
1173
1174   struct pivot_dimension *contrasts = pivot_dimension_create (
1175     table, PIVOT_AXIS_ROW, N_("Contrast"));
1176   contrasts->root->show_label = true;
1177   int n_contrasts = ll_count (&cmd->contrast_list);
1178   for (int i = 1; i <= n_contrasts; i++)
1179     pivot_category_create_leaf (contrasts->root, pivot_value_new_integer (i));
1180
1181   pivot_dimension_create (table, PIVOT_AXIS_ROW, N_("Assumption"),
1182                           N_("Assume equal variances"),
1183                           N_("Does not assume equal variances"));
1184
1185   struct pivot_dimension *variables = pivot_dimension_create (
1186     table, PIVOT_AXIS_ROW, N_("Dependent Variable"));
1187
1188   for (int v = 0; v < cmd->n_vars; ++v)
1189     {
1190       const struct per_var_ws *pvw = &ws->vws[v];
1191       const struct categoricals *cats = covariance_get_categoricals (pvw->cov);
1192       if (!categoricals_is_complete (cats))
1193         continue;
1194
1195       int var_idx = pivot_category_create_leaf (
1196         variables->root, pivot_value_new_variable (cmd->vars[v]));
1197
1198       struct contrasts_node *cn;
1199       int contrast_idx = 0;
1200       ll_for_each (cn, struct contrasts_node, ll, &cmd->contrast_list)
1201         {
1202
1203           /* Note: The calculation of the degrees of freedom in the
1204              "variances not equal" case is painfull!!
1205              The following formula may help to understand it:
1206              \frac{\left (\sum_{i=1}^k{c_i^2\frac{s_i^2}{n_i}}\right)^2}
1207              {
1208              \sum_{i=1}^k\left (
1209              \frac{\left (c_i^2\frac{s_i^2}{n_i}\right)^2}  {n_i-1}
1210              \right)
1211              }
1212           */
1213
1214           double grand_n;
1215           moments1_calculate (ws->dd_total[v]->mom, &grand_n, NULL, NULL,
1216                               NULL, NULL);
1217           double df = grand_n - pvw->n_groups;
1218
1219           double contrast_value = 0.0;
1220           double coef_msq = 0.0;
1221           double sec_vneq = 0.0;
1222           double df_denominator = 0.0;
1223           double df_numerator = 0.0;
1224           struct coeff_node *coeffn;
1225           int ci = 0;
1226           ll_for_each (coeffn, struct coeff_node, ll, &cn->coefficient_list)
1227             {
1228               const struct descriptive_data *dd
1229                 = categoricals_get_user_data_by_category (cats, ci);
1230               const double coef = coeffn->coeff;
1231
1232               double n, mean, variance;
1233               moments1_calculate (dd->mom, &n, &mean, &variance, NULL, NULL);
1234
1235               double winv = variance / n;
1236               contrast_value += coef * mean;
1237               coef_msq += pow2 (coef) / n;
1238               sec_vneq += pow2 (coef) * variance / n;
1239               df_numerator += pow2 (coef) * winv;
1240               df_denominator += pow2(pow2 (coef) * winv) / (n - 1);
1241
1242               ci++;
1243             }
1244           sec_vneq = sqrt (sec_vneq);
1245           df_numerator = pow2 (df_numerator);
1246
1247           double std_error_contrast = sqrt (pvw->mse * coef_msq);
1248           double T = contrast_value / std_error_contrast;
1249           double T_ne = contrast_value / sec_vneq;
1250           double df_ne = df_numerator / df_denominator;
1251
1252           struct entry
1253             {
1254               int stat_idx;
1255               int assumption_idx;
1256               double x;
1257             }
1258           entries[] =
1259             {
1260               /* Assume equal. */
1261               { 0, 0, contrast_value },
1262               { 1, 0, std_error_contrast },
1263               { 2, 0, T },
1264               { 3, 0, df },
1265               { 4, 0, 2 * gsl_cdf_tdist_Q (fabs(T), df) },
1266               /* Do not assume equal. */
1267               { 0, 1, contrast_value },
1268               { 1, 1, sec_vneq },
1269               { 2, 1, T_ne },
1270               { 3, 1, df_ne },
1271               { 4, 1, 2 * gsl_cdf_tdist_Q (fabs(T_ne), df_ne) },
1272             };
1273
1274           for (size_t i = 0; i < sizeof entries / sizeof *entries; i++)
1275             {
1276               const struct entry *e = &entries[i];
1277               pivot_table_put4 (
1278                 table, e->stat_idx, contrast_idx, e->assumption_idx, var_idx,
1279                 pivot_value_new_number (e->x));
1280             }
1281
1282           contrast_idx++;
1283         }
1284     }
1285
1286   pivot_table_submit (table);
1287 }
1288
1289 static void
1290 show_comparisons (const struct oneway_spec *cmd, const struct oneway_workspace *ws, int v)
1291 {
1292   struct pivot_table *table = pivot_table_create__ (
1293     pivot_value_new_user_text_nocopy (xasprintf (
1294                                         _("Multiple Comparisons (%s)"),
1295                                         var_to_string (cmd->vars[v]))),
1296     "Multiple Comparisons");
1297
1298   struct pivot_dimension *statistics = pivot_dimension_create (
1299     table, PIVOT_AXIS_COLUMN, N_("Statistics"),
1300     N_("Mean Difference (I - J)"), PIVOT_RC_OTHER,
1301     N_("Std. Error"), PIVOT_RC_OTHER,
1302     N_("Sig."), PIVOT_RC_SIGNIFICANCE);
1303   struct pivot_category *interval = pivot_category_create_group__ (
1304     statistics->root,
1305     pivot_value_new_text_format (N_("%g%% Confidence Interval"),
1306                                  (1 - cmd->alpha) * 100.0));
1307   pivot_category_create_leaves (interval,
1308                                 N_("Lower Bound"), PIVOT_RC_OTHER,
1309                                 N_("Upper Bound"), PIVOT_RC_OTHER);
1310
1311   struct pivot_dimension *j_family = pivot_dimension_create (
1312     table, PIVOT_AXIS_ROW, N_("(J) Family"));
1313   j_family->root->show_label = true;
1314
1315   struct pivot_dimension *i_family = pivot_dimension_create (
1316     table, PIVOT_AXIS_ROW, N_("(J) Family"));
1317   i_family->root->show_label = true;
1318
1319   const struct per_var_ws *pvw = &ws->vws[v];
1320   const struct categoricals *cat = pvw->cat;
1321   for (int i = 0; i < pvw->n_groups; i++)
1322     {
1323       const struct ccase *gcc = categoricals_get_case_by_category (cat, i);
1324       for (int j = 0; j < 2; j++)
1325         pivot_category_create_leaf (
1326           j ? j_family->root : i_family->root,
1327           pivot_value_new_var_value (cmd->indep_var,
1328                                      case_data (gcc, cmd->indep_var)));
1329     }
1330
1331   struct pivot_dimension *test = pivot_dimension_create (
1332     table, PIVOT_AXIS_ROW, N_("Test"));
1333
1334   for (int p = 0; p < cmd->n_posthoc; ++p)
1335     {
1336       const struct posthoc *ph = &ph_tests[cmd->posthoc[p]];
1337
1338       int test_idx = pivot_category_create_leaf (
1339         test->root, pivot_value_new_text (ph->label));
1340
1341       for (int i = 0; i < pvw->n_groups; ++i)
1342         {
1343           struct descriptive_data *dd_i
1344             = categoricals_get_user_data_by_category (cat, i);
1345           double weight_i, mean_i, var_i;
1346           moments1_calculate (dd_i->mom, &weight_i, &mean_i, &var_i, 0, 0);
1347
1348           for (int j = 0; j < pvw->n_groups; ++j)
1349             {
1350               if (j == i)
1351                 continue;
1352
1353               struct descriptive_data *dd_j
1354                 = categoricals_get_user_data_by_category (cat, j);
1355               double weight_j, mean_j, var_j;
1356               moments1_calculate (dd_j->mom, &weight_j, &mean_j, &var_j, 0, 0);
1357
1358               double std_err = pvw->mse;
1359               std_err *= weight_i + weight_j;
1360               std_err /= weight_i * weight_j;
1361               std_err = sqrt (std_err);
1362
1363               double sig = 2 * multiple_comparison_sig (std_err, pvw,
1364                                                         dd_i, dd_j, ph);
1365               double half_range = mc_half_range (cmd, pvw, std_err,
1366                                                  dd_i, dd_j, ph);
1367               double entries[] = {
1368                 mean_i - mean_j,
1369                 std_err,
1370                 sig,
1371                 (mean_i - mean_j) - half_range,
1372                 (mean_i - mean_j) + half_range,
1373               };
1374               for (size_t k = 0; k < sizeof entries / sizeof *entries; k++)
1375                 pivot_table_put4 (table, k, j, i, test_idx,
1376                                   pivot_value_new_number (entries[k]));
1377             }
1378         }
1379     }
1380
1381   pivot_table_submit (table);
1382 }