source: branches/kendall-score/yat/statistics/Kendall.cc @ 4068

Last change on this file since 4068 was 4068, checked in by Peter, 20 months ago

implement move constructor and assignment for Kendall class.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 11.9 KB
Line 
1// $Id: Kendall.cc 4068 2021-08-05 23:28:50Z peter $
2
3/*
4  Copyright (C) 2011, 2012, 2020 Peter Johansson
5
6  This file is part of the yat library, http://dev.thep.lu.se/yat
7
8  The yat library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU General Public License as
10  published by the Free Software Foundation; either version 3 of the
11  License, or (at your option) any later version.
12
13  The yat library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with yat. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#include <config.h>
23
24#include "Kendall.h"
25
26#include <yat/utility/Ranking.h>
27#include <yat/utility/stl_utility.h>
28
29#include <gsl/gsl_cdf.h>
30
31#include <boost/scoped_ptr.hpp>
32
33#include <algorithm>
34#include <cassert>
35#include <cmath>
36#include <iterator>
37#include <limits>
38#include <map>
39#include <set>
40#include <utility>
41#include <vector>
42
43
44#include <iostream> // debug
45namespace theplu {
46namespace yat {
47namespace statistics {
48
49
50  /**
51     Calculate sum over all pair
52
53     \sum_ij f((first[i]-first[j])(first2[i]-first2[j]))
54     where f(x) is -1 if x<0,
55                    0 if x=0,
56               and +1 if x>0.
57   */
58  template<typename Iterator1, typename Iterator2>
59  long int count(Iterator1 first1, Iterator1 last1, Iterator2 first2)
60  {
61    long int count=0;
62    for (Iterator1 i=first1 ; i!=last1; ++i)
63      for (Iterator1 j=first1; j<i; ++j) {
64        if (*i==*j || first2[i-first1]==first2[j-first1])
65          continue;
66        if ((*i > *j) == (first2[i-first1] > first2[j-first1]))
67          ++count;
68        else
69          --count;
70      }
71    return count;
72  }
73
74
75
76  class Kendall::Pimpl
77  {
78    class Count
79    {
80    public:
81      Count(const std::multiset<std::pair<double, double> >& data);
82      long int count(void) const;
83      double score(void) const;
84      class Ties
85      {
86      public:
87        Ties(void);
88        void add(size_t n);
89        bool have_ties(void) const { return n_pairs_; }
90        // \return \sum x * (x-1)
91        unsigned long int n_pairs(void) const { return n_pairs_;}
92        // \return \sum x * (x-1) * (x-2)
93        unsigned long int n_triples(void) const { return n_triples_; }
94        // \return \sum x * (x-1) * (2*x+5)
95        unsigned long int v_correction(void) const { return v_correction_; }
96      private:
97        unsigned long int n_pairs_;
98        unsigned long int n_triples_;
99        unsigned long int v_correction_;
100      };
101
102      const Ties& x_ties(void) const;
103      const Ties& y_ties(void) const;
104
105    private:
106      Ties x_ties_;
107      Ties y_ties_;
108
109      // # pairs such that (x_i-x_j)(y_i-y_j) > 0
110      long int concordant_;
111      // # pairs such that (x_i-x_j)(y_i-y_j) < 0
112      long int discordant_;
113      // # pairs such that x_i!=x_j && y_i==y_j
114      long int extraX_;
115      // # pairs such that x_i==x_j && y_i!=y_j
116      long int extraY_;
117      // # pairs such that x_i==x_j && y_i==y_j
118      //long int spare_;
119
120      template<typename Iterator>
121      void calculate_ties(Iterator first, Iterator last, Ties& ties)
122      {
123        while (first != last) {
124          Iterator upper = first;
125          size_t n = 1;
126          ++upper;
127          while (upper!=last && *upper==*first) {
128            ++n;
129            ++upper;
130          }
131          ties.add(n);
132          first = upper;
133        }
134      }
135    };
136
137  public:
138    Pimpl(void);
139    Pimpl(const Pimpl& other);
140    Pimpl& operator=(const Pimpl& rhs);
141    void add(double x, double y);
142    size_t n(void) const;
143    /// \return one-sided p-value
144    double p_approx(bool right) const;
145    double p_exact(bool right, bool left) const;
146    void reset(void);
147    double score(void) const;
148  private:
149    // return # concordant pairs minus # discordant pairs
150    long int count(void) const;
151    // return estimated variance of score
152    double variance(void) const;
153    // data always sort wrt first and then second (if first are equal)
154    std::multiset<std::pair<double, double> > data_;
155    // calculated in score(void)
156    boost::scoped_ptr<Count> count_;
157  };
158
159
160  // Kendall class
161  Kendall::Kendall(void)
162    : pimpl_(new Pimpl)
163  {
164  }
165
166
167  Kendall::Kendall(const Kendall& rhs)
168    : pimpl_(new Pimpl(*rhs.pimpl_))
169  {
170  }
171
172
173  Kendall::Kendall(Kendall&& rhs)
174    : pimpl_(rhs.pimpl_)
175  {
176    rhs.pimpl_ = nullptr;
177  }
178
179
180  Kendall::~Kendall(void)
181  {
182    delete pimpl_;
183  }
184
185
186  void Kendall::add(double x, double y)
187  {
188    pimpl_->add(x, y);
189  }
190
191
192  size_t Kendall::n(void) const
193  {
194    return pimpl_->n();
195  }
196
197
198  double Kendall::score(void) const
199  {
200    return pimpl_->score();
201  }
202
203
204  double Kendall::p_left(bool exact) const
205  {
206    if (!exact)
207      return pimpl_->p_approx(false);
208    return pimpl_->p_exact(false, true);
209  }
210
211
212  double Kendall::p_right(bool exact) const
213  {
214    if (!exact)
215      return pimpl_->p_approx(true);
216    return pimpl_->p_exact(true, false);
217  }
218
219
220  double Kendall::p_value(bool exact) const
221  {
222    if (exact)
223      return pimpl_->p_exact(true, true);
224    if (score()>0.0)
225      return 2*p_right(false);
226    return 2*p_left(false);
227  }
228
229
230  void Kendall::reset(void)
231  {
232    pimpl_->reset();
233  }
234
235
236  Kendall& Kendall::operator=(const Kendall& rhs)
237  {
238    if (&rhs == this)
239      return *this;
240
241    assert(pimpl_);
242    assert(rhs.pimpl_);
243    *pimpl_ = *rhs.pimpl_;
244    return *this;
245  }
246
247
248  Kendall& Kendall::operator=(Kendall&& rhs)
249  {
250    std::swap(pimpl_, rhs.pimpl_);
251    return *this;
252  }
253
254
255  Kendall::Pimpl::Count::Count(const std::multiset<std::pair<double,double>>& data)
256  {
257    // We follow 3 Algorithm SDTau for some-duplicate datasets in
258    // 'Fast Algorithms For The Calculation Of Kendall's Tau'
259    // by David Christen (Computational Statistics, March 2005)
260
261    // data is sorted w.r.t. ::first
262    calculate_ties(utility::pair_first_iterator(data.begin()),
263                   utility::pair_first_iterator(data.end()),
264                   x_ties_);
265
266    /*
267                y1 < y2  y2 == y2  y2 > y2
268      x1 <  x2     C        eX        D
269      x1 == x2    eY       spare      -
270      x1 >  x2     -        -         -
271
272      We categorise pairs into five categories:
273      C: Concordant
274      D: Discordant
275      eX: extra X; Ys and only Ys are equal
276      eY: extra Y; Xs and only Xs are equal
277      spare: both Xs and Yy are equal
278
279      Due to symmetry reasons and because data container is sorted, we
280      can ignore lower part of the matrix above.
281     */
282
283    concordant_ = 0;
284    discordant_ = 0;
285    extraX_ = 0;
286    extraY_ = 0;
287
288    unsigned long int eY = 0;
289    // size of the current equal range, i.e., number of data points
290    // for X_i : X_j == X_i, Y_j == Y_i, j <= i including the current
291    // point
292    unsigned long int ties = 1; // because loop below skip first entry
293    utility::Ranking<double> Y;
294
295    // loop over data, which is sorted w.r.t. ::first
296    auto previous = data.cbegin();
297    assert(previous != data.cend());
298    Y.insert(previous->second);
299    auto it = std::next(previous);
300    while (it!=data.cend()) {
301      assert(previous->first <= it->first);
302      // X not equal
303      if (it->first != previous->first) {
304        eY = 0;
305        ties = 1;
306      }
307      // y also equal
308      else if (it->second == previous->second)
309        ++ties;
310      else { // x equal, y not equal
311        eY += ties;
312        ties = 1;
313      }
314
315      Y.insert(it->second);
316      // FIXME can we use return value from insert instead
317      auto lower = Y.lower_bound(it->second);
318      // number of element in Y smaller than it->second
319      int n_smaller = Y.ranking(lower);
320      // number of element in Y equal to it->second
321      int n_equal = 1;
322      assert(lower != Y.cend());
323      auto upper = std::next(lower);
324      while (upper!=Y.cend() && *upper==*lower) {
325        ++upper;
326        ++n_equal;
327      }
328      size_t i = Y.size();
329
330      // n_smaller (y<yi) is the union of concordant (y<yi,x<xi)
331      // and eY (y<yi,x==xi)
332      int C = n_smaller - eY;
333
334      int eX =  n_equal - ties;
335
336      int D = i - (C + eX + eY + ties);
337
338      extraY_ += eY;
339      extraX_ += eX;
340      concordant_ += C;
341      discordant_ += D;
342      previous = it;
343      ++it;
344    }
345
346  }
347
348
349  long int Kendall::Pimpl::Count::count(void) const
350  {
351    return concordant_ - discordant_;
352  }
353
354
355  double Kendall::Pimpl::Count::score(void) const
356  {
357    double numerator = count();
358    double denominator = concordant_ + discordant_;
359    if (extraX_ || extraY_) {
360      denominator =
361        std::sqrt((denominator + extraX_)*(denominator + extraY_));
362    }
363    return numerator / denominator;
364  }
365
366
367  const Kendall::Pimpl::Count::Ties& Kendall::Pimpl::Count::x_ties(void) const
368  {
369    return x_ties_;
370  }
371
372
373  const Kendall::Pimpl::Count::Ties& Kendall::Pimpl::Count::y_ties(void) const
374  {
375    return y_ties_;
376  }
377
378
379  double Kendall::Pimpl::variance(void) const
380  {
381    /*
382      According to wikipedia,
383      z = k / sqrt(v)
384      is approximately standard normal
385      v = (v0 - vt - vu)/18 + v1 + v2
386      v0 = n(n-1)(2n+5)
387      vt = \sum t(t-1)(2t+5)
388      vu = \sum u(u-1)(2u+5)
389      v1 = \sum t(t-1)) * \sum u(u-1) / (2n(n-1))
390      v2 = sum t(t-1)(t-2) \sum u(u-1)(u-2) / (9n(n-1)(n-2))
391
392      where t is number of equal values in group i and similarly u for
393      y.
394    */
395    double n = data_.size();
396    double v0 = n*(n-1)*(2*n+5);
397    double vt = 0;
398    double vu = 0;
399    double v1 = 0;
400    double v2 = 0;
401    assert(count_);
402    auto& x_ties = count_->x_ties();
403    auto& y_ties = count_->y_ties();
404    // all correction terms above are zero in absence of ties
405    bool x_have_ties = x_ties.have_ties();
406    bool y_have_ties = y_ties.have_ties();
407    if (x_have_ties || y_have_ties) {
408      if (x_have_ties)
409        vt = x_ties.v_correction();
410      if (y_have_ties) {
411        vu = y_ties.v_correction();
412        if (x_have_ties) {
413          v1 = x_ties.n_pairs() * (y_ties.n_pairs() / (2*n*(n-1)));
414          v2 = x_ties.n_triples();
415          if (v2)
416            v2 *= y_ties.n_triples() / (9*n*(n-1)*(n-2));
417        }
418      }
419    }
420    return (v0 - vt - vu)/18 + v1 + v2;
421  }
422
423
424  Kendall::Pimpl::Count::Ties::Ties(void)
425    : n_pairs_(0), n_triples_(0), v_correction_(0)
426  {}
427
428
429  void Kendall::Pimpl::Count::Ties::add(size_t n)
430  {
431    unsigned long int factor = n * (n-1);
432    n_pairs_ += factor;
433    n_triples_ += factor * (n-2);
434    v_correction_ += factor * (2*n+5);
435  }
436
437
438  Kendall::Pimpl::Pimpl(void)
439  {}
440
441
442  Kendall::Pimpl::Pimpl(const Pimpl& other)
443    : data_(other.data_)
444  {}
445
446
447  Kendall::Pimpl& Kendall::Pimpl::operator=(const Pimpl& rhs)
448  {
449    data_ = rhs.data_;
450    count_.reset();
451    return *this;
452  }
453
454
455  void Kendall::Pimpl::add(double x, double y)
456  {
457    data_.insert(std::make_pair(x, y));
458    count_.reset();
459  }
460
461
462  size_t Kendall::Pimpl::n(void) const
463  {
464    return data_.size();
465  }
466
467
468  double Kendall::Pimpl::p_approx(bool right) const
469  {
470    double k = count();
471    if (!right)
472      k = -k;
473    return gsl_cdf_gaussian_Q(k, std::sqrt(variance()));
474  }
475
476
477  double Kendall::Pimpl::p_exact(bool right, bool left) const
478  {
479    long int upper = 0;
480    long int lower = 0;
481    if (right) {
482      if (left) {
483        upper = std::max(count(), -count());
484        lower = -upper;
485      }
486      else {
487        upper = count();
488        lower = std::numeric_limits<long int>::min();
489      }
490    }
491    else {
492      assert(left && "left or right must be true");
493      upper = std::numeric_limits<long int>::max();
494      lower = count();
495    }
496
497    // create a copy of the data, sort it with respect to ::second and
498    // then iterate through the permutations of second while keeping
499    // first constant. It means we need to do one extra initial sort,
500    // but OTOH the permuted data is always almost sorted.
501    std::vector<std::pair<double,double>> data(data_.begin(), data_.end());
502    using utility::pair_second_iterator;
503    std::sort(pair_second_iterator(data.begin()),
504              pair_second_iterator(data.end()));
505    unsigned int n = 0;
506    unsigned int total = 0;
507    do {
508      std::multiset<std::pair<double,double>>
509                    dataset(data.begin(), data.end());
510      Count count(dataset);
511      if (count.count() <= lower || count.count() >= upper)
512        ++n;
513      ++total;
514    }
515    while (std::next_permutation(pair_second_iterator(data.begin()),
516                                 pair_second_iterator(data.end())));
517
518    return static_cast<double>(n)/static_cast<double>(total);
519  }
520
521
522  void Kendall::Pimpl::reset(void)
523  {
524    Pimpl tmp;
525    *this = tmp;
526  }
527
528
529  double Kendall::Pimpl::score(void) const
530  {
531    count();
532    assert(count_.get());
533    return count_->score();
534  }
535
536
537  long int Kendall::Pimpl::count(void) const
538  {
539    if (!count_)
540      // const_cast to allow lazy eval is more restrictive than
541      // making count_ mutable.
542      const_cast<Pimpl*>(this)->count_.reset(new Count(data_));
543    return count_->count();
544  }
545
546}}} // of namespace statistics, yat, and theplu
Note: See TracBrowser for help on using the repository browser.