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