Fix memory leak in Chisquare
[pspp] / src / language / stats / chisquare.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2006, 2007, 2009, 2010, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "language/stats/chisquare.h"
20
21 #include <gsl/gsl_cdf.h>
22 #include <math.h>
23 #include <stdlib.h>
24
25 #include "data/case.h"
26 #include "data/casereader.h"
27 #include "data/dataset.h"
28 #include "data/dictionary.h"
29 #include "data/format.h"
30 #include "data/value-labels.h"
31 #include "data/variable.h"
32 #include "language/stats/freq.h"
33 #include "language/stats/npar.h"
34 #include "libpspp/array.h"
35 #include "libpspp/assertion.h"
36 #include "libpspp/cast.h"
37 #include "libpspp/compiler.h"
38 #include "libpspp/hash-functions.h"
39 #include "libpspp/message.h"
40 #include "libpspp/taint.h"
41 #include "output/tab.h"
42
43 #include "gl/xalloc.h"
44
45 #include "gettext.h"
46 #define _(msgid) gettext (msgid)
47
48 /* Adds frequency counts of each value of VAR in INPUT between LO and HI to
49    FREQ_HASH.  LO and HI and each input value is truncated to an integer.
50    Returns true if successful, false on input error.  It is the caller's
51    responsibility to initialize FREQ_HASH and to free it when no longer
52    required, even on failure. */
53 static bool
54 create_freq_hash_with_range (const struct dictionary *dict,
55                              struct casereader *input,
56                              const struct variable *var,
57                              double lo_, double hi_,
58                              struct hmap *freq_hash)
59 {
60   struct freq **entries;
61   bool warn = true;
62   struct ccase *c;
63   double lo, hi;
64   double i_d;
65
66   assert (var_is_numeric (var));
67   lo = trunc (lo_);
68   hi = trunc (hi_);
69
70   /* Populate the hash with zero entries */
71   entries = xnmalloc (hi - lo + 1, sizeof *entries);
72   for (i_d = lo; i_d <= hi; i_d += 1.0 )
73     {
74       size_t ofs = i_d - lo;
75       union value value = { i_d };
76       entries[ofs] = freq_hmap_insert (freq_hash, &value, 0,
77                                        value_hash (&value, 0, 0));
78     }
79
80   for (; (c = casereader_read (input)) != NULL; case_unref (c))
81     {
82       double x = trunc (case_num (c, var));
83       if (x >= lo && x <= hi)
84         {
85           size_t ofs = x - lo;
86           struct freq *fr = entries[ofs];
87           fr->count += dict_get_case_weight (dict, c, &warn);
88         }
89     }
90
91   free (entries);
92
93   return casereader_destroy (input);
94 }
95
96 /* Adds frequency counts of each value of VAR in INPUT to FREQ_HASH.  LO and HI
97    and each input value is truncated to an integer.  Returns true if
98    successful, false on input error.  It is the caller's responsibility to
99    initialize FREQ_HASH and to free it when no longer required, even on
100    failure. */
101 static bool
102 create_freq_hash (const struct dictionary *dict,
103                   struct casereader *input,
104                   const struct variable *var,
105                   struct hmap *freq_hash)
106 {
107   int width = var_get_width (var);
108   bool warn = true;
109   struct ccase *c;
110
111   for (; (c = casereader_read (input)) != NULL; case_unref (c))
112     {
113       const union value *value = case_data (c, var);
114       size_t hash = value_hash (value, width, 0);
115       double weight = dict_get_case_weight (dict, c, &warn);
116       struct freq *f;
117
118       f = freq_hmap_search (freq_hash, value, width, hash);
119       if (f == NULL)
120         f = freq_hmap_insert (freq_hash, value, width, hash);
121
122       f->count += weight;
123     }
124
125   return casereader_destroy (input);
126 }
127
128 static struct tab_table *
129 create_variable_frequency_table (const struct dictionary *dict,
130                                  struct casereader *input,
131                                  const struct chisquare_test *test,
132                                  int v, struct hmap *freq_hash)
133
134 {
135   int i;
136   const struct one_sample_test *ost = (const struct one_sample_test*)test;
137   int n_cells;
138   struct tab_table *table ;
139   const struct variable *var =  ost->vars[v];
140
141   hmap_init (freq_hash);
142   if (!create_freq_hash (dict, input, var, freq_hash))
143     {
144       freq_hmap_destroy (freq_hash, var_get_width (var));
145       return NULL;
146     }
147
148   n_cells = hmap_count (freq_hash);
149
150   if ( test->n_expected > 0 && n_cells != test->n_expected )
151     {
152       msg(ME, _("CHISQUARE test specified %d expected values, but"
153                 " %d distinct values were encountered in variable %s."),
154           test->n_expected, n_cells,
155           var_get_name (var)
156           );
157       return NULL;
158     }
159
160   table = tab_create(4, n_cells + 2);
161
162   tab_title (table, "%s", var_to_string(var));
163   tab_text (table, 1, 0, TAB_LEFT, _("Observed N"));
164   tab_text (table, 2, 0, TAB_LEFT, _("Expected N"));
165   tab_text (table, 3, 0, TAB_LEFT, _("Residual"));
166
167   tab_headers (table, 1, 0, 1, 0);
168
169   tab_box (table, TAL_1, TAL_1, -1, -1,
170            0, 0, tab_nc (table) - 1, tab_nr(table) - 1 );
171
172   tab_hline (table, TAL_1, 0, tab_nc(table) - 1, 1);
173
174   tab_vline (table, TAL_2, 1, 0, tab_nr(table) - 1);
175   for ( i = 2 ; i < 4 ; ++i )
176     tab_vline (table, TAL_1, i, 0, tab_nr(table) - 1);
177
178
179   tab_text (table, 0, tab_nr (table) - 1, TAB_LEFT, _("Total"));
180
181   return table;
182 }
183
184
185 static struct tab_table *
186 create_combo_frequency_table (const struct chisquare_test *test)
187 {
188   int i;
189   const struct one_sample_test *ost = (const struct one_sample_test*)test;
190
191   struct tab_table *table ;
192
193   int n_cells = test->hi - test->lo + 1;
194
195   table = tab_create(1 + ost->n_vars * 4, n_cells + 3);
196
197   tab_title (table, _("Frequencies"));
198   for ( i = 0 ; i < ost->n_vars ; ++i )
199     {
200       const struct variable *var = ost->vars[i];
201       tab_text (table, i * 4 + 1, 1, TAB_LEFT, _("Category"));
202       tab_text (table, i * 4 + 2, 1, TAB_LEFT, _("Observed N"));
203       tab_text (table, i * 4 + 3, 1, TAB_LEFT, _("Expected N"));
204       tab_text (table, i * 4 + 4, 1, TAB_LEFT, _("Residual"));
205
206       tab_vline (table, TAL_2, i * 4 + 1,
207                  0, tab_nr (table) - 1);
208
209       tab_vline (table, TAL_1, i * 4 + 2,
210                  0, tab_nr (table) - 1);
211
212       tab_vline (table, TAL_1, i * 4 + 3,
213                  1, tab_nr (table) - 1);
214
215       tab_vline (table, TAL_1, i * 4 + 4,
216                  1, tab_nr (table) - 1);
217
218
219       tab_joint_text (table,
220                       i * 4 + 1, 0,
221                       i * 4 + 4, 0,
222                       TAB_CENTER,
223                       var_to_string (var));
224     }
225
226   for ( i = test->lo ; i <= test->hi ; ++i )
227     tab_fixed (table, 0, 2 + i - test->lo,
228                 TAB_LEFT, 1 + i - test->lo, 8, 0);
229
230   tab_headers (table, 1, 0, 2, 0);
231
232   tab_box (table, TAL_1, TAL_1, -1, -1,
233            0, 0, tab_nc (table) - 1, tab_nr(table) - 1 );
234
235   tab_hline (table, TAL_1, 1, tab_nc(table) - 1, 1);
236   tab_hline (table, TAL_1, 0, tab_nc(table) - 1, 2);
237
238   tab_text (table, 0, tab_nr (table) - 1, TAB_LEFT, _("Total"));
239
240   return table;
241 }
242
243
244 static struct tab_table *
245 create_stats_table (const struct chisquare_test *test)
246 {
247   const struct one_sample_test *ost = (const struct one_sample_test*) test;
248
249   struct tab_table *table;
250   table = tab_create (1 + ost->n_vars, 4);
251   tab_title (table, _("Test Statistics"));
252   tab_headers (table, 1, 0, 1, 0);
253
254   tab_box (table, TAL_1, TAL_1, -1, -1,
255            0, 0, tab_nc(table) - 1, tab_nr(table) - 1 );
256
257   tab_box (table, -1, -1, -1, TAL_1,
258            1, 0, tab_nc(table) - 1, tab_nr(table) - 1 );
259
260
261   tab_vline (table, TAL_2, 1, 0, tab_nr (table) - 1);
262   tab_hline (table, TAL_1, 0, tab_nc (table) - 1, 1);
263
264
265   tab_text (table, 0, 1, TAB_LEFT, _("Chi-Square"));
266   tab_text (table, 0, 2, TAB_LEFT, _("df"));
267   tab_text (table, 0, 3, TAB_LEFT, _("Asymp. Sig."));
268
269   return table;
270 }
271
272
273 void
274 chisquare_execute (const struct dataset *ds,
275                    struct casereader *input,
276                    enum mv_class exclude,
277                    const struct npar_test *test,
278                    bool exact UNUSED,
279                    double timer UNUSED)
280 {
281   const struct dictionary *dict = dataset_dict (ds);
282   int v, i;
283   struct chisquare_test *cst = UP_CAST (test, struct chisquare_test,
284                                         parent.parent);
285   struct one_sample_test *ost = &cst->parent;
286   int n_cells = 0;
287   double total_expected = 0.0;
288   const struct variable *wvar = dict_get_weight (dict);
289   const struct fmt_spec *wfmt = wvar ?
290     var_get_print_format (wvar) : & F_8_0;
291
292   double *df = xzalloc (sizeof (*df) * ost->n_vars);
293   double *xsq = xzalloc (sizeof (*df) * ost->n_vars);
294   bool ok;
295
296   for ( i = 0 ; i < cst->n_expected ; ++i )
297     total_expected += cst->expected[i];
298
299   if ( cst->ranged == false )
300     {
301       for ( v = 0 ; v < ost->n_vars ; ++v )
302         {
303           const struct variable *var = ost->vars[v];
304           double total_obs = 0.0;
305           struct hmap freq_hash;
306           struct casereader *reader =
307             casereader_create_filter_missing (casereader_clone (input),
308                                               &var, 1, exclude,
309                                               NULL, NULL);
310           struct tab_table *freq_table =
311             create_variable_frequency_table(dict, reader, cst, v, &freq_hash);
312
313           struct freq **ff;
314
315           if ( NULL == freq_table )
316             continue;
317           ff = freq_hmap_sort (&freq_hash, var_get_width (var));
318
319           n_cells = hmap_count (&freq_hash);
320
321           for ( i = 0 ; i < n_cells ; ++i )
322             total_obs += ff[i]->count;
323
324           xsq[v] = 0.0;
325           for ( i = 0 ; i < n_cells ; ++i )
326             {
327               struct string str;
328               double exp;
329               const union value *observed_value = &ff[i]->value;
330
331               ds_init_empty (&str);
332               var_append_value_name (var, observed_value, &str);
333
334               /* The key */
335               tab_text (freq_table, 0, i + 1, TAB_LEFT, ds_cstr (&str));
336               ds_destroy (&str);
337
338
339               /* The observed N */
340               tab_double (freq_table, 1, i + 1, TAB_NONE,
341                          ff[i]->count, wfmt);
342
343               if ( cst->n_expected > 0 )
344                 exp = cst->expected[i] * total_obs / total_expected ;
345               else
346                 exp = total_obs / (double) n_cells;
347
348               tab_double (freq_table, 2, i + 1, TAB_NONE,
349                          exp, NULL);
350
351               /* The residual */
352               tab_double (freq_table, 3, i + 1, TAB_NONE,
353                          ff[i]->count - exp, NULL);
354
355               xsq[v] += (ff[i]->count - exp) * (ff[i]->count - exp) / exp;
356             }
357
358           df[v] = n_cells - 1.0;
359
360           tab_double (freq_table, 1, i + 1, TAB_NONE,
361                      total_obs, wfmt);
362
363           tab_submit (freq_table);
364
365           freq_hmap_destroy (&freq_hash, var_get_width (var));
366           free (ff);
367         }
368     }
369   else  /* ranged == true */
370     {
371       struct tab_table *freq_table = create_combo_frequency_table (cst);
372
373       n_cells = cst->hi - cst->lo + 1;
374
375       for ( v = 0 ; v < ost->n_vars ; ++v )
376         {
377           const struct variable *var = ost->vars[v];
378           double total_obs = 0.0;
379           struct casereader *reader =
380             casereader_create_filter_missing (casereader_clone (input),
381                                               &var, 1, exclude,
382                                               NULL, NULL);
383           struct hmap freq_hash;
384           struct freq **ff;
385
386           hmap_init (&freq_hash);
387           if (!create_freq_hash_with_range (dict, reader, var,
388                                             cst->lo, cst->hi, &freq_hash))
389             {
390               freq_hmap_destroy (&freq_hash, var_get_width (var));
391               continue;
392             }
393
394           ff = freq_hmap_sort (&freq_hash, var_get_width (var));
395
396           for ( i = 0 ; i < hmap_count (&freq_hash) ; ++i )
397             total_obs += ff[i]->count;
398
399           xsq[v] = 0.0;
400           for ( i = 0 ; i < hmap_count (&freq_hash) ; ++i )
401             {
402               struct string str;
403               double exp;
404
405               const union value *observed_value = &ff[i]->value;
406
407               ds_init_empty (&str);
408               var_append_value_name (ost->vars[v], observed_value, &str);
409               /* The key */
410               tab_text  (freq_table, v * 4 + 1, i + 2 , TAB_LEFT,
411                          ds_cstr (&str));
412               ds_destroy (&str);
413
414               /* The observed N */
415               tab_double (freq_table, v * 4 + 2, i + 2 , TAB_NONE,
416                          ff[i]->count, wfmt);
417
418               if ( cst->n_expected > 0 )
419                 exp = cst->expected[i] * total_obs / total_expected ;
420               else
421                 exp = total_obs / (double) hmap_count (&freq_hash);
422
423               /* The expected N */
424               tab_double (freq_table, v * 4 + 3, i + 2 , TAB_NONE,
425                          exp, NULL);
426
427               /* The residual */
428               tab_double (freq_table, v * 4 + 4, i + 2 , TAB_NONE,
429                          ff[i]->count - exp, NULL);
430
431               xsq[v] += (ff[i]->count - exp) * (ff[i]->count - exp) / exp;
432             }
433
434
435           tab_double (freq_table, v * 4 + 2, tab_nr (freq_table) - 1, TAB_NONE,
436                      total_obs, wfmt);
437
438           df[v] = n_cells - 1.0;
439
440           freq_hmap_destroy (&freq_hash, var_get_width (var));
441           free (ff);
442         }
443
444       tab_submit (freq_table);
445     }
446   ok = !taint_has_tainted_successor (casereader_get_taint (input));
447   casereader_destroy (input);
448
449   if (ok)
450     {
451       struct tab_table *stats_table = create_stats_table (cst);
452
453       /* Populate the summary statistics table */
454       for ( v = 0 ; v < ost->n_vars ; ++v )
455         {
456           const struct variable *var = ost->vars[v];
457
458           tab_text (stats_table, 1 + v, 0, TAB_CENTER, var_get_name (var));
459
460           tab_double (stats_table, 1 + v, 1, TAB_NONE, xsq[v], NULL);
461           tab_fixed (stats_table, 1 + v, 2, TAB_NONE, df[v], 8, 0);
462
463           tab_double (stats_table, 1 + v, 3, TAB_NONE,
464                      gsl_cdf_chisq_Q (xsq[v], df[v]), NULL);
465         }
466       tab_submit (stats_table);
467     }
468
469   free (xsq);
470   free (df);
471 }
472