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::Correlation)
158 {
159 X.array() -= (X.rowwise().sum().array() / static_cast<double>(p)).replicate(1, p);
160 MatrixXd Xnorm = X.array().pow(2).rowwise().sum().sqrt();
161 X.array() /= Xnorm.replicate(1, p).array();
162 }
163
164 // Set up uniform initialization bounds if needed
165 RowVectorXd Xmins, Xmaxs;
166 if (m_start == KMeansStart::Uniform)
167 {
168 if (m_distance == KMeansDistance::Hamming)
169 {
170 qWarning("KMeans: Uniform initialization is not supported for Hamming distance.");
171 return false;
172 }
173 Xmins = X.colwise().minCoeff();
174 Xmaxs = X.colwise().maxCoeff();
175 }
176
177 // Prepare online-update workspace
178 if (m_bOnline)
179 {
180 Del = MatrixXd::Constant(n, k, std::numeric_limits<double>::quiet_NaN());
181 }
182
183 double totsumDBest = std::numeric_limits<double>::max();
184 emptyErrCnt = 0;
185
186 VectorXi idxBest;
187 MatrixXd Cbest;
188 VectorXd sumDBest;
189 MatrixXd Dbest;
190
191 std::uniform_int_distribution<qint32> sampleDist(0, n - 1);
192
193 for (qint32 rep = 0; rep < m_iReps; ++rep)
194 {
195 // --- Initialize centroids ---
196 if (m_start == KMeansStart::Uniform)
197 {
198 C = MatrixXd::Zero(k, p);
199 for (qint32 i = 0; i < k; ++i)
200 {
201 for (qint32 j = 0; j < p; ++j)
202 {
203 std::uniform_real_distribution<double> dist(Xmins[j], Xmaxs[j]);
204 C(i, j) = dist(m_rng);
205 }
206 }
207 if (m_distance == KMeansDistance::Correlation)
208 C.array() -= (C.array().rowwise().sum() / p).replicate(1, p).array();
209 }
210 else if (m_start == KMeansStart::Sample)
211 {
212 C = MatrixXd::Zero(k, p);
213 for (qint32 i = 0; i < k; ++i)
214 C.row(i) = X.row(sampleDist(m_rng));
215 }
216
217 // Compute initial distances and assignments
218 D = distfun(X, C);
219 idx = VectorXi::Zero(n);
220 d = VectorXd::Zero(n);
221
222 for (qint32 i = 0; i < n; ++i)
223 d[i] = D.row(i).minCoeff(&idx[i]);
224
225 m = VectorXi::Zero(k);
226 for (qint32 i = 0; i < n; ++i)
227 ++m[idx[i]];
228
229 try
230 {
231 // Phase 1: batch reassignments
232 bool converged = batchUpdate(X, C, idx);
233
234 // Phase 2: single reassignments
235 if (m_bOnline)
236 converged = onlineUpdate(X, C, idx);
237
238 if (!converged)
239 qWarning("KMeans: Failed to converge during replicate %d.", rep);
240
241 // Recompute distances for non-empty clusters only
242 VectorXi nonempties = (m.array() > 0).cast<int>();
243 qint32 count = nonempties.sum();
244
245 MatrixXd C_tmp(count, C.cols());
246 qint32 ci = 0;
247 for (qint32 i = 0; i < k; ++i)
248 if (nonempties[i])
249 C_tmp.row(ci++) = C.row(i);
250
251 MatrixXd D_tmp = distfun(X, C_tmp);
252 ci = 0;
253 for (qint32 i = 0; i < k; ++i)
254 {
255 if (nonempties[i])
256 {
257 D.col(i) = D_tmp.col(ci);
258 C.row(i) = C_tmp.row(ci);
259 ++ci;
260 }
261 }
262
263 // Per-point distance to assigned centroid
264 d = VectorXd::Zero(n);
265 for (qint32 i = 0; i < n; ++i)
266 d[i] = D(i, idx[i]);
267
268 // Cluster-wise sum of distances
269 sumD = VectorXd::Zero(k);
270 for (qint32 i = 0; i < n; ++i)
271 sumD[idx[i]] += d[i];
272
273 totsumD = sumD.sum();
274
275 // Keep the best replicate
276 if (totsumD < totsumDBest)
277 {
278 totsumDBest = totsumD;
279 idxBest = idx;
280 Cbest = C;
281 sumDBest = sumD;
282 Dbest = D;
283 }
284 }
285 catch (int)
286 {
287 if (m_iReps == 1)
288 return false;
289
290 ++emptyErrCnt;
291 if (emptyErrCnt == m_iReps)
292 return false;
293 }
294 }
295
296 idx = idxBest;
297 C = Cbest;
298 sumD = sumDBest;
299 D = Dbest;
300
301 return true;
302}
303
304//=============================================================================================================
305
306bool KMeans::batchUpdate(const MatrixXd& X, MatrixXd& C, VectorXi& idx)
307{
308 // Every point moved, every cluster will need an update
309 qint32 i = 0;
310 VectorXi moved(n);
311 for (i = 0; i < n; ++i)
312 moved[i] = i;
313
314 VectorXi changed(k);
315 for (i = 0; i < k; ++i)
316 changed[i] = i;
317
318 previdx = VectorXi::Zero(n);
319 prevtotsumD = std::numeric_limits<double>::max();
320
321 MatrixXd D = MatrixXd::Zero(n, k);
322
323 iter = 0;
324 bool converged = false;
325 while (true)
326 {
327 ++iter;
328
329 // Recompute centroids for changed clusters and their distances
330 MatrixXd C_new;
331 VectorXi m_new;
332 gcentroids(X, idx, changed, C_new, m_new);
333 MatrixXd D_new = distfun(X, C_new);
334
335 for (qint32 i = 0; i < changed.rows(); ++i)
336 {
337 C.row(changed[i]) = C_new.row(i);
338 D.col(changed[i]) = D_new.col(i);
339 m[changed[i]] = m_new[i];
340 }
341
342 // Handle clusters that just lost all members
343 VectorXi empties = VectorXi::Zero(changed.rows());
344 for (qint32 i = 0; i < changed.rows(); ++i)
345 if (m(i) == 0)
346 empties[i] = 1;
347
348 if (empties.sum() > 0)
349 {
350 if (m_emptyact == KMeansEmptyAction::Error)
351 {
352 return converged;
353 }
354 // Drop and Singleton actions: TODO — not yet implemented (kept as no-op)
355 }
356
357 // Total sum of distances for the current configuration
358 totsumD = 0;
359 for (qint32 i = 0; i < n; ++i)
360 totsumD += D(i, idx[i]);
361
362 // Cycle detection: if objective did not decrease, revert last step
363 if (prevtotsumD <= totsumD)
364 {
365 idx = previdx;
366 MatrixXd C_rev;
367 VectorXi m_rev;
368 gcentroids(X, idx, changed, C_rev, m_rev);
369 C.block(0, 0, k, C.cols()) = C_rev;
370 m.block(0, 0, k, 1) = m_rev;
371 --iter;
372 break;
373 }
374
375 if (iter >= m_iMaxit)
376 break;
377
378 // Reassign points to nearest centroid
379 previdx = idx;
380 prevtotsumD = totsumD;
381
382 VectorXi nidx(n);
383 for (qint32 i = 0; i < n; ++i)
384 d[i] = D.row(i).minCoeff(&nidx[i]);
385
386 // Determine which points moved
387 std::vector<int> movedVec;
388 movedVec.reserve(n);
389 for (qint32 i = 0; i < n; ++i)
390 {
391 if (nidx[i] != previdx[i])
392 movedVec.push_back(i);
393 }
394
395 // Resolve ties in favor of not moving
396 std::vector<int> movedFinal;
397 movedFinal.reserve(movedVec.size());
398 for (int mi : movedVec)
399 {
400 if (D(mi, previdx[mi]) > d[mi])
401 movedFinal.push_back(mi);
402 }
403
404 if (movedFinal.empty())
405 {
406 converged = true;
407 break;
408 }
409
410 for (int mi : movedFinal)
411 idx[mi] = nidx[mi];
412
413 // Find clusters that gained or lost members
414 std::vector<int> tmp;
415 tmp.reserve(2 * movedFinal.size());
416 for (int mi : movedFinal)
417 {
418 tmp.push_back(idx[mi]);
419 tmp.push_back(previdx[mi]);
420 }
421 std::sort(tmp.begin(), tmp.end());
422 tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
423
424 changed.resize(tmp.size());
425 for (size_t i = 0; i < tmp.size(); ++i)
426 changed[i] = tmp[i];
427 }
428 return converged;
429}
430
431//=============================================================================================================
432
433bool KMeans::onlineUpdate(const MatrixXd& X, MatrixXd& C, VectorXi& idx)
434{
435 // Initialize city-block median tracking if needed
436 MatrixXd Xmid1, Xmid2;
437 if (m_distance == KMeansDistance::CityBlock)
438 {
439 Xmid1 = MatrixXd::Zero(k, p);
440 Xmid2 = MatrixXd::Zero(k, p);
441 for (qint32 i = 0; i < k; ++i)
442 {
443 if (m[i] > 0)
444 {
445 MatrixXd Xsorted(m[i], p);
446 qint32 c = 0;
447 for (qint32 j = 0; j < n; ++j)
448 if (idx[j] == i)
449 Xsorted.row(c++) = X.row(j);
450
451 for (qint32 j = 0; j < p; ++j)
452 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
453
454 qint32 nn = static_cast<qint32>(std::floor(0.5 * m[i])) - 1;
455 if ((m[i] % 2) == 0)
456 {
457 Xmid1.row(i) = Xsorted.row(nn);
458 Xmid2.row(i) = Xsorted.row(nn + 1);
459 }
460 else if (m[i] > 1)
461 {
462 Xmid1.row(i) = Xsorted.row(nn);
463 Xmid2.row(i) = Xsorted.row(nn + 2);
464 }
465 else
466 {
467 Xmid1.row(i) = Xsorted.row(0);
468 Xmid2.row(i) = Xsorted.row(0);
469 }
470 }
471 }
472 }
473
474 // Build list of non-empty clusters
475 VectorXi changed(m.rows());
476 qint32 count = 0;
477 for (qint32 i = 0; i < m.rows(); ++i)
478 if (m[i] > 0)
479 changed[count++] = i;
480 changed.conservativeResize(count);
481
482 qint32 lastmoved = 0;
483 qint32 nummoved = 0;
484 qint32 iter1 = iter;
485 bool converged = false;
486
487 while (iter < m_iMaxit)
488 {
489 // Compute reassignment criterion Del for changed clusters
490 if (m_distance == KMeansDistance::SquaredEuclidean)
491 {
492 for (qint32 j = 0; j < changed.rows(); ++j)
493 {
494 qint32 i = changed[j];
495 VectorXi mbrs = VectorXi::Zero(n);
496 for (qint32 l = 0; l < n; ++l)
497 if (idx[l] == i)
498 mbrs[l] = 1;
499
500 VectorXi sgn = 1 - 2 * mbrs.array();
501 if (m[i] == 1)
502 for (qint32 l = 0; l < n; ++l)
503 if (mbrs[l])
504 sgn[l] = 0;
505
506 Del.col(i) = (static_cast<double>(m[i]) / (static_cast<double>(m[i]) + sgn.cast<double>().array()));
507 Del.col(i).array() *= (X.rowwise() - C.row(i)).array().pow(2).rowwise().sum().array();
508 }
509 }
510 else if (m_distance == KMeansDistance::CityBlock)
511 {
512 for (qint32 j = 0; j < changed.rows(); ++j)
513 {
514 qint32 i = changed[j];
515 if (m(i) % 2 == 0)
516 {
517 MatrixXd ldist = Xmid1.row(i).replicate(n, 1) - X;
518 MatrixXd rdist = X - Xmid2.row(i).replicate(n, 1);
519 VectorXd mbrs = VectorXd::Zero(n);
520 for (qint32 l = 0; l < n; ++l)
521 if (idx[l] == i)
522 mbrs[l] = 1;
523 MatrixXd sgn = ((-2 * mbrs).array() + 1).replicate(1, p);
524 rdist = sgn.array() * rdist.array();
525 ldist = sgn.array() * ldist.array();
526
527 for (qint32 l = 0; l < n; ++l)
528 {
529 double sum = 0;
530 for (qint32 h = 0; h < p; ++h)
531 sum += std::max(0.0, std::max(rdist(l, h), ldist(l, h)));
532 Del(l, i) = sum;
533 }
534 }
535 else
536 {
537 Del.col(i) = (X.rowwise() - C.row(i)).array().abs().rowwise().sum();
538 }
539 }
540 }
541 else if (m_distance == KMeansDistance::Cosine || m_distance == KMeansDistance::Correlation)
542 {
543 MatrixXd normC = C.array().pow(2).rowwise().sum().sqrt();
544 for (qint32 j = 0; j < changed.rows(); ++j)
545 {
546 qint32 i = changed[j];
547 MatrixXd XCi = X * C.row(i).transpose();
548
549 VectorXi mbrs = VectorXi::Zero(n);
550 for (qint32 l = 0; l < n; ++l)
551 if (idx[l] == i)
552 mbrs[l] = 1;
553
554 VectorXi sgn = 1 - 2 * mbrs.array();
555 double A = static_cast<double>(m[i]) * normC(i, 0);
556 double B = A * A;
557
558 Del.col(i) = 1 + sgn.cast<double>().array() *
559 (A - (B + 2 * sgn.cast<double>().array() * m[i] * XCi.array() + 1).sqrt());
560 }
561 }
562 // Hamming: not yet implemented
563
564 // Find best move for each point
565 previdx = idx;
566 prevtotsumD = totsumD;
567
568 VectorXi nidx = VectorXi::Zero(n);
569 VectorXd minDel = VectorXd::Zero(n);
570 for (qint32 i = 0; i < n; ++i)
571 minDel[i] = Del.row(i).minCoeff(&nidx[i]);
572
573 // Identify points that would move
574 std::vector<int> movedVec;
575 movedVec.reserve(n);
576 for (qint32 i = 0; i < n; ++i)
577 if (previdx[i] != nidx[i])
578 movedVec.push_back(i);
579
580 // Resolve ties in favor of not moving
581 std::vector<int> movedFinal;
582 movedFinal.reserve(movedVec.size());
583 for (int mi : movedVec)
584 if (Del(mi, previdx[mi]) > minDel(mi))
585 movedFinal.push_back(mi);
586
587 if (movedFinal.empty())
588 {
589 if ((iter == iter1) || nummoved > 0)
590 ++iter;
591 converged = true;
592 break;
593 }
594
595 // Pick the next move in cyclic order
596 int bestMoved = movedFinal[0];
597 int bestDist = ((movedFinal[0] - lastmoved) % n + n) % n;
598 for (size_t i = 1; i < movedFinal.size(); ++i)
599 {
600 int d_i = ((movedFinal[i] - lastmoved) % n + n) % n;
601 if (d_i < bestDist)
602 {
603 bestDist = d_i;
604 bestMoved = movedFinal[i];
605 }
606 }
607 int movedPt = bestMoved;
608
609 if (movedPt <= lastmoved)
610 {
611 ++iter;
612 if (iter >= m_iMaxit)
613 break;
614 nummoved = 0;
615 }
616 ++nummoved;
617 lastmoved = movedPt;
618
619 qint32 oidx = idx[movedPt];
620 qint32 nidx_pt = nidx[movedPt];
621 totsumD += Del(movedPt, nidx_pt) - Del(movedPt, oidx);
622
623 idx[movedPt] = nidx_pt;
624 m(nidx_pt) += 1;
625 m(oidx) -= 1;
626
627 // Update centroids for the affected clusters
628 if (m_distance == KMeansDistance::SquaredEuclidean)
629 {
630 C.row(nidx_pt) += (X.row(movedPt) - C.row(nidx_pt)) / m[nidx_pt];
631 C.row(oidx) -= (X.row(movedPt) - C.row(oidx)) / m[oidx];
632 }
633 else if (m_distance == KMeansDistance::CityBlock)
634 {
635 VectorXi onidx(2);
636 onidx << oidx, nidx_pt;
637
638 for (qint32 h = 0; h < 2; ++h)
639 {
640 qint32 ci = onidx[h];
641 MatrixXd Xsorted(m[ci], p);
642 qint32 c = 0;
643 for (qint32 j = 0; j < n; ++j)
644 if (idx[j] == ci)
645 Xsorted.row(c++) = X.row(j);
646
647 for (qint32 j = 0; j < p; ++j)
648 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
649
650 qint32 nn = static_cast<qint32>(std::floor(0.5 * m[ci])) - 1;
651 if ((m[ci] % 2) == 0)
652 {
653 C.row(ci) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
654 Xmid1.row(ci) = Xsorted.row(nn);
655 Xmid2.row(ci) = Xsorted.row(nn + 1);
656 }
657 else
658 {
659 C.row(ci) = Xsorted.row(nn + 1);
660 if (m(ci) > 1)
661 {
662 Xmid1.row(ci) = Xsorted.row(nn);
663 Xmid2.row(ci) = Xsorted.row(nn + 2);
664 }
665 else
666 {
667 Xmid1.row(ci) = Xsorted.row(0);
668 Xmid2.row(ci) = Xsorted.row(0);
669 }
670 }
671 }
672 }
673 else if (m_distance == KMeansDistance::Cosine || m_distance == KMeansDistance::Correlation)
674 {
675 C.row(nidx_pt).array() += (X.row(movedPt) - C.row(nidx_pt)).array() / m[nidx_pt];
676 C.row(oidx).array() += (X.row(movedPt) - C.row(oidx)).array() / m[oidx];
677 }
678
679 VectorXi sorted_onidx(2);
680 sorted_onidx << oidx, nidx_pt;
681 std::sort(sorted_onidx.data(), sorted_onidx.data() + sorted_onidx.rows());
682 changed = sorted_onidx;
683 }
684
685 return converged;
686}
687
688//=============================================================================================================
689
690MatrixXd KMeans::distfun(const MatrixXd& X, const MatrixXd& C)
691{
692 const qint32 nclusts = C.rows();
693 MatrixXd D = MatrixXd::Zero(n, nclusts);
694
695 switch (m_distance)
696 {
698 for (qint32 i = 0; i < nclusts; ++i)
699 D.col(i) = (X.rowwise() - C.row(i)).rowwise().squaredNorm();
700 break;
701
703 for (qint32 i = 0; i < nclusts; ++i)
704 D.col(i) = (X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum();
705 break;
706
709 {
710 VectorXd normC = C.rowwise().norm();
711 for (qint32 i = 0; i < nclusts; ++i)
712 {
713 RowVectorXd C_normed = C.row(i) / normC(i);
714 D.col(i) = (1.0 - (X * C_normed.transpose()).array()).cwiseMax(0.0);
715 }
716 break;
717 }
718
720 for (qint32 i = 0; i < nclusts; ++i)
721 D.col(i) = (X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum() / p;
722 break;
723 }
724
725 return D;
726}
727
728//=============================================================================================================
729
730void KMeans::gcentroids(const MatrixXd& X, const VectorXi& index, const VectorXi& clusts,
731 MatrixXd& centroids, VectorXi& counts)
732{
733 const qint32 num = clusts.rows();
734 centroids = MatrixXd::Constant(num, p, std::numeric_limits<double>::quiet_NaN());
735 counts = VectorXi::Zero(num);
736
737 for (qint32 i = 0; i < num; ++i)
738 {
739 // Collect member indices for cluster clusts[i]
740 std::vector<int> members;
741 members.reserve(n);
742 for (qint32 j = 0; j < index.rows(); ++j)
743 if (index[j] == clusts[i])
744 members.push_back(j);
745
746 counts[i] = static_cast<qint32>(members.size());
747 if (members.empty())
748 continue;
749
750 switch (m_distance)
751 {
755 {
756 centroids.row(i) = RowVectorXd::Zero(p);
757 for (int j : members)
758 centroids.row(i) += X.row(j);
759 centroids.row(i) /= counts[i];
760 break;
761 }
762
764 {
765 MatrixXd Xsorted(counts[i], p);
766 qint32 c = 0;
767 for (int j : members)
768 Xsorted.row(c++) = X.row(j);
769
770 for (qint32 j = 0; j < p; ++j)
771 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
772
773 qint32 nn = static_cast<qint32>(std::floor(0.5 * counts[i])) - 1;
774 if (counts[i] % 2 == 0)
775 centroids.row(i) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
776 else
777 centroids.row(i) = Xsorted.row(nn + 1);
778 break;
779 }
780
782 // Not yet implemented
783 break;
784 }
785 }
786}
#define X
KMeans class declaration.
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