b5a4166dfa40643c5a71c88f37ceb7b4c604b1d5
[pspp] / src / math / covariance.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009 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 <libpspp/assertion.h>
20 #include "covariance.h"
21 #include <gl/xalloc.h>
22 #include "moments.h"
23 #include <gsl/gsl_matrix.h>
24 #include <data/case.h>
25 #include <data/variable.h>
26 #include <libpspp/misc.h>
27 #include "categoricals.h"
28
29 #define n_MOMENTS (MOMENT_VARIANCE + 1)
30
31
32 /* Create a new matrix of NEW_SIZE x NEW_SIZE and copy the elements of
33    matrix IN into it.  IN must be a square matrix, and in normal usage
34    it will be smaller than NEW_SIZE.
35    IN is destroyed by this function.  The return value must be destroyed
36    when no longer required.
37 */
38 static gsl_matrix *
39 resize_matrix (gsl_matrix *in, size_t new_size)
40 {
41   size_t i, j;
42
43   gsl_matrix *out = NULL;
44
45   assert (in->size1 == in->size2);
46
47   if (new_size <= in->size1)
48     return in;
49
50   out = gsl_matrix_calloc (new_size, new_size);
51
52   for (i = 0; i < in->size1; ++i)
53     {
54       for (j = 0; j < in->size2; ++j)
55         {
56           double x = gsl_matrix_get (in, i, j);
57
58           gsl_matrix_set (out, i, j, x);
59         }
60     }
61     
62   gsl_matrix_free (in);
63
64   return out;
65 }
66
67 struct covariance
68 {
69   /* The variables for which the covariance matrix is to be calculated. */
70   size_t n_vars;
71   const struct variable **vars;
72
73   /* Categorical variables. */
74   struct categoricals *categoricals;
75
76   /* Array containing number of categories per categorical variable. */
77   size_t *n_categories;
78
79   /* Dimension of the covariance matrix. */
80   size_t dim;
81
82   /* The weight variable (or NULL if none) */
83   const struct variable *wv;
84
85   /* A set of matrices containing the 0th, 1st and 2nd moments */
86   gsl_matrix **moments;
87
88   /* The class of missing values to exclude */
89   enum mv_class exclude;
90
91   /* An array of doubles representing the covariance matrix.
92      Only the top triangle is included, and no diagonals */
93   double *cm;
94   int n_cm;
95
96   /* 1 for single pass algorithm; 
97      2 for double pass algorithm
98   */
99   short passes;
100
101   /*
102     0 : No pass has  been made
103     1 : First pass has been started
104     2 : Second pass has been 
105     
106     IE: How many passes have been (partially) made. */
107   short state;
108
109   /* Flags indicating that the first case has been seen */
110   bool pass_one_first_case_seen;
111   bool pass_two_first_case_seen;
112 };
113
114
115
116 /* Return a matrix containing the M th moments.
117    The matrix is of size  NxN where N is the number of variables.
118    Each row represents the moments of a variable.
119    In the absence of missing values, the columns of this matrix will
120    be identical.  If missing values are involved, then element (i,j)
121    is the moment of the i th variable, when paired with the j th variable.
122  */
123 const gsl_matrix *
124 covariance_moments (const struct covariance *cov, int m)
125 {
126   return cov->moments[m];
127 }
128
129
130
131 /* Create a covariance struct.
132  */
133 struct covariance *
134 covariance_1pass_create (size_t n_vars, const struct variable **vars,
135                          const struct variable *weight, enum mv_class exclude)
136 {
137   size_t i;
138   struct covariance *cov = xzalloc (sizeof *cov);
139
140   cov->passes = 1;
141   cov->state = 0;
142   cov->pass_one_first_case_seen = cov->pass_two_first_case_seen = false;
143   
144   cov->vars = vars;
145
146   cov->wv = weight;
147   cov->n_vars = n_vars;
148   cov->dim = n_vars;
149
150   cov->moments = xmalloc (sizeof *cov->moments * n_MOMENTS);
151   
152   for (i = 0; i < n_MOMENTS; ++i)
153     cov->moments[i] = gsl_matrix_calloc (n_vars, n_vars);
154
155   cov->exclude = exclude;
156
157   cov->n_cm = (n_vars * (n_vars - 1)  ) / 2;
158
159   if (cov->n_cm > 0)
160     cov->cm = xcalloc (sizeof *cov->cm, cov->n_cm);
161   cov->categoricals = NULL;
162
163   return cov;
164 }
165
166 /*
167   Create a covariance struct for a two-pass algorithm. If categorical
168   variables are involed, the dimension cannot be know until after the
169   first data pass, so the actual covariances will not be allocated
170   until then.
171  */
172 struct covariance *
173 covariance_2pass_create (size_t n_vars, const struct variable **vars,
174                          size_t n_catvars, const struct variable **catvars, 
175                          const struct variable *wv, enum mv_class exclude)
176 {
177   size_t i;
178   struct covariance *cov = xmalloc (sizeof *cov);
179
180   cov->passes = 2;
181   cov->state = 0;
182   cov->pass_one_first_case_seen = cov->pass_two_first_case_seen = false;
183   
184   cov->vars = vars;
185
186   cov->wv = wv;
187   cov->n_vars = n_vars;
188   cov->dim = n_vars;
189
190   cov->moments = xmalloc (sizeof *cov->moments * n_MOMENTS);
191   
192   for (i = 0; i < n_MOMENTS; ++i)
193     cov->moments[i] = gsl_matrix_calloc (n_vars, n_vars);
194
195   cov->exclude = exclude;
196
197   cov->n_cm = -1;
198   cov->cm = NULL;
199
200   cov->categoricals = categoricals_create (catvars, n_catvars, wv, exclude);
201
202   return cov;
203 }
204
205 /* Return an integer, which can be used to index 
206    into COV->cm, to obtain the I, J th element
207    of the covariance matrix.  If COV->cm does not
208    contain that element, then a negative value
209    will be returned.
210 */
211 static int
212 cm_idx (const struct covariance *cov, int i, int j)
213 {
214   int as;
215   const int n2j = cov->dim - 2 - j;
216   const int nj = cov->dim - 2 ;
217   
218   assert (i >= 0);
219   assert (j < cov->dim);
220
221   if ( i == 0)
222     return -1;
223
224   if (j >= cov->dim - 1)
225     return -1;
226
227   if ( i <= j) 
228     return -1 ;
229
230   as = nj * (nj + 1) ;
231   as -= n2j * (n2j + 1) ; 
232   as /= 2;
233
234   return i - 1 + as;
235 }
236
237
238 /*
239   Returns true iff the variable corresponding to the Ith element of the covariance matrix 
240    has a missing value for case C
241 */
242 static bool
243 is_missing (const struct covariance *cov, int i, const struct ccase *c)
244 {
245   const struct variable *var = i < cov->n_vars ?
246     cov->vars[i] : 
247     categoricals_get_variable_by_subscript (cov->categoricals, i - cov->n_vars);
248
249   const union value *val = case_data (c, var);
250
251   return var_is_value_missing (var, val, cov->exclude);
252 }
253
254
255 static double
256 get_val (const struct covariance *cov, int i, const struct ccase *c)
257 {
258   if ( i < cov->n_vars)
259     {
260       const struct variable *var = cov->vars[i];
261
262       const union value *val = case_data (c, var);
263
264       return val->f;
265     }
266
267   return categoricals_get_binary_by_subscript (cov->categoricals, i - cov->n_vars, c);
268 }
269
270 #if 0
271 void
272 dump_matrix (const gsl_matrix *m)
273 {
274   size_t i, j;
275
276   for (i = 0 ; i < m->size1; ++i)
277     {
278       for (j = 0 ; j < m->size2; ++j)
279         printf ("%02f ", gsl_matrix_get (m, i, j));
280       printf ("\n");
281     }
282 }
283 #endif
284
285 /* Call this function for every case in the data set */
286 void
287 covariance_accumulate_pass1 (struct covariance *cov, const struct ccase *c)
288 {
289   size_t i, j, m;
290   const double weight = cov->wv ? case_data (c, cov->wv)->f : 1.0;
291
292   assert (cov->passes == 2);
293   if (!cov->pass_one_first_case_seen)
294     {
295       assert (cov->state == 0);
296       cov->state = 1;
297     }
298
299   categoricals_update (cov->categoricals, c);
300
301   for (i = 0 ; i < cov->dim; ++i)
302     {
303       double v1 = get_val (cov, i, c);
304
305       if ( is_missing (cov, i, c))
306         continue;
307
308       for (j = 0 ; j < cov->dim; ++j)
309         {
310           double pwr = 1.0;
311
312           if ( is_missing (cov, j, c))
313             continue;
314
315           for (m = 0 ; m <= MOMENT_MEAN; ++m)
316             {
317               double *x = gsl_matrix_ptr (cov->moments[m], i, j);
318
319               *x += pwr * weight;
320               pwr *= v1;
321             }
322         }
323     }
324
325   cov->pass_one_first_case_seen = true;
326 }
327
328
329 /* Call this function for every case in the data set */
330 void
331 covariance_accumulate_pass2 (struct covariance *cov, const struct ccase *c)
332 {
333   size_t i, j;
334   const double weight = cov->wv ? case_data (c, cov->wv)->f : 1.0;
335
336   assert (cov->passes == 2);
337   assert (cov->state >= 1);
338
339   if (! cov->pass_two_first_case_seen)
340     {
341       size_t m;
342       assert (cov->state == 1);
343       cov->state = 2;
344
345       cov->dim = cov->n_vars +
346         categoricals_total (cov->categoricals) - categoricals_get_n_variables (cov->categoricals);
347
348       cov->n_cm = (cov->dim * (cov->dim - 1)  ) / 2;
349       cov->cm = xcalloc (sizeof *cov->cm, cov->n_cm);
350
351       /* Grow the moment matrices so that they're large enough to accommodate the
352          categorical elements */
353       for (i = 0; i < n_MOMENTS; ++i)
354         {
355           cov->moments[i] = resize_matrix (cov->moments[i], cov->dim);
356         }
357
358       categoricals_done (cov->categoricals);
359
360       /* Populate the moments matrices with the categorical value elements */
361       for (i = cov->n_vars; i < cov->dim; ++i)
362         {
363           for (j = 0 ; j < cov->dim ; ++j) /* FIXME: This is WRONG !!! */
364             {
365               double w = categoricals_get_weight_by_subscript (cov->categoricals, i - cov->n_vars);
366
367               gsl_matrix_set (cov->moments[MOMENT_NONE], i, j, w);
368
369               w = categoricals_get_sum_by_subscript (cov->categoricals, i - cov->n_vars);
370
371               gsl_matrix_set (cov->moments[MOMENT_MEAN], i, j, w);
372             }
373         }
374
375       /* FIXME: This is WRONG!!  It must be fixed to properly handle missing values.  For
376        now it assumes there are none */
377       for (m = 0 ; m < n_MOMENTS; ++m)
378         {
379           for (i = 0 ; i < cov->dim ; ++i)
380             {
381               double x = gsl_matrix_get (cov->moments[m], i, cov->n_vars -1);
382               for (j = cov->n_vars; j < cov->dim; ++j)
383                 {
384                   gsl_matrix_set (cov->moments[m], i, j, x);
385                 }
386             }
387         }
388
389       /* Divide the means by the number of samples */
390       for (i = 0; i < cov->dim; ++i)
391         {
392           for (j = 0; j < cov->dim; ++j)
393             {
394               double *x = gsl_matrix_ptr (cov->moments[MOMENT_MEAN], i, j);
395               *x /= gsl_matrix_get (cov->moments[MOMENT_NONE], i, j);
396             }
397         }
398     }
399
400   for (i = 0 ; i < cov->dim; ++i)
401     {
402       double v1 = get_val (cov, i, c);
403
404       if ( is_missing (cov, i, c))
405         continue;
406
407       for (j = 0 ; j < cov->dim; ++j)
408         {
409           int idx;
410           double ss ;
411           double v2 = get_val (cov, j, c);
412
413           const double s = pow2 (v1 - gsl_matrix_get (cov->moments[MOMENT_MEAN], i, j)) * weight;
414
415           if ( is_missing (cov, j, c))
416             continue;
417
418           {
419             double *x = gsl_matrix_ptr (cov->moments[MOMENT_VARIANCE], i, j);
420             *x += s;
421           }
422
423           ss = 
424             (v1 - gsl_matrix_get (cov->moments[MOMENT_MEAN], i, j))
425             * 
426             (v2 - gsl_matrix_get (cov->moments[MOMENT_MEAN], i, j))
427             * weight
428             ;
429
430           idx = cm_idx (cov, i, j);
431           if (idx >= 0)
432             {
433               cov->cm [idx] += ss;
434             }
435         }
436     }
437
438   cov->pass_two_first_case_seen = true;
439 }
440
441
442 /* Call this function for every case in the data set.
443    After all cases have been passed, call covariance_calculate
444  */
445 void
446 covariance_accumulate (struct covariance *cov, const struct ccase *c)
447 {
448   size_t i, j, m;
449   const double weight = cov->wv ? case_data (c, cov->wv)->f : 1.0;
450
451   assert (cov->passes == 1);
452
453   if ( !cov->pass_one_first_case_seen)
454     {
455       assert ( cov->state == 0);
456       cov->state = 1;
457     }
458
459   for (i = 0 ; i < cov->dim; ++i)
460     {
461       const union value *val1 = case_data (c, cov->vars[i]);
462
463       if ( is_missing (cov, i, c))
464         continue;
465
466       for (j = 0 ; j < cov->dim; ++j)
467         {
468           double pwr = 1.0;
469           int idx;
470           const union value *val2 = case_data (c, cov->vars[j]);
471
472           if ( is_missing (cov, j, c))
473             continue;
474
475           idx = cm_idx (cov, i, j);
476           if (idx >= 0)
477             {
478               cov->cm [idx] += val1->f * val2->f * weight;
479             }
480
481           for (m = 0 ; m < n_MOMENTS; ++m)
482             {
483               double *x = gsl_matrix_ptr (cov->moments[m], i, j);
484
485               *x += pwr * weight;
486               pwr *= val1->f;
487             }
488         }
489     }
490
491   cov->pass_one_first_case_seen = true;
492 }
493
494
495 /* 
496    Allocate and return a gsl_matrix containing the covariances of the
497    data.
498 */
499 static gsl_matrix *
500 cm_to_gsl (struct covariance *cov)
501 {
502   int i, j;
503   gsl_matrix *m = gsl_matrix_calloc (cov->dim, cov->dim);
504
505   /* Copy the non-diagonal elements from cov->cm */
506   for ( j = 0 ; j < cov->dim - 1; ++j)
507     {
508       for (i = j+1 ; i < cov->dim; ++i)
509         {
510           double x = cov->cm [cm_idx (cov, i, j)];
511           gsl_matrix_set (m, i, j, x);
512           gsl_matrix_set (m, j, i, x);
513         }
514     }
515
516   /* Copy the diagonal elements from cov->moments[2] */
517   for (j = 0 ; j < cov->dim ; ++j)
518     {
519       double sigma = gsl_matrix_get (cov->moments[2], j, j);
520       gsl_matrix_set (m, j, j, sigma);
521     }
522
523   return m;
524 }
525
526
527 static const gsl_matrix *
528 covariance_calculate_double_pass (struct covariance *cov)
529 {
530   size_t i, j;
531   for (i = 0 ; i < cov->dim; ++i)
532     {
533       for (j = 0 ; j < cov->dim; ++j)
534         {
535           int idx;
536           double *x = gsl_matrix_ptr (cov->moments[MOMENT_VARIANCE], i, j);
537           *x /= gsl_matrix_get (cov->moments[MOMENT_NONE], i, j);
538
539           idx = cm_idx (cov, i, j);
540           if ( idx >= 0)
541             {
542               x = &cov->cm [idx];
543               *x /= gsl_matrix_get (cov->moments[MOMENT_NONE], i, j);
544             }
545         }
546     }
547
548   return  cm_to_gsl (cov);
549 }
550
551 static const gsl_matrix *
552 covariance_calculate_single_pass (struct covariance *cov)
553 {
554   size_t i, j;
555   size_t m;
556
557   for (m = 0; m < n_MOMENTS; ++m)
558     {
559       /* Divide the moments by the number of samples */
560       if ( m > 0)
561         {
562           for (i = 0 ; i < cov->dim; ++i)
563             {
564               for (j = 0 ; j < cov->dim; ++j)
565                 {
566                   double *x = gsl_matrix_ptr (cov->moments[m], i, j);
567                   *x /= gsl_matrix_get (cov->moments[0], i, j);
568
569                   if ( m == MOMENT_VARIANCE)
570                     *x -= pow2 (gsl_matrix_get (cov->moments[1], i, j));
571                 }
572             }
573         }
574     }
575
576   /* Centre the moments */
577   for ( j = 0 ; j < cov->dim - 1; ++j)
578     {
579       for (i = j + 1 ; i < cov->dim; ++i)
580         {
581           double *x = &cov->cm [cm_idx (cov, i, j)];
582           
583           *x /= gsl_matrix_get (cov->moments[0], i, j);
584
585           *x -=
586             gsl_matrix_get (cov->moments[MOMENT_MEAN], i, j) 
587             *
588             gsl_matrix_get (cov->moments[MOMENT_MEAN], j, i); 
589         }
590     }
591
592   return cm_to_gsl (cov);
593 }
594
595
596 /* 
597    Return a pointer to gsl_matrix containing the pairwise covariances.
598    The matrix remains owned by the COV object, and must not be freed.
599    Call this function only after all data have been accumulated.
600 */
601 const gsl_matrix *
602 covariance_calculate (struct covariance *cov)
603 {
604   if ( cov->state <= 0 )
605     return NULL;
606
607   switch (cov->passes)
608     {
609     case 1:
610       return covariance_calculate_single_pass (cov);  
611       break;
612     case 2:
613       return covariance_calculate_double_pass (cov);  
614       break;
615     default:
616       NOT_REACHED ();
617     }
618 }
619
620 /*
621   Covariance computed without dividing by the sample size.
622  */
623 static const gsl_matrix *
624 covariance_calculate_double_pass_unnormalized (struct covariance *cov)
625 {
626   size_t i, j;
627   for (i = 0 ; i < cov->dim; ++i)
628     {
629       for (j = 0 ; j < cov->dim; ++j)
630         {
631           int idx;
632           double *x = gsl_matrix_ptr (cov->moments[MOMENT_VARIANCE], i, j);
633
634           idx = cm_idx (cov, i, j);
635           if ( idx >= 0)
636             {
637               x = &cov->cm [idx];
638             }
639         }
640     }
641
642   return  cm_to_gsl (cov);
643 }
644
645 static const gsl_matrix *
646 covariance_calculate_single_pass_unnormalized (struct covariance *cov)
647 {
648   size_t i, j;
649
650   for (i = 0 ; i < cov->dim; ++i)
651     {
652       for (j = 0 ; j < cov->dim; ++j)
653         {
654           double *x = gsl_matrix_ptr (cov->moments[MOMENT_VARIANCE], i, j);
655           *x -= pow2 (gsl_matrix_get (cov->moments[MOMENT_MEAN], i, j))
656             / gsl_matrix_get (cov->moments[MOMENT_NONE], i, j);
657         }
658     }
659   for ( j = 0 ; j < cov->dim - 1; ++j)
660     {
661       for (i = j + 1 ; i < cov->dim; ++i)
662         {
663           double *x = &cov->cm [cm_idx (cov, i, j)];
664           
665           *x -=
666             gsl_matrix_get (cov->moments[MOMENT_MEAN], i, j) 
667             *
668             gsl_matrix_get (cov->moments[MOMENT_MEAN], j, i) 
669           / gsl_matrix_get (cov->moments[MOMENT_NONE], i, j);
670         }
671     }
672
673   return cm_to_gsl (cov);
674 }
675
676
677 /* 
678    Return a pointer to gsl_matrix containing the pairwise covariances.
679    The matrix remains owned by the COV object, and must not be freed.
680    Call this function only after all data have been accumulated.
681 */
682 const gsl_matrix *
683 covariance_calculate_unnormalized (struct covariance *cov)
684 {
685   if ( cov->state <= 0 )
686     return NULL;
687
688   switch (cov->passes)
689     {
690     case 1:
691       return covariance_calculate_single_pass_unnormalized (cov);  
692       break;
693     case 2:
694       return covariance_calculate_double_pass_unnormalized (cov);  
695       break;
696     default:
697       NOT_REACHED ();
698     }
699 }
700
701
702
703 /* Destroy the COV object */
704 void
705 covariance_destroy (struct covariance *cov)
706 {
707   size_t i;
708
709   categoricals_destroy (cov->categoricals);
710
711   for (i = 0; i < n_MOMENTS; ++i)
712     gsl_matrix_free (cov->moments[i]);
713
714   free (cov->moments);
715   free (cov->cm);
716   free (cov);
717 }