v2.0.0
Loading...
Searching...
No Matches
kmeans.cpp
Go to the documentation of this file.
1//=============================================================================================================
27
28//=============================================================================================================
29// INCLUDES
30//=============================================================================================================
31
32#include "kmeans.h"
33
34#include <cmath>
35#include <iostream>
36#include <algorithm>
37#include <vector>
38
39//=============================================================================================================
40// QT INCLUDES
41//=============================================================================================================
42
43#include <QDebug>
44
45//=============================================================================================================
46// USED NAMESPACES
47//=============================================================================================================
48
49using namespace UTILSLIB;
50using namespace Eigen;
51
52//=============================================================================================================
53// DEFINE MEMBER METHODS
54//=============================================================================================================
55
56KMeans::KMeans(QString distance,
57 QString start,
58 qint32 replicates,
59 QString emptyact,
60 bool online,
61 qint32 maxit)
62: m_distance(distanceFromString(distance.toStdString()))
63, m_start(startFromString(start.toStdString()))
64, m_emptyact(emptyactFromString(emptyact.toStdString()))
65, m_iReps(std::max(replicates, qint32(1)))
66, m_iMaxit(maxit)
67, m_bOnline(online)
68, m_rng(std::random_device{}())
69, emptyErrCnt(0)
70, iter(0)
71, k(0)
72, n(0)
73, p(0)
74, totsumD(0)
75, prevtotsumD(0)
76{
77}
78
79//=============================================================================================================
80
82 KMeansStart start,
83 qint32 replicates,
84 KMeansEmptyAction emptyact,
85 bool online,
86 qint32 maxit)
87: m_distance(distance)
88, m_start(start)
89, m_emptyact(emptyact)
90, m_iReps(std::max(replicates, qint32(1)))
91, m_iMaxit(maxit)
92, m_bOnline(online)
93, m_rng(std::random_device{}())
94, emptyErrCnt(0)
95, iter(0)
96, k(0)
97, n(0)
98, p(0)
99, totsumD(0)
100, prevtotsumD(0)
101{
102}
103
104//=============================================================================================================
105
106KMeansDistance KMeans::distanceFromString(const std::string& name)
107{
108 if (name == "cityblock") return KMeansDistance::CityBlock;
109 if (name == "cosine") return KMeansDistance::Cosine;
110 if (name == "correlation") return KMeansDistance::Correlation;
111 if (name == "hamming") return KMeansDistance::Hamming;
113}
114
115KMeansStart KMeans::startFromString(const std::string& name)
116{
117 if (name == "uniform") return KMeansStart::Uniform;
118 if (name == "cluster") return KMeansStart::Cluster;
119 return KMeansStart::Sample;
120}
121
122KMeansEmptyAction KMeans::emptyactFromString(const std::string& name)
123{
124 if (name == "drop") return KMeansEmptyAction::Drop;
125 if (name == "singleton") return KMeansEmptyAction::Singleton;
127}
128
129//=============================================================================================================
130
131bool KMeans::calculate(const MatrixXd& X_in,
132 qint32 kClusters,
133 VectorXi& idx,
134 MatrixXd& C,
135 VectorXd& sumD,
136 MatrixXd& D)
137{
138 if (kClusters < 1)
139 return false;
140
141 // Work on a local copy only when normalization is needed
142 MatrixXd X = X_in;
143
144 k = kClusters;
145 n = X.rows();
146 p = X.cols();
147
148 if (m_distance == KMeansDistance::Cosine)
149 {
150 // Normalize each row to unit length for cosine distance
151 VectorXd Xnorm = X.array().pow(2).rowwise().sum().sqrt();
152 for (qint32 i = 0; i < n; ++i)
153 {
154 if (Xnorm(i) > 0)
155 X.row(i) /= Xnorm(i);
156 }
157 }
158 else if (m_distance == KMeansDistance::Correlation)
159 {
160 // Mean-center each row, then normalize to unit length
161 X.array() -= (X.rowwise().sum().array() / static_cast<double>(p)).replicate(1, p);
162 VectorXd Xnorm = X.array().pow(2).rowwise().sum().sqrt();
163 for (qint32 i = 0; i < n; ++i)
164 {
165 if (Xnorm(i) > 0)
166 X.row(i) /= Xnorm(i);
167 }
168 }
169
170 // Set up uniform initialization bounds if needed
171 RowVectorXd Xmins, Xmaxs;
172 if (m_start == KMeansStart::Uniform)
173 {
174 if (m_distance == KMeansDistance::Hamming)
175 {
176 qWarning("KMeans: Uniform initialization is not supported for Hamming distance.");
177 return false;
178 }
179 Xmins = X.colwise().minCoeff();
180 Xmaxs = X.colwise().maxCoeff();
181 }
182
183 // Prepare online-update workspace
184 if (m_bOnline)
185 {
186 Del = MatrixXd::Constant(n, k, std::numeric_limits<double>::quiet_NaN());
187 }
188
189 double totsumDBest = std::numeric_limits<double>::max();
190 emptyErrCnt = 0;
191
192 VectorXi idxBest;
193 MatrixXd Cbest;
194 VectorXd sumDBest;
195 MatrixXd Dbest;
196
197 std::uniform_int_distribution<qint32> sampleDist(0, n - 1);
198
199 for (qint32 rep = 0; rep < m_iReps; ++rep)
200 {
201 // --- Initialize centroids ---
202 if (m_start == KMeansStart::Uniform)
203 {
204 C = MatrixXd::Zero(k, p);
205 for (qint32 i = 0; i < k; ++i)
206 {
207 for (qint32 j = 0; j < p; ++j)
208 {
209 std::uniform_real_distribution<double> dist(Xmins[j], Xmaxs[j]);
210 C(i, j) = dist(m_rng);
211 }
212 }
213 if (m_distance == KMeansDistance::Correlation)
214 C.array() -= (C.array().rowwise().sum() / p).replicate(1, p).array();
215 }
216 else if (m_start == KMeansStart::Sample)
217 {
218 C = MatrixXd::Zero(k, p);
219 for (qint32 i = 0; i < k; ++i)
220 C.row(i) = X.row(sampleDist(m_rng));
221 }
222
223 // Compute initial distances and assignments
224 D = distfun(X, C);
225 idx = VectorXi::Zero(n);
226 d = VectorXd::Zero(n);
227
228 for (qint32 i = 0; i < n; ++i)
229 d[i] = D.row(i).minCoeff(&idx[i]);
230
231 m = VectorXi::Zero(k);
232 for (qint32 i = 0; i < n; ++i)
233 ++m[idx[i]];
234
235 try
236 {
237 // Phase 1: batch reassignments
238 bool converged = batchUpdate(X, C, idx);
239
240 // Phase 2: single reassignments
241 if (m_bOnline)
242 converged = onlineUpdate(X, C, idx);
243
244 if (!converged)
245 qWarning("KMeans: Failed to converge during replicate %d.", rep);
246
247 // Recompute distances for non-empty clusters only
248 VectorXi nonempties = (m.array() > 0).cast<int>();
249 qint32 count = nonempties.sum();
250
251 MatrixXd C_tmp(count, C.cols());
252 qint32 ci = 0;
253 for (qint32 i = 0; i < k; ++i)
254 if (nonempties[i])
255 C_tmp.row(ci++) = C.row(i);
256
257 MatrixXd D_tmp = distfun(X, C_tmp);
258 ci = 0;
259 for (qint32 i = 0; i < k; ++i)
260 {
261 if (nonempties[i])
262 {
263 D.col(i) = D_tmp.col(ci);
264 C.row(i) = C_tmp.row(ci);
265 ++ci;
266 }
267 }
268
269 // Per-point distance to assigned centroid
270 d = VectorXd::Zero(n);
271 for (qint32 i = 0; i < n; ++i)
272 d[i] = D(i, idx[i]);
273
274 // Cluster-wise sum of distances
275 sumD = VectorXd::Zero(k);
276 for (qint32 i = 0; i < n; ++i)
277 sumD[idx[i]] += d[i];
278
279 totsumD = sumD.sum();
280
281 // Keep the best replicate
282 if (totsumD < totsumDBest)
283 {
284 totsumDBest = totsumD;
285 idxBest = idx;
286 Cbest = C;
287 sumDBest = sumD;
288 Dbest = D;
289 }
290 }
291 catch (int)
292 {
293 if (m_iReps == 1)
294 return false;
295
296 ++emptyErrCnt;
297 if (emptyErrCnt == m_iReps)
298 return false;
299 }
300 }
301
302 idx = idxBest;
303 C = Cbest;
304 sumD = sumDBest;
305 D = Dbest;
306
307 return true;
308}
309
310//=============================================================================================================
311
312bool KMeans::batchUpdate(const MatrixXd& X, MatrixXd& C, VectorXi& idx)
313{
314 // Every point moved, every cluster will need an update
315 qint32 i = 0;
316 VectorXi moved(n);
317 for (i = 0; i < n; ++i)
318 moved[i] = i;
319
320 VectorXi changed(k);
321 for (i = 0; i < k; ++i)
322 changed[i] = i;
323
324 previdx = VectorXi::Zero(n);
325 prevtotsumD = std::numeric_limits<double>::max();
326
327 MatrixXd D = MatrixXd::Zero(n, k);
328
329 iter = 0;
330 bool converged = false;
331 while (true)
332 {
333 ++iter;
334
335 // Recompute centroids for changed clusters and their distances
336 MatrixXd C_new;
337 VectorXi m_new;
338 gcentroids(X, idx, changed, C_new, m_new);
339 MatrixXd D_new = distfun(X, C_new);
340
341 for (qint32 i = 0; i < changed.rows(); ++i)
342 {
343 C.row(changed[i]) = C_new.row(i);
344 D.col(changed[i]) = D_new.col(i);
345 m[changed[i]] = m_new[i];
346 }
347
348 // Handle clusters that just lost all members
349 VectorXi empties = VectorXi::Zero(changed.rows());
350 for (qint32 i = 0; i < changed.rows(); ++i)
351 if (m(i) == 0)
352 empties[i] = 1;
353
354 if (empties.sum() > 0)
355 {
356 if (m_emptyact == KMeansEmptyAction::Error)
357 {
358 return converged;
359 }
360 // Drop and Singleton actions: not yet implemented (kept as no-op)
361 }
362
363 // Total sum of distances for the current configuration
364 totsumD = 0;
365 for (qint32 i = 0; i < n; ++i)
366 totsumD += D(i, idx[i]);
367
368 // Cycle detection: if objective did not decrease, revert last step
369 if (prevtotsumD <= totsumD)
370 {
371 idx = previdx;
372 MatrixXd C_rev;
373 VectorXi m_rev;
374 gcentroids(X, idx, changed, C_rev, m_rev);
375 C.block(0, 0, k, C.cols()) = C_rev;
376 m.block(0, 0, k, 1) = m_rev;
377 --iter;
378 break;
379 }
380
381 if (iter >= m_iMaxit)
382 break;
383
384 // Reassign points to nearest centroid
385 previdx = idx;
386 prevtotsumD = totsumD;
387
388 VectorXi nidx(n);
389 for (qint32 i = 0; i < n; ++i)
390 d[i] = D.row(i).minCoeff(&nidx[i]);
391
392 // Determine which points moved
393 std::vector<int> movedVec;
394 movedVec.reserve(n);
395 for (qint32 i = 0; i < n; ++i)
396 {
397 if (nidx[i] != previdx[i])
398 movedVec.push_back(i);
399 }
400
401 // Resolve ties in favor of not moving
402 std::vector<int> movedFinal;
403 movedFinal.reserve(movedVec.size());
404 for (int mi : movedVec)
405 {
406 if (D(mi, previdx[mi]) > d[mi])
407 movedFinal.push_back(mi);
408 }
409
410 if (movedFinal.empty())
411 {
412 converged = true;
413 break;
414 }
415
416 for (int mi : movedFinal)
417 idx[mi] = nidx[mi];
418
419 // Find clusters that gained or lost members
420 std::vector<int> tmp;
421 tmp.reserve(2 * movedFinal.size());
422 for (int mi : movedFinal)
423 {
424 tmp.push_back(idx[mi]);
425 tmp.push_back(previdx[mi]);
426 }
427 std::sort(tmp.begin(), tmp.end());
428 tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
429
430 changed.resize(tmp.size());
431 for (size_t i = 0; i < tmp.size(); ++i)
432 changed[i] = tmp[i];
433 }
434 return converged;
435}
436
437//=============================================================================================================
438
439bool KMeans::onlineUpdate(const MatrixXd& X, MatrixXd& C, VectorXi& idx)
440{
441 // Initialize city-block median tracking if needed
442 MatrixXd Xmid1, Xmid2;
443 if (m_distance == KMeansDistance::CityBlock)
444 {
445 Xmid1 = MatrixXd::Zero(k, p);
446 Xmid2 = MatrixXd::Zero(k, p);
447 for (qint32 i = 0; i < k; ++i)
448 {
449 if (m[i] > 0)
450 {
451 MatrixXd Xsorted(m[i], p);
452 qint32 c = 0;
453 for (qint32 j = 0; j < n; ++j)
454 if (idx[j] == i)
455 Xsorted.row(c++) = X.row(j);
456
457 for (qint32 j = 0; j < p; ++j)
458 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
459
460 qint32 nn = static_cast<qint32>(std::floor(0.5 * m[i])) - 1;
461 if ((m[i] % 2) == 0)
462 {
463 Xmid1.row(i) = Xsorted.row(nn);
464 Xmid2.row(i) = Xsorted.row(nn + 1);
465 }
466 else if (m[i] > 1)
467 {
468 Xmid1.row(i) = Xsorted.row(nn);
469 Xmid2.row(i) = Xsorted.row(nn + 2);
470 }
471 else
472 {
473 Xmid1.row(i) = Xsorted.row(0);
474 Xmid2.row(i) = Xsorted.row(0);
475 }
476 }
477 }
478 }
479
480 // Build list of non-empty clusters
481 VectorXi changed(m.rows());
482 qint32 count = 0;
483 for (qint32 i = 0; i < m.rows(); ++i)
484 if (m[i] > 0)
485 changed[count++] = i;
486 changed.conservativeResize(count);
487
488 qint32 lastmoved = 0;
489 qint32 nummoved = 0;
490 qint32 iter1 = iter;
491 bool converged = false;
492
493 while (iter < m_iMaxit)
494 {
495 // Compute reassignment criterion Del for changed clusters
496 if (m_distance == KMeansDistance::SquaredEuclidean)
497 {
498 for (qint32 j = 0; j < changed.rows(); ++j)
499 {
500 qint32 i = changed[j];
501 VectorXi mbrs = VectorXi::Zero(n);
502 for (qint32 l = 0; l < n; ++l)
503 if (idx[l] == i)
504 mbrs[l] = 1;
505
506 VectorXi sgn = 1 - 2 * mbrs.array();
507 if (m[i] == 1)
508 for (qint32 l = 0; l < n; ++l)
509 if (mbrs[l])
510 sgn[l] = 0;
511
512 Del.col(i) = (static_cast<double>(m[i]) / (static_cast<double>(m[i]) + sgn.cast<double>().array()));
513 Del.col(i).array() *= (X.rowwise() - C.row(i)).array().pow(2).rowwise().sum().array();
514 }
515 }
516 else if (m_distance == KMeansDistance::CityBlock)
517 {
518 for (qint32 j = 0; j < changed.rows(); ++j)
519 {
520 qint32 i = changed[j];
521 if (m(i) % 2 == 0)
522 {
523 MatrixXd ldist = Xmid1.row(i).replicate(n, 1) - X;
524 MatrixXd rdist = X - Xmid2.row(i).replicate(n, 1);
525 VectorXd mbrs = VectorXd::Zero(n);
526 for (qint32 l = 0; l < n; ++l)
527 if (idx[l] == i)
528 mbrs[l] = 1;
529 MatrixXd sgn = ((-2 * mbrs).array() + 1).replicate(1, p);
530 rdist = sgn.array() * rdist.array();
531 ldist = sgn.array() * ldist.array();
532
533 for (qint32 l = 0; l < n; ++l)
534 {
535 double sum = 0;
536 for (qint32 h = 0; h < p; ++h)
537 sum += std::max(0.0, std::max(rdist(l, h), ldist(l, h)));
538 Del(l, i) = sum;
539 }
540 }
541 else
542 {
543 Del.col(i) = (X.rowwise() - C.row(i)).array().abs().rowwise().sum();
544 }
545 }
546 }
547 else if (m_distance == KMeansDistance::Cosine || m_distance == KMeansDistance::Correlation)
548 {
549 MatrixXd normC = C.array().pow(2).rowwise().sum().sqrt();
550 for (qint32 j = 0; j < changed.rows(); ++j)
551 {
552 qint32 i = changed[j];
553 MatrixXd XCi = X * C.row(i).transpose();
554
555 VectorXi mbrs = VectorXi::Zero(n);
556 for (qint32 l = 0; l < n; ++l)
557 if (idx[l] == i)
558 mbrs[l] = 1;
559
560 VectorXi sgn = 1 - 2 * mbrs.array();
561 double A = static_cast<double>(m[i]) * normC(i, 0);
562 double B = A * A;
563
564 Del.col(i) = 1 + sgn.cast<double>().array() *
565 (A - (B + 2 * sgn.cast<double>().array() * m[i] * XCi.array() + 1).sqrt());
566 }
567 }
568 // Hamming: not yet implemented
569
570 // Find best move for each point
571 previdx = idx;
572 prevtotsumD = totsumD;
573
574 VectorXi nidx = VectorXi::Zero(n);
575 VectorXd minDel = VectorXd::Zero(n);
576 for (qint32 i = 0; i < n; ++i)
577 minDel[i] = Del.row(i).minCoeff(&nidx[i]);
578
579 // Identify points that would move
580 std::vector<int> movedVec;
581 movedVec.reserve(n);
582 for (qint32 i = 0; i < n; ++i)
583 if (previdx[i] != nidx[i])
584 movedVec.push_back(i);
585
586 // Resolve ties in favor of not moving
587 std::vector<int> movedFinal;
588 movedFinal.reserve(movedVec.size());
589 for (int mi : movedVec)
590 if (Del(mi, previdx[mi]) > minDel(mi))
591 movedFinal.push_back(mi);
592
593 if (movedFinal.empty())
594 {
595 if ((iter == iter1) || nummoved > 0)
596 ++iter;
597 converged = true;
598 break;
599 }
600
601 // Pick the next move in cyclic order
602 int bestMoved = movedFinal[0];
603 int bestDist = ((movedFinal[0] - lastmoved) % n + n) % n;
604 for (size_t i = 1; i < movedFinal.size(); ++i)
605 {
606 int d_i = ((movedFinal[i] - lastmoved) % n + n) % n;
607 if (d_i < bestDist)
608 {
609 bestDist = d_i;
610 bestMoved = movedFinal[i];
611 }
612 }
613 int movedPt = bestMoved;
614
615 if (movedPt <= lastmoved)
616 {
617 ++iter;
618 if (iter >= m_iMaxit)
619 break;
620 nummoved = 0;
621 }
622 ++nummoved;
623 lastmoved = movedPt;
624
625 qint32 oidx = idx[movedPt];
626 qint32 nidx_pt = nidx[movedPt];
627 totsumD += Del(movedPt, nidx_pt) - Del(movedPt, oidx);
628
629 idx[movedPt] = nidx_pt;
630 m(nidx_pt) += 1;
631 m(oidx) -= 1;
632
633 // Update centroids for the affected clusters
634 if (m_distance == KMeansDistance::SquaredEuclidean)
635 {
636 C.row(nidx_pt) += (X.row(movedPt) - C.row(nidx_pt)) / m[nidx_pt];
637 C.row(oidx) -= (X.row(movedPt) - C.row(oidx)) / m[oidx];
638 }
639 else if (m_distance == KMeansDistance::CityBlock)
640 {
641 VectorXi onidx(2);
642 onidx << oidx, nidx_pt;
643
644 for (qint32 h = 0; h < 2; ++h)
645 {
646 qint32 ci = onidx[h];
647 MatrixXd Xsorted(m[ci], p);
648 qint32 c = 0;
649 for (qint32 j = 0; j < n; ++j)
650 if (idx[j] == ci)
651 Xsorted.row(c++) = X.row(j);
652
653 for (qint32 j = 0; j < p; ++j)
654 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
655
656 qint32 nn = static_cast<qint32>(std::floor(0.5 * m[ci])) - 1;
657 if ((m[ci] % 2) == 0)
658 {
659 C.row(ci) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
660 Xmid1.row(ci) = Xsorted.row(nn);
661 Xmid2.row(ci) = Xsorted.row(nn + 1);
662 }
663 else
664 {
665 C.row(ci) = Xsorted.row(nn + 1);
666 if (m(ci) > 1)
667 {
668 Xmid1.row(ci) = Xsorted.row(nn);
669 Xmid2.row(ci) = Xsorted.row(nn + 2);
670 }
671 else
672 {
673 Xmid1.row(ci) = Xsorted.row(0);
674 Xmid2.row(ci) = Xsorted.row(0);
675 }
676 }
677 }
678 }
679 else if (m_distance == KMeansDistance::Cosine || m_distance == KMeansDistance::Correlation)
680 {
681 C.row(nidx_pt).array() += (X.row(movedPt) - C.row(nidx_pt)).array() / m[nidx_pt];
682 C.row(oidx).array() += (X.row(movedPt) - C.row(oidx)).array() / m[oidx];
683 }
684
685 VectorXi sorted_onidx(2);
686 sorted_onidx << oidx, nidx_pt;
687 std::sort(sorted_onidx.data(), sorted_onidx.data() + sorted_onidx.rows());
688 changed = sorted_onidx;
689 }
690
691 return converged;
692}
693
694//=============================================================================================================
695
696MatrixXd KMeans::distfun(const MatrixXd& X, const MatrixXd& C)
697{
698 const qint32 nclusts = C.rows();
699 MatrixXd D = MatrixXd::Zero(n, nclusts);
700
701 switch (m_distance)
702 {
704 for (qint32 i = 0; i < nclusts; ++i)
705 D.col(i) = (X.rowwise() - C.row(i)).rowwise().squaredNorm();
706 break;
707
709 for (qint32 i = 0; i < nclusts; ++i)
710 D.col(i) = (X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum();
711 break;
712
715 {
716 VectorXd normC = C.rowwise().norm();
717 for (qint32 i = 0; i < nclusts; ++i)
718 {
719 RowVectorXd C_normed = C.row(i) / normC(i);
720 D.col(i) = (1.0 - (X * C_normed.transpose()).array()).cwiseMax(0.0);
721 }
722 break;
723 }
724
726 for (qint32 i = 0; i < nclusts; ++i)
727 D.col(i) = (X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum() / p;
728 break;
729 }
730
731 return D;
732}
733
734//=============================================================================================================
735
736void KMeans::gcentroids(const MatrixXd& X, const VectorXi& index, const VectorXi& clusts,
737 MatrixXd& centroids, VectorXi& counts)
738{
739 const qint32 num = clusts.rows();
740 centroids = MatrixXd::Constant(num, p, std::numeric_limits<double>::quiet_NaN());
741 counts = VectorXi::Zero(num);
742
743 for (qint32 i = 0; i < num; ++i)
744 {
745 // Collect member indices for cluster clusts[i]
746 std::vector<int> members;
747 members.reserve(n);
748 for (qint32 j = 0; j < index.rows(); ++j)
749 if (index[j] == clusts[i])
750 members.push_back(j);
751
752 counts[i] = static_cast<qint32>(members.size());
753 if (members.empty())
754 continue;
755
756 switch (m_distance)
757 {
761 {
762 centroids.row(i) = RowVectorXd::Zero(p);
763 for (int j : members)
764 centroids.row(i) += X.row(j);
765 centroids.row(i) /= counts[i];
766 break;
767 }
768
770 {
771 MatrixXd Xsorted(counts[i], p);
772 qint32 c = 0;
773 for (int j : members)
774 Xsorted.row(c++) = X.row(j);
775
776 for (qint32 j = 0; j < p; ++j)
777 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
778
779 qint32 nn = static_cast<qint32>(std::floor(0.5 * counts[i])) - 1;
780 if (counts[i] % 2 == 0)
781 centroids.row(i) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
782 else
783 centroids.row(i) = Xsorted.row(nn + 1);
784 break;
785 }
786
788 // Not yet implemented
789 break;
790 }
791 }
792}
constexpr int X
K-means partitional clustering with multiple distance metrics, initialisations and empty-cluster poli...
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
KMeansDistance
Distance metric for K-Means clustering.
Definition kmeans.h:77
KMeansEmptyAction
Action to take when a K-Means cluster becomes empty.
Definition kmeans.h:95
KMeansStart
Initialization strategy for K-Means clustering.
Definition kmeans.h:87
bool calculate(const Eigen::MatrixXd &X, qint32 kClusters, Eigen::VectorXi &idx, Eigen::MatrixXd &C, Eigen::VectorXd &sumD, Eigen::MatrixXd &D)
Definition kmeans.cpp:131
KMeans(QString distance=QString("sqeuclidean"), QString start=QString("sample"), qint32 replicates=1, QString emptyact=QString("error"), bool online=true, qint32 maxit=100)
Definition kmeans.cpp:56