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