First attempt at Mann-Whitney U test.
[pspp-builds.git] / src / language / stats / mann-whitney.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010 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 "mann-whitney.h"
20
21 #include <gsl/gsl_cdf.h>
22
23 #include <libpspp/cast.h>
24 #include <libpspp/misc.h>
25
26 #include <math/sort.h>
27 #include <data/case.h>
28 #include <data/dictionary.h>
29 #include <data/procedure.h>
30 #include <data/casereader.h>
31 #include <data/format.h>
32 #include <data/variable.h>
33
34 /* Calculates the adjustment necessary for tie compensation */
35 static void
36 distinct_callback (double v UNUSED, casenumber t, double w UNUSED, void *aux)
37 {
38   double *tiebreaker = aux;
39
40   *tiebreaker += (pow3 (t) - t) / 12.0;
41 }
42
43 struct mw
44 {
45   double rank_sum[2];
46   double n[2];
47
48
49   double u;  /* The Mann-Whitney U statistic */
50   double w;  /* The Wilcoxon Rank Sum W statistic */
51   double z;  
52 };
53
54 static void show_ranks_box (const struct n_sample_test *nst, const struct mw *mw);
55 static void show_statistics_box (const struct n_sample_test *nst, const struct mw *mw, bool exact);
56
57
58 void
59 mann_whitney_execute (const struct dataset *ds,
60                       struct casereader *input,
61                       enum mv_class exclude,
62                       const struct npar_test *test,
63                       bool exact,
64                       double timer)
65 {
66   int i;
67   const struct dictionary *dict = dataset_dict (ds);
68   const struct n_sample_test *nst = UP_CAST (test, const struct n_sample_test, parent);
69
70   const struct caseproto *proto = casereader_get_proto (input);
71   size_t rank_idx = caseproto_get_n_widths (proto);
72
73   struct mw *mw = xcalloc (nst->n_vars, sizeof *mw);
74
75   for (i = 0; i < nst->n_vars; ++i)
76     {
77       double tiebreaker = 0.0;
78       bool warn = true;
79       enum rank_error rerr = 0;
80       struct casereader *rr;
81       struct ccase *c;
82       const struct variable *var = nst->vars[i];
83       
84       struct casereader *reader =
85         sort_execute_1var (casereader_clone (input), var);
86
87       rr = casereader_create_append_rank (reader, var,
88                                           dict_get_weight (dict),
89                                           &rerr,
90                                           distinct_callback, &tiebreaker);
91
92       for (; (c = casereader_read (rr)); case_unref (c))
93         {
94           const union value *val = case_data (c, var);
95           const union value *group = case_data (c, nst->indep_var);
96           const size_t group_var_width = var_get_width (nst->indep_var);
97           const double rank = case_data_idx (c, rank_idx)->f;
98
99           if ( var_is_value_missing (var, val, exclude))
100             continue;
101
102           if ( value_equal (group, &nst->val1, group_var_width))
103             {
104               mw[i].rank_sum[0] += rank;
105               mw[i].n[0] += dict_get_case_weight (dict, c, &warn);
106             }
107           else if ( value_equal (group, &nst->val2, group_var_width))
108             {
109               mw[i].rank_sum[1] += rank;
110               mw[i].n[1] += dict_get_case_weight (dict, c, &warn);
111             }
112         }
113       casereader_destroy (rr);
114
115       {
116         double n;
117         double denominator;
118         struct mw *mwv = &mw[i];
119
120         mwv->u = mwv->n[0] * mwv->n[1] ;
121         mwv->u += mwv->n[0] * (mwv->n[0] + 1) / 2.0;
122         mwv->u -= mwv->rank_sum[0];
123
124         mwv->w = mwv->rank_sum[0];
125         if ( mwv->u > mwv->n[0] * mwv->n[1] / 2.0)
126           {
127             mwv->u =  mwv->n[0] * mwv->n[1] - mwv->u;
128             mwv->w = mwv->rank_sum[1];
129           }
130         mwv->z = mwv->u - mwv->n[0] * mwv->n[1] / 2.0;
131         n = mwv->n[0] + mwv->n[1];
132         denominator = pow3(n) - n;
133         denominator /= 12;
134         denominator -= tiebreaker;
135         denominator *= mwv->n[0] * mwv->n[1];
136         denominator /= n * (n - 1);
137       
138         mwv->z /= sqrt (denominator);
139       }
140     }
141   casereader_destroy (input);
142
143   show_ranks_box (nst, mw);
144   show_statistics_box (nst, mw, exact);
145
146   free (mw);
147 }
148
149 \f
150
151 #include <output/tab.h>
152 #include "gettext.h"
153 #define _(msgid) gettext (msgid)
154
155 static void
156 show_ranks_box (const struct n_sample_test *nst, const struct mw *mwv)
157 {
158   int i;
159   const int row_headers = 1;
160   const int column_headers = 2;
161   struct tab_table *table =
162     tab_create (row_headers + 7, column_headers + nst->n_vars);
163
164   tab_headers (table, row_headers, 0, column_headers, 0);
165
166   tab_title (table, _("Ranks"));
167
168   /* Vertical lines inside the box */
169   tab_box (table, 1, 0, -1, TAL_1,
170            row_headers, 0, tab_nc (table) - 1, tab_nr (table) - 1 );
171
172   /* Box around the table */
173   tab_box (table, TAL_2, TAL_2, -1, -1,
174            0,  0, tab_nc (table) - 1, tab_nr (table) - 1 );
175
176   tab_hline (table, TAL_2, 0, tab_nc (table) -1, column_headers);
177   tab_vline (table, TAL_2, row_headers, 0, tab_nr (table) - 1);
178
179   tab_hline (table, TAL_1, row_headers, tab_nc (table) -1, 1);
180
181   tab_text (table, 1, 1, TAT_TITLE | TAB_CENTER, _("group1"));
182   tab_text (table, 2, 1, TAT_TITLE | TAB_CENTER, _("group2"));
183   tab_text (table, 3, 1, TAT_TITLE | TAB_CENTER, _("Total"));
184   tab_joint_text (table, 1, 0, 3, 0,
185                   TAT_TITLE | TAB_CENTER, _("N"));
186   tab_vline (table, TAL_2, 4, 0, tab_nr (table) - 1);
187
188   tab_text (table, 4, 1, TAT_TITLE | TAB_CENTER, _("group1"));
189   tab_text (table, 5, 1, TAT_TITLE | TAB_CENTER, _("group2"));
190   tab_joint_text (table, 4, 0, 5, 0,
191                   TAT_TITLE | TAB_CENTER, _("Mean Rank"));
192   tab_vline (table, TAL_2, 6, 0, tab_nr (table) - 1);
193
194   tab_text (table, 6, 1, TAT_TITLE | TAB_CENTER, _("group1"));
195   tab_text (table, 7, 1, TAT_TITLE | TAB_CENTER, _("group2"));
196   tab_joint_text (table, 6, 0, 7, 0,
197                   TAT_TITLE | TAB_CENTER, _("Sum of Ranks"));
198
199   for (i = 0 ; i < nst->n_vars ; ++i)
200     {
201       const struct mw *mw = &mwv[i];
202       tab_text (table, 0, column_headers + i, TAT_TITLE,
203                 var_to_string (nst->vars[i]));
204
205       tab_double (table, 1, column_headers + i, 0,
206                   mw->n[0], 0);
207
208       tab_double (table, 2, column_headers + i, 0,
209                   mw->n[1], 0);
210
211       tab_double (table, 3, column_headers + i, 0,
212                   mw->n[1] + mw->n[0], 0);
213
214       /* Mean Ranks */
215       tab_double (table, 4, column_headers + i, 0,
216                   mw->rank_sum[0] / mw->n[0], 0);
217
218       tab_double (table, 5, column_headers + i, 0,
219                   mw->rank_sum[1] / mw->n[1], 0);
220
221       /* Sum of Ranks */
222       tab_double (table, 6, column_headers + i, 0,
223                   mw->rank_sum[0], 0);
224
225       tab_double (table, 7, column_headers + i, 0,
226                   mw->rank_sum[1], 0);
227     }
228
229   tab_submit (table);
230 }
231
232 static void
233 show_statistics_box (const struct n_sample_test *nst, const struct mw *mwv, bool exact)
234 {
235   int i;
236   const int row_headers = 1;
237   const int column_headers = 1;
238   struct tab_table *table =
239     tab_create (row_headers + (exact ? 6 : 4), column_headers + nst->n_vars);
240
241   tab_headers (table, row_headers, 0, column_headers, 0);
242
243   tab_title (table, _("Test Statistics"));
244
245   /* Vertical lines inside the box */
246   tab_box (table, 1, 0, -1, TAL_1,
247            row_headers, 0, tab_nc (table) - 1, tab_nr (table) - 1 );
248
249   /* Box around the table */
250   tab_box (table, TAL_2, TAL_2, -1, -1,
251            0,  0, tab_nc (table) - 1, tab_nr (table) - 1 );
252
253   tab_hline (table, TAL_2, 0, tab_nc (table) -1, column_headers);
254   tab_vline (table, TAL_2, row_headers, 0, tab_nr (table) - 1);
255
256   tab_text (table, 1, 0, TAT_TITLE | TAB_CENTER, _("Mann-Whitney U"));
257   tab_text (table, 2, 0, TAT_TITLE | TAB_CENTER, _("Wilcoxon W"));
258   tab_text (table, 3, 0, TAT_TITLE | TAB_CENTER, _("Z"));
259   tab_text (table, 4, 0, TAT_TITLE | TAB_CENTER, _("Asymp. Sig. (2-tailed)"));
260
261   if (exact) 
262     {
263       tab_text (table, 5, 0, TAT_TITLE | TAB_CENTER, _("Exact Sig. (2-tailed)"));
264       tab_text (table, 6, 0, TAT_TITLE | TAB_CENTER, _("Point Probability"));
265     }
266
267   for (i = 0 ; i < nst->n_vars ; ++i)
268     {
269       const struct mw *mw = &mwv[i];
270
271       tab_text (table, 0, column_headers + i, TAT_TITLE,
272                 var_to_string (nst->vars[i]));
273
274       tab_double (table, 1, column_headers + i, 0,
275                   mw->u, 0);
276
277       tab_double (table, 2, column_headers + i, 0,
278                   mw->w, 0);
279
280       tab_double (table, 3, column_headers + i, 0,
281                   mw->z, 0);
282
283       tab_double (table, 4, column_headers + i, 0,
284                   2.0 * gsl_cdf_ugaussian_P (mw->z), 0);
285     }
286
287   tab_submit (table);
288 }