statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
distance_metrics.hpp
Go to the documentation of this file.
1
6#pragma once
7
8#include <cmath>
9#include <stdexcept>
10#include <iterator>
11#include <vector>
12
13namespace statcpp {
14
30template <typename Iterator1, typename Iterator2>
31double euclidean_distance(Iterator1 first1, Iterator1 last1,
32 Iterator2 first2, Iterator2 last2)
33{
34 auto n1 = std::distance(first1, last1);
35 auto n2 = std::distance(first2, last2);
36
37 if (n1 != n2) {
38 throw std::invalid_argument("statcpp::euclidean_distance: sequences must have the same length");
39 }
40 if (n1 == 0) {
41 throw std::invalid_argument("statcpp::euclidean_distance: empty sequences");
42 }
43
44 double sum_sq = 0.0;
45 auto it1 = first1;
46 auto it2 = first2;
47
48 while (it1 != last1) {
49 double diff = static_cast<double>(*it1) - static_cast<double>(*it2);
50 sum_sq += diff * diff;
51 ++it1;
52 ++it2;
53 }
54
55 return std::sqrt(sum_sq);
56}
57
61template <typename Iterator1, typename Iterator2, typename Proj1, typename Proj2>
62double euclidean_distance(Iterator1 first1, Iterator1 last1,
63 Iterator2 first2, Iterator2 last2,
64 Proj1 proj1, Proj2 proj2)
65{
66 auto n1 = std::distance(first1, last1);
67 auto n2 = std::distance(first2, last2);
68
69 if (n1 != n2) {
70 throw std::invalid_argument("statcpp::euclidean_distance: sequences must have the same length");
71 }
72 if (n1 == 0) {
73 throw std::invalid_argument("statcpp::euclidean_distance: empty sequences");
74 }
75
76 double sum_sq = 0.0;
77 auto it1 = first1;
78 auto it2 = first2;
79
80 while (it1 != last1) {
81 double v1 = static_cast<double>(proj1(*it1));
82 double v2 = static_cast<double>(proj2(*it2));
83 double diff = v1 - v2;
84 sum_sq += diff * diff;
85 ++it1;
86 ++it2;
87 }
88
89 return std::sqrt(sum_sq);
90}
91
107template <typename Iterator1, typename Iterator2>
108double manhattan_distance(Iterator1 first1, Iterator1 last1,
109 Iterator2 first2, Iterator2 last2)
110{
111 auto n1 = std::distance(first1, last1);
112 auto n2 = std::distance(first2, last2);
113
114 if (n1 != n2) {
115 throw std::invalid_argument("statcpp::manhattan_distance: sequences must have the same length");
116 }
117 if (n1 == 0) {
118 throw std::invalid_argument("statcpp::manhattan_distance: empty sequences");
119 }
120
121 double sum_abs = 0.0;
122 auto it1 = first1;
123 auto it2 = first2;
124
125 while (it1 != last1) {
126 double diff = static_cast<double>(*it1) - static_cast<double>(*it2);
127 sum_abs += std::abs(diff);
128 ++it1;
129 ++it2;
130 }
131
132 return sum_abs;
133}
134
138template <typename Iterator1, typename Iterator2, typename Proj1, typename Proj2>
139double manhattan_distance(Iterator1 first1, Iterator1 last1,
140 Iterator2 first2, Iterator2 last2,
141 Proj1 proj1, Proj2 proj2)
142{
143 auto n1 = std::distance(first1, last1);
144 auto n2 = std::distance(first2, last2);
145
146 if (n1 != n2) {
147 throw std::invalid_argument("statcpp::manhattan_distance: sequences must have the same length");
148 }
149 if (n1 == 0) {
150 throw std::invalid_argument("statcpp::manhattan_distance: empty sequences");
151 }
152
153 double sum_abs = 0.0;
154 auto it1 = first1;
155 auto it2 = first2;
156
157 while (it1 != last1) {
158 double v1 = static_cast<double>(proj1(*it1));
159 double v2 = static_cast<double>(proj2(*it2));
160 double diff = v1 - v2;
161 sum_abs += std::abs(diff);
162 ++it1;
163 ++it2;
164 }
165
166 return sum_abs;
167}
168
189template <typename Iterator1, typename Iterator2>
190double cosine_similarity(Iterator1 first1, Iterator1 last1,
191 Iterator2 first2, Iterator2 last2)
192{
193 auto n1 = std::distance(first1, last1);
194 auto n2 = std::distance(first2, last2);
195
196 if (n1 != n2) {
197 throw std::invalid_argument("statcpp::cosine_similarity: sequences must have the same length");
198 }
199 if (n1 == 0) {
200 throw std::invalid_argument("statcpp::cosine_similarity: empty sequences");
201 }
202
203 double dot_product = 0.0;
204 double norm1_sq = 0.0;
205 double norm2_sq = 0.0;
206
207 auto it1 = first1;
208 auto it2 = first2;
209
210 while (it1 != last1) {
211 double v1 = static_cast<double>(*it1);
212 double v2 = static_cast<double>(*it2);
213
214 dot_product += v1 * v2;
215 norm1_sq += v1 * v1;
216 norm2_sq += v2 * v2;
217
218 ++it1;
219 ++it2;
220 }
221
222 if (norm1_sq == 0.0 || norm2_sq == 0.0) {
223 throw std::invalid_argument("statcpp::cosine_similarity: zero vector encountered");
224 }
225
226 return dot_product / (std::sqrt(norm1_sq) * std::sqrt(norm2_sq));
227}
228
232template <typename Iterator1, typename Iterator2, typename Proj1, typename Proj2>
233double cosine_similarity(Iterator1 first1, Iterator1 last1,
234 Iterator2 first2, Iterator2 last2,
235 Proj1 proj1, Proj2 proj2)
236{
237 auto n1 = std::distance(first1, last1);
238 auto n2 = std::distance(first2, last2);
239
240 if (n1 != n2) {
241 throw std::invalid_argument("statcpp::cosine_similarity: sequences must have the same length");
242 }
243 if (n1 == 0) {
244 throw std::invalid_argument("statcpp::cosine_similarity: empty sequences");
245 }
246
247 double dot_product = 0.0;
248 double norm1_sq = 0.0;
249 double norm2_sq = 0.0;
250
251 auto it1 = first1;
252 auto it2 = first2;
253
254 while (it1 != last1) {
255 double v1 = static_cast<double>(proj1(*it1));
256 double v2 = static_cast<double>(proj2(*it2));
257
258 dot_product += v1 * v2;
259 norm1_sq += v1 * v1;
260 norm2_sq += v2 * v2;
261
262 ++it1;
263 ++it2;
264 }
265
266 if (norm1_sq == 0.0 || norm2_sq == 0.0) {
267 throw std::invalid_argument("statcpp::cosine_similarity: zero vector encountered");
268 }
269
270 return dot_product / (std::sqrt(norm1_sq) * std::sqrt(norm2_sq));
271}
272
287template <typename Iterator1, typename Iterator2>
288double cosine_distance(Iterator1 first1, Iterator1 last1,
289 Iterator2 first2, Iterator2 last2)
290{
291 return 1.0 - cosine_similarity(first1, last1, first2, last2);
292}
293
297template <typename Iterator1, typename Iterator2, typename Proj1, typename Proj2>
298double cosine_distance(Iterator1 first1, Iterator1 last1,
299 Iterator2 first2, Iterator2 last2,
300 Proj1 proj1, Proj2 proj2)
301{
302 return 1.0 - cosine_similarity(first1, last1, first2, last2, proj1, proj2);
303}
304
322inline double mahalanobis_distance(const std::vector<double>& x,
323 const std::vector<double>& mean,
324 const std::vector<std::vector<double>>& cov_matrix)
325{
326 if (x.size() != mean.size()) {
327 throw std::invalid_argument("statcpp::mahalanobis_distance: x and mean must have same dimension");
328 }
329 if (x.size() != 2) {
330 throw std::invalid_argument("statcpp::mahalanobis_distance: only 2D is currently supported");
331 }
332 if (cov_matrix.size() != 2 || cov_matrix[0].size() != 2 || cov_matrix[1].size() != 2) {
333 throw std::invalid_argument("statcpp::mahalanobis_distance: covariance matrix must be 2x2");
334 }
335
336 // Compute difference vector: d = x - mean
337 double d0 = x[0] - mean[0];
338 double d1 = x[1] - mean[1];
339
340 // Compute inverse of 2x2 covariance matrix
341 // Σ = [a b]
342 // [c d]
343 // Σ⁻¹ = (1/det) * [d -b]
344 // [-c a]
345
346 double a = cov_matrix[0][0];
347 double b = cov_matrix[0][1];
348 double c = cov_matrix[1][0];
349 double d = cov_matrix[1][1];
350
351 double det = a * d - b * c;
352
353 if (std::abs(det) < 1e-10) {
354 throw std::invalid_argument("statcpp::mahalanobis_distance: covariance matrix is singular");
355 }
356
357 // Inverse matrix elements
358 double inv_a = d / det;
359 double inv_b = -b / det;
360 double inv_c = -c / det;
361 double inv_d = a / det;
362
363 // Compute dᵀ Σ⁻¹ d
364 // First compute Σ⁻¹ d
365 double temp0 = inv_a * d0 + inv_b * d1;
366 double temp1 = inv_c * d0 + inv_d * d1;
367
368 // Then compute dᵀ (Σ⁻¹ d)
369 double mahalanobis_sq = d0 * temp0 + d1 * temp1;
370
371 if (mahalanobis_sq < 0.0) {
372 // Due to numerical errors, ensure non-negative
373 mahalanobis_sq = 0.0;
374 }
375
376 return std::sqrt(mahalanobis_sq);
377}
378
400template <typename Iterator1, typename Iterator2>
401double minkowski_distance(Iterator1 first1, Iterator1 last1,
402 Iterator2 first2, Iterator2 last2,
403 double p)
404{
405 if (p < 1.0) {
406 throw std::invalid_argument("statcpp::minkowski_distance: p must be >= 1");
407 }
408
409 auto n1 = std::distance(first1, last1);
410 auto n2 = std::distance(first2, last2);
411
412 if (n1 != n2) {
413 throw std::invalid_argument("statcpp::minkowski_distance: sequences must have the same length");
414 }
415 if (n1 == 0) {
416 throw std::invalid_argument("statcpp::minkowski_distance: empty sequences");
417 }
418
419 double sum = 0.0;
420 auto it1 = first1;
421 auto it2 = first2;
422
423 while (it1 != last1) {
424 double diff = std::abs(static_cast<double>(*it1) - static_cast<double>(*it2));
425 sum += std::pow(diff, p);
426 ++it1;
427 ++it2;
428 }
429
430 return std::pow(sum, 1.0 / p);
431}
432
436template <typename Iterator1, typename Iterator2, typename Proj1, typename Proj2>
437double minkowski_distance(Iterator1 first1, Iterator1 last1,
438 Iterator2 first2, Iterator2 last2,
439 double p, Proj1 proj1, Proj2 proj2)
440{
441 if (p < 1.0) {
442 throw std::invalid_argument("statcpp::minkowski_distance: p must be >= 1");
443 }
444
445 auto n1 = std::distance(first1, last1);
446 auto n2 = std::distance(first2, last2);
447
448 if (n1 != n2) {
449 throw std::invalid_argument("statcpp::minkowski_distance: sequences must have the same length");
450 }
451 if (n1 == 0) {
452 throw std::invalid_argument("statcpp::minkowski_distance: empty sequences");
453 }
454
455 double sum = 0.0;
456 auto it1 = first1;
457 auto it2 = first2;
458
459 while (it1 != last1) {
460 double v1 = static_cast<double>(proj1(*it1));
461 double v2 = static_cast<double>(proj2(*it2));
462 double diff = std::abs(v1 - v2);
463 sum += std::pow(diff, p);
464 ++it1;
465 ++it2;
466 }
467
468 return std::pow(sum, 1.0 / p);
469}
470
486template <typename Iterator1, typename Iterator2>
487double chebyshev_distance(Iterator1 first1, Iterator1 last1,
488 Iterator2 first2, Iterator2 last2)
489{
490 auto n1 = std::distance(first1, last1);
491 auto n2 = std::distance(first2, last2);
492
493 if (n1 != n2) {
494 throw std::invalid_argument("statcpp::chebyshev_distance: sequences must have the same length");
495 }
496 if (n1 == 0) {
497 throw std::invalid_argument("statcpp::chebyshev_distance: empty sequences");
498 }
499
500 double max_diff = 0.0;
501 auto it1 = first1;
502 auto it2 = first2;
503
504 while (it1 != last1) {
505 double diff = std::abs(static_cast<double>(*it1) - static_cast<double>(*it2));
506 if (diff > max_diff) {
507 max_diff = diff;
508 }
509 ++it1;
510 ++it2;
511 }
512
513 return max_diff;
514}
515
519template <typename Iterator1, typename Iterator2, typename Proj1, typename Proj2>
520double chebyshev_distance(Iterator1 first1, Iterator1 last1,
521 Iterator2 first2, Iterator2 last2,
522 Proj1 proj1, Proj2 proj2)
523{
524 auto n1 = std::distance(first1, last1);
525 auto n2 = std::distance(first2, last2);
526
527 if (n1 != n2) {
528 throw std::invalid_argument("statcpp::chebyshev_distance: sequences must have the same length");
529 }
530 if (n1 == 0) {
531 throw std::invalid_argument("statcpp::chebyshev_distance: empty sequences");
532 }
533
534 double max_diff = 0.0;
535 auto it1 = first1;
536 auto it2 = first2;
537
538 while (it1 != last1) {
539 double v1 = static_cast<double>(proj1(*it1));
540 double v2 = static_cast<double>(proj2(*it2));
541 double diff = std::abs(v1 - v2);
542 if (diff > max_diff) {
543 max_diff = diff;
544 }
545 ++it1;
546 ++it2;
547 }
548
549 return max_diff;
550}
551
552} // namespace statcpp
double cosine_distance(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2)
Cosine distance.
double minkowski_distance(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, double p)
Minkowski distance (generalized Lp distance)
auto sum(Iterator first, Iterator last)
Sum.
double cosine_similarity(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2)
Cosine similarity.
double chebyshev_distance(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2)
Chebyshev distance (L∞ norm, maximum metric)
double mean(Iterator first, Iterator last)
Arithmetic mean.
std::vector< double > diff(Iterator first, Iterator last, std::size_t order=1)
Difference series (first-order or d-th order differencing)
double mahalanobis_distance(const std::vector< double > &x, const std::vector< double > &mean, const std::vector< std::vector< double > > &cov_matrix)
Mahalanobis distance.
double euclidean_distance(const std::vector< double > &a, const std::vector< double > &b)
Euclidean distance.
double manhattan_distance(const std::vector< double > &a, const std::vector< double > &b)
Manhattan distance.