From 1cd89853b83f4214b47ea326e35f7636ecdf0e27 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Mon, 18 Dec 2023 15:05:22 +0200 Subject: [PATCH 01/18] Added validity checks for `k` and the number of observations --- DESCRIPTION | 2 +- NEWS.md | 4 +++- R/TGL_kmeans.R | 9 +++++++++ tests/testthat/test-clustering.R | 5 +++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3981cb1..7f63a2c 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: tglkmeans Title: Efficient Implementation of K-Means++ Algorithm -Version: 0.3.11 +Version: 0.3.12 Authors@R: c(person(given = "Aviezer", family = "Lifshitz", diff --git a/NEWS.md b/NEWS.md index e171a23..e3a9fbb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,6 @@ -# tglkmeans (development version) +# tglkmeans 0.3.12 + +* Added validity checks for `k` and the number of observations. # tgkmeans 0.3.11 diff --git a/R/TGL_kmeans.R b/R/TGL_kmeans.R index 09735c4..df3cc98 100755 --- a/R/TGL_kmeans.R +++ b/R/TGL_kmeans.R @@ -76,6 +76,15 @@ TGL_kmeans_tidy <- function(df, df <- add_id_column(df) } } + + if (k < 1) { + stop("k must be greater than 0") + } + + if (nrow(df) < k) { + stop(paste0("number of observations (", nrow(df), ") must be greater than k (", k, ")")) + } + mat <- t(df[, -1]) # Thorw an error if there are rows that do not contain any value diff --git a/tests/testthat/test-clustering.R b/tests/testthat/test-clustering.R index ef30f85..858b69b 100755 --- a/tests/testthat/test-clustering.R +++ b/tests/testthat/test-clustering.R @@ -240,3 +240,8 @@ test_that("true_clust column is not added when add_true_clust is FALSE", { data <- simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL, add_true_clust = FALSE) expect_true(!("true_clust" %in% colnames(data))) }) + +test_that("and error is thrown when number of observations is less than number of clusters", { + expect_error(TGL_kmeans(data.frame(id = 1:10, V1 = rnorm(10)), 30, metric = "euclid", verbose = FALSE, seed = 60427)) + expect_error(TGL_kmeans(data.frame(id = numeric(0)))) +}) \ No newline at end of file From 6f66d75b5fe77f47801851ab833c195e64baa12c Mon Sep 17 00:00:00 2001 From: aviezerl Date: Mon, 18 Dec 2023 15:05:29 +0200 Subject: [PATCH 02/18] added github actions --- .Rbuildignore | 1 + .github/.gitignore | 1 + .github/workflows/R-CMD-check.yaml | 49 ++++++++++++++++++++ .github/workflows/pkgdown.yaml | 48 ++++++++++++++++++++ .github/workflows/style.yaml | 73 ++++++++++++++++++++++++++++++ README.Rmd | 1 + 6 files changed, 173 insertions(+) create mode 100644 .github/.gitignore create mode 100644 .github/workflows/R-CMD-check.yaml create mode 100644 .github/workflows/pkgdown.yaml create mode 100644 .github/workflows/style.yaml diff --git a/.Rbuildignore b/.Rbuildignore index 0cd4c56..6533a25 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -15,3 +15,4 @@ ^Meta$ ^cran-comments\.md$ ^CRAN-SUBMISSION$ +^\.github$ diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..a3ac618 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,49 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: R-CMD-check + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..ed7650c --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,48 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + release: + types: [published] + workflow_dispatch: + +name: pkgdown + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.4.1 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/style.yaml b/.github/workflows/style.yaml new file mode 100644 index 0000000..005b32d --- /dev/null +++ b/.github/workflows/style.yaml @@ -0,0 +1,73 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + paths: ["**.[rR]", "**.[qrR]md", "**.[rR]markdown", "**.[rR]nw", "**.[rR]profile"] + +name: Style + +jobs: + style: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup R + uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - name: Install dependencies + uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::styler, any::roxygen2 + needs: styler + + - name: Enable styler cache + run: styler::cache_activate() + shell: Rscript {0} + + - name: Determine cache location + id: styler-location + run: | + cat( + "location=", + styler::cache_info(format = "tabular")$location, + "\n", + file = Sys.getenv("GITHUB_OUTPUT"), + append = TRUE, + sep = "" + ) + shell: Rscript {0} + + - name: Cache styler + uses: actions/cache@v3 + with: + path: ${{ steps.styler-location.outputs.location }} + key: ${{ runner.os }}-styler-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-styler- + ${{ runner.os }}- + + - name: Style + run: styler::style_pkg(indent_by = 4) + shell: Rscript {0} + + - name: Commit and push changes + run: | + if FILES_TO_COMMIT=($(git diff-index --name-only ${{ github.sha }} \ + | egrep --ignore-case '\.(R|[qR]md|Rmarkdown|Rnw|Rprofile)$')) + then + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" + git commit ${FILES_TO_COMMIT[*]} -m "Style code (GHA)" + git pull --ff-only + git push origin + else + echo "No changes to commit." + fi diff --git a/README.Rmd b/README.Rmd index c7f0dd0..4f127fa 100755 --- a/README.Rmd +++ b/README.Rmd @@ -13,6 +13,7 @@ knitr::opts_chunk$set( [![CRAN status](https://www.r-pkg.org/badges/version/tglkmeans)](https://CRAN.R-project.org/package=tglkmeans) [![Codecov test coverage](https://codecov.io/gh/tanaylab/tglkmeans/branch/master/graph/badge.svg)](https://app.codecov.io/gh/tanaylab/tglkmeans?branch=master) +[![R-CMD-check](https://github.com/tanaylab/tglkmeans/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tanaylab/tglkmeans/actions/workflows/R-CMD-check.yaml) From 5921a1e7179035e259a0d9f6d1370851fea61ef8 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Mon, 25 Dec 2023 10:00:53 +0200 Subject: [PATCH 03/18] Added parallelizm to initialization step --- .Rbuildignore | 1 + .gitignore | 1 + DESCRIPTION | 6 ++-- man/TGL_kmeans.Rd | 2 +- man/TGL_kmeans_tidy.Rd | 2 +- src/KMeans.cpp | 48 +++++++++++--------------------- src/Makevar | 1 + src/UpdateMinDistanceWorker.cpp | 22 +++++++++++++++ src/UpdateMinDistanceWorker.h | 27 ++++++++++++++++++ tests/testthat/test-clustering.R | 2 +- 10 files changed, 75 insertions(+), 37 deletions(-) create mode 100644 src/Makevar create mode 100644 src/UpdateMinDistanceWorker.cpp create mode 100644 src/UpdateMinDistanceWorker.h diff --git a/.Rbuildignore b/.Rbuildignore index 6533a25..874914d 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -16,3 +16,4 @@ ^cran-comments\.md$ ^CRAN-SUBMISSION$ ^\.github$ +^work$ diff --git a/.gitignore b/.gitignore index 6553538..e72778e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ README.html /doc/ /Meta/ src/symbols.rds +work diff --git a/DESCRIPTION b/DESCRIPTION index 7f63a2c..9981e9e 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -29,7 +29,8 @@ Imports: parallel (>= 3.3.2), plyr (>= 1.8.4), purrr (>= 0.2.0), - tgstat (>= 1.0.0) + tgstat (>= 1.0.0), + RcppParallel Suggests: covr, knitr, @@ -38,7 +39,8 @@ Suggests: testthat, withr LinkingTo: - Rcpp + Rcpp, + RcppParallel VignetteBuilder: knitr Encoding: UTF-8 diff --git a/man/TGL_kmeans.Rd b/man/TGL_kmeans.Rd index c35a93f..3c0d841 100644 --- a/man/TGL_kmeans.Rd +++ b/man/TGL_kmeans.Rd @@ -9,7 +9,7 @@ TGL_kmeans( k, metric = "euclid", max_iter = 40, - min_delta = 1e-04, + min_delta = 0.0001, verbose = FALSE, keep_log = FALSE, id_column = TRUE, diff --git a/man/TGL_kmeans_tidy.Rd b/man/TGL_kmeans_tidy.Rd index ce83002..f60a740 100644 --- a/man/TGL_kmeans_tidy.Rd +++ b/man/TGL_kmeans_tidy.Rd @@ -9,7 +9,7 @@ TGL_kmeans_tidy( k, metric = "euclid", max_iter = 40, - min_delta = 1e-04, + min_delta = 0.0001, verbose = FALSE, keep_log = FALSE, id_column = TRUE, diff --git a/src/KMeans.cpp b/src/KMeans.cpp index 63805ac..aa0dcb1 100644 --- a/src/KMeans.cpp +++ b/src/KMeans.cpp @@ -5,6 +5,7 @@ #include #include "KMeans.h" #include "Random.h" +#include "UpdateMinDistanceWorker.h" #include KMeans::KMeans(const vector> &data, int k, vector ¢ers) : @@ -15,49 +16,50 @@ KMeans::KMeans(const vector> &data, int k, vector min_assign_change_fraction) { - Rcpp::Rcout << "KMEans: iter " << iter << endl; + Rcpp::Rcout << "tglkmeans: iter " << iter << endl; m_changes = 0; update_centers(); reassign(); iter++; - Rcpp::Rcout << "KMEans: iter " << iter << " changed " << m_changes << endl; + Rcpp::Rcout << "tglkmeans: iter " << iter << " changed " << m_changes << endl; } } void KMeans::generate_seeds() { - Rcpp::Rcout << "KMeans into generate seeds" << endl; + Rcpp::Rcout << "tglkmeans: generating seeds" << endl; for (int i = 0; i < m_k; i++) { - Rcpp::Rcout << "at seed " << i << endl; + Rcpp::Rcout << "tglkmeans: at seed " << i << endl; m_min_dist.resize(0); //compute minimal distance from centers //select next seed by sampling int seed_i = -1; if (i == 0) { + // select the first seed randomly seed_i = Random::fraction() * m_data.size(); } else { update_min_distance(i); - Rcpp::Rcout << "done update min distance" << endl; + Rcpp::Rcout << "tglkmeans: done update min distance" << endl; sort(m_min_dist.begin(), m_min_dist.end()); //select from 1/k of the data which is in the 1-1/2k quantile of the min distance int to_i = int(m_min_dist.size() * (1 - 1 / (2 * m_k))); int from_i = to_i - int(m_data.size() / m_k); - Rcpp::Rcout << "seed range " << from_i << " " << to_i << endl; + Rcpp::Rcout << "tglkmeans: seed range " << from_i << " " << to_i << endl; if (from_i < 0) { from_i = 0; } int rnd_i = from_i + int(Random::fraction() * (to_i - from_i)); seed_i = m_min_dist[rnd_i].second; - Rcpp::Rcout << "picked up " << seed_i << " dist was " << m_min_dist[rnd_i].first << endl; + Rcpp::Rcout << "tglkmeans: picked up " << seed_i << " dist was " << m_min_dist[rnd_i].first << endl; } add_new_core(seed_i, i); @@ -66,32 +68,14 @@ void KMeans::generate_seeds() { void KMeans::update_min_distance(int cur_k) { - vector::iterator assign_i = m_assignment.begin(); - int samp_i = 0; - for (auto data_i = m_data.begin(); data_i != m_data.end(); data_i++) { - if (*assign_i != -1) { - samp_i++; - assign_i++; - continue; - } - float best_dist = REAL_MAX; - int id_i = 0; - for (auto cent_i = m_centers.begin(); id_i < cur_k; cent_i++) { - float dist = (*cent_i)->dist(*data_i); - if (dist < best_dist) { - best_dist = dist; - } - id_i++; - } - m_min_dist.push_back(pair(best_dist, samp_i)); - - samp_i++; - assign_i++; - } + m_min_dist.resize(m_data.size()); + UpdateMinDistanceWorker worker(m_data, m_centers, m_min_dist, cur_k); + RcppParallel::parallelFor(0, m_data.size(), worker); } + void KMeans::add_new_core(int seed_i, int center_i) { - Rcpp::Rcout << "add new core from " << seed_i << " to " << center_i << endl; + Rcpp::Rcout << "tglkmeans: add new core from " << seed_i << " to " << center_i << endl; m_centers[center_i]->reset_votes(); m_centers[center_i]->vote(m_data[seed_i], 1); m_centers[center_i]->init_to_votes(); diff --git a/src/Makevar b/src/Makevar new file mode 100644 index 0000000..76ec185 --- /dev/null +++ b/src/Makevar @@ -0,0 +1 @@ +PKG_LIBS += $(shell ${R_HOME}/bin/Rscript -e "RcppParallel::RcppParallelLibs()") \ No newline at end of file diff --git a/src/UpdateMinDistanceWorker.cpp b/src/UpdateMinDistanceWorker.cpp new file mode 100644 index 0000000..2cd1154 --- /dev/null +++ b/src/UpdateMinDistanceWorker.cpp @@ -0,0 +1,22 @@ +#include "UpdateMinDistanceWorker.h" + +UpdateMinDistanceWorker::UpdateMinDistanceWorker(const vector>& data, + vector& centers, + vector>& min_dist, + int cur_k) + : data(data), centers(centers), min_dist(min_dist), cur_k(cur_k) {} + +void UpdateMinDistanceWorker::operator()(std::size_t begin, std::size_t end) { + for (std::size_t i = begin; i < end; ++i) { + float best_dist = REAL_MAX; + int id_i = 0; + for (auto cent_i = centers.begin(); id_i < cur_k; cent_i++) { + float dist = (*cent_i)->dist(data[i]); + if (dist < best_dist) { + best_dist = dist; + } + id_i++; + } + min_dist[i] = std::make_pair(best_dist, i); + } +} diff --git a/src/UpdateMinDistanceWorker.h b/src/UpdateMinDistanceWorker.h new file mode 100644 index 0000000..0f59f47 --- /dev/null +++ b/src/UpdateMinDistanceWorker.h @@ -0,0 +1,27 @@ +#ifndef UPDATEMINDISTANCEWORKER_H +#define UPDATEMINDISTANCEWORKER_H + +// [[Rcpp::depends(RcppParallel)]] +#include +#include "KMeansCenterBase.h" +#include + +using namespace std; + +class UpdateMinDistanceWorker : public RcppParallel::Worker { +private: + const vector>& data; + vector& centers; + vector>& min_dist; + const int cur_k; + +public: + UpdateMinDistanceWorker(const vector>& data, + vector& centers, + vector>& min_dist, + int cur_k); + + void operator()(std::size_t begin, std::size_t end); +}; + +#endif // UPDATEMINDISTANCEWORKER_H diff --git a/tests/testthat/test-clustering.R b/tests/testthat/test-clustering.R index 858b69b..f740046 100755 --- a/tests/testthat/test-clustering.R +++ b/tests/testthat/test-clustering.R @@ -244,4 +244,4 @@ test_that("true_clust column is not added when add_true_clust is FALSE", { test_that("and error is thrown when number of observations is less than number of clusters", { expect_error(TGL_kmeans(data.frame(id = 1:10, V1 = rnorm(10)), 30, metric = "euclid", verbose = FALSE, seed = 60427)) expect_error(TGL_kmeans(data.frame(id = numeric(0)))) -}) \ No newline at end of file +}) From 41316c8632ed88fcc0c24a498cc20f59d8df1e83 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Mon, 25 Dec 2023 10:08:30 +0200 Subject: [PATCH 04/18] Added Rcpp::checkUserInterrupt calls --- src/KMeans.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/KMeans.cpp b/src/KMeans.cpp index aa0dcb1..554ca6b 100644 --- a/src/KMeans.cpp +++ b/src/KMeans.cpp @@ -32,12 +32,13 @@ void KMeans::cluster(int max_iter, float min_assign_change_fraction) { reassign(); iter++; Rcpp::Rcout << "tglkmeans: iter " << iter << " changed " << m_changes << endl; + Rcpp::checkUserInterrupt(); } } void KMeans::generate_seeds() { Rcpp::Rcout << "tglkmeans: generating seeds" << endl; - for (int i = 0; i < m_k; i++) { + for (int i = 0; i < m_k; i++) { Rcpp::Rcout << "tglkmeans: at seed " << i << endl; m_min_dist.resize(0); //compute minimal distance from centers @@ -63,6 +64,7 @@ void KMeans::generate_seeds() { } add_new_core(seed_i, i); + Rcpp::checkUserInterrupt(); } } @@ -109,6 +111,7 @@ void KMeans::update_centers() { for (int i = 0; i < m_k; i++) { m_centers[i]->init_to_votes(); m_centers[i]->reset_votes(); + Rcpp::checkUserInterrupt(); } } From 075a575757749003dc2d06218af1c04d6f119463 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Mon, 25 Dec 2023 16:03:58 +0200 Subject: [PATCH 05/18] Added parallelism to reassignment + more - Use R RNG - Fix: UpdateMinDistanceWorker calculated also the distance of the already chosen nodes - Updated tests to testthat V3 --- .Rbuildignore | 1 + .gitignore | 1 + DESCRIPTION | 3 +- R/RcppExports.R | 4 +- R/TGL_kmeans.R | 15 ++------ src/KMeans.cpp | 66 +++++++++++++------------------- src/Random.cpp | 21 ---------- src/Random.h | 25 ------------ src/RcppExports.cpp | 10 ++--- src/ReassignWorker.cpp | 51 ++++++++++++++++++++++++ src/ReassignWorker.h | 31 +++++++++++++++ src/TGLkmeans.cpp | 7 +--- src/UpdateMinDistanceWorker.cpp | 14 ++++++- src/UpdateMinDistanceWorker.h | 8 +++- tests/testthat/test-clustering.R | 22 +++++------ tests/testthat/test-misc.R | 4 +- vignettes/usage.Rmd | 2 +- 17 files changed, 155 insertions(+), 130 deletions(-) delete mode 100644 src/Random.cpp delete mode 100644 src/Random.h create mode 100644 src/ReassignWorker.cpp create mode 100644 src/ReassignWorker.h diff --git a/.Rbuildignore b/.Rbuildignore index 874914d..7576489 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,3 +17,4 @@ ^CRAN-SUBMISSION$ ^\.github$ ^work$ +^\.vscode$ diff --git a/.gitignore b/.gitignore index e72778e..738099b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ README.html /Meta/ src/symbols.rds work +.vscode diff --git a/DESCRIPTION b/DESCRIPTION index 9981e9e..d94a0fe 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -43,7 +43,8 @@ LinkingTo: RcppParallel VignetteBuilder: knitr +Config/testthat/edition: 3 +Config/testthat/parallel: false Encoding: UTF-8 NeedsCompilation: yes -Packaged: 2018-02-25 13:32:32 UTC; aviezerl RoxygenNote: 7.2.3 diff --git a/R/RcppExports.R b/R/RcppExports.R index c05c58f..570bb44 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -9,7 +9,7 @@ reduce_num_trials <- function(boot_nodes_l, cc_mat) { invisible(.Call('_tglkmeans_reduce_num_trials', PACKAGE = 'tglkmeans', boot_nodes_l, cc_mat)) } -TGL_kmeans_cpp <- function(ids, mat, k, metric, max_iter = 40, min_delta = 0.0001, random_seed = TRUE, seed = -1L) { - .Call('_tglkmeans_TGL_kmeans_cpp', PACKAGE = 'tglkmeans', ids, mat, k, metric, max_iter, min_delta, random_seed, seed) +TGL_kmeans_cpp <- function(ids, mat, k, metric, max_iter = 40, min_delta = 0.0001) { + .Call('_tglkmeans_TGL_kmeans_cpp', PACKAGE = 'tglkmeans', ids, mat, k, metric, max_iter, min_delta) } diff --git a/R/TGL_kmeans.R b/R/TGL_kmeans.R index df3cc98..df68a10 100755 --- a/R/TGL_kmeans.R +++ b/R/TGL_kmeans.R @@ -54,11 +54,8 @@ TGL_kmeans_tidy <- function(df, hclust_intra_clusters = FALSE, seed = NULL, parallel = getOption("tglkmeans.parallel")) { - if (is.null(seed)) { - random_seed <- TRUE - seed <- -1 - } else { - random_seed <- FALSE + if (!is.null(seed)) { + set.seed(seed) } df <- as.data.frame(df) @@ -104,9 +101,7 @@ TGL_kmeans_tidy <- function(df, k = k, metric = metric, max_iter = max_iter, - min_delta = min_delta, - random_seed = random_seed, - seed = seed + min_delta = min_delta ) } else { log <- utils::capture.output( @@ -116,9 +111,7 @@ TGL_kmeans_tidy <- function(df, k = k, metric = metric, max_iter = max_iter, - min_delta = min_delta, - random_seed = random_seed, - seed = seed + min_delta = min_delta ) ) } diff --git a/src/KMeans.cpp b/src/KMeans.cpp index 554ca6b..973ba6c 100644 --- a/src/KMeans.cpp +++ b/src/KMeans.cpp @@ -4,8 +4,8 @@ #include #include "KMeans.h" -#include "Random.h" #include "UpdateMinDistanceWorker.h" +#include "ReassignWorker.h" #include KMeans::KMeans(const vector> &data, int k, vector ¢ers) : @@ -16,51 +16,51 @@ KMeans::KMeans(const vector> &data, int k, vector min_assign_change_fraction) { - Rcpp::Rcout << "tglkmeans: iter " << iter << endl; + Rcpp::Rcout << "iter " << iter << endl; m_changes = 0; update_centers(); reassign(); iter++; - Rcpp::Rcout << "tglkmeans: iter " << iter << " changed " << m_changes << endl; + Rcpp::Rcout << "iter " << iter << " changed " << m_changes << endl; Rcpp::checkUserInterrupt(); } } void KMeans::generate_seeds() { - Rcpp::Rcout << "tglkmeans: generating seeds" << endl; + Rcpp::Rcout << "generating seeds" << endl; for (int i = 0; i < m_k; i++) { - Rcpp::Rcout << "tglkmeans: at seed " << i << endl; + Rcpp::Rcout << "at seed " << i << endl; m_min_dist.resize(0); //compute minimal distance from centers //select next seed by sampling int seed_i = -1; if (i == 0) { // select the first seed randomly - seed_i = Random::fraction() * m_data.size(); + seed_i = R::runif(0, 1) * m_data.size(); } else { update_min_distance(i); - Rcpp::Rcout << "tglkmeans: done update min distance" << endl; - sort(m_min_dist.begin(), m_min_dist.end()); + Rcpp::Rcout << "done update min distance" << endl; + //select from 1/k of the data which is in the 1-1/2k quantile of the min distance int to_i = int(m_min_dist.size() * (1 - 1 / (2 * m_k))); int from_i = to_i - int(m_data.size() / m_k); - Rcpp::Rcout << "tglkmeans: seed range " << from_i << " " << to_i << endl; + Rcpp::Rcout << "seed range " << from_i << " " << to_i << endl; if (from_i < 0) { from_i = 0; } - int rnd_i = from_i + int(Random::fraction() * (to_i - from_i)); + int rnd_i = from_i + int(R::runif(0, 1) * (to_i - from_i)); seed_i = m_min_dist[rnd_i].second; - Rcpp::Rcout << "tglkmeans: picked up " << seed_i << " dist was " << m_min_dist[rnd_i].first << endl; + Rcpp::Rcout << "picked up " << seed_i << " dist was " << m_min_dist[rnd_i].first << endl; } add_new_core(seed_i, i); @@ -71,13 +71,15 @@ void KMeans::generate_seeds() { void KMeans::update_min_distance(int cur_k) { m_min_dist.resize(m_data.size()); - UpdateMinDistanceWorker worker(m_data, m_centers, m_min_dist, cur_k); + UpdateMinDistanceWorker worker(m_data, m_centers, m_min_dist, m_assignment, cur_k); RcppParallel::parallelFor(0, m_data.size(), worker); + worker.prepare_min_dist(m_min_dist); + sort(m_min_dist.begin(), m_min_dist.end()); } void KMeans::add_new_core(int seed_i, int center_i) { - Rcpp::Rcout << "tglkmeans: add new core from " << seed_i << " to " << center_i << endl; + Rcpp::Rcout << "add new core from " << seed_i << " to " << center_i << endl; m_centers[center_i]->reset_votes(); m_centers[center_i]->vote(m_data[seed_i], 1); m_centers[center_i]->init_to_votes(); @@ -116,33 +118,17 @@ void KMeans::update_centers() { } void KMeans::reassign() { - vector::iterator assign_i = m_assignment.begin(); - - for (auto data_i = m_data.begin(); data_i != m_data.end(); data_i++) { - int id_i = 0; - float best_dist = REAL_MAX; - int best_id_i = -1; - for (auto cent_i = m_centers.begin(); cent_i != m_centers.end(); cent_i++) { - float dist = (*cent_i)->dist(*data_i); - if (dist < best_dist) { - best_dist = dist; - best_id_i = id_i; - } - id_i++; - } + // Initialize the ReassignWorker with data, centers, and assignments + ReassignWorker worker(m_data, m_centers, m_assignment); + + // Perform parallel computation for reassignment + RcppParallel::parallelFor(0, m_data.size(), worker); - if (best_id_i == -1) { - throw std::logic_error( - "Cannot assign any center to element " + to_string(data_i - m_data.begin() + 1) + " all dist is NA"); - } + // Apply accumulated votes to the centers + worker.apply_votes(); - if (*assign_i != best_id_i) { - *assign_i = best_id_i; - m_changes++; - } - m_centers[best_id_i]->vote(*data_i, 1); - assign_i++; - } + // Update the number of changes based on the worker's results + m_changes = worker.get_changes(); } void KMeans::report_centers(ostream ¢er_tab) { diff --git a/src/Random.cpp b/src/Random.cpp deleted file mode 100644 index 4af0caf..0000000 --- a/src/Random.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// -// Created by aviezerl on 6/5/17. -// - -#include "Random.h" - -std::random_device Random::m_rd; -std::mt19937 Random::m_rng(Random::m_rd()); - -Random::Random() { -} - -float Random::fraction() { - std::uniform_real_distribution<> dis(0, 1); - return (dis(Random::m_rng)); -} - -void Random::seed(const int &seed) { - Random::m_rng.seed(seed); -} - diff --git a/src/Random.h b/src/Random.h deleted file mode 100644 index e299f21..0000000 --- a/src/Random.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Created by aviezerl on 6/5/17. -// - -#ifndef TGLKMEANS_RANDOM_H -#define TGLKMEANS_RANDOM_H - - -#include - -class Random { -private: - static std::random_device m_rd; - static std::mt19937 m_rng; -public: - Random(); - - static void seed(const int& seed); - - static float fraction(); - -}; - - -#endif //TGLKMEANS_RANDOM_H diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 37b1e57..b55fc27 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -34,8 +34,8 @@ BEGIN_RCPP END_RCPP } // TGL_kmeans_cpp -List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter, const double& min_delta, const bool& random_seed, const int& seed); -RcppExport SEXP _tglkmeans_TGL_kmeans_cpp(SEXP idsSEXP, SEXP matSEXP, SEXP kSEXP, SEXP metricSEXP, SEXP max_iterSEXP, SEXP min_deltaSEXP, SEXP random_seedSEXP, SEXP seedSEXP) { +List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter, const double& min_delta); +RcppExport SEXP _tglkmeans_TGL_kmeans_cpp(SEXP idsSEXP, SEXP matSEXP, SEXP kSEXP, SEXP metricSEXP, SEXP max_iterSEXP, SEXP min_deltaSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; @@ -45,9 +45,7 @@ BEGIN_RCPP Rcpp::traits::input_parameter< const String& >::type metric(metricSEXP); Rcpp::traits::input_parameter< const double& >::type max_iter(max_iterSEXP); Rcpp::traits::input_parameter< const double& >::type min_delta(min_deltaSEXP); - Rcpp::traits::input_parameter< const bool& >::type random_seed(random_seedSEXP); - Rcpp::traits::input_parameter< const int& >::type seed(seedSEXP); - rcpp_result_gen = Rcpp::wrap(TGL_kmeans_cpp(ids, mat, k, metric, max_iter, min_delta, random_seed, seed)); + rcpp_result_gen = Rcpp::wrap(TGL_kmeans_cpp(ids, mat, k, metric, max_iter, min_delta)); return rcpp_result_gen; END_RCPP } @@ -55,7 +53,7 @@ END_RCPP static const R_CallMethodDef CallEntries[] = { {"_tglkmeans_reduce_coclust", (DL_FUNC) &_tglkmeans_reduce_coclust, 3}, {"_tglkmeans_reduce_num_trials", (DL_FUNC) &_tglkmeans_reduce_num_trials, 2}, - {"_tglkmeans_TGL_kmeans_cpp", (DL_FUNC) &_tglkmeans_TGL_kmeans_cpp, 8}, + {"_tglkmeans_TGL_kmeans_cpp", (DL_FUNC) &_tglkmeans_TGL_kmeans_cpp, 6}, {NULL, NULL, 0} }; diff --git a/src/ReassignWorker.cpp b/src/ReassignWorker.cpp new file mode 100644 index 0000000..4539b42 --- /dev/null +++ b/src/ReassignWorker.cpp @@ -0,0 +1,51 @@ +#include "ReassignWorker.h" + +ReassignWorker::ReassignWorker(const std::vector>& data, + std::vector& centers, + std::vector& assignment) + : data(data), centers(centers), assignment(assignment), changes(0) { + votes.resize(centers.size()); + for (auto& v : votes) { + v.resize(data.size(), 0); + } + changes.resize(data.size(), 0); +} + +void ReassignWorker::operator()(std::size_t begin, std::size_t end) { + for (std::size_t i = begin; i < end; i++) { + int best_id_i = -1; + float best_dist = std::numeric_limits::max(); + + // Determine the closest center + for (size_t j = 0; j < centers.size(); j++) { + float dist = centers[j]->dist(data[i]); + if (dist < best_dist) { + best_dist = dist; + best_id_i = j; + } + } + + if (best_id_i == -1) { + throw std::logic_error("No valid center found for data point."); + } + + // Accumulate vote + votes[best_id_i][i] = 1; + + // Track changes in assignments + if (assignment[i] != best_id_i) { + assignment[i] = best_id_i; + changes[i]++; + } + } +} + +void ReassignWorker::apply_votes() { + for (size_t i = 0; i < centers.size(); i++) { + for (size_t j = 0; j < data.size(); j++) { + if (votes[i][j] > 0) { + centers[i]->vote(data[j], votes[i][j]); + } + } + } +} diff --git a/src/ReassignWorker.h b/src/ReassignWorker.h new file mode 100644 index 0000000..4d5cc1e --- /dev/null +++ b/src/ReassignWorker.h @@ -0,0 +1,31 @@ +#ifndef REASSIGNWORKER_H +#define REASSIGNWORKER_H + +#include +#include "KMeansCenterBase.h" +#include +#include + +class ReassignWorker : public RcppParallel::Worker { +private: + const std::vector>& data; + std::vector& centers; + std::vector& assignment; + std::vector> votes; // Thread-safe structure for votes + std::vector changes; // To track changes in assignments + +public: + ReassignWorker(const std::vector>& data, + std::vector& centers, + std::vector& assignment); + + void operator()(std::size_t begin, std::size_t end) override; + + void apply_votes(); + + size_t get_changes() const { + return std::accumulate(changes.begin(), changes.end(), 0); + } +}; + +#endif // REASSIGNWORKER_H diff --git a/src/TGLkmeans.cpp b/src/TGLkmeans.cpp index e4d4049..2b0b5b1 100755 --- a/src/TGLkmeans.cpp +++ b/src/TGLkmeans.cpp @@ -9,7 +9,6 @@ #include "KMeansCenterMeanEuclid.h" #include "KMeansCenterMeanPearson.h" #include "KMeansCenterMeanSpearman.h" -#include "Random.h" using namespace Rcpp; using namespace std; @@ -48,11 +47,7 @@ void real_max_to_na(DataFrame& df){ } // [[Rcpp::export]] -List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter=40, const double& min_delta=0.0001, const bool& random_seed=true, const int& seed=-1){ - - if (!random_seed){ - Random::seed(seed); - } +List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter=40, const double& min_delta=0.0001){ replace_na(mat); diff --git a/src/UpdateMinDistanceWorker.cpp b/src/UpdateMinDistanceWorker.cpp index 2cd1154..4b9aab2 100644 --- a/src/UpdateMinDistanceWorker.cpp +++ b/src/UpdateMinDistanceWorker.cpp @@ -3,11 +3,16 @@ UpdateMinDistanceWorker::UpdateMinDistanceWorker(const vector>& data, vector& centers, vector>& min_dist, - int cur_k) - : data(data), centers(centers), min_dist(min_dist), cur_k(cur_k) {} + const vector& assignment, + const int& cur_k) + : data(data), centers(centers), min_dist(min_dist), assignment(assignment), cur_k(cur_k) {} void UpdateMinDistanceWorker::operator()(std::size_t begin, std::size_t end) { for (std::size_t i = begin; i < end; ++i) { + if (assignment[i] != -1) { + min_dist[i] = std::make_pair(REAL_MAX, i); + continue; + } float best_dist = REAL_MAX; int id_i = 0; for (auto cent_i = centers.begin(); id_i < cur_k; cent_i++) { @@ -20,3 +25,8 @@ void UpdateMinDistanceWorker::operator()(std::size_t begin, std::size_t end) { min_dist[i] = std::make_pair(best_dist, i); } } + +void UpdateMinDistanceWorker::prepare_min_dist(vector>& min_dist) { + // remove REAL_MAX elements from min_dist + min_dist.erase(std::remove_if(min_dist.begin(), min_dist.end(), [](const std::pair& p) { return p.first == REAL_MAX; }), min_dist.end()); +} diff --git a/src/UpdateMinDistanceWorker.h b/src/UpdateMinDistanceWorker.h index 0f59f47..9b4b512 100644 --- a/src/UpdateMinDistanceWorker.h +++ b/src/UpdateMinDistanceWorker.h @@ -13,15 +13,19 @@ class UpdateMinDistanceWorker : public RcppParallel::Worker { const vector>& data; vector& centers; vector>& min_dist; - const int cur_k; + const vector& assignment; + const int& cur_k; public: UpdateMinDistanceWorker(const vector>& data, vector& centers, vector>& min_dist, - int cur_k); + const vector& assignment, + const int& cur_k); void operator()(std::size_t begin, std::size_t end); + + void prepare_min_dist(vector>& min_dist); }; #endif // UPDATEMINDISTANCEWORKER_H diff --git a/tests/testthat/test-clustering.R b/tests/testthat/test-clustering.R index f740046..47afe82 100755 --- a/tests/testthat/test-clustering.R +++ b/tests/testthat/test-clustering.R @@ -22,7 +22,7 @@ clustering_ok <- function(data, res, nclust, ndims, order = TRUE) { expect_equal(nrow(data), sum(res$size$n)) } -context("Missing data") +# Missing data: test_that("Stop when there are rows which contain only missing data", { data <- as.data.frame(simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL)) data[3, -1] <- NA @@ -30,7 +30,7 @@ test_that("Stop when there are rows which contain only missing data", { expect_error(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = FALSE, seed = 60427), "The following rows contain only missing values: 3,4") }) -context("Matrix input") +# Matrix input: test_that("Do not fail when input is matrix", { nclust <- 30 ndims <- 5 @@ -43,7 +43,7 @@ test_that("Do not fail when input is matrix", { clustering_ok(data, res, nclust, ndims, order = FALSE) }) -context("Rownames") +# Rownames: test_that("Use rownames if exists", { nclust <- 30 ndims <- 5 @@ -76,7 +76,7 @@ test_that("Dot not fail when rownames do not exist", { res_non_tidy <- TGL_kmeans(data, 30, id_column = FALSE, metric = "euclid", verbose = FALSE, seed = 60427) }) -context("Metrics") +# Metrics: test_that("Pearson metric works", { nclust <- 30 ndims <- 5 @@ -93,7 +93,7 @@ test_that("Spearman metric works", { clustering_ok(data, res, nclust, ndims, order = FALSE) }) -context("Correct output") +# Correct output: test_that("all ids and clusters are present", { nclust <- 30 @@ -172,7 +172,7 @@ test_that("reorder func works when set to NULL", { clustering_ok(data, res, nclust, ndims, order = FALSE) }) -context("Verbosity") +# Verbosity: test_that("quiet if verbose is turned off", { data <- simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL) expect_silent(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = FALSE, seed = 60427)) @@ -186,7 +186,7 @@ test_that("not quiet when verbose is turned on", { test_that("Log is saved when 'keep_log' is turned on", { data <- simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL) expect_warning(res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = TRUE, seed = 60427, keep_log = TRUE)) - expect_warning(res <- TGL_kmeans(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = TRUE, seed = 60427, keep_log = TRUE)) + expect_warning(expect_warning(res <- TGL_kmeans(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = TRUE, seed = 60427, keep_log = TRUE))) res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = FALSE, seed = 60427, keep_log = TRUE) expect_type(res$log, "character") @@ -194,7 +194,7 @@ test_that("Log is saved when 'keep_log' is turned on", { expect_type(res$log, "character") }) -context("Random seed") +# Random seed: test_that("setting the seed returns reproducible results", { nclust <- 30 data <- simulate_data(n = 100, sd = 0.3, nclust = nclust, frac_na = NULL) @@ -203,7 +203,7 @@ test_that("setting the seed returns reproducible results", { expect_true(all(res1$centers[, -1] == res2$centers[, -1])) }) -context("Correct Classification (low dim)") +# Correct Classification (low dim): test_that("clustering is reasonable (low dim): euclid", { test_params <- expand.grid(n = c(100), sd = c(0.05, 0.1, 0.3), nclust = c(5, 30, 100), dims = c(2, 10)) %>% filter(nclust < n) apply(test_params, 1, function(x) { @@ -218,7 +218,7 @@ test_that("clustering with NA is reasonable (low dim): euclid", { }) }) -context("Correct Classification (high dim)") +# Correct Classification (high dim): test_that("clustering is reasonable (high dim): euclid", { skip_on_cran() test_params <- expand.grid(n = c(500), sd = c(0.3), nclust = c(5, 30), dims = c(300)) %>% filter(nclust < n) @@ -235,7 +235,7 @@ test_that("clustering with NA is reasonable (high dim): euclid", { }) }) -context("Data simulation") +# Data simulation: test_that("true_clust column is not added when add_true_clust is FALSE", { data <- simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL, add_true_clust = FALSE) expect_true(!("true_clust" %in% colnames(data))) diff --git a/tests/testthat/test-misc.R b/tests/testthat/test-misc.R index 9faaa45..a53863a 100644 --- a/tests/testthat/test-misc.R +++ b/tests/testthat/test-misc.R @@ -1,4 +1,4 @@ -context("onLoad") +# onLoad: test_that("onLoad does not fail", { library(tglkmeans) cores <- round(parallel::detectCores() / 2) @@ -9,7 +9,7 @@ test_that("onLoad does not fail", { } }) -context("number of threads") +# number of threads: test_that("parallel is turned off when number of threads <= 1", { withr::with_options( list(tglkmeans.parallel = TRUE), diff --git a/vignettes/usage.Rmd b/vignettes/usage.Rmd index 6952b33..32900f4 100755 --- a/vignettes/usage.Rmd +++ b/vignettes/usage.Rmd @@ -158,7 +158,7 @@ sum(d$true_clust == d$new_clust, na.rm = TRUE) / sum(!is.na(d$new_clust)) We can see that kmeans++ clusters significantly better than R vanilla kmeans. ## Random seed -we can set the seed for the c++ random number generator, for reproducible results: +we can set the seed for reproducible results: ```{r} km1 <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), From eaec170c3db67ab3fb3de9356d16fb2cad624bf9 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Mon, 25 Dec 2023 18:00:45 +0200 Subject: [PATCH 06/18] Added `use_cpp_random` parameter for backward compatibility --- DESCRIPTION | 2 +- R/RcppExports.R | 4 ++-- R/TGL_kmeans.R | 21 ++++++++++++++++----- README.Rmd | 4 ++++ man/TGL_kmeans.Rd | 6 +++++- man/TGL_kmeans_tidy.Rd | 6 +++++- src/KMeans.cpp | 19 +++++++++++++++---- src/KMeans.h | 6 +++++- src/Random.cpp | 21 +++++++++++++++++++++ src/Random.h | 25 +++++++++++++++++++++++++ src/RcppExports.cpp | 10 ++++++---- src/TGLkmeans.cpp | 8 ++++++-- tests/testthat/test-clustering.R | 6 +++--- 13 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 src/Random.cpp create mode 100644 src/Random.h diff --git a/DESCRIPTION b/DESCRIPTION index d94a0fe..f1f6d4d 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: tglkmeans Title: Efficient Implementation of K-Means++ Algorithm -Version: 0.3.12 +Version: 0.4.0 Authors@R: c(person(given = "Aviezer", family = "Lifshitz", diff --git a/R/RcppExports.R b/R/RcppExports.R index 570bb44..8e3121b 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -9,7 +9,7 @@ reduce_num_trials <- function(boot_nodes_l, cc_mat) { invisible(.Call('_tglkmeans_reduce_num_trials', PACKAGE = 'tglkmeans', boot_nodes_l, cc_mat)) } -TGL_kmeans_cpp <- function(ids, mat, k, metric, max_iter = 40, min_delta = 0.0001) { - .Call('_tglkmeans_TGL_kmeans_cpp', PACKAGE = 'tglkmeans', ids, mat, k, metric, max_iter, min_delta) +TGL_kmeans_cpp <- function(ids, mat, k, metric, max_iter = 40, min_delta = 0.0001, use_cpp_random = FALSE, seed = -1L) { + .Call('_tglkmeans_TGL_kmeans_cpp', PACKAGE = 'tglkmeans', ids, mat, k, metric, max_iter, min_delta, use_cpp_random, seed) } diff --git a/R/TGL_kmeans.R b/R/TGL_kmeans.R index df68a10..63f6679 100755 --- a/R/TGL_kmeans.R +++ b/R/TGL_kmeans.R @@ -15,6 +15,8 @@ #' @param hclust_intra_clusters run hierarchical clustering within each cluster and return an ordering of the observations. #' @param seed seed for the c++ random number generator #' @param parallel cluster every cluster parallelly (if hclust_intra_clusters is true) +#' @param use_cpp_random use c++ random number generator instead of R's. This should be used for only for +#' backwards compatibility, as from version 0.4.0 onwards the default random number generator was changed o R. #' #' @return list with the following components: #' \describe{ @@ -53,9 +55,12 @@ TGL_kmeans_tidy <- function(df, add_to_data = FALSE, hclust_intra_clusters = FALSE, seed = NULL, - parallel = getOption("tglkmeans.parallel")) { + parallel = getOption("tglkmeans.parallel"), + use_cpp_random = FALSE) { if (!is.null(seed)) { set.seed(seed) + } else { + seed <- -1 } df <- as.data.frame(df) @@ -101,7 +106,9 @@ TGL_kmeans_tidy <- function(df, k = k, metric = metric, max_iter = max_iter, - min_delta = min_delta + min_delta = min_delta, + use_cpp_random = use_cpp_random, + seed = seed ) } else { log <- utils::capture.output( @@ -111,7 +118,9 @@ TGL_kmeans_tidy <- function(df, k = k, metric = metric, max_iter = max_iter, - min_delta = min_delta + min_delta = min_delta, + use_cpp_random = use_cpp_random, + seed = seed ) ) } @@ -286,7 +295,8 @@ TGL_kmeans <- function(df, reorder_func = "hclust", hclust_intra_clusters = FALSE, seed = NULL, - parallel = getOption("tglkmeans.parallel")) { + parallel = getOption("tglkmeans.parallel"), + use_cpp_random = FALSE) { res <- TGL_kmeans_tidy( df = df, k = k, @@ -299,7 +309,8 @@ TGL_kmeans <- function(df, reorder_func = reorder_func, seed = seed, hclust_intra_clusters = hclust_intra_clusters, - parallel = parallel + parallel = parallel, + use_cpp_random = use_cpp_random ) diff --git a/README.Rmd b/README.Rmd index 4f127fa..7e8d9e6 100755 --- a/README.Rmd +++ b/README.Rmd @@ -78,3 +78,7 @@ Please refer to the package vignettes for usage and workflow, or look at the [us browseVignettes("usage") ``` + +## A note regarding random number generation + +From version 0.4.0 onwards, the package uses R random number generation functions instead of the C++11 random number generation functions. Note that this may result in different results from previous versions. To get the same results as previous versions, set the `use_cpp_random` argument to `TRUE` in the `TGL_kmeans` function. diff --git a/man/TGL_kmeans.Rd b/man/TGL_kmeans.Rd index 3c0d841..6a04d54 100644 --- a/man/TGL_kmeans.Rd +++ b/man/TGL_kmeans.Rd @@ -16,7 +16,8 @@ TGL_kmeans( reorder_func = "hclust", hclust_intra_clusters = FALSE, seed = NULL, - parallel = getOption("tglkmeans.parallel") + parallel = getOption("tglkmeans.parallel"), + use_cpp_random = FALSE ) } \arguments{ @@ -45,6 +46,9 @@ if NULL, no reordering would be done.} \item{seed}{seed for the c++ random number generator} \item{parallel}{cluster every cluster parallelly (if hclust_intra_clusters is true)} + +\item{use_cpp_random}{use c++ random number generator instead of R's. This should be used for only for +backwards compatibility, as from version 0.4.0 onwards the default random number generator was changed o R.} } \value{ list with the following components: diff --git a/man/TGL_kmeans_tidy.Rd b/man/TGL_kmeans_tidy.Rd index f60a740..d887bb7 100644 --- a/man/TGL_kmeans_tidy.Rd +++ b/man/TGL_kmeans_tidy.Rd @@ -17,7 +17,8 @@ TGL_kmeans_tidy( add_to_data = FALSE, hclust_intra_clusters = FALSE, seed = NULL, - parallel = getOption("tglkmeans.parallel") + parallel = getOption("tglkmeans.parallel"), + use_cpp_random = FALSE ) } \arguments{ @@ -48,6 +49,9 @@ if NULL, no reordering would be done.} \item{seed}{seed for the c++ random number generator} \item{parallel}{cluster every cluster parallelly (if hclust_intra_clusters is true)} + +\item{use_cpp_random}{use c++ random number generator instead of R's. This should be used for only for +backwards compatibility, as from version 0.4.0 onwards the default random number generator was changed o R.} } \value{ list with the following components: diff --git a/src/KMeans.cpp b/src/KMeans.cpp index 973ba6c..9c1ac14 100644 --- a/src/KMeans.cpp +++ b/src/KMeans.cpp @@ -6,13 +6,23 @@ #include "KMeans.h" #include "UpdateMinDistanceWorker.h" #include "ReassignWorker.h" +#include "Random.h" #include -KMeans::KMeans(const vector> &data, int k, vector ¢ers) : +KMeans::KMeans(const vector> &data, int k, vector ¢ers, const bool& use_cpp_random) : m_k(k), m_centers(centers), m_assignment(data.size(), -1), - m_data(data) { + m_data(data), + m_use_cpp_random(use_cpp_random) { +} + +float KMeans::random_fraction() { + if (m_use_cpp_random){ + return Random::fraction(); + } else { + return R::runif(0, 1); + } } void KMeans::cluster(int max_iter, float min_assign_change_fraction) { @@ -46,7 +56,7 @@ void KMeans::generate_seeds() { int seed_i = -1; if (i == 0) { // select the first seed randomly - seed_i = R::runif(0, 1) * m_data.size(); + seed_i = random_fraction() * m_data.size(); } else { update_min_distance(i); Rcpp::Rcout << "done update min distance" << endl; @@ -58,7 +68,8 @@ void KMeans::generate_seeds() { if (from_i < 0) { from_i = 0; } - int rnd_i = from_i + int(R::runif(0, 1) * (to_i - from_i)); + + int rnd_i = from_i + int(random_fraction() * (to_i - from_i)); seed_i = m_min_dist[rnd_i].second; Rcpp::Rcout << "picked up " << seed_i << " dist was " << m_min_dist[rnd_i].first << endl; } diff --git a/src/KMeans.h b/src/KMeans.h index 7be3f80..0386b2f 100644 --- a/src/KMeans.h +++ b/src/KMeans.h @@ -25,9 +25,11 @@ class KMeans { float m_changes; + bool m_use_cpp_random; + public: - KMeans(const vector > &data, int k, vector ¢ers); + KMeans(const vector > &data, int k, vector ¢ers, const bool& use_cpp_random); void cluster(int max_iter, float min_delta_assign); @@ -48,6 +50,8 @@ class KMeans { void report_assignment(vector &row_names, ostream &assign_tab); vector report_assignment_to_vector(); + + float random_fraction(); }; diff --git a/src/Random.cpp b/src/Random.cpp new file mode 100644 index 0000000..4af0caf --- /dev/null +++ b/src/Random.cpp @@ -0,0 +1,21 @@ +// +// Created by aviezerl on 6/5/17. +// + +#include "Random.h" + +std::random_device Random::m_rd; +std::mt19937 Random::m_rng(Random::m_rd()); + +Random::Random() { +} + +float Random::fraction() { + std::uniform_real_distribution<> dis(0, 1); + return (dis(Random::m_rng)); +} + +void Random::seed(const int &seed) { + Random::m_rng.seed(seed); +} + diff --git a/src/Random.h b/src/Random.h new file mode 100644 index 0000000..e299f21 --- /dev/null +++ b/src/Random.h @@ -0,0 +1,25 @@ +// +// Created by aviezerl on 6/5/17. +// + +#ifndef TGLKMEANS_RANDOM_H +#define TGLKMEANS_RANDOM_H + + +#include + +class Random { +private: + static std::random_device m_rd; + static std::mt19937 m_rng; +public: + Random(); + + static void seed(const int& seed); + + static float fraction(); + +}; + + +#endif //TGLKMEANS_RANDOM_H diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index b55fc27..d189d69 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -34,8 +34,8 @@ BEGIN_RCPP END_RCPP } // TGL_kmeans_cpp -List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter, const double& min_delta); -RcppExport SEXP _tglkmeans_TGL_kmeans_cpp(SEXP idsSEXP, SEXP matSEXP, SEXP kSEXP, SEXP metricSEXP, SEXP max_iterSEXP, SEXP min_deltaSEXP) { +List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter, const double& min_delta, const bool& use_cpp_random, const int& seed); +RcppExport SEXP _tglkmeans_TGL_kmeans_cpp(SEXP idsSEXP, SEXP matSEXP, SEXP kSEXP, SEXP metricSEXP, SEXP max_iterSEXP, SEXP min_deltaSEXP, SEXP use_cpp_randomSEXP, SEXP seedSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; @@ -45,7 +45,9 @@ BEGIN_RCPP Rcpp::traits::input_parameter< const String& >::type metric(metricSEXP); Rcpp::traits::input_parameter< const double& >::type max_iter(max_iterSEXP); Rcpp::traits::input_parameter< const double& >::type min_delta(min_deltaSEXP); - rcpp_result_gen = Rcpp::wrap(TGL_kmeans_cpp(ids, mat, k, metric, max_iter, min_delta)); + Rcpp::traits::input_parameter< const bool& >::type use_cpp_random(use_cpp_randomSEXP); + Rcpp::traits::input_parameter< const int& >::type seed(seedSEXP); + rcpp_result_gen = Rcpp::wrap(TGL_kmeans_cpp(ids, mat, k, metric, max_iter, min_delta, use_cpp_random, seed)); return rcpp_result_gen; END_RCPP } @@ -53,7 +55,7 @@ END_RCPP static const R_CallMethodDef CallEntries[] = { {"_tglkmeans_reduce_coclust", (DL_FUNC) &_tglkmeans_reduce_coclust, 3}, {"_tglkmeans_reduce_num_trials", (DL_FUNC) &_tglkmeans_reduce_num_trials, 2}, - {"_tglkmeans_TGL_kmeans_cpp", (DL_FUNC) &_tglkmeans_TGL_kmeans_cpp, 6}, + {"_tglkmeans_TGL_kmeans_cpp", (DL_FUNC) &_tglkmeans_TGL_kmeans_cpp, 8}, {NULL, NULL, 0} }; diff --git a/src/TGLkmeans.cpp b/src/TGLkmeans.cpp index 2b0b5b1..2bef79c 100755 --- a/src/TGLkmeans.cpp +++ b/src/TGLkmeans.cpp @@ -6,6 +6,7 @@ #include #include "KMeans.h" +#include "Random.h" #include "KMeansCenterMeanEuclid.h" #include "KMeansCenterMeanPearson.h" #include "KMeansCenterMeanSpearman.h" @@ -47,8 +48,11 @@ void real_max_to_na(DataFrame& df){ } // [[Rcpp::export]] -List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter=40, const double& min_delta=0.0001){ +List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const String& metric, const double& max_iter=40, const double& min_delta=0.0001, const bool& use_cpp_random=false, const int& seed=-1){ + if (use_cpp_random){ + Random::seed(seed); + } replace_na(mat); vector > data = as > >(mat); @@ -72,7 +76,7 @@ List TGL_kmeans_cpp(const StringVector& ids, DataFrame& mat, const int& k, const stop("possible metrics are 'euclid', 'pearson' and 'spearman'"); } - KMeans kmeans(data, k, centers); + KMeans kmeans(data, k, centers, use_cpp_random); kmeans.cluster(max_iter, min_delta); diff --git a/tests/testthat/test-clustering.R b/tests/testthat/test-clustering.R index 47afe82..bd10cdd 100755 --- a/tests/testthat/test-clustering.R +++ b/tests/testthat/test-clustering.R @@ -52,7 +52,7 @@ test_that("Use rownames if exists", { as.data.frame() %>% select(id, starts_with("V")) %>% mutate(id = paste0("id_", id)) %>% - column_to_rownames("id") + tibble::column_to_rownames("id") res <- TGL_kmeans_tidy(data, 30, id_column = FALSE, metric = "euclid", verbose = FALSE, seed = 60427) clustering_ok(data, res, nclust, ndims, order = FALSE) @@ -68,7 +68,7 @@ test_that("Dot not fail when rownames do not exist", { data <- data %>% select(starts_with("V")) %>% as.data.frame() - data <- remove_rownames(data) + data <- tibble::remove_rownames(data) res <- TGL_kmeans_tidy(data, 30, id_column = FALSE, metric = "euclid", verbose = FALSE, seed = 60427) clustering_ok(data, res, nclust, ndims, order = FALSE) @@ -148,7 +148,7 @@ test_that("add_to_data works", { as.data.frame() %>% select(id, starts_with("V")) %>% mutate(id = paste0("id_", id)) %>% - column_to_rownames("id") + tibble::column_to_rownames("id") res <- TGL_kmeans_tidy(data, 30, id_column = FALSE, metric = "euclid", verbose = FALSE, seed = 60427, add_to_data = TRUE) expect_equal(res$data %>% select(starts_with("V")), data %>% select(starts_with("V"))) From 04054f9cb83d63c7d9298cc87be89faf3f450247 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 09:17:52 +0200 Subject: [PATCH 07/18] added RcppParallel::setThreadOptions to tglkmeans.set_parallel --- R/misc.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/misc.R b/R/misc.R index eb22ffe..bab6d71 100755 --- a/R/misc.R +++ b/R/misc.R @@ -12,10 +12,12 @@ tglkmeans.set_parallel <- function(thread_num) { if (thread_num <= 1) { options(tglkmeans.parallel = FALSE) + RcppParallel::setThreadOptions(numThreads = 1) } else { doFuture::registerDoFuture() future::plan(future::multicore, workers = thread_num) options(tglkmeans.parallel = TRUE) options(tglkmeans.parallel.thread_num = thread_num) + RcppParallel::setThreadOptions(numThreads = thread_num) } } From 11f82a86de157b073553df7e1bda2ed6255b420d Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 09:22:21 +0200 Subject: [PATCH 08/18] updated NEWS.md --- NEWS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS.md b/NEWS.md index e3a9fbb..ca126db 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# tglkmeans 0.4.0 + +* Use R random number generator instead of C++11 random number generator. For backwards compatibility, the old random number generator can be used by setting `use_cpp_random` to `TRUE`. +* Added parallelization using `RcppParallel`. + # tglkmeans 0.3.12 * Added validity checks for `k` and the number of observations. From 15a5314a6a9af58984ad6e014bfbd2ab4ebe9a50 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 09:46:31 +0200 Subject: [PATCH 09/18] renamed Makevar to Makevars --- DESCRIPTION | 44 ++++++++++++++++++++++----------------- NAMESPACE | 1 + R/tglkmeans-package.R | 1 + src/{Makevar => Makevars} | 0 4 files changed, 27 insertions(+), 19 deletions(-) rename src/{Makevar => Makevars} (100%) diff --git a/DESCRIPTION b/DESCRIPTION index f1f6d4d..61bab2e 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,36 +1,40 @@ Package: tglkmeans Title: Efficient Implementation of K-Means++ Algorithm Version: 0.4.0 -Authors@R: - c(person(given = "Aviezer", - family = "Lifshitz", - role = c("aut", "cre"), - email = "aviezer.lifshitz@weizmann.ac.il"), - person(given = "Amos", - family = "Tanay", - role = "aut"), - person("Weizmann Institute of Science", role="cph")) -Author: Aviezer Lifshitz [aut, cre], Amos Tanay [aut], Weizmann Institute of Science [cph] +Authors@R: c( + person("Aviezer", "Lifshitz", , "aviezer.lifshitz@weizmann.ac.il", role = c("aut", "cre")), + person("Amos", "Tanay", role = "aut"), + person("Weizmann Institute of Science", role = "cph") + ) +Author: Aviezer Lifshitz [aut, cre], Amos Tanay [aut], Weizmann Institute + of Science [cph] Maintainer: Aviezer Lifshitz -Description: Efficient implementation of K-Means++ algorithm. For more information see (1) "kmeans++ the advantages of the k-means++ algorithm" by David Arthur and Sergei Vassilvitskii (2007), Proceedings of the eighteenth annual ACM-SIAM symposium on Discrete algorithms, Society for Industrial and Applied Mathematics, Philadelphia, PA, USA, pp. 1027-1035, and (2) "The Effectiveness of Lloyd-Type Methods for the k-Means Problem" by Rafail Ostrovsky, Yuval Rabani, Leonard J. Schulman and Chaitanya Swamy . +Description: Efficient implementation of K-Means++ algorithm. For more + information see (1) "kmeans++ the advantages of the k-means++ + algorithm" by David Arthur and Sergei Vassilvitskii (2007), + Proceedings of the eighteenth annual ACM-SIAM symposium on Discrete + algorithms, Society for Industrial and Applied Mathematics, + Philadelphia, PA, USA, pp. 1027-1035, and (2) "The Effectiveness of + Lloyd-Type Methods for the k-Means Problem" by Rafail Ostrovsky, Yuval + Rabani, Leonard J. Schulman and Chaitanya Swamy + . License: MIT + file LICENSE BugReports: https://github.com/tanaylab/tglkmeans/issues -OS_type: unix Depends: R (>= 4.0.0) Imports: - Rcpp (>= 0.12.11), doFuture, + dplyr (>= 0.5.0), future, - dplyr (>= 0.5.0), - ggplot2 (>= 2.2.0), + ggplot2 (>= 2.2.0), magrittr, - tibble (>= 3.2.1), - parallel (>= 3.3.2), + parallel (>= 3.3.2), plyr (>= 1.8.4), - purrr (>= 0.2.0), + purrr (>= 0.2.0), + Rcpp (>= 0.12.11), + RcppParallel, tgstat (>= 1.0.0), - RcppParallel + tibble (>= 3.2.1) Suggests: covr, knitr, @@ -47,4 +51,6 @@ Config/testthat/edition: 3 Config/testthat/parallel: false Encoding: UTF-8 NeedsCompilation: yes +OS_type: unix RoxygenNote: 7.2.3 +SystemRequirements: GNU make diff --git a/NAMESPACE b/NAMESPACE index 29eaf5f..c77e9a8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,7 @@ export(tglkmeans.set_parallel) import(dplyr) import(ggplot2) importFrom(Rcpp,sourceCpp) +importFrom(RcppParallel,RcppParallelLibs) importFrom(grDevices,colorRampPalette) importFrom(grDevices,dev.off) importFrom(magrittr,"%>%") diff --git a/R/tglkmeans-package.R b/R/tglkmeans-package.R index 80f2ec6..cc03dd4 100755 --- a/R/tglkmeans-package.R +++ b/R/tglkmeans-package.R @@ -12,6 +12,7 @@ #' @importFrom tgstat tgs_cor #' @importFrom tgstat tgs_dist #' @importFrom tibble remove_rownames column_to_rownames rownames_to_column has_rownames rowid_to_column as_tibble tibble +#' @importFrom RcppParallel RcppParallelLibs #' #' @useDynLib tglkmeans #' @importFrom Rcpp sourceCpp diff --git a/src/Makevar b/src/Makevars similarity index 100% rename from src/Makevar rename to src/Makevars From 5f85e02add8754cceed15db275ea323bf44cd943 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 12:35:41 +0200 Subject: [PATCH 10/18] changed default of `id_column` to `FALSE` Changed stop,warning and message to cli calls --- DESCRIPTION | 1 + NAMESPACE | 5 +++ NEWS.md | 1 + R/TGL_kmeans.R | 76 +++++++++++++++----------------- R/tests.R | 14 ++++-- R/tglkmeans-package.R | 11 ++--- man/TGL_kmeans.Rd | 9 ++-- man/TGL_kmeans_tidy.Rd | 11 ++--- man/simulate_data.Rd | 6 ++- tests/testthat/test-clustering.R | 37 +++++++--------- 10 files changed, 93 insertions(+), 78 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 61bab2e..da49f6c 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,6 +23,7 @@ BugReports: https://github.com/tanaylab/tglkmeans/issues Depends: R (>= 4.0.0) Imports: + cli, doFuture, dplyr (>= 0.5.0), future, diff --git a/NAMESPACE b/NAMESPACE index c77e9a8..afb2449 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,6 +9,11 @@ import(dplyr) import(ggplot2) importFrom(Rcpp,sourceCpp) importFrom(RcppParallel,RcppParallelLibs) +importFrom(cli,cli_abort) +importFrom(cli,cli_alert) +importFrom(cli,cli_alert_info) +importFrom(cli,cli_alert_success) +importFrom(cli,cli_warn) importFrom(grDevices,colorRampPalette) importFrom(grDevices,dev.off) importFrom(magrittr,"%>%") diff --git a/NEWS.md b/NEWS.md index ca126db..6ef08ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # tglkmeans 0.4.0 +* Default of `id_column` parameter was changed to `FALSE`. Note that this is a breaking change, and if you want to use an id column, you need to set it explicitly to `TRUE`. * Use R random number generator instead of C++11 random number generator. For backwards compatibility, the old random number generator can be used by setting `use_cpp_random` to `TRUE`. * Added parallelization using `RcppParallel`. diff --git a/R/TGL_kmeans.R b/R/TGL_kmeans.R index 63f6679..7ca21d8 100755 --- a/R/TGL_kmeans.R +++ b/R/TGL_kmeans.R @@ -1,7 +1,8 @@ #' TGL kmeans with 'tidy' output #' -#' @param df data frame. Each row is a single observation and each column is a dimension. -#' the first column can contain id for each observation (if id_column is TRUE). +#' @param df a data frame or a matrix. Each row is a single observation and each column is a dimension. +#' the first column can contain id for each observation (if id_column is TRUE), +#' otherwise the rownames are used. #' @param k number of clusters. Note that in some cases the algorithm might return less clusters than k. #' @param metric distance metric for kmeans++ seeding. can be 'euclid', 'pearson' or 'spearman' #' @param max_iter maximal number of iterations @@ -24,7 +25,7 @@ #' \item{centers:}{tibble with `clust` column and the cluster centers.} #' \item{size:}{tibble with `clust` column and `n` column with the number of points in each cluster.} #' \item{data:}{tibble with `clust` column the original data frame.} -#' \item{log:}{messages from the algorithm run (only if \code{id_column = TRUE}).} +#' \item{log:}{messages from the algorithm run (only if \code{id_column = FALSE}).} #' \item{order:}{tibble with 'id' column, 'clust' column, 'order' column with a new ordering if the observations and 'intra_clust_order' column with the order within each cluster. (only if hclust_intra_clusters = TRUE)} #' } #' @@ -34,7 +35,7 @@ #' } #' #' # create 5 clusters normally distributed around 1:5 -#' d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE) +#' d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) #' head(d) #' #' # cluster @@ -50,7 +51,7 @@ TGL_kmeans_tidy <- function(df, min_delta = 0.0001, verbose = FALSE, keep_log = FALSE, - id_column = TRUE, + id_column = FALSE, reorder_func = "hclust", add_to_data = FALSE, hclust_intra_clusters = FALSE, @@ -63,46 +64,52 @@ TGL_kmeans_tidy <- function(df, seed <- -1 } + if (!is.matrix(df) && !is.data.frame(df)) { + cli_abort("{.field df} must be a matrix or a data frame") + } + + # mat has the ids as rownames, df is the original input with the ids as the first column + mat <- df df <- as.data.frame(df) - if (!id_column) { - df <- add_id_column(df) + if (id_column) { + rownames(mat) <- mat[, 1] + mat <- mat[, -1] } else { - if (rlang::has_name(df, "id")) { - df$id <- as.character(df$id) - if (verbose) { - message(sprintf("id column: %s", colnames(df)[1])) - } - } else { - warning("Input doesn't have a column named \"id\". Using rownames instead.") - df <- add_id_column(df) + if (is.null(rownames(mat))) { + rownames(mat) <- 1:nrow(mat) } + + df$id <- as.character(rownames(mat)) + + # make sure that 'id' is the first column + df <- df[, c(ncol(df), 1:(ncol(df) - 1))] } + df[, 1] <- as.character(df[, 1]) if (k < 1) { - stop("k must be greater than 0") + cli_abort("k must be greater than 0") } if (nrow(df) < k) { - stop(paste0("number of observations (", nrow(df), ") must be greater than k (", k, ")")) + cli_abort("number of observations ({.val {nrow(df)}} must be greater than k ({.val {k}})") } - mat <- t(df[, -1]) - # Thorw an error if there are rows that do not contain any value - n_not_missing <- colSums(!is.na(mat)) + n_not_missing <- rowSums(!is.na(mat)) if (any(n_not_missing == 0)) { all_nas <- which(n_not_missing == 0) - stop(sprintf("The following rows contain only missing values: %s", paste(all_nas, collapse = ","))) + cli_abort("The following rows contain only missing values: {.val {all_nas}}") } ids <- as.character(df[, 1]) column_names <- as.character(colnames(df)[-1]) + if (verbose) { km <- TGL_kmeans_cpp( ids = ids, - mat = mat, + mat = t(mat), k = k, metric = metric, max_iter = max_iter, @@ -114,7 +121,7 @@ TGL_kmeans_tidy <- function(df, log <- utils::capture.output( km <- TGL_kmeans_cpp( ids = ids, - mat = mat, + mat = t(mat), k = k, metric = metric, max_iter = max_iter, @@ -148,7 +155,7 @@ TGL_kmeans_tidy <- function(df, if (keep_log) { if (verbose) { - warning("cannot keep log when verbose option is true") + cli_warn("cannot keep log when {.field verbose=TRUE}") } else { km$log <- log } @@ -168,7 +175,7 @@ TGL_kmeans_tidy <- function(df, } if (hclust_intra_clusters) { - message("running hclust within each cluster") + cli_alert_info("running hclust within each cluster") km$order <- hclust_every_cluster(km, df, parallel = parallel) } @@ -176,17 +183,6 @@ TGL_kmeans_tidy <- function(df, } -add_id_column <- function(df) { - if (!has_rownames(df)) { - df <- df %>% rowid_to_column("id") - } else { - df <- df %>% rownames_to_column("id") - } - return(df) -} - - - reorder_clusters <- function(km, func = "hclust") { # if there is an empty cluster, remove it and renumber the clusters empty_clusters <- km$centers %>% @@ -220,7 +216,7 @@ reorder_clusters <- function(km, func = "hclust") { if (identical(func, "hclust") || identical(func, hclust)) { if (min(apply(km$centers[, -1], 1, var, na.rm = TRUE), na.rm = TRUE) == 0) { - warning("standard deviation of kmeans center is 0") + cli_warn("standard deviation of kmeans center is 0") } else { cm <- km$centers[, -1] %>% t() %>% @@ -273,7 +269,7 @@ reorder_clusters <- function(km, func = "hclust") { #' } #' #' # create 5 clusters normally distributed around 1:5 -#' d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE) +#' d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) #' head(d) #' #' # cluster @@ -291,7 +287,7 @@ TGL_kmeans <- function(df, min_delta = 0.0001, verbose = FALSE, keep_log = FALSE, - id_column = TRUE, + id_column = FALSE, reorder_func = "hclust", hclust_intra_clusters = FALSE, seed = NULL, @@ -329,7 +325,7 @@ TGL_kmeans <- function(df, if (keep_log) { if (verbose) { - warning("cannot keep log when verbose option is true") + cli_warn("cannot keep log when {.field verbose=TRUE}") } else { km$log <- res$log } diff --git a/R/tests.R b/R/tests.R index e70602d..e8fd7f2 100755 --- a/R/tests.R +++ b/R/tests.R @@ -8,16 +8,18 @@ #' @param dims number of dimensions #' @param frac_na fraction of NA in the first dimension #' @param add_true_clust add a column with the true cluster ids +#' @param id_column add a column with the id #' #' @return simulated data -#' @export #' #' @examples #' simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2) #' #' # add 20% missing data #' simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, frac_na = 0.2) -simulate_data <- function(n = 100, sd = 0.3, nclust = 30, dims = 2, frac_na = NULL, add_true_clust = TRUE) { +#' +#' @export +simulate_data <- function(n = 100, sd = 0.3, nclust = 30, dims = 2, frac_na = NULL, add_true_clust = TRUE, id_column = TRUE) { data <- purrr::map_dfr(1:nclust, ~ as.data.frame(matrix(rnorm(n * dims, mean = .x, sd = sd), ncol = dims)) %>% mutate(true_clust = .x)) %>% @@ -31,6 +33,12 @@ simulate_data <- function(n = 100, sd = 0.3, nclust = 30, dims = 2, frac_na = NU if (!add_true_clust) { data <- data %>% select(-true_clust) } + if (!id_column) { + data <- as.data.frame(data) + rownames(data) <- data$id + data <- data %>% select(-id) + } + return(as.data.frame(data)) } @@ -47,7 +55,7 @@ match_clusters <- function(data, res, nclust) { test_clustering <- function(n, sd, nclust, dims = 2, method = "euclid", frac_na = NULL) { data <- simulate_data(n = n, sd = sd, nclust = nclust, dims = dims) - res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, method, verbose = FALSE) + res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, method, id_column = TRUE, verbose = FALSE) mres <- match_clusters(data, res, nclust) frac_success <- sum(mres$true_clust == mres$new_clust, na.rm = TRUE) / sum(!is.na(mres$new_clust)) return(frac_success) diff --git a/R/tglkmeans-package.R b/R/tglkmeans-package.R index cc03dd4..2946924 100755 --- a/R/tglkmeans-package.R +++ b/R/tglkmeans-package.R @@ -2,19 +2,20 @@ "_PACKAGE" ## usethis namespace: start +#' #' @import dplyr #' @import ggplot2 +#' @importFrom cli cli_abort cli_warn cli_alert cli_alert_info cli_alert_success +#' @importFrom grDevices colorRampPalette dev.off #' @importFrom purrr set_names +#' @importFrom Rcpp sourceCpp +#' @importFrom RcppParallel RcppParallelLibs #' @importFrom stats cor dist hclust var -#' @importFrom utils capture.output -#' @importFrom grDevices colorRampPalette dev.off #' @importFrom stats cutree #' @importFrom tgstat tgs_cor #' @importFrom tgstat tgs_dist #' @importFrom tibble remove_rownames column_to_rownames rownames_to_column has_rownames rowid_to_column as_tibble tibble -#' @importFrom RcppParallel RcppParallelLibs -#' +#' @importFrom utils capture.output #' @useDynLib tglkmeans -#' @importFrom Rcpp sourceCpp ## usethis namespace: end NULL diff --git a/man/TGL_kmeans.Rd b/man/TGL_kmeans.Rd index 6a04d54..00db7a4 100644 --- a/man/TGL_kmeans.Rd +++ b/man/TGL_kmeans.Rd @@ -12,7 +12,7 @@ TGL_kmeans( min_delta = 0.0001, verbose = FALSE, keep_log = FALSE, - id_column = TRUE, + id_column = FALSE, reorder_func = "hclust", hclust_intra_clusters = FALSE, seed = NULL, @@ -21,8 +21,9 @@ TGL_kmeans( ) } \arguments{ -\item{df}{data frame. Each row is a single observation and each column is a dimension. -the first column can contain id for each observation (if id_column is TRUE).} +\item{df}{a data frame or a matrix. Each row is a single observation and each column is a dimension. +the first column can contain id for each observation (if id_column is TRUE), +otherwise the rownames are used.} \item{k}{number of clusters. Note that in some cases the algorithm might return less clusters than k.} @@ -69,7 +70,7 @@ tglkmeans.set_parallel(1) } # create 5 clusters normally distributed around 1:5 -d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE) +d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) head(d) # cluster diff --git a/man/TGL_kmeans_tidy.Rd b/man/TGL_kmeans_tidy.Rd index d887bb7..beeef5e 100644 --- a/man/TGL_kmeans_tidy.Rd +++ b/man/TGL_kmeans_tidy.Rd @@ -12,7 +12,7 @@ TGL_kmeans_tidy( min_delta = 0.0001, verbose = FALSE, keep_log = FALSE, - id_column = TRUE, + id_column = FALSE, reorder_func = "hclust", add_to_data = FALSE, hclust_intra_clusters = FALSE, @@ -22,8 +22,9 @@ TGL_kmeans_tidy( ) } \arguments{ -\item{df}{data frame. Each row is a single observation and each column is a dimension. -the first column can contain id for each observation (if id_column is TRUE).} +\item{df}{a data frame or a matrix. Each row is a single observation and each column is a dimension. +the first column can contain id for each observation (if id_column is TRUE), +otherwise the rownames are used.} \item{k}{number of clusters. Note that in some cases the algorithm might return less clusters than k.} @@ -60,7 +61,7 @@ list with the following components: \item{centers:}{tibble with `clust` column and the cluster centers.} \item{size:}{tibble with `clust` column and `n` column with the number of points in each cluster.} \item{data:}{tibble with `clust` column the original data frame.} - \item{log:}{messages from the algorithm run (only if \code{id_column = TRUE}).} + \item{log:}{messages from the algorithm run (only if \code{id_column = FALSE}).} \item{order:}{tibble with 'id' column, 'clust' column, 'order' column with a new ordering if the observations and 'intra_clust_order' column with the order within each cluster. (only if hclust_intra_clusters = TRUE)} } } @@ -73,7 +74,7 @@ tglkmeans.set_parallel(1) } # create 5 clusters normally distributed around 1:5 -d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE) +d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) head(d) # cluster diff --git a/man/simulate_data.Rd b/man/simulate_data.Rd index c102403..4f327aa 100644 --- a/man/simulate_data.Rd +++ b/man/simulate_data.Rd @@ -10,7 +10,8 @@ simulate_data( nclust = 30, dims = 2, frac_na = NULL, - add_true_clust = TRUE + add_true_clust = TRUE, + id_column = TRUE ) } \arguments{ @@ -25,6 +26,8 @@ simulate_data( \item{frac_na}{fraction of NA in the first dimension} \item{add_true_clust}{add a column with the true cluster ids} + +\item{id_column}{add a column with the id} } \value{ simulated data @@ -37,4 +40,5 @@ simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2) # add 20\% missing data simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, frac_na = 0.2) + } diff --git a/tests/testthat/test-clustering.R b/tests/testthat/test-clustering.R index bd10cdd..404d31c 100755 --- a/tests/testthat/test-clustering.R +++ b/tests/testthat/test-clustering.R @@ -27,7 +27,7 @@ test_that("Stop when there are rows which contain only missing data", { data <- as.data.frame(simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL)) data[3, -1] <- NA data[4, -1] <- NA - expect_error(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = FALSE, seed = 60427), "The following rows contain only missing values: 3,4") + expect_error(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427)) }) # Matrix input: @@ -56,9 +56,6 @@ test_that("Use rownames if exists", { res <- TGL_kmeans_tidy(data, 30, id_column = FALSE, metric = "euclid", verbose = FALSE, seed = 60427) clustering_ok(data, res, nclust, ndims, order = FALSE) - - expect_warning(res1 <- TGL_kmeans_tidy(data, 30, id_column = TRUE, metric = "euclid", verbose = FALSE, seed = 60427)) - clustering_ok(data, res1, nclust, ndims, order = FALSE) }) test_that("Dot not fail when rownames do not exist", { @@ -99,7 +96,7 @@ test_that("all ids and clusters are present", { nclust <- 30 ndims <- 5 data <- simulate_data(n = 200, sd = 0.3, dims = 5, nclust = nclust, frac_na = 0.05) - res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE) + res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE) clustering_ok(data, res, nclust, ndims, order = FALSE) }) @@ -108,8 +105,8 @@ test_that("non tidy version works", { nclust <- 30 ndims <- 5 data <- simulate_data(n = 200, sd = 0.3, dims = 5, nclust = nclust, frac_na = 0.05) - res <- TGL_kmeans(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, seed = 60427) - res_tidy <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, seed = 60427) + res <- TGL_kmeans(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427) + res_tidy <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427) clustering_ok(data, res_tidy, nclust, ndims, order = FALSE) @@ -126,9 +123,9 @@ test_that("hclust intra cluster works", { nclust <- 30 ndims <- 5 data <- simulate_data(n = 200, sd = 0.3, dims = 5, nclust = nclust, frac_na = 0.05) - res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, hclust_intra_clusters = TRUE, parallel = FALSE, seed = 60427) + res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, hclust_intra_clusters = TRUE, parallel = FALSE, seed = 60427) clustering_ok(data, res, nclust, ndims, order = TRUE) - res_non_tidy <- TGL_kmeans(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, hclust_intra_clusters = TRUE, parallel = FALSE, , seed = 60427) + res_non_tidy <- TGL_kmeans(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, hclust_intra_clusters = TRUE, parallel = FALSE, , seed = 60427) expect_equal(res_non_tidy$order, res$order$order) }) @@ -137,7 +134,7 @@ test_that("add_to_data works", { nclust <- 30 ndims <- 5 data <- simulate_data(n = 200, sd = 0.3, dims = 5, nclust = nclust, frac_na = 0.05) - res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, add_to_data = TRUE) + res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, add_to_data = TRUE) clustering_ok(data, res, nclust, ndims, order = FALSE) @@ -158,7 +155,7 @@ test_that("reorder func works when set to mean", { nclust <- 30 ndims <- 5 data <- simulate_data(n = 200, sd = 0.3, dims = 5, nclust = nclust, frac_na = 0.05) - res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, reorder_func = mean) + res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, reorder_func = mean) clustering_ok(data, res, nclust, ndims, order = FALSE) }) @@ -167,7 +164,7 @@ test_that("reorder func works when set to NULL", { nclust <- 30 ndims <- 5 data <- simulate_data(n = 200, sd = 0.3, dims = 5, nclust = nclust, frac_na = 0.05) - res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, reorder_func = NULL) + res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, reorder_func = NULL) clustering_ok(data, res, nclust, ndims, order = FALSE) }) @@ -175,22 +172,22 @@ test_that("reorder func works when set to NULL", { # Verbosity: test_that("quiet if verbose is turned off", { data <- simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL) - expect_silent(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = FALSE, seed = 60427)) + expect_silent(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427)) }) test_that("not quiet when verbose is turned on", { data <- simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL) - expect_message(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = TRUE, seed = 60427)) + expect_output(TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", id_column = TRUE, verbose = TRUE, seed = 60427)) }) test_that("Log is saved when 'keep_log' is turned on", { data <- simulate_data(n = 100, sd = 0.3, nclust = 30, frac_na = NULL) - expect_warning(res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = TRUE, seed = 60427, keep_log = TRUE)) - expect_warning(expect_warning(res <- TGL_kmeans(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = TRUE, seed = 60427, keep_log = TRUE))) + expect_warning(res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, id_column = TRUE, metric = "euclid", verbose = TRUE, seed = 60427, keep_log = TRUE)) + expect_warning(expect_warning(res <- TGL_kmeans(data %>% select(id, starts_with("V")), 30, metric = "euclid", id_column = TRUE, verbose = TRUE, seed = 60427, keep_log = TRUE))) - res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = FALSE, seed = 60427, keep_log = TRUE) + res <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), 30, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427, keep_log = TRUE) expect_type(res$log, "character") - res <- TGL_kmeans(data %>% select(id, starts_with("V")), 30, metric = "euclid", verbose = FALSE, seed = 60427, keep_log = TRUE) + res <- TGL_kmeans(data %>% select(id, starts_with("V")), 30, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427, keep_log = TRUE) expect_type(res$log, "character") }) @@ -198,8 +195,8 @@ test_that("Log is saved when 'keep_log' is turned on", { test_that("setting the seed returns reproducible results", { nclust <- 30 data <- simulate_data(n = 100, sd = 0.3, nclust = nclust, frac_na = NULL) - res1 <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, seed = 60427) - res2 <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", verbose = FALSE, seed = 60427) + res1 <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427) + res2 <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), nclust, metric = "euclid", id_column = TRUE, verbose = FALSE, seed = 60427) expect_true(all(res1$centers[, -1] == res2$centers[, -1])) }) From 6e007c1f5724a9b86a09c384f6ff11b452c8d5f9 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 15:45:02 +0200 Subject: [PATCH 11/18] some style refactoring --- R/TGL_kmeans.R | 94 +++++++++++++++++++++----------- R/intra_clustering.R | 11 +--- R/zzz.R | 4 +- man/TGL_kmeans.Rd | 10 +++- man/TGL_kmeans_tidy.Rd | 10 +++- tests/testthat/test-clustering.R | 2 +- 6 files changed, 85 insertions(+), 46 deletions(-) diff --git a/R/TGL_kmeans.R b/R/TGL_kmeans.R index 7ca21d8..3853c06 100755 --- a/R/TGL_kmeans.R +++ b/R/TGL_kmeans.R @@ -35,7 +35,15 @@ #' } #' #' # create 5 clusters normally distributed around 1:5 -#' d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) +#' d <- simulate_data( +#' n = 100, +#' sd = 0.3, +#' nclust = 5, +#' dims = 2, +#' add_true_clust = FALSE, +#' id_column = FALSE +#' ) +#' #' head(d) #' #' # cluster @@ -64,35 +72,39 @@ TGL_kmeans_tidy <- function(df, seed <- -1 } - if (!is.matrix(df) && !is.data.frame(df)) { - cli_abort("{.field df} must be a matrix or a data frame") + if (!(metric %in% c("euclid", "pearson", "spearman"))) { + cli_abort("{.field metric} must be one of 'euclid', 'pearson' or 'spearman'") } - # mat has the ids as rownames, df is the original input with the ids as the first column - mat <- df - df <- as.data.frame(df) + if (max_iter < 1) { + cli_abort("{.field max_iter} must be greater than 0") + } - if (id_column) { - rownames(mat) <- mat[, 1] - mat <- mat[, -1] - } else { - if (is.null(rownames(mat))) { - rownames(mat) <- 1:nrow(mat) - } + if (min_delta < 0 || min_delta > 1) { + cli_abort("{.field min_delta} must be between 0 and 1") + } - df$id <- as.character(rownames(mat)) + if (!is.matrix(df) && !is.data.frame(df)) { + cli_abort("{.field df} must be a matrix or a data frame") + } - # make sure that 'id' is the first column - df <- df[, c(ncol(df), 1:(ncol(df) - 1))] + # Extract IDs if necessary + ids <- as.character(1:nrow(df)) + id_column_name <- "id" + if (id_column) { + ids <- as.character(df[, 1]) + id_column_name <- colnames(df)[1] + df <- df[, -1] } - df[, 1] <- as.character(df[, 1]) + + mat <- df if (k < 1) { cli_abort("k must be greater than 0") } - if (nrow(df) < k) { - cli_abort("number of observations ({.val {nrow(df)}} must be greater than k ({.val {k}})") + if (nrow(mat) < k) { + cli_abort("number of observations ({.val {nrow(mat)}} must be greater than k ({.val {k}})") } # Thorw an error if there are rows that do not contain any value @@ -102,10 +114,6 @@ TGL_kmeans_tidy <- function(df, cli_abort("The following rows contain only missing values: {.val {all_nas}}") } - ids <- as.character(df[, 1]) - - column_names <- as.character(colnames(df)[-1]) - if (verbose) { km <- TGL_kmeans_cpp( ids = ids, @@ -132,9 +140,11 @@ TGL_kmeans_tidy <- function(df, ) } + # Processing the output + km$centers <- t(km$centers) %>% as_tibble(.name_repair = "minimal") %>% - purrr::set_names(column_names) %>% + purrr::set_names(colnames(mat)) %>% mutate(clust = 1:n()) %>% select(clust, everything()) %>% as_tibble() @@ -147,7 +157,7 @@ TGL_kmeans_tidy <- function(df, km <- reorder_clusters(km, func = reorder_func) } - colnames(km$cluster)[1] <- colnames(df)[1] + colnames(km$cluster)[1] <- id_column_name km$size <- km$cluster %>% count(clust) %>% @@ -162,11 +172,7 @@ TGL_kmeans_tidy <- function(df, } if (add_to_data) { - km$data <- df %>% - mutate(id = as.character(id)) %>% - left_join(km$cluster, by = colnames(df)[1]) %>% - select(clust, everything()) %>% - as_tibble() + km$data <- add_data_to_km_object(df, km$cluster, ids, id_column_name) if (!id_column) { km$data <- as.data.frame(km$data) rownames(km$data) <- km$data$id @@ -176,12 +182,28 @@ TGL_kmeans_tidy <- function(df, if (hclust_intra_clusters) { cli_alert_info("running hclust within each cluster") - km$order <- hclust_every_cluster(km, df, parallel = parallel) + if (is.null(km$data)) { + full_data <- add_data_to_km_object(df, km$cluster, ids, id_column_name) + } else { + full_data <- km$data + } + full_data <- full_data %>% + select(clust, !!id_column_name, everything()) + km$order <- hclust_every_cluster(km, full_data, parallel = parallel) } return(km) } +add_data_to_km_object <- function(df, cluster, ids, id_column_name) { + df %>% + # add the ids at id_column_name + mutate(!!id_column_name := as.character(ids)) %>% + left_join(cluster, by = id_column_name) %>% + select(clust, everything()) %>% + as_tibble() +} + reorder_clusters <- function(km, func = "hclust") { # if there is an empty cluster, remove it and renumber the clusters @@ -269,7 +291,15 @@ reorder_clusters <- function(km, func = "hclust") { #' } #' #' # create 5 clusters normally distributed around 1:5 -#' d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) +#' d <- simulate_data( +#' n = 100, +#' sd = 0.3, +#' nclust = 5, +#' dims = 2, +#' add_true_clust = FALSE, +#' id_column = FALSE +#' ) +#' #' head(d) #' #' # cluster diff --git a/R/intra_clustering.R b/R/intra_clustering.R index 783e981..a15bdba 100755 --- a/R/intra_clustering.R +++ b/R/intra_clustering.R @@ -1,12 +1,5 @@ hclust_every_cluster <- function(km, df, parallel = TRUE) { - if (!rlang::has_name(km, "data")) { - km$data <- df %>% - left_join(km$cluster, by = colnames(df)[1]) %>% - select(clust, everything()) %>% - as_tibble() - } - - all_hc <- km$data %>% + all_hc <- df %>% plyr::dlply(plyr::.(clust), function(x) { ids <- x$id hc <- as.matrix(x[, -1:-2]) %>% @@ -18,7 +11,7 @@ hclust_every_cluster <- function(km, df, parallel = TRUE) { }, .parallel = parallel) %>% purrr::map_df(~.x) - res <- km$data %>% + res <- df %>% select(id, clust) %>% mutate(idx = 1:n()) %>% left_join(all_hc, by = c("id", "clust")) %>% diff --git a/R/zzz.R b/R/zzz.R index 1385578..e855ca9 100755 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,5 +1,5 @@ .onLoad <- function(libname, pkgname) { tglkmeans.set_parallel(pmax(1, round(parallel::detectCores() / 2))) - utils::suppressForeignCheck(c("clust", "new_clust", "true_clust", "intra_clust_order", "idx")) - utils::globalVariables(c("clust", "new_clust", "true_clust", "intra_clust_order", "idx")) + utils::suppressForeignCheck(c("clust", "new_clust", "true_clust", "intra_clust_order", "idx", ":=")) + utils::globalVariables(c("clust", "new_clust", "true_clust", "intra_clust_order", "idx", ":=")) } diff --git a/man/TGL_kmeans.Rd b/man/TGL_kmeans.Rd index 00db7a4..f3679b2 100644 --- a/man/TGL_kmeans.Rd +++ b/man/TGL_kmeans.Rd @@ -70,7 +70,15 @@ tglkmeans.set_parallel(1) } # create 5 clusters normally distributed around 1:5 -d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) +d <- simulate_data( + n = 100, + sd = 0.3, + nclust = 5, + dims = 2, + add_true_clust = FALSE, + id_column = FALSE +) + head(d) # cluster diff --git a/man/TGL_kmeans_tidy.Rd b/man/TGL_kmeans_tidy.Rd index beeef5e..567060e 100644 --- a/man/TGL_kmeans_tidy.Rd +++ b/man/TGL_kmeans_tidy.Rd @@ -74,7 +74,15 @@ tglkmeans.set_parallel(1) } # create 5 clusters normally distributed around 1:5 -d <- simulate_data(n = 100, sd = 0.3, nclust = 5, dims = 2, add_true_clust = FALSE, id_column = FALSE) +d <- simulate_data( + n = 100, + sd = 0.3, + nclust = 5, + dims = 2, + add_true_clust = FALSE, + id_column = FALSE +) + head(d) # cluster diff --git a/tests/testthat/test-clustering.R b/tests/testthat/test-clustering.R index 404d31c..480d5ff 100755 --- a/tests/testthat/test-clustering.R +++ b/tests/testthat/test-clustering.R @@ -148,7 +148,7 @@ test_that("add_to_data works", { tibble::column_to_rownames("id") res <- TGL_kmeans_tidy(data, 30, id_column = FALSE, metric = "euclid", verbose = FALSE, seed = 60427, add_to_data = TRUE) - expect_equal(res$data %>% select(starts_with("V")), data %>% select(starts_with("V"))) + expect_equal(res$data %>% select(starts_with("V")), data %>% select(starts_with("V")), ignore_attr = TRUE) }) test_that("reorder func works when set to mean", { From 1e09d678c52cc45839ed9f843e7d058387aa5153 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 15:49:50 +0200 Subject: [PATCH 12/18] build readme --- README-clustering-1.png | Bin 54824 -> 54819 bytes README.md | 57 +++++++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/README-clustering-1.png b/README-clustering-1.png index 45d4a62b1271814f67e28a52beec9c37b1629d6b..62a6bc033f9ebf68b443b92668a88312ec6df186 100644 GIT binary patch literal 54819 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
    ~`LX)vCdH+W2}|zY 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 # tglkmeans - efficient implementation of kmeans++ algorithm @@ -47,12 +48,12 @@ data <- rbind( colnames(data) <- c("x", "y") head(data) #> x y -#> [1,] 0.8247609 0.8524763 -#> [2,] 0.8617387 1.2519285 -#> [3,] 1.0966104 1.5035216 -#> [4,] 0.6755113 0.7076129 -#> [5,] 1.3306283 0.7485884 -#> [6,] 1.0784311 1.2460204 +#> [1,] 0.6897329 1.1501881 +#> [2,] 0.7505575 0.8284940 +#> [3,] 0.8450581 1.3703770 +#> [4,] 1.2699876 0.6754681 +#> [5,] 1.1362468 0.9854456 +#> [6,] 0.9806104 0.7005027 ``` Cluster using kmeans++: @@ -64,41 +65,41 @@ km #> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 #> 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 -#> 4 4 4 4 4 4 4 4 4 4 4 3 4 4 4 4 4 4 4 4 +#> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 #> 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 -#> 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 +#> 4 4 4 4 4 4 4 4 4 4 5 5 5 5 4 5 5 5 5 5 #> 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 -#> 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 +#> 4 5 5 2 5 5 5 5 5 5 4 5 5 5 5 5 5 5 5 5 #> 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 -#> 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +#> 5 5 5 5 5 5 4 5 5 5 5 5 5 5 5 5 5 5 5 5 #> 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 -#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +#> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 #> 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 -#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +#> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 #> 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 -#> 1 1 1 1 1 1 1 1 3 1 2 2 2 2 2 2 1 2 2 2 +#> 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 #> 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 -#> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 #> 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 -#> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 5 2 2 2 +#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 #> 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 -#> 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 +#> 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 #> 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 -#> 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 +#> 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 #> 241 242 243 244 245 246 247 248 249 250 -#> 5 5 5 5 5 5 5 5 5 5 +#> 3 3 3 3 3 3 3 3 3 3 #> #> $centers #> x y -#> [1,] 2.968139 2.950851 -#> [2,] 4.021329 3.991103 -#> [3,] 1.965148 1.992092 -#> [4,] 0.997430 1.012817 -#> [5,] 4.952484 4.973394 +#> [1,] 4.081080 4.003856 +#> [2,] 3.021008 2.995157 +#> [3,] 5.017328 5.031485 +#> [4,] 1.000223 1.029708 +#> [5,] 1.947112 2.075661 #> #> $size #> 1 2 3 4 5 -#> 52 48 50 49 51 +#> 52 51 48 54 45 ``` Plot the results: @@ -119,3 +120,11 @@ section in the site. ``` r browseVignettes("usage") ``` + +## A note regarding random number generation + +From version 0.4.0 onwards, the package uses R random number generation +functions instead of the C++11 random number generation functions. Note +that this may result in different results from previous versions. To get +the same results as previous versions, set the `use_cpp_random` argument +to `TRUE` in the `TGL_kmeans` function. From f3bd084f732288c236785f7e678b6e37d6e27401 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 16:25:00 +0200 Subject: [PATCH 13/18] changed default number of cores to 0.75 of the machine --- R/zzz.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/zzz.R b/R/zzz.R index e855ca9..0907dd9 100755 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,5 +1,5 @@ .onLoad <- function(libname, pkgname) { - tglkmeans.set_parallel(pmax(1, round(parallel::detectCores() / 2))) + tglkmeans.set_parallel(pmax(1, round(parallel::detectCores() * 0.75))) utils::suppressForeignCheck(c("clust", "new_clust", "true_clust", "intra_clust_order", "idx", ":=")) utils::globalVariables(c("clust", "new_clust", "true_clust", "intra_clust_order", "idx", ":=")) } From 7e93f3320b349e81331bbc16b229eb8dfc90b846 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 16:36:06 +0200 Subject: [PATCH 14/18] updated vignette --- vignettes/usage.Rmd | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vignettes/usage.Rmd b/vignettes/usage.Rmd index 32900f4..687f72e 100755 --- a/vignettes/usage.Rmd +++ b/vignettes/usage.Rmd @@ -18,6 +18,7 @@ Basic usage of the package. library(dplyr) library(ggplot2) library(tglkmeans) +theme_set(theme_classic()) ``` First, let's create 5 clusters normally distributed around 1 to 5, with sd of 0.3: @@ -38,7 +39,8 @@ data %>% ggplot(aes(x = V1, y = V2, color = factor(true_clust))) + Now we can cluster it using kmeans++: ```{r} -data_for_clust <- data %>% select(id, starts_with("V")) +rownames(data) <- data$id +data_for_clust <- data %>% select(starts_with("V")) km <- TGL_kmeans_tidy(data_for_clust, k = 5, metric = "euclid", @@ -137,10 +139,13 @@ data <- simulate_data(n = 100, sd = 0.3, nclust = 30, dims = 300) km <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), k = 30, metric = "euclid", - verbose = FALSE + verbose = FALSE, + id_column = TRUE ) ``` +Note that here we supplied `id_column = TRUE` to indicate that the first column is the id column. + ```{r} d <- tglkmeans:::match_clusters(data, km, 30) sum(d$true_clust == d$new_clust, na.rm = TRUE) / sum(!is.na(d$new_clust)) @@ -161,13 +166,13 @@ We can see that kmeans++ clusters significantly better than R vanilla kmeans. we can set the seed for reproducible results: ```{r} -km1 <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), +km1 <- TGL_kmeans_tidy(data %>% select(starts_with("V")), k = 30, metric = "euclid", verbose = FALSE, seed = 60427 ) -km2 <- TGL_kmeans_tidy(data %>% select(id, starts_with("V")), +km2 <- TGL_kmeans_tidy(data %>% select(starts_with("V")), k = 30, metric = "euclid", verbose = FALSE, From cc082a109eff2b4b74315e0ed6ee920e32bd8df7 Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 16:39:44 +0200 Subject: [PATCH 15/18] added missing URL --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index da49f6c..f8fdbae 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,6 +20,7 @@ Description: Efficient implementation of K-Means++ algorithm. For more . License: MIT + file LICENSE BugReports: https://github.com/tanaylab/tglkmeans/issues +URL: https://tanaylab.github.io/tglkmeans/, https://github.com/tanaylab/tglkmeans Depends: R (>= 4.0.0) Imports: From 134491d04ddbce364c7c7e87c2abab08bb2d43c8 Mon Sep 17 00:00:00 2001 From: Aviezer Lifshitz Date: Tue, 26 Dec 2023 21:18:45 +0200 Subject: [PATCH 16/18] validate that the input is numeric --- R/TGL_kmeans.R | 9 +++++++++ src/AParamStat.h | 4 ++-- vignettes/usage.Rmd | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/R/TGL_kmeans.R b/R/TGL_kmeans.R index 3853c06..fa050e9 100755 --- a/R/TGL_kmeans.R +++ b/R/TGL_kmeans.R @@ -99,6 +99,15 @@ TGL_kmeans_tidy <- function(df, mat <- df + # make sure that the input is numeric + mat <- as.matrix(mat) + if (!is.numeric(mat)) { + if (any(!is.numeric(mat[, 1])) && missing(id_column) && !id_column) { + cli_abort("{.field df} must be numeric. Note that the default of {.field id_column} was changed to FALSE in version {.field 0.4.0}. If you want to use the first column as ids, please set {.field id_column=TRUE}") + } + cli_abort("{.field df} must be numeric.") + } + if (k < 1) { cli_abort("k must be greater than 0") } diff --git a/src/AParamStat.h b/src/AParamStat.h index 88c6f65..be35c60 100644 --- a/src/AParamStat.h +++ b/src/AParamStat.h @@ -55,7 +55,7 @@ float wilcoxon_rank_sum(list &samples, int type = 1) { float EU = n1 * n2 / 2.0; float VarU = n1 * n2 * (samples.size() + 1) / 12.0; - Rcpp::Rcout << "W " << W << " n2 " << n2 << " EU " << EU << " Var " << VarU << endl; + Rcpp::Rcout << "W " << W << " n2 " << n2 << " EU " << EU << " Var " << VarU << " t2_minus_t " << t3_minus_t << endl; float pv = erfc((U - EU) / sqrt(VarU)); @@ -124,7 +124,7 @@ float siegel_tukey(list &samples, int type = 1) { float EU = n1 * n2 / 2.0; float VarU = n1 * n2 * (samples.size() + 1) / 12.0; - Rcpp::Rcout << "W " << W << " n2 " << n2 << " EU " << EU << " Var " << VarU << endl; + Rcpp::Rcout << "W " << W << " n2 " << n2 << " EU " << EU << " Var " << VarU << " t2_minus_t " << t3_minus_t << endl; float pv = erfc((U - EU) / sqrt(VarU)); return (pv); diff --git a/vignettes/usage.Rmd b/vignettes/usage.Rmd index 687f72e..f79a6be 100755 --- a/vignettes/usage.Rmd +++ b/vignettes/usage.Rmd @@ -19,6 +19,7 @@ library(dplyr) library(ggplot2) library(tglkmeans) theme_set(theme_classic()) +set.seed(60427) ``` First, let's create 5 clusters normally distributed around 1 to 5, with sd of 0.3: From 560a805721fe298b25f611631e4757f9152ab41b Mon Sep 17 00:00:00 2001 From: Aviezer Lifshitz Date: Tue, 26 Dec 2023 21:30:55 +0200 Subject: [PATCH 17/18] fix typos --- NEWS.md | 6 +++--- README.Rmd | 2 +- README.md | 2 +- man/TGL_kmeans.Rd | 2 +- man/TGL_kmeans_tidy.Rd | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6ef08ad..7844b04 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,15 +8,15 @@ * Added validity checks for `k` and the number of observations. -# tgkmeans 0.3.11 +# tglkmeans 0.3.11 * Changed pkgdoc, see: https://github.com/r-lib/roxygen2/issues/1491. -# tgkmeans 0.3.10 +# tglkmeans 0.3.10 * Removed broken link to one of the references in the description. -# tgkmeans 0.3.9 +# tglkmeans 0.3.9 * Remove empty clusters. This may happen when the number of clusters is larger than the number of observations, and currently caused an error in the reordering step. diff --git a/README.Rmd b/README.Rmd index 7e8d9e6..da15fb8 100755 --- a/README.Rmd +++ b/README.Rmd @@ -81,4 +81,4 @@ browseVignettes("usage") ## A note regarding random number generation -From version 0.4.0 onwards, the package uses R random number generation functions instead of the C++11 random number generation functions. Note that this may result in different results from previous versions. To get the same results as previous versions, set the `use_cpp_random` argument to `TRUE` in the `TGL_kmeans` function. +From version 0.4.0 onward, the package uses R random number generation functions instead of the C++11 random number generation functions. Note that this may result in different results from previous versions. To get the same results as previous versions, set the `use_cpp_random` argument to `TRUE` in the `TGL_kmeans` function. diff --git a/README.md b/README.md index 5ba1171..4ab1454 100755 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ browseVignettes("usage") ## A note regarding random number generation -From version 0.4.0 onwards, the package uses R random number generation +From version 0.4.0 onward, the package uses R random number generation functions instead of the C++11 random number generation functions. Note that this may result in different results from previous versions. To get the same results as previous versions, set the `use_cpp_random` argument diff --git a/man/TGL_kmeans.Rd b/man/TGL_kmeans.Rd index f3679b2..a3ef07a 100644 --- a/man/TGL_kmeans.Rd +++ b/man/TGL_kmeans.Rd @@ -9,7 +9,7 @@ TGL_kmeans( k, metric = "euclid", max_iter = 40, - min_delta = 0.0001, + min_delta = 1e-04, verbose = FALSE, keep_log = FALSE, id_column = FALSE, diff --git a/man/TGL_kmeans_tidy.Rd b/man/TGL_kmeans_tidy.Rd index 567060e..f304ba8 100644 --- a/man/TGL_kmeans_tidy.Rd +++ b/man/TGL_kmeans_tidy.Rd @@ -9,7 +9,7 @@ TGL_kmeans_tidy( k, metric = "euclid", max_iter = 40, - min_delta = 0.0001, + min_delta = 1e-04, verbose = FALSE, keep_log = FALSE, id_column = FALSE, From 22b7340352b258d34f5e427af1aeca6c1354845f Mon Sep 17 00:00:00 2001 From: aviezerl Date: Tue, 26 Dec 2023 21:40:04 +0200 Subject: [PATCH 18/18] updated cran-comments --- README-clustering-1.png | Bin 54819 -> 54373 bytes README.md | 50 +++++++++++++++++++-------------------- cran-comments.md | 5 ++-- man/tglkmeans-package.Rd | 2 ++ 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/README-clustering-1.png b/README-clustering-1.png index 62a6bc033f9ebf68b443b92668a88312ec6df186..9b630e46f88c8bd68202effc2b30b0fbb32d391f 100644 GIT binary patch literal 54373 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

    PhMI$@`bYF>+AQn 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

    }B;^yFQq(;PnKUn;LI9`regG)H?4y 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

      ~`LX)vCdH+W2}|zY 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 x y -#> [1,] 0.6897329 1.1501881 -#> [2,] 0.7505575 0.8284940 -#> [3,] 0.8450581 1.3703770 -#> [4,] 1.2699876 0.6754681 -#> [5,] 1.1362468 0.9854456 -#> [6,] 0.9806104 0.7005027 +#> [1,] 0.9399229 0.8845469 +#> [2,] 1.3150523 0.8857986 +#> [3,] 1.0690177 1.1334614 +#> [4,] 0.7128039 0.7431859 +#> [5,] 0.4193698 1.1405633 +#> [6,] 1.2876690 0.9883643 ``` Cluster using kmeans++: @@ -63,43 +63,43 @@ km <- TGL_kmeans(data, k = 5, id_column = FALSE) km #> $cluster #> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -#> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 +#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 #> 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 -#> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 +#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 #> 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 -#> 4 4 4 4 4 4 4 4 4 4 5 5 5 5 4 5 5 5 5 5 +#> 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 #> 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 -#> 4 5 5 2 5 5 5 5 5 5 4 5 5 5 5 5 5 5 5 5 +#> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 #> 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 -#> 5 5 5 5 5 5 4 5 5 5 5 5 5 5 5 5 5 5 5 5 -#> 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +#> 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 +#> 3 3 3 3 3 3 4 3 3 3 3 3 3 3 3 3 3 3 3 3 #> 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 -#> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +#> 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 #> 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 -#> 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +#> 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 #> 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 -#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +#> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 4 5 4 4 4 #> 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 -#> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +#> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 #> 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 -#> 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +#> 5 5 5 5 5 5 5 5 5 5 5 5 4 5 5 5 5 5 5 5 #> 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 -#> 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 +#> 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 #> 241 242 243 244 245 246 247 248 249 250 -#> 3 3 3 3 3 3 3 3 3 3 +#> 5 5 5 5 5 5 5 5 5 5 #> #> $centers #> x y -#> [1,] 4.081080 4.003856 -#> [2,] 3.021008 2.995157 -#> [3,] 5.017328 5.031485 -#> [4,] 1.000223 1.029708 -#> [5,] 1.947112 2.075661 +#> [1,] 1.015769 1.025221 +#> [2,] 2.025743 2.022156 +#> [3,] 2.985030 2.857020 +#> [4,] 4.049870 3.921439 +#> [5,] 4.998449 4.983747 #> #> $size #> 1 2 3 4 5 -#> 52 51 48 54 45 +#> 50 51 48 50 51 ``` Plot the results: diff --git a/cran-comments.md b/cran-comments.md index af57004..549f603 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,8 +1,9 @@ ## R CMD check results -0 errors | 0 warnings | 0 notes +0 errors | 0 warnings | 1 note -* Changed pkgdoc as requested by Kurt Hornik. +* The requirement for GNU make is due to the use of RcppParallel which requires GNU make. +* This is a unix only package since it is using tgstat which is not available on windows. diff --git a/man/tglkmeans-package.Rd b/man/tglkmeans-package.Rd index adba0b0..6bb814b 100644 --- a/man/tglkmeans-package.Rd +++ b/man/tglkmeans-package.Rd @@ -11,6 +11,8 @@ Efficient implementation of K-Means++ algorithm. For more information see (1) "k \seealso{ Useful links: \itemize{ + \item \url{https://tanaylab.github.io/tglkmeans/} + \item \url{https://github.com/tanaylab/tglkmeans} \item Report bugs at \url{https://github.com/tanaylab/tglkmeans/issues} }