ranks.hpp
1 // Copyright © 2020 Thomas Nagler
2 //
3 // This file is part of the wdm library and licensed under the terms of
4 // the MIT license. For a copy, see the LICENSE file in the root directory
5 // or https://github.com/tnagler/wdm/blob/master/LICENSE.
6 
7 #pragma once
8 
9 #include "nan_handling.hpp"
10 #include "random.hpp"
11 #include "utils.hpp"
12 
13 #include <memory>
14 
15 namespace wdm {
16 
17 namespace impl {
18 
28 inline std::vector<double>
29 rank(std::vector<double> x,
30  std::vector<double> weights = std::vector<double>(),
31  std::string ties_method = "min",
32  std::vector<int> seeds = std::vector<int>())
33 {
34  if ((ties_method != "min") && (ties_method != "average") &&
35  (ties_method != "first") && (ties_method != "random"))
36  throw std::runtime_error(
37  "ties method must be one of 'min', 'average', 'first', 'random'.");
38 
39  // set default weights if necessary
40  size_t n = x.size();
41  if (weights.size() == 0)
42  weights = std::vector<double>(n, 1.0);
43 
44  if (weights.size() != n) {
45  throw std::runtime_error("weights and data must have same size.");
46  }
47 
48  // NaN-handling
49  std::vector<double> nans;
50  if (utils::any_nan(x)) {
51  nans.resize(n, 0);
52  for (size_t i = 0; i < n; i++) {
53  if (std::isnan(x[i])) {
54  x[i] = std::numeric_limits<double>::max();
55  nans[i] = 1;
56  weights[i] = 0;
57  }
58  }
59  }
60 
61  double w_mean =
62  utils::sum(weights) / static_cast<double>(n - utils::sum(nans));
63  for (auto& w : weights) {
64  w = w / w_mean;
65  }
66 
67  // permutation that brings 'x' in ascending order
68  std::vector<size_t> perm = utils::get_order(x);
69 
70  // all tie groups draw from the same stream, so that they are shuffled
71  // independently of one another
72  std::unique_ptr<random::RandomGenerator> random_gen;
73  if (ties_method == "random")
74  random_gen.reset(new random::RandomGenerator(seeds));
75 
76  double w_acc = 0.0, w_batch;
77  for (size_t i = 0, reps; i < n; i += reps) {
78  // find replications
79  reps = 0;
80  w_batch = 0.0;
81  while ((i + reps < n) && (x[perm[i]] == x[perm[i + reps]]))
82  w_batch += weights[perm[i + reps++]];
83 
84  // assign min rank
85  for (size_t k = 0; k < reps; ++k)
86  x[perm[i + k]] = w_acc + weights[perm[i]];
87 
88  if (reps > 1) {
89  if ((ties_method == "first") || (ties_method == "random")) {
90  // break ties by assigning the cumulative weights, in order of
91  // appearance ("first") or in random order ("random")
92  std::vector<size_t> ord(reps);
93  std::iota(ord.begin(), ord.end(), 0); // 0, 1, 2, ...
94  if (ties_method == "random")
95  random::shuffle(ord, *random_gen);
96 
97  double ww = 0.0;
98  for (size_t k = 0; k < reps; ++k) {
99  ww += weights[perm[i + ord[k]]];
100  x[perm[i + ord[k]]] = w_acc + ww;
101  }
102  } else if (ties_method == "average") {
103  // assign average rank to tied values
104  for (size_t k = 0; k < reps; ++k)
105  x[perm[i + k]] += (w_batch - weights[perm[i]]) / 2;
106  }
107  }
108 
109  // accumulate weights for current batch
110  w_acc += w_batch;
111  }
112 
113  if (nans.size() == n) {
114  for (size_t i = 0; i < x.size(); i++) {
115  if (nans[i]) {
116  x[i] = NAN;
117  }
118  }
119  }
120 
121  return x;
122 }
123 
133 inline std::vector<double>
134 rank0(std::vector<double> x,
135  std::vector<double> weights = std::vector<double>(),
136  std::string ties_method = "min")
137 {
138  if ((ties_method != "min") && (ties_method != "average") &&
139  (ties_method != "max"))
140  throw std::runtime_error(
141  "ties_method must be either 'min', 'average', or 'max'.");
142 
143  // set default weights if necessary
144  size_t n = x.size();
145  if (weights.size() == 0)
146  weights = std::vector<double>(n, 1.0);
147 
148  // permutation that brings 'x' in ascending order
149  std::vector<size_t> perm = utils::get_order(x);
150 
151  double w_acc = 0.0, w_batch;
152  for (size_t i = 0, reps; i < n; i += reps) {
153  // find replications
154  reps = 0;
155  w_batch = 0.0;
156  while ((i + reps < n) && (x[perm[i]] == x[perm[i + reps]]))
157  w_batch += weights[perm[i + reps++]];
158 
159  // assign min rank
160  for (size_t k = 0; k < reps; ++k)
161  x[perm[i + k]] = w_acc;
162 
163  // accumulate weights for current batch
164  w_acc += w_batch;
165 
166  // assign average rank to tied values
167  if ((ties_method == "average") && (reps > 1)) {
168  std::vector<double> ww(reps);
169  for (size_t k = 0; k < reps; ++k)
170  ww[k] = weights[perm[i + k]];
171  double offset = utils::perm_sum(ww, 2) / w_batch;
172  for (size_t k = 0; k < reps; ++k)
173  x[perm[i + k]] += offset;
174  } else if (ties_method == "max") {
175  // w_acc now holds the weight of everything up to and including the batch
176  for (size_t k = 0; k < reps; ++k)
177  x[perm[i + k]] = w_acc;
178  }
179  }
180 
181  return x;
182 }
183 
188 inline std::vector<double>
189 bivariate_rank(std::vector<double> x,
190  std::vector<double> y,
191  std::vector<double> weights = std::vector<double>())
192 {
193  utils::check_sizes(x, y, weights);
194 
195  // get inverse of permutation that brings x in ascending order
196  std::vector<size_t> perm_x = utils::get_order(x);
197  perm_x = utils::invert_permutation(perm_x);
198 
199  // sort x, y, and weights according to x, breaking ties with y
200  utils::sort_all(x, y, weights);
201 
202  // get inverse of permutation that brings y in descending order
203  std::vector<size_t> perm_y = utils::get_order(y, false);
204  perm_y = utils::invert_permutation(perm_y);
205 
206  // sort y in descending order counting inversions
207  std::vector<double> counts(y.size(), 0.0);
208  utils::merge_sort_count_per_element(y, weights, counts);
209 
210  // bring counts back in original order
211  std::vector<double> counts_tmp = counts;
212  for (size_t i = 0; i < counts.size(); i++)
213  counts[i] = counts_tmp[perm_y[perm_x[i]]];
214 
215  return counts;
216 }
217 
220 inline double
221 median(const std::vector<double>& x,
222  std::vector<double> weights = std::vector<double>())
223 {
224  utils::check_sizes(x, x, weights);
225  size_t n = x.size();
226 
227  // sort x and weights in x order
228  auto perm = utils::get_order(x);
229  auto xx = x;
230  auto w = weights;
231  for (size_t i = 0; i < n; i++) {
232  xx[i] = x[perm[i]];
233  if (w.size() > 0)
234  w[i] = weights[perm[i]];
235  }
236 
237  // compute weighted ranks and the "average rank" (corresponds to the
238  // median)
239  auto ranks = rank0(xx, w, "average");
240  if (weights.size() == 0)
241  weights = std::vector<double>(n, 1.0);
242  double rank_avrg = utils::perm_sum(weights, 2) / utils::sum(weights);
243 
244  // weighted median splits data below and above rank_avrg
245  size_t i = 0;
246  while (ranks[i] < rank_avrg)
247  i++;
248  if (ranks[i] == rank_avrg)
249  return xx[i];
250  else
251  return 0.5 * (xx[i - 1] + xx[i]);
252 }
253 }
254 }
Weighted dependence measures.
Definition: wdm.hpp:19