102 const std::vector<std::vector<double>>& data,
105 std::size_t n = data.size();
107 std::vector<std::vector<double>> centroids;
108 centroids.reserve(k);
111 std::uniform_int_distribution<std::size_t> init_dist(0, n - 1);
114 centroids.push_back(data[init_dist(rng)]);
117 std::vector<double> distances(n);
119 for (std::size_t c = 1; c < k; ++c) {
120 double total_dist = 0.0;
122 for (std::size_t i = 0; i < n; ++i) {
123 double min_dist = std::numeric_limits<double>::max();
124 for (
const auto& centroid : centroids) {
126 min_dist = std::min(min_dist, dist);
128 distances[i] = min_dist * min_dist;
129 total_dist += distances[i];
133 if (total_dist <= 0.0) {
134 std::uniform_int_distribution<std::size_t> rand_dist(0, n - 1);
135 centroids.push_back(data[rand_dist(rng)]);
140 std::uniform_real_distribution<double> prob_dist(0.0, total_dist);
141 double threshold = prob_dist(rng);
144 for (std::size_t i = 0; i < n; ++i) {
145 cumsum += distances[i];
146 if (cumsum >= threshold) {
147 centroids.push_back(data[i]);
169 const std::vector<std::vector<double>>& data,
171 std::size_t max_iter = 100,
175 throw std::invalid_argument(
"statcpp::kmeans: empty data");
178 throw std::invalid_argument(
"statcpp::kmeans: k must be positive");
180 if (k > data.size()) {
181 throw std::invalid_argument(
"statcpp::kmeans: k exceeds number of data points");
184 std::size_t n = data.size();
185 std::size_t dim = data[0].size();
190 std::vector<std::size_t> labels(n);
191 std::size_t iter = 0;
193 for (iter = 0; iter < max_iter; ++iter) {
195 for (std::size_t i = 0; i < n; ++i) {
196 double min_dist = std::numeric_limits<double>::max();
197 std::size_t best_cluster = 0;
199 for (std::size_t c = 0; c < k; ++c) {
201 if (dist < min_dist) {
206 labels[i] = best_cluster;
210 std::vector<std::vector<double>> new_centroids(k, std::vector<double>(dim, 0.0));
211 std::vector<std::size_t> cluster_sizes(k, 0);
213 for (std::size_t i = 0; i < n; ++i) {
214 std::size_t c = labels[i];
216 for (std::size_t d = 0; d < dim; ++d) {
217 new_centroids[c][d] += data[i][d];
221 for (std::size_t c = 0; c < k; ++c) {
222 if (cluster_sizes[c] > 0) {
223 for (std::size_t d = 0; d < dim; ++d) {
224 new_centroids[c][d] /=
static_cast<double>(cluster_sizes[c]);
228 double max_dist = -1.0;
229 std::size_t farthest = 0;
230 for (std::size_t i = 0; i < n; ++i) {
232 if (dist > max_dist) {
237 new_centroids[c] = data[farthest];
242 double max_shift = 0.0;
243 for (std::size_t c = 0; c < k; ++c) {
245 max_shift = std::max(max_shift, shift);
248 centroids = std::move(new_centroids);
250 if (max_shift < tol) {
257 double inertia = 0.0;
258 for (std::size_t i = 0; i < n; ++i) {
260 inertia += dist * dist;
263 return {labels, centroids, inertia, iter};
303 const std::vector<std::vector<double>>& data,
307 throw std::invalid_argument(
"statcpp::hierarchical_clustering: empty data");
310 std::size_t n = data.size();
313 std::vector<std::vector<double>> dist_matrix(n, std::vector<double>(n, 0.0));
314 for (std::size_t i = 0; i < n; ++i) {
315 for (std::size_t j = i + 1; j < n; ++j) {
317 dist_matrix[i][j] = d;
318 dist_matrix[j][i] = d;
323 std::vector<bool> active(2 * n - 1,
false);
324 for (std::size_t i = 0; i < n; ++i) {
329 std::vector<std::size_t> cluster_size(2 * n - 1, 1);
332 std::vector<dendrogram_node> dendrogram;
333 dendrogram.reserve(n - 1);
338 std::vector<std::vector<double>> cluster_dist(2 * n - 1, std::vector<double>(2 * n - 1, std::numeric_limits<double>::max()));
340 for (std::size_t i = 0; i < n; ++i) {
341 for (std::size_t j = 0; j < n; ++j) {
342 double d = dist_matrix[i][j];
343 cluster_dist[i][j] = use_squared ? d * d : d;
347 for (std::size_t step = 0; step < n - 1; ++step) {
349 double min_dist = std::numeric_limits<double>::max();
350 std::size_t min_i = 0, min_j = 0;
352 for (std::size_t i = 0; i < n + step; ++i) {
353 if (!active[i])
continue;
354 for (std::size_t j = i + 1; j < n + step; ++j) {
355 if (!active[j])
continue;
356 if (cluster_dist[i][j] < min_dist) {
357 min_dist = cluster_dist[i][j];
365 std::size_t new_cluster = n + step;
366 active[min_i] =
false;
367 active[min_j] =
false;
368 active[new_cluster] =
true;
370 cluster_size[new_cluster] = cluster_size[min_i] + cluster_size[min_j];
373 double dendro_dist = use_squared ? std::sqrt(min_dist) : min_dist;
374 dendrogram.push_back({min_i, min_j, dendro_dist, cluster_size[new_cluster]});
377 for (std::size_t k = 0; k < new_cluster; ++k) {
378 if (!active[k])
continue;
383 new_dist = std::min(cluster_dist[min_i][k], cluster_dist[min_j][k]);
386 new_dist = std::max(cluster_dist[min_i][k], cluster_dist[min_j][k]);
389 new_dist = (cluster_size[min_i] * cluster_dist[min_i][k] +
390 cluster_size[min_j] * cluster_dist[min_j][k]) /
391 static_cast<double>(cluster_size[min_i] + cluster_size[min_j]);
395 double ni =
static_cast<double>(cluster_size[min_i]);
396 double nj =
static_cast<double>(cluster_size[min_j]);
397 double nk =
static_cast<double>(cluster_size[k]);
398 double nijk = ni + nj + nk;
399 new_dist = ((ni + nk) * cluster_dist[min_i][k] +
400 (nj + nk) * cluster_dist[min_j][k] -
401 nk * min_dist) / nijk;
406 cluster_dist[new_cluster][k] = new_dist;
407 cluster_dist[k][new_cluster] = new_dist;
426 const std::vector<dendrogram_node>& dendrogram,
430 if (k == 0 || k > n_data) {
431 throw std::invalid_argument(
"statcpp::cut_dendrogram: invalid k");
434 std::vector<std::size_t> labels(n_data);
435 std::iota(labels.begin(), labels.end(), 0);
442 std::size_t n_merges = n_data - k;
444 std::vector<std::size_t> cluster_map(2 * n_data - 1);
445 std::iota(cluster_map.begin(), cluster_map.end(), 0);
447 for (std::size_t i = 0; i < n_merges; ++i) {
448 const auto& node = dendrogram[i];
449 std::size_t new_cluster = n_data + i;
452 std::size_t left_label = cluster_map[node.left];
453 std::size_t right_label = cluster_map[node.right];
455 for (std::size_t j = 0; j < 2 * n_data - 1; ++j) {
456 if (cluster_map[j] == right_label) {
457 cluster_map[j] = left_label;
460 cluster_map[new_cluster] = left_label;
464 for (std::size_t i = 0; i < n_data; ++i) {
465 labels[i] = cluster_map[i];
469 std::map<std::size_t, std::size_t> label_map;
470 std::size_t next_label = 0;
471 for (std::size_t i = 0; i < n_data; ++i) {
472 if (label_map.find(labels[i]) == label_map.end()) {
473 label_map[labels[i]] = next_label++;
475 labels[i] = label_map[labels[i]];
499 const std::vector<std::vector<double>>& data,
500 const std::vector<std::size_t>& labels)
503 throw std::invalid_argument(
"statcpp::silhouette_score: empty data");
505 if (data.size() != labels.size()) {
506 throw std::invalid_argument(
"statcpp::silhouette_score: data and labels size mismatch");
509 std::size_t n = data.size();
512 std::size_t k = *std::max_element(labels.begin(), labels.end()) + 1;
517 double total_silhouette = 0.0;
519 for (std::size_t i = 0; i < n; ++i) {
522 std::size_t same_cluster_count = 0;
524 for (std::size_t j = 0; j < n; ++j) {
525 if (i != j && labels[j] == labels[i]) {
527 same_cluster_count++;
530 if (same_cluster_count > 0) {
531 a /=
static_cast<double>(same_cluster_count);
535 double b = std::numeric_limits<double>::max();
537 for (std::size_t c = 0; c < k; ++c) {
538 if (c == labels[i])
continue;
540 double cluster_dist = 0.0;
541 std::size_t cluster_count = 0;
543 for (std::size_t j = 0; j < n; ++j) {
544 if (labels[j] == c) {
550 if (cluster_count > 0) {
551 cluster_dist /=
static_cast<double>(cluster_count);
552 b = std::min(b, cluster_dist);
558 if (same_cluster_count > 0) {
559 s = (b - a) / std::max(a, b);
561 total_silhouette += s;
564 return total_silhouette /
static_cast<double>(n);