diff --git a/.github/workflows/check-non-master.yaml b/.github/workflows/check-non-master.yaml index 5eed1af2..c79917bd 100644 --- a/.github/workflows/check-non-master.yaml +++ b/.github/workflows/check-non-master.yaml @@ -1,9 +1,4 @@ -# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. -# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions on: - push: - branches-ignore: - - master pull_request: branches: - master diff --git a/CHANGELOG.md b/CHANGELOG.md index bbcbcace..3025aa3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog -## Version master +## Version 5.6.0 * Update Makevars for ARM version of Windows. +* Sanitize internal usage of `do.call` to avoid huge backtraces. ## Version 5.5.12 * Remove explicit C++ requirements. diff --git a/DESCRIPTION b/DESCRIPTION index ee08b47f..b83eaeae 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -47,7 +47,7 @@ Suggests: knitr, rmarkdown, testthat -Date: 2023-02-28 +Date: 2024-06-22 Author: Alexis Sarda-Espinosa Maintainer: Alexis Sarda BugReports: https://github.com/asardaes/dtwclust/issues diff --git a/NAMESPACE b/NAMESPACE index c370b0bd..22569d42 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -122,7 +122,17 @@ importFrom(parallel,splitIndices) importFrom(proxy,dist) importFrom(proxy,pr_DB) importFrom(reshape2,melt) +importFrom(rlang,"!!!") importFrom(rlang,.data) +importFrom(rlang,as_environment) +importFrom(rlang,as_string) +importFrom(rlang,enexpr) +importFrom(rlang,enexprs) +importFrom(rlang,env_bind) +importFrom(rlang,exprs) +importFrom(rlang,is_call) +importFrom(rlang,list2) +importFrom(rlang,syms) importFrom(shiny,runApp) importFrom(shiny,shinyApp) importFrom(shinyjs,useShinyjs) diff --git a/R/CENTROIDS-pam.R b/R/CENTROIDS-pam.R index 0ceebf09..18f55fd9 100644 --- a/R/CENTROIDS-pam.R +++ b/R/CENTROIDS-pam.R @@ -3,6 +3,7 @@ #' Extract the medoid time series based on a distance measure. #' #' @export +#' @importFrom rlang exprs #' @importFrom Matrix rowSums #' #' @param series The time series in one of the formats accepted by [tslist()]. @@ -42,7 +43,7 @@ pam_cent <- function(series, distance, ids = seq_along(series), distmat = NULL, if (missing(distance)) distance <- attr(distmat, "method") - args <- list( + args <- rlang::exprs( distmat = distmat, series = series, dist_args = dots, @@ -59,7 +60,7 @@ pam_cent <- function(series, distance, ids = seq_along(series), distmat = NULL, } # S4-Distmat.R - distmat <- do.call(Distmat$new, args, TRUE) + distmat <- do.call(Distmat$new, args) } d <- distmat[ids, ids, drop = FALSE] diff --git a/R/CENTROIDS-shape-extraction.R b/R/CENTROIDS-shape-extraction.R index a836aafb..16875785 100644 --- a/R/CENTROIDS-shape-extraction.R +++ b/R/CENTROIDS-shape-extraction.R @@ -73,7 +73,7 @@ shape_extraction <- function(X, centroid = NULL, znorm = FALSE, ..., error.check new_c <- Map(mv$series, mv$cent, f = function(xx, cc, ...) { new_c <- shape_extraction(xx, cc, znorm = znorm, ..., error.check = FALSE) }) - return(do.call(cbind, new_c, TRUE)) + return(call_cbind(new_c)) } Xz <- if (znorm) zscore(X, ..., error.check = FALSE) else X @@ -87,18 +87,18 @@ shape_extraction <- function(X, centroid = NULL, znorm = FALSE, ..., error.check if (is.null(centroid)) { if (!different_lengths(Xz)) { - A <- do.call(rbind, Xz, TRUE) # use all + A <- call_rbind(Xz) # use all } else { centroid <- Xz[[sample(length(Xz), 1L)]] # random choice as reference A <- lapply(Xz, function(a) { SBD(centroid, a)$yshift }) - A <- do.call(rbind, A, TRUE) + A <- call_rbind(A) } } else { centroid <- zscore(centroid, ..., error.check = FALSE) # use given reference A <- lapply(Xz, function(a) { SBD(centroid, a)$yshift }) - A <- do.call(rbind, A, TRUE) + A <- call_rbind(A) } Y <- zscore(A, ..., error.check = FALSE) diff --git a/R/CLUSTERING-all-cent2.R b/R/CLUSTERING-all-cent2.R index 64f6cfd2..61d307a1 100644 --- a/R/CLUSTERING-all-cent2.R +++ b/R/CLUSTERING-all-cent2.R @@ -41,6 +41,7 @@ reinit_clusters <- function(x, cent, cent_case, num_empty, empty_clusters, distm # -------------------------------------------------------------------------------------------------- # shape shape_cent <- function(x, x_split, cent, id_changed, cl_id, ..., distmat) { + force(x) # not all arguments are used, but I want them to be isolated from ellipsis dots <- list(...) dots$error.check <- FALSE @@ -106,12 +107,12 @@ mean_cent <- function(x_split, ...) { if (is_multivariate(xx)) { ncols <- ncol(xx[[1L]]) # number of dimensions should be equal ncols <- rep(1L:ncols, length(xx)) - xx <- do.call(cbind, xx, TRUE) + xx <- call_cbind(xx) xx <- split.data.frame(t(xx), ncols) - do.call(cbind, lapply(xx, colMeans), TRUE) + call_cbind(lapply(xx, colMeans)) } else { - xx <- do.call(cbind, xx, TRUE) + xx <- call_cbind(xx) rowMeans(xx) } }) @@ -125,12 +126,12 @@ median_cent <- function(x_split, ...) { if (is_multivariate(xx)) { ncols <- ncol(xx[[1L]]) # number of dimensions should be equal ncols <- rep(1L:ncols, length(xx)) - xx <- do.call(cbind, xx, TRUE) + xx <- call_cbind(xx) xx <- split.data.frame(t(xx), ncols) - do.call(cbind, lapply(xx, colMedians), TRUE) + call_cbind(lapply(xx, colMedians)) } else { - xx <- do.call(rbind, xx, TRUE) + xx <- call_rbind(xx) colMedians(xx) } }) @@ -150,7 +151,7 @@ fcm_cent <- function(x, u, k, ..., distmat) { cent } else { - cent <- t(u) %*% do.call(rbind, x, TRUE) + cent <- t(u) %*% call_rbind(x) apply(cent, 2L, "/", e2 = colSums(u)) } } diff --git a/R/CLUSTERING-compare-clusterings.R b/R/CLUSTERING-compare-clusterings.R index 5d6ffbb1..96b0661b 100644 --- a/R/CLUSTERING-compare-clusterings.R +++ b/R/CLUSTERING-compare-clusterings.R @@ -62,7 +62,7 @@ pdc_configs <- function(type = c("preproc", "distance", "centroid"), ..., if (length(shared) > 0L && length(share.config) > 0L) { # careful, singular and plural below shared_cfg <- Map(shared, names(shared), f = function(shared_args, fun) { - cfg <- quoted_call(expand.grid, foo = fun, stringsAsFactors = FALSE, dots = shared_args) + cfg <- quoted_call(expand.grid, fun, stringsAsFactors = FALSE, dots = shared_args) names(cfg)[1L] <- type cfg }) @@ -86,7 +86,7 @@ pdc_configs <- function(type = c("preproc", "distance", "centroid"), ..., if (!is.list(config) || is.null(config_names)) stop("All parameters must be named lists.") # nocov cfg <- Map(config, config_names, f = function(config_args, fun) { - cfg <- quoted_call(expand.grid, foo = fun, stringsAsFactors = FALSE, dots = config_args) + cfg <- quoted_call(expand.grid, fun, stringsAsFactors = FALSE, dots = config_args) names(cfg)[1L] <- type cfg }) @@ -631,7 +631,7 @@ compare_clusterings <- function(series = NULL, types = c("p", "h", "f", "t"), export <- c("trace", "score.clus", "return.objects", "dots", "centroids_included", - "check_consistency", "quoted_call", "enlist", "subset_dots", "get_from_callers", + "check_consistency", "do_call", "quoted_call", "enlist", "subset_dots", "get_from_callers", "setnames_inplace", custom_preprocs, custom_centroids) @@ -682,16 +682,17 @@ compare_clusterings <- function(series = NULL, types = c("p", "h", "f", "t"), }) setnames_inplace(args, c("preproc", "dist", "cent")) - args <- do.call(tsclust_args, args, TRUE) + args <- do_call("tsclust_args", args) # ---------------------------------------------------------------------------------- # controls for this configuration # ---------------------------------------------------------------------------------- - control_fun <- match.fun(paste0(type, "_control")) + control_fun_name <- paste0(type, "_control") + control_fun <- match.fun(control_fun_name) control_args <- subset_dots(as.list(cfg), control_fun) control_args <- lapply(control_args, unlist, recursive = FALSE) - control <- do.call(control_fun, control_args, TRUE) + control <- do_call(control_fun_name, control_args) # ---------------------------------------------------------------------------------- # get processed series @@ -719,7 +720,7 @@ compare_clusterings <- function(series = NULL, types = c("p", "h", "f", "t"), distance <- cfg$distance dist_entry <- dist_entries[[distance]] if (!check_consistency(dist_entry$names[1L], "dist")) - do.call(proxy::pr_DB$set_entry, dist_entry, TRUE) # nocov + do_call(proxy::pr_DB$set_entry, dist_entry) # nocov } else distance <- NULL # dummy @@ -749,7 +750,7 @@ compare_clusterings <- function(series = NULL, types = c("p", "h", "f", "t"), if (centroid_char == "default") { # do not specify centroid - tsc <- do.call(tsclust, this_args, TRUE) + tsc <- do_call("tsclust", this_args) } else if (type %in% c("partitional", "fuzzy") && centroid_char %in% centroids_included) { # with included centroid @@ -982,7 +983,8 @@ compare_clusterings <- function(series = NULL, types = c("p", "h", "f", "t"), f = function(result, cols) { order_args <- as.list(result[cols]) names(order_args) <- NULL - result[do.call(base::order, order_args, TRUE), , drop = FALSE] + base_order <- base::order + result[do_call("base_order", order_args), , drop = FALSE] }) # return results results diff --git a/R/CLUSTERING-cvi-evaluators.R b/R/CLUSTERING-cvi-evaluators.R index 8607624d..664f9301 100644 --- a/R/CLUSTERING-cvi-evaluators.R +++ b/R/CLUSTERING-cvi-evaluators.R @@ -78,7 +78,7 @@ cvi_evaluators <- function(type = "valid", fuzzy = FALSE, ground.truth = NULL) { } score <- function(objs, ...) { - do.call(rbind, lapply(objs, function(obj) { + call_rbind(lapply(objs, function(obj) { if (length(internal) > 0L) cvis <- cvi(a = obj, type = internal, ...) else @@ -115,7 +115,7 @@ cvi_evaluators <- function(type = "valid", fuzzy = FALSE, ground.truth = NULL) { best_overall <- Map(results, best_by_type, f = function(result, row_id) { result[row_id, type, drop = FALSE] }) - best_overall <- do.call(rbind, best_overall) + best_overall <- call_rbind(best_overall) best_overall <- apply(best_overall, 2L, which.max) if (length(type) > 1L && length(unique(best_overall)) == length(best_overall)) stop("All votes are distinct, so majority voting is inconclusive.") diff --git a/R/CLUSTERING-ddist2.R b/R/CLUSTERING-ddist2.R index 36ece92d..e48b4d54 100644 --- a/R/CLUSTERING-ddist2.R +++ b/R/CLUSTERING-ddist2.R @@ -150,7 +150,7 @@ ddist2 <- function(distance, control) { warned <- FALSE # variables/functions from the parent environments that should be exported - export <- c("check_consistency", "quoted_call", "parallel_symmetric", "distance", "dist_entry") + export <- c("check_consistency", "do_call", "quoted_call", "parallel_symmetric", "distance", "dist_entry") ret <- function(result, ...) { ret <- structure(result, method = toupper(distance), ...) @@ -219,7 +219,7 @@ ddist2 <- function(distance, control) { .export = export ) %op% { if (!check_consistency(dist_entry$names[1L], "dist")) { - do.call(proxy::pr_DB$set_entry, dist_entry, TRUE) # nocov + do_call(proxy::pr_DB$set_entry, dist_entry) # nocov } parallel_symmetric(d_desc, ids, x, distance, dots) @@ -269,7 +269,7 @@ ddist2 <- function(distance, control) { .export = export ) %op% { if (!check_consistency(dist_entry$names[1L], "dist")) { - do.call(proxy::pr_DB$set_entry, dist_entry, TRUE) + do_call(proxy::pr_DB$set_entry, dist_entry) } quoted_call(proxy::dist, x = x, y = centroids, method = distance, dots = dots) diff --git a/R/CLUSTERING-repeat-clustering.R b/R/CLUSTERING-repeat-clustering.R index 67799ab6..72c51ccd 100644 --- a/R/CLUSTERING-repeat-clustering.R +++ b/R/CLUSTERING-repeat-clustering.R @@ -95,33 +95,40 @@ repeat_clustering <- function(series, clusterings, config_id, ...) { } # set control args - args$control <- do.call(paste0(clus_type, "_control"), control_args, TRUE) + args$control <- do_call(paste0(clus_type, "_control"), control_args) # set remaining tsclust args centroid_char <- args$centroid if (centroid_char != "default") { if (clus_type %in% c("hierarchical", "tadpole") || !(centroid_char %in% centroids_included)) { - centroid <- get_from_callers(centroid_char, "function") - args$centroid <- as.name("centroid") + args$centroid <- get_from_callers(centroid_char, "function") + } else { + args$centroid <- centroid_char } } else { args$centroid <- NULL } + preproc_char <- if (is.null(args$preproc)) "none" else args$preproc - preproc <- if (preproc_char == "none") NULL else get_from_callers(preproc_char, "function") - args$preproc <- as.name("preproc") + if (preproc_char != "none") { + args$preproc <- get_from_callers(preproc_char, "function") + } else { + args$preproc <- NULL + } + args$series <- series args$type <- clus_type args$seed <- seed - args$args <- do.call(tsclust_args, quote = TRUE, args = list( + args$args <- do_call("tsclust_args", args = list( preproc = preproc_args, dist = distance_args, - cent = centroid_args) - ) + cent = centroid_args + )) # create TSClusters - ret <- do.call(tsclust, c(args, list(...)), FALSE) + args <- c(args, list(...)) + ret <- do_call("tsclust", args) ret@args <- lapply(ret@args, function(arg) { arg$.rng_ <- NULL; arg }) ret@dots$.rng_ <- NULL ret@preproc <- preproc_char diff --git a/R/CLUSTERING-tsclust.R b/R/CLUSTERING-tsclust.R index 34cc2ebf..d6ab7a9d 100644 --- a/R/CLUSTERING-tsclust.R +++ b/R/CLUSTERING-tsclust.R @@ -442,7 +442,7 @@ tsclust <- function(series = NULL, type = "partitional", k = 2L, ..., else { # I need to re-register any custom distances in each parallel worker dist_entry <- proxy::pr_DB$get_entry(distance) - export <- c("pfclust", "check_consistency", "quoted_call") + export <- c("pfclust", "check_consistency", "quoted_call", "do_call") if (is.null(.rng_)) .rng_ <- rng_seq(length(k) * nrep, seed = seed, simplify = FALSE) # UTILS-rng.R # if %do% is used, the outer loop replaces values in this envir @@ -475,7 +475,7 @@ tsclust <- function(series = NULL, type = "partitional", k = 2L, ..., assign(".Random.seed", rng[[i]], .GlobalEnv) if (!check_consistency(dist_entry$names[1L], "dist")) - do.call(proxy::pr_DB$set_entry, dist_entry, TRUE) # nocov + do_call(proxy::pr_DB$set_entry, dist_entry) # nocov # return list(quoted_call( diff --git a/R/S4-TSClusters-methods.R b/R/S4-TSClusters-methods.R index 35a72717..8fa4b4e0 100644 --- a/R/S4-TSClusters-methods.R +++ b/R/S4-TSClusters-methods.R @@ -19,6 +19,7 @@ NULL #' @importFrom methods callNextMethod #' @importFrom methods initialize #' @importFrom methods new +#' @importFrom rlang enexprs #' #' @param .Object A `TSClusters` prototype. You *shouldn't* use this, see Initialize section and the #' examples. @@ -73,29 +74,40 @@ NULL #' setMethod("initialize", "TSClusters", function(.Object, ..., override.family = TRUE) { tic <- proc.time() - dots <- list(...) + parent_dots <- list(...) + dots <- rlang::enexprs(...) + dots$.Object <- quote(.Object) + # some minor checks - if (!is.null(dots$datalist)) dots$datalist <- tslist(dots$datalist) - if (!is.null(dots$centroids)) dots$centroids <- tslist(dots$centroids) + if (!is.null(parent_dots$datalist)) { + datalist <- tslist(parent_dots$datalist) + dots$datalist <- quote(datalist) + } + if (!is.null(parent_dots$centroids)) { + centroids <- tslist(parent_dots$centroids) + dots$centroids <- quote(centroids) + } + # avoid infinite recursion (see https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16629) - if (is.null(dots$call)) { + if (is.null(parent_dots$call)) { call <- match.call() } else { - call <- dots$call + call <- parent_dots$call dots$call <- NULL } + # apparently a non-NULL value is needed if proc_time class is virtual? - if (is.null(dots$proctime)) { - dots$proctime <- tic + if (is.null(parent_dots$proctime)) { + dots$proctime <- quote(tic) fill_proctime <- TRUE } else { fill_proctime <- FALSE # nocov } - # no quoted_call here, apparently do.call evaluates as parent and callNextMethod needs that - .Object <- do.call(methods::callNextMethod, enlist(.Object = .Object, dots = dots), TRUE) + # no shenanigans here, apparently do.call evaluates as parent and callNextMethod needs that + .Object <- do.call(methods::callNextMethod, dots) .Object@call <- call # some "defaults" @@ -490,8 +502,8 @@ setMethod("predict", methods::signature(object = "TSClusters"), predict.TSCluste #' via the ellipsis (`...`). #' #' Otherwise, the function plots the time series of each cluster along with the obtained centroid. -#' The default values for cluster centroids are: `linetype = "dashed"`, `size = 1.5`, `colour = -#' "black"`, `alpha = 0.5`. You can change this by means of the ellipsis (`...`). +#' The default values for cluster centroids are: `linetype = "dashed"`, `linewidth = 1.5`, +#' `colour = "black"`, `alpha = 0.5`. You can change this by means of the ellipsis (`...`). #' #' You can choose what to plot with the `type` parameter. Possible options are: #' @@ -639,8 +651,8 @@ plot.TSClusters <- function(x, y, ..., }) # bind - dfm <- data.frame(dfm, do.call(rbind, dfm_tcc, TRUE)) - dfcm <- data.frame(dfcm, do.call(rbind, dfcm_tc, TRUE)) + dfm <- data.frame(dfm, call_rbind(dfm_tcc)) + dfcm <- data.frame(dfcm, call_rbind(dfcm_tc)) # make factor dfm$cl <- factor(dfm$cl) dfcm$cl <- factor(dfcm$cl) @@ -661,7 +673,7 @@ plot.TSClusters <- function(x, y, ..., if (length(list(...)) == 0L) gg <- gg + ggplot2::geom_line(data = dfcm[dfcm$cl %in% clus, ], linetype = "dashed", - size = 1.5, + linewidth = 1.5, colour = "black", alpha = 0.5) else @@ -715,7 +727,7 @@ plot.TSClusters <- function(x, y, ..., } labels$data <- labels$data[labels$data$cl %in% clus,] labels$inherit.aes <- FALSE - gg <- gg + do.call(ggrepel::geom_label_repel, labels, TRUE) + gg <- gg + do_call(ggrepel::geom_label_repel, labels) } # add facets, remove legend, apply kinda black-white theme diff --git a/R/S4-tsclustFamily.R b/R/S4-tsclustFamily.R index 707e43d2..d4c0bf5b 100644 --- a/R/S4-tsclustFamily.R +++ b/R/S4-tsclustFamily.R @@ -123,19 +123,20 @@ f_cluster <- function(distmat, m) { #' @importFrom methods callNextMethod #' @importFrom methods initialize #' @importFrom methods setMethod +#' @importFrom rlang enexprs +#' @importFrom rlang env_bind #' setMethod("initialize", "tsclustFamily", function(.Object, dist, allcent, ..., control = list(), fuzzy = FALSE) { - dots <- list(...) - dots$.Object <- .Object + rlang::env_bind(environment(), ...) + dots <- rlang::enexprs(...) + dots$.Object <- quote(.Object) if (!missing(dist)) { - if (is.character(dist)) - dots$dist <- ddist2(dist, control) - else - dots$dist <- dist + if (is.character(dist)) dist <- ddist2(dist, control) + dots$dist <- quote(dist) } if (fuzzy) { - dots$cluster <- f_cluster + dots$cluster <- quote(f_cluster) if (!missing(allcent) && is.character(allcent)) allcent <- match.arg(allcent, c("fcm", "fcmdd")) } @@ -147,12 +148,12 @@ setMethod("initialize", "tsclustFamily", distmat = base::as.matrix(control$distmat) ) } - dots$allcent <- all_cent2(allcent, control) + allcent <- all_cent2(allcent, control) } - else if (is.function(allcent)) - dots$allcent <- allcent - else + else if (!is.function(allcent)) { stop("Centroid definition must be either a function or a character") + } + dots$allcent <- quote(allcent) } - do.call(methods::callNextMethod, dots, TRUE) + do.call(methods::callNextMethod, dots) }) diff --git a/R/UTILS-utils.R b/R/UTILS-utils.R index f1e91d06..1ef59788 100644 --- a/R/UTILS-utils.R +++ b/R/UTILS-utils.R @@ -100,7 +100,9 @@ check_consistency <- function(obj, case, ..., clus_type, different_lengths <- function(x) { any(diff(lengths(x)) != 0L) } # Enlist parameters for do.calls -enlist <- function(..., dots = NULL) { c(list(...), dots) } +#' @importFrom rlang !!! +#' @importFrom rlang list2 +enlist <- function(..., dots = NULL) { rlang::list2(..., !!!dots) } # Check if a function has the ellipsis in its formals has_dots <- function(foo) { is.function(foo) && !is.null(formals(foo)$`...`) } @@ -134,9 +136,73 @@ get_from_callers <- function(obj_name, mode = "any") { stop("Could not find object '", obj_name, "' of mode '", mode, "'") # nocov } -# do.call but always quoted +#' @importFrom rlang as_environment +#' @importFrom rlang as_string +#' @importFrom rlang enexpr +#' @importFrom rlang is_call quoted_call <- function(fun, ..., dots = NULL) { - do.call(fun, enlist(..., dots = dots), quote = TRUE) + fun_expr <- rlang::enexpr(fun) + fun_name <- if (rlang::is_call(fun_expr)) { + fn <- as.character(fun_expr) + paste0(fn[2L], fn[1L], fn[3L], collapse = "") + } else { + rlang::as_string(fun_expr) + } + fun_name <- gsub("[@$:]", "_", fun_name) + + l <- list(match.fun(fun)) + names(l) <- fun_name + parent <- rlang::as_environment(l, parent = parent.frame()) + do_call(fun_name, enlist(..., dots = dots), parent = parent) +} + +#' @importFrom rlang as_environment +#' @importFrom rlang syms +call_cbind <- function(args) { + original_names <- names(args) + names(args) <- paste0(".arg", seq_along(args)) + ans <- do.call("cbind", rlang::syms(names(args)), envir = rlang::as_environment(args, .GlobalEnv)) + + if (!is.null(original_names) && ncol(ans) == length(original_names)) { + colnames(ans) <- original_names + } + else { + ans <- unname(ans) + } + + ans +} + +#' @importFrom rlang as_environment +#' @importFrom rlang syms +call_rbind <- function(args) { + names(args) <- paste0(".arg", seq_along(args)) + ans <- do.call("rbind", rlang::syms(names(args)), envir = rlang::as_environment(args, .GlobalEnv)) + rownames(ans) <- NULL + ans +} + +#' @importFrom rlang as_environment +#' @importFrom rlang syms +do_call <- function(f, args, parent = parent.frame()) { + original_names <- tmp_names <- names(args) + unnamed <- tmp_names == "" + + if (is.null(original_names)) { + tmp_names <- paste0(".arg", seq_along(args)) + names(args) <- tmp_names + } + else if (any(unnamed)) { + tmp_names[unnamed] <- paste0(".arg", seq_len(sum(unnamed))) + names(args) <- tmp_names + names(tmp_names) <- original_names + } + else { + names(tmp_names) <- original_names + } + + envir <- rlang::as_environment(args, parent) + do.call(f, rlang::syms(tmp_names), envir = envir) } # ================================================================================================== diff --git a/cran-comments.md b/cran-comments.md index a8e73ade..4b0b91ec 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,6 +1,7 @@ -## Update to version TBD +## Update to version 5.6.0 * Update Makevars for ARM version of Windows. +* Internal optimizations of do.call usages. ## Test environments * Local GNU/Linux, R release diff --git a/inst/COPYRIGHTS b/inst/COPYRIGHTS index 820cc9eb..f794f7eb 100644 --- a/inst/COPYRIGHTS +++ b/inst/COPYRIGHTS @@ -2,7 +2,7 @@ Overall license =============== dtwclust: Time Series Clustering Along With Optimizations for DTW -Copyright (C) 2015-2023 Alexis Sarda-Espinosa +Copyright (C) 2015-2024 Alexis Sarda-Espinosa This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/inst/NEWS.Rd b/inst/NEWS.Rd index 509ce012..e3c0041c 100644 --- a/inst/NEWS.Rd +++ b/inst/NEWS.Rd @@ -7,8 +7,9 @@ Full changelog available at \url{https://github.com/asardaes/dtwclust/blob/master/CHANGELOG.md} } -\section{Changes in version 5.5.12.9000}{ +\section{Changes in version 5.6.0}{ \itemize{ \item Update Makevars for ARM version of Windows. + \item Sanitize internal usage of \code{do.call} to avoid huge backtraces. } } diff --git a/inst/interactive-clustering/main.R b/inst/interactive-clustering/main.R index b587d311..d6ae173e 100644 --- a/inst/interactive-clustering/main.R +++ b/inst/interactive-clustering/main.R @@ -79,7 +79,7 @@ main <- quote({ args$centroid <- NULL if (type == "t") args$distance <- NULL - do.call(tsclust, args, TRUE) + do_call("tsclust", args) }, error = function(e) { e diff --git a/inst/ssdtwclust/main.R b/inst/ssdtwclust/main.R index 75ab425e..6303c586 100644 --- a/inst/ssdtwclust/main.R +++ b/inst/ssdtwclust/main.R @@ -94,7 +94,7 @@ main <- quote({ score.clus = score_fun, dots = dots ) - do.call(compare_clusterings, args, TRUE) + do_call("compare_clusterings", args) }, error = function(e) { e diff --git a/man/tsclusters-methods.Rd b/man/tsclusters-methods.Rd index a52762d3..ad3fd362 100644 --- a/man/tsclusters-methods.Rd +++ b/man/tsclusters-methods.Rd @@ -158,7 +158,8 @@ dendrogram is plotted by default; you can pass any extra parameters to \code{\li via the ellipsis (\code{...}). Otherwise, the function plots the time series of each cluster along with the obtained centroid. -The default values for cluster centroids are: \code{linetype = "dashed"}, \code{size = 1.5}, \code{colour = "black"}, \code{alpha = 0.5}. You can change this by means of the ellipsis (\code{...}). +The default values for cluster centroids are: \code{linetype = "dashed"}, \code{linewidth = 1.5}, +\code{colour = "black"}, \code{alpha = 0.5}. You can change this by means of the ellipsis (\code{...}). You can choose what to plot with the \code{type} parameter. Possible options are: \itemize{ diff --git a/tests/testthat/unit/methods.R b/tests/testthat/unit/methods.R index 3d8c9cb4..e2b23c46 100644 --- a/tests/testthat/unit/methods.R +++ b/tests/testthat/unit/methods.R @@ -274,7 +274,7 @@ test_that("Methods for TSClusters objects are dispatched correctly.", { info = "Plotting series and centroids providing data returns a gg object invisibly") expect_true(inherits(plot(fuzzy_object, type = "series", plot = FALSE, labels = list()), "ggplot"), info = "Plotting multivariate series returns a gg object invisibly") - expect_s3_class(plot(tadpole_object, plot = FALSE, size = 1.5), "ggplot") + expect_s3_class(plot(tadpole_object, plot = FALSE, linewidth = 1.5), "ggplot") object_with_repeated_series <- partitional_object object_with_repeated_series@datalist[[1L]] <- object_with_repeated_series@datalist[[2L]]