From c31d2dbb90f6fb41e9c1b59ae7d6023bab8cfb2f Mon Sep 17 00:00:00 2001 From: gmillot <gael.millot@pasteur.fr> Date: Tue, 19 Jul 2022 16:25:55 +0200 Subject: [PATCH] release v11.5: modification of ggplot2::guides() in fun_gg_boxplot() and fun_gg_scatter() to do not have the warning message about FALSE replaced by none --- README.md | 5 + check/TODO.txt | 7 + cute_little_R_functions.R | 26652 ++++++++++++++-------------- cute_little_R_functions.docx | Bin 498993 -> 499127 bytes dev/internal.function.check.fun.R | 91 + fun_gg_boxplot.docx | Bin 115756 -> 115871 bytes fun_gg_scatter.docx | Bin 121137 -> 121152 bytes 7 files changed, 13429 insertions(+), 13326 deletions(-) create mode 100644 dev/internal.function.check.fun.R diff --git a/README.md b/README.md index 7f4c4f2..ee7618e 100755 --- a/README.md +++ b/README.md @@ -170,6 +170,11 @@ Gitlab developers ## WHAT'S NEW IN +### v11.5.0 + +1) modification of ggplot2::guides() in fun_gg_boxplot() and fun_gg_scatter() to do not have the warning message about FALSE replaced by "none" + + ### v11.4.0 1) fun_slide() now correctly works (argument env removed from the parallelization) diff --git a/check/TODO.txt b/check/TODO.txt index 844723d..ce76e92 100644 --- a/check/TODO.txt +++ b/check/TODO.txt @@ -3,6 +3,13 @@ See bitbucket to make the wiki pages. Example: https://immcantation.readthedocs.io/en/stable/docker/intro.html https://bitbucket.org/kleinstein/immcantation/src/c79aba1f73470e3acbefde8cdb19f27d28fc9752/docs/docker/intro.rst?mode=view +See also: +https://github.com/javh/markr +and contact vanderheiden.jason@gene.com if required + + + + 20210616 diff --git a/cute_little_R_functions.R b/cute_little_R_functions.R index 636784a..35485dd 100755 --- a/cute_little_R_functions.R +++ b/cute_little_R_functions.R @@ -98,343 +98,343 @@ # -> transferred into the cute package # Do not modify this function in cute_little_R_function anymore. See the cute repo fun_check <- function( - data, - class = NULL, - typeof = NULL, - mode = NULL, - length = NULL, - prop = FALSE, - double.as.integer.allowed = FALSE, - options = NULL, - all.options.in.data = FALSE, - na.contain = FALSE, - neg.values = TRUE, - print = FALSE, - data.name = NULL, - fun.name = NULL +data, +class = NULL, +typeof = NULL, +mode = NULL, +length = NULL, +prop = FALSE, +double.as.integer.allowed = FALSE, +options = NULL, +all.options.in.data = FALSE, +na.contain = FALSE, +neg.values = TRUE, +print = FALSE, +data.name = NULL, +fun.name = NULL ){ - # AIM - # Check the class, type, mode and length of the data argument - # Mainly used to check the arguments of other functions - # Check also other kind of data parameters, is it a proportion? Is it type double but numbers without decimal part? - # If options == NULL, then at least class or type or mode or length argument must be non-null - # If options is non-null, then class, type and mode must be NULL, and length can be NULL or specified - # WARNINGS - # The function tests what is written in its arguments, even if what is written is incoherent. For instance, fun_check(data = factor(1), class = "factor", mode = "character") will return a problem, whatever the object tested in the data argument, because no object can be class "factor" and mode "character" (factors are class "factor" and mode "numeric"). Of note, length of object of class "environment" is always 0 - # If the tested object is NULL, then the function will always return a checking problem - # Since R >= 4.0.0, class(matrix()) returns "matrix" "array", and not "matrix" alone as before. However, use argument class = "matrix" to check for matrix object (of class "matrix" "array" in R >= 4.0.0) and use argument class = "array" to check for array object (of class "array" in R >= 4.0.0) - # ARGUMENTS - # data: object to test - # class: character string. Either one of the class() result (But see the warning section above) or "vector" or "ggplot2" (i.e., objects of class c("gg", "ggplot")) or NULL - # typeof: character string. Either one of the typeof() result or NULL - # mode: character string. Either one of the mode() result (for non-vector object) or NULL - # length: numeric value indicating the length of the object. Not considered if NULL - # prop: logical. Are the numeric values between 0 and 1 (proportion)? If TRUE, can be used alone, without considering class, etc. - # double.as.integer.allowed: logical. If TRUE, no error is reported in the cheking message if argument is set to typeof == "integer" or class == "integer", while the reality is typeof == "double" or class == "numeric" but the numbers strictly have zero as modulo (remainder of a division). This means that i <- 1, which is typeof(i) -> "double" is considered as integer with double.as.integer.allowed = TRUE. WARNING: data%%1 == 0L but not isTRUE(all.equal(data%%1, 0)) is used here because the argument checks for integers stored as double (does not check for decimal numbers that are approximate integers) - # options: a vector of character strings or integers indicating all the possible option values for the data argument, or NULL. Numbers of type "double" are accepted if they have a 0 modulo - # all.options.in.data: logical. If TRUE, all of the options must be present at least once in the data argument, and nothing else. If FALSE, some or all of the options must be present in the data argument, and nothing else. Ignored if options is NULL - # na.contain: logical. Can the data argument contain NA? - # neg.values: logical. Are negative numeric values authorized? Warning: the default setting is TRUE, meaning that, in that case, no check is performed for the presence of negative values. The neg.values argument is activated only when set to FALSE. In addition, (1) neg.values = FALSE can only be used when class, typeof or mode arguments are not NULL, otherwise return an error message, (2) the presence of negative values is not checked with neg.values = FALSE if the tested object is a factor and the following checking message is returned "OBJECT MUST BE MADE OF NON NEGATIVE VALUES BUT IS A FACTOR" - # print: logical. Print the message if $problem is TRUE? Warning: set by default to FALSE, which facilitates the control of the checking message output when using fun_check() inside functions. See the example section - # data.name: character string indicating the name of the object to test. If NULL, use what is assigned to the data argument for the returned message - # fun.name: character string indicating the name of the function checked (i.e., when fun_check() is used to check the arguments of this function). If non-null, the value of fun.name will be added into the message returned by fun_check() - # RETURN - # A list containing: - # $problem: logical. Is there any problem detected? - # $text: message indicating the details of the problem, or the absence of problem - # $object.name: value of the data.name argument (i.e., name of the checked object if provided, NULL otherwise) - # REQUIRED PACKAGES - # None - # REQUIRED FUNCTIONS FROM THE cute PACKAGE - # None - # EXAMPLE - # test <- matrix(1:3) ; fun_check(data = test, print = TRUE, class = "vector", mode = "numeric") - # see http - # DEBUGGING - # data = mean ; class = NULL ; typeof = NULL ; mode = NULL ; length = NULL ; prop = FALSE ; double.as.integer.allowed = FALSE ; options = "a" ; all.options.in.data = FALSE ; na.contain = FALSE ; neg.values = TRUE ; print = TRUE ; data.name = NULL ; fun.name = NULL - # function name - # no used in this function for the error message, to avoid env colliding - # end function name - # required function checking - # end required function checking - # reserved words - # end reserved words - # fun.name checked first because required next - if( ! is.null(fun.name)){ # I have to use this way to deal with every kind of class for fun.name - if(all(base::class(fun.name) == "character")){ # all() without na.rm -> ok because class(NA) is "logical" - if(base::length(fun.name) != 1){ - tempo.cat <- paste0("ERROR IN fun_check(): THE fun.name ARGUMENT MUST BE A CHARACTER VECTOR OF LENGTH 1: ", paste(fun.name, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(any(is.na(fun.name))){ # normally no NA with is.na() - tempo.cat <- paste0("ERROR IN fun_check(): NO ARGUMENT EXCEPT data AND options CAN HAVE NA VALUES\nPROBLEMATIC ARGUMENT IS fun.name") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else{ - tempo.cat <- paste0("ERROR IN fun_check(): THE fun.name ARGUMENT MUST BE A CHARACTER VECTOR OF LENGTH 1") # paste(fun.name, collapse = " ") removed here because does not work with objects like function - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end fun.name checked first because required next - # arg with no default values - mandat.args <- c( - "data" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status - # end argument primary checking - # second round of checking and data preparation - # management of special classes - basic.class <- c( - "NULL", # because class(NULL) is "NULL". The NULL aspect will be dealt later - "logical", - "integer", - "numeric", - # "complex", - "character" - # "matrix", - # "array", - # "data.frame", - # "list", - # "factor", - # "table", - # "expression", - # "name", - # "symbol", - # "function", - # "uneval", - # "environment", - # "ggplot2", - # "ggplot_built", - # "call" - ) - tempo.arg.base <-c( # no names(formals(fun = sys.function(sys.parent(n = 2)))) used with fun_check() to be sure to deal with the correct environment - "class", - "typeof", - "mode", - "length", - "prop", - "double.as.integer.allowed", - "options", - "all.options.in.data", - "na.contain", - "neg.values", - "print", - "data.name", - "fun.name" - ) - tempo.class <-list( # no get() used to be sure to deal with the correct environment - base::class(class), - base::class(typeof), - base::class(mode), - base::class(length), - base::class(prop), - base::class(double.as.integer.allowed), - base::class(options), - base::class(all.options.in.data), - base::class(na.contain), - base::class(neg.values), - base::class(print), - base::class(data.name), - base::class(fun.name) - ) - tempo <- ! sapply(lapply(tempo.class, FUN = "%in%", basic.class), FUN = all) - if(any(tempo)){ - tempo.cat1 <- tempo.arg.base[tempo] - tempo.cat2 <- sapply(tempo.class[tempo], FUN = paste0, collapse = " ") - tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": ANY ARGUMENT EXCEPT data MUST HAVE A BASIC CLASS\nPROBLEMATIC ARGUMENT", ifelse(base::length(tempo.cat1) > 1, "S", ""), " AND ASSOCIATED CLASS", ifelse(base::length(tempo.cat1) > 1, "ES ARE", " IS"), ":\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n")) # normally no NA with is.na() - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of special classes - # management of NA arguments - if(any(is.na(data.name)) | any(is.na(class)) | any(is.na(typeof)) | any(is.na(mode)) | any(is.na(length)) | any(is.na(prop)) | any(is.na(double.as.integer.allowed)) | any(is.na(all.options.in.data)) | any(is.na(na.contain)) | any(is.na(neg.values)) | any(is.na(print)) | any(is.na(fun.name))){ # normally no NA with is.na() - tempo <- c("data.name", "class", "typeof", "mode", "length", "prop", "double.as.integer.allowed", "all.options.in.data", "na.contain", "neg.values", "print", "fun.name")[c(any(is.na(data.name)), any(is.na(class)), any(is.na(typeof)), any(is.na(mode)), any(is.na(length)), any(is.na(prop)), any(is.na(double.as.integer.allowed)), any(is.na(all.options.in.data)), any(is.na(na.contain)), any(is.na(neg.values)), any(is.na(print)), any(is.na(fun.name)))] - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": NO ARGUMENT EXCEPT data AND options CAN HAVE NA VALUES\nPROBLEMATIC ARGUMENT", ifelse(length(tempo) > 1, "S ARE", " IS"), ":\n", paste(tempo, collapse = "\n")) # normally no NA with is.na() - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "prop", - "double.as.integer.allowed", - "all.options.in.data", - "na.contain", - "neg.values", - "print" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){ # normally no NA with is.null() - tempo.cat <- paste0("ERROR IN fun.check():\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT BE NULL:\n", paste0(tempo.arg[tempo.log], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # dealing with logical - # tested below - # end dealing with logical - # code that protects set.seed() in the global environment - # end code that protects set.seed() in the global environment - # warning initiation - # end warning initiation - # other checkings - if( ! is.null(data.name)){ - if( ! (base::length(data.name) == 1L & all(base::class(data.name) == "character"))){ # all() without na.rm -> ok because class(NA) is "logical" - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": data.name ARGUMENT MUST BE A SINGLE CHARACTER ELEMENT AND NOT ", paste(data.name, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(is.null(options) & is.null(class) & is.null(typeof) & is.null(mode) & prop == FALSE & is.null(length)){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": AT LEAST ONE OF THE options, class, typeof, mode, prop, OR length ARGUMENT MUST BE SPECIFIED (I.E, TRUE FOR prop)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(options) & ( ! is.null(class) | ! is.null(typeof) | ! is.null(mode) | prop == TRUE)){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE class, typeof, mode ARGUMENTS MUST BE NULL, AND prop FALSE, IF THE options ARGUMENT IS SPECIFIED\nTHE options ARGUMENT MUST BE NULL IF THE class AND/OR typeof AND/OR mode AND/OR prop ARGUMENT IS SPECIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! (all(base::class(neg.values) == "logical") & base::length(neg.values) == 1L)){ # all() without na.rm -> ok because class(NA) is "logical" - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE neg.values ARGUMENT MUST BE TRUE OR FALSE ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(neg.values == FALSE & is.null(class) & is.null(typeof) & is.null(mode)){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE neg.values ARGUMENT CANNOT BE SWITCHED TO FALSE IF class, typeof AND mode ARGUMENTS ARE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(class)){ # may add "formula" and "Date" as in https://renenyffenegger.ch/notes/development/languages/R/functions/class - if( ! all(class %in% c("vector", "logical", "integer", "numeric", "complex", "character", "matrix", "array", "data.frame", "list", "factor", "table", "expression", "name", "symbol", "function", "uneval", "environment", "ggplot2", "ggplot_built", "call") & base::length(class) == 1L)){ # length == 1L here because of class(matrix()) since R4.0.0 # all() without na.rm -> ok because class cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": class ARGUMENT MUST BE ONE OF THESE VALUE:\n\"vector\", \"logical\", \"integer\", \"numeric\", \"complex\", \"character\", \"matrix\", \"array\", \"data.frame\", \"list\", \"factor\", \"table\", \"expression\", \"name\", \"symbol\", \"function\", \"environment\", \"ggplot2\", \"ggplot_built\", \"call\"") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(neg.values == FALSE & ! any(class %in% c("vector", "numeric", "integer", "matrix", "array", "data.frame", "table"))){ # no need of na.rm = TRUE for any() because %in% does not output NA - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": class ARGUMENT CANNOT BE OTHER THAN \"vector\", \"numeric\", \"integer\", \"matrix\", \"array\", \"data.frame\", \"table\" IF neg.values ARGUMENT IS SWITCHED TO FALSE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! is.null(typeof)){ # all the types are here: https://renenyffenegger.ch/notes/development/languages/R/functions/typeof - if( ! (all(typeof %in% c("logical", "integer", "double", "complex", "character", "list", "expression", "symbol", "closure", "special", "builtin", "environment", "S4", "language")) & base::length(typeof) == 1L)){ # "language" is the type of object of class "call" # all() without na.rm -> ok because typeof cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": typeof ARGUMENT MUST BE ONE OF THESE VALUE:\n\"logical\", \"integer\", \"double\", \"complex\", \"character\", \"list\", \"expression\", \"name\", \"symbol\", \"closure\", \"special\", \"builtin\", \"environment\", \"S4\", \"language\"") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(neg.values == FALSE & ! typeof %in% c("double", "integer")){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": typeof ARGUMENT CANNOT BE OTHER THAN \"double\" OR \"integer\" IF neg.values ARGUMENT IS SWITCHED TO FALSE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! is.null(mode)){ # all the types are here: https://renenyffenegger.ch/notes/development/languages/R/functions/typeof - if( ! (all(mode %in% c("logical", "numeric", "complex", "character", "list", "expression", "name", "symbol", "function", "environment", "S4", "call")) & base::length(mode) == 1L)){ # all() without na.rm -> ok because mode cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": mode ARGUMENT MUST BE ONE OF THESE VALUE:\n\"logical\", \"numeric\", \"complex\", \"character\", \"list\", \"expression\", \"name\", \"symbol\", \"function\", \"environment\", \"S4\", \"call\"") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(neg.values == FALSE & mode != "numeric"){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": mode ARGUMENT CANNOT BE OTHER THAN \"numeric\" IF neg.values ARGUMENT IS SWITCHED TO FALSE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! is.null(length)){ - if( ! (is.numeric(length) & base::length(length) == 1L & all( ! grepl(length, pattern = "\\.")))){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": length ARGUMENT MUST BE A SINGLE INTEGER VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! (is.logical(prop) & base::length(prop) == 1L)){ # is.na() already checked for prop - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": prop ARGUMENT MUST BE TRUE OR FALSE ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(prop == TRUE){ - if( ! is.null(class)){ - if( ! any(class %in% c("vector", "numeric", "matrix", "array", "data.frame", "table"))){ # no need of na.rm = TRUE for any() because %in% does not output NA - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": class ARGUMENT CANNOT BE OTHER THAN NULL, \"vector\", \"numeric\", \"matrix\", \"array\", \"data.frame\", \"table\" IF prop ARGUMENT IS TRUE") # not integer because prop - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! is.null(mode)){ - if(mode != "numeric"){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": mode ARGUMENT CANNOT BE OTHER THAN NULL OR \"numeric\" IF prop ARGUMENT IS TRUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! is.null(typeof)){ - if(typeof != "double"){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": typeof ARGUMENT CANNOT BE OTHER THAN NULL OR \"double\" IF prop ARGUMENT IS TRUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - } - if( ! (all(base::class(double.as.integer.allowed) == "logical") & base::length(double.as.integer.allowed) == 1L)){ # all() without na.rm -> ok because class() never returns NA - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE double.as.integer.allowed ARGUMENT MUST BE TRUE OR FALSE ONLY: ", paste(double.as.integer.allowed, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! (is.logical(all.options.in.data) & base::length(all.options.in.data) == 1L)){ - tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": all.options.in.data ARGUMENT MUST BE A SINGLE LOGICAL VALUE (TRUE OR FALSE ONLY): ", paste(all.options.in.data, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! (all(base::class(na.contain) == "logical") & base::length(na.contain) == 1L)){ # all() without na.rm -> ok because class() never returns NA - tempo.cat <- paste0("ERROR IN fun_check(): THE na.contain ARGUMENT MUST BE TRUE OR FALSE ONLY: ", paste(na.contain, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! (all(base::class(print) == "logical") & base::length(print) == 1L)){ # all() without na.rm -> ok because class() never returns NA - tempo.cat <- paste0("ERROR IN fun_check(): THE print ARGUMENT MUST BE TRUE OR FALSE ONLY: ", paste(print, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # data.name and fun.name tested at the beginning - # end other checkings - # end second round of checking and data preparation - # package checking - # end package checking - # main code - if(is.null(data.name)){ - data.name <- deparse(substitute(data)) - } - problem <- FALSE - text <- paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT") - if(( ! is.null(options)) & (all(base::typeof(data) == "character") | all(base::typeof(data) == "integer") | all(base::typeof(data) == "double"))){ # all() without na.rm -> ok because typeof() never returns NA - if(all(base::typeof(data) == "double")){ - if( ! all(data %% 1 == 0L, na.rm = TRUE)){ - problem <- TRUE - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE SOME OF THESE OPTIONS: ", paste(options, collapse = " "), "\nBUT IS NOT EVEN TYPE CHARACTER OR INTEGER") - } - }else{ - text <- "" - if( ! all(data %in% options)){ # no need of na.rm = TRUE for all() because %in% does not output NA - problem <- TRUE - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE SOME OF THESE OPTIONS: ", paste(options, collapse = " "), "\nTHE PROBLEMATIC ELEMENTS OF ", data.name, " ARE: ", paste(unique(data[ ! (data %in% options)]), collapse = " ")) - } - if(all.options.in.data == TRUE){ - if( ! all(options %in% data)){ # no need of na.rm = TRUE for all() because %in% does not output NA - problem <- TRUE - text <- paste0(ifelse(text == "", "", paste0(text, "\n")), ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE MADE OF ALL THESE OPTIONS: ", paste(options, collapse = " "), "\nTHE MISSING ELEMENTS OF THE options ARGUMENT ARE: ", paste(unique(options[ ! (options %in% data)]), collapse = " ")) - } - } - if( ! is.null(length)){ - if(base::length(data) != length){ - problem <- TRUE - text <- paste0(ifelse(text == "", "", paste0(text, "\n")), ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE LENGTH OF ", data.name, " MUST BE ", length, " AND NOT ", base::length(data)) - } - } - if(text == ""){ - text <- paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT") - } - } - }else if( ! is.null(options)){ - problem <- TRUE - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE SOME OF THESE OPTIONS: ", paste(options, collapse = " "), "\nBUT IS NOT EVEN TYPE CHARACTER OR INTEGER") - } - arg.names <- c("class", "typeof", "mode", "length") - if( ! is.null(class)){ - if(class == "matrix"){ # because of class(matric()) since R4.0.0 - class <- c("matrix", "array") - }else if(class == "factor" & all(base::class(data) %in% c("factor", "ordered"))){ # to deal with ordered factors # all() without na.rm -> ok because class(NA) is "logical" - class <- c("factor", "ordered") - } - } - if(is.null(options)){ - for(i2 in 1:base::length(arg.names)){ - if( ! is.null(get(arg.names[i2], env = sys.nframe(), inherit = FALSE))){ - # script to execute - tempo.script <- ' +# AIM +# Check the class, type, mode and length of the data argument +# Mainly used to check the arguments of other functions +# Check also other kind of data parameters, is it a proportion? Is it type double but numbers without decimal part? +# If options == NULL, then at least class or type or mode or length argument must be non-null +# If options is non-null, then class, type and mode must be NULL, and length can be NULL or specified +# WARNINGS +# The function tests what is written in its arguments, even if what is written is incoherent. For instance, fun_check(data = factor(1), class = "factor", mode = "character") will return a problem, whatever the object tested in the data argument, because no object can be class "factor" and mode "character" (factors are class "factor" and mode "numeric"). Of note, length of object of class "environment" is always 0 +# If the tested object is NULL, then the function will always return a checking problem +# Since R >= 4.0.0, class(matrix()) returns "matrix" "array", and not "matrix" alone as before. However, use argument class = "matrix" to check for matrix object (of class "matrix" "array" in R >= 4.0.0) and use argument class = "array" to check for array object (of class "array" in R >= 4.0.0) +# ARGUMENTS +# data: object to test +# class: character string. Either one of the class() result (But see the warning section above) or "vector" or "ggplot2" (i.e., objects of class c("gg", "ggplot")) or NULL +# typeof: character string. Either one of the typeof() result or NULL +# mode: character string. Either one of the mode() result (for non-vector object) or NULL +# length: numeric value indicating the length of the object. Not considered if NULL +# prop: logical. Are the numeric values between 0 and 1 (proportion)? If TRUE, can be used alone, without considering class, etc. +# double.as.integer.allowed: logical. If TRUE, no error is reported in the cheking message if argument is set to typeof == "integer" or class == "integer", while the reality is typeof == "double" or class == "numeric" but the numbers strictly have zero as modulo (remainder of a division). This means that i <- 1, which is typeof(i) -> "double" is considered as integer with double.as.integer.allowed = TRUE. WARNING: data%%1 == 0L but not isTRUE(all.equal(data%%1, 0)) is used here because the argument checks for integers stored as double (does not check for decimal numbers that are approximate integers) +# options: a vector of character strings or integers indicating all the possible option values for the data argument, or NULL. Numbers of type "double" are accepted if they have a 0 modulo +# all.options.in.data: logical. If TRUE, all of the options must be present at least once in the data argument, and nothing else. If FALSE, some or all of the options must be present in the data argument, and nothing else. Ignored if options is NULL +# na.contain: logical. Can the data argument contain NA? +# neg.values: logical. Are negative numeric values authorized? Warning: the default setting is TRUE, meaning that, in that case, no check is performed for the presence of negative values. The neg.values argument is activated only when set to FALSE. In addition, (1) neg.values = FALSE can only be used when class, typeof or mode arguments are not NULL, otherwise return an error message, (2) the presence of negative values is not checked with neg.values = FALSE if the tested object is a factor and the following checking message is returned "OBJECT MUST BE MADE OF NON NEGATIVE VALUES BUT IS A FACTOR" +# print: logical. Print the message if $problem is TRUE? Warning: set by default to FALSE, which facilitates the control of the checking message output when using fun_check() inside functions. See the example section +# data.name: character string indicating the name of the object to test. If NULL, use what is assigned to the data argument for the returned message +# fun.name: character string indicating the name of the function checked (i.e., when fun_check() is used to check the arguments of this function). If non-null, the value of fun.name will be added into the message returned by fun_check() +# RETURN +# A list containing: +# $problem: logical. Is there any problem detected? +# $text: message indicating the details of the problem, or the absence of problem +# $object.name: value of the data.name argument (i.e., name of the checked object if provided, NULL otherwise) +# REQUIRED PACKAGES +# None +# REQUIRED FUNCTIONS FROM THE cute PACKAGE +# None +# EXAMPLE +# test <- matrix(1:3) ; fun_check(data = test, print = TRUE, class = "vector", mode = "numeric") +# see http +# DEBUGGING +# data = mean ; class = NULL ; typeof = NULL ; mode = NULL ; length = NULL ; prop = FALSE ; double.as.integer.allowed = FALSE ; options = "a" ; all.options.in.data = FALSE ; na.contain = FALSE ; neg.values = TRUE ; print = TRUE ; data.name = NULL ; fun.name = NULL +# function name +# no used in this function for the error message, to avoid env colliding +# end function name +# required function checking +# end required function checking +# reserved words +# end reserved words +# fun.name checked first because required next +if( ! is.null(fun.name)){ # I have to use this way to deal with every kind of class for fun.name +if(all(base::class(fun.name) == "character")){ # all() without na.rm -> ok because class(NA) is "logical" +if(base::length(fun.name) != 1){ +tempo.cat <- paste0("ERROR IN fun_check(): THE fun.name ARGUMENT MUST BE A CHARACTER VECTOR OF LENGTH 1: ", paste(fun.name, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(any(is.na(fun.name))){ # normally no NA with is.na() +tempo.cat <- paste0("ERROR IN fun_check(): NO ARGUMENT EXCEPT data AND options CAN HAVE NA VALUES\nPROBLEMATIC ARGUMENT IS fun.name") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else{ +tempo.cat <- paste0("ERROR IN fun_check(): THE fun.name ARGUMENT MUST BE A CHARACTER VECTOR OF LENGTH 1") # paste(fun.name, collapse = " ") removed here because does not work with objects like function +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end fun.name checked first because required next +# arg with no default values +mandat.args <- c( +"data" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status +# end argument primary checking +# second round of checking and data preparation +# management of special classes +basic.class <- c( +"NULL", # because class(NULL) is "NULL". The NULL aspect will be dealt later +"logical", +"integer", +"numeric", +# "complex", +"character" +# "matrix", +# "array", +# "data.frame", +# "list", +# "factor", +# "table", +# "expression", +# "name", +# "symbol", +# "function", +# "uneval", +# "environment", +# "ggplot2", +# "ggplot_built", +# "call" +) +tempo.arg.base <-c( # no names(formals(fun = sys.function(sys.parent(n = 2)))) used with fun_check() to be sure to deal with the correct environment +"class", +"typeof", +"mode", +"length", +"prop", +"double.as.integer.allowed", +"options", +"all.options.in.data", +"na.contain", +"neg.values", +"print", +"data.name", +"fun.name" +) +tempo.class <-list( # no get() used to be sure to deal with the correct environment +base::class(class), +base::class(typeof), +base::class(mode), +base::class(length), +base::class(prop), +base::class(double.as.integer.allowed), +base::class(options), +base::class(all.options.in.data), +base::class(na.contain), +base::class(neg.values), +base::class(print), +base::class(data.name), +base::class(fun.name) +) +tempo <- ! sapply(lapply(tempo.class, FUN = "%in%", basic.class), FUN = all) +if(any(tempo)){ +tempo.cat1 <- tempo.arg.base[tempo] +tempo.cat2 <- sapply(tempo.class[tempo], FUN = paste0, collapse = " ") +tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": ANY ARGUMENT EXCEPT data MUST HAVE A BASIC CLASS\nPROBLEMATIC ARGUMENT", ifelse(base::length(tempo.cat1) > 1, "S", ""), " AND ASSOCIATED CLASS", ifelse(base::length(tempo.cat1) > 1, "ES ARE", " IS"), ":\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n")) # normally no NA with is.na() +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of special classes +# management of NA arguments +if(any(is.na(data.name)) | any(is.na(class)) | any(is.na(typeof)) | any(is.na(mode)) | any(is.na(length)) | any(is.na(prop)) | any(is.na(double.as.integer.allowed)) | any(is.na(all.options.in.data)) | any(is.na(na.contain)) | any(is.na(neg.values)) | any(is.na(print)) | any(is.na(fun.name))){ # normally no NA with is.na() +tempo <- c("data.name", "class", "typeof", "mode", "length", "prop", "double.as.integer.allowed", "all.options.in.data", "na.contain", "neg.values", "print", "fun.name")[c(any(is.na(data.name)), any(is.na(class)), any(is.na(typeof)), any(is.na(mode)), any(is.na(length)), any(is.na(prop)), any(is.na(double.as.integer.allowed)), any(is.na(all.options.in.data)), any(is.na(na.contain)), any(is.na(neg.values)), any(is.na(print)), any(is.na(fun.name)))] +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": NO ARGUMENT EXCEPT data AND options CAN HAVE NA VALUES\nPROBLEMATIC ARGUMENT", ifelse(length(tempo) > 1, "S ARE", " IS"), ":\n", paste(tempo, collapse = "\n")) # normally no NA with is.na() +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"prop", +"double.as.integer.allowed", +"all.options.in.data", +"na.contain", +"neg.values", +"print" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){ # normally no NA with is.null() +tempo.cat <- paste0("ERROR IN fun.check():\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT BE NULL:\n", paste0(tempo.arg[tempo.log], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# dealing with logical +# tested below +# end dealing with logical +# code that protects set.seed() in the global environment +# end code that protects set.seed() in the global environment +# warning initiation +# end warning initiation +# other checkings +if( ! is.null(data.name)){ +if( ! (base::length(data.name) == 1L & all(base::class(data.name) == "character"))){ # all() without na.rm -> ok because class(NA) is "logical" +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": data.name ARGUMENT MUST BE A SINGLE CHARACTER ELEMENT AND NOT ", paste(data.name, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(is.null(options) & is.null(class) & is.null(typeof) & is.null(mode) & prop == FALSE & is.null(length)){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": AT LEAST ONE OF THE options, class, typeof, mode, prop, OR length ARGUMENT MUST BE SPECIFIED (I.E, TRUE FOR prop)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(options) & ( ! is.null(class) | ! is.null(typeof) | ! is.null(mode) | prop == TRUE)){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE class, typeof, mode ARGUMENTS MUST BE NULL, AND prop FALSE, IF THE options ARGUMENT IS SPECIFIED\nTHE options ARGUMENT MUST BE NULL IF THE class AND/OR typeof AND/OR mode AND/OR prop ARGUMENT IS SPECIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! (all(base::class(neg.values) == "logical") & base::length(neg.values) == 1L)){ # all() without na.rm -> ok because class(NA) is "logical" +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE neg.values ARGUMENT MUST BE TRUE OR FALSE ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(neg.values == FALSE & is.null(class) & is.null(typeof) & is.null(mode)){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE neg.values ARGUMENT CANNOT BE SWITCHED TO FALSE IF class, typeof AND mode ARGUMENTS ARE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(class)){ # may add "formula" and "Date" as in https://renenyffenegger.ch/notes/development/languages/R/functions/class +if( ! all(class %in% c("vector", "logical", "integer", "numeric", "complex", "character", "matrix", "array", "data.frame", "list", "factor", "table", "expression", "name", "symbol", "function", "uneval", "environment", "ggplot2", "ggplot_built", "call") & base::length(class) == 1L)){ # length == 1L here because of class(matrix()) since R4.0.0 # all() without na.rm -> ok because class cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": class ARGUMENT MUST BE ONE OF THESE VALUE:\n\"vector\", \"logical\", \"integer\", \"numeric\", \"complex\", \"character\", \"matrix\", \"array\", \"data.frame\", \"list\", \"factor\", \"table\", \"expression\", \"name\", \"symbol\", \"function\", \"environment\", \"ggplot2\", \"ggplot_built\", \"call\"") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(neg.values == FALSE & ! any(class %in% c("vector", "numeric", "integer", "matrix", "array", "data.frame", "table"))){ # no need of na.rm = TRUE for any() because %in% does not output NA +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": class ARGUMENT CANNOT BE OTHER THAN \"vector\", \"numeric\", \"integer\", \"matrix\", \"array\", \"data.frame\", \"table\" IF neg.values ARGUMENT IS SWITCHED TO FALSE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if( ! is.null(typeof)){ # all the types are here: https://renenyffenegger.ch/notes/development/languages/R/functions/typeof +if( ! (all(typeof %in% c("logical", "integer", "double", "complex", "character", "list", "expression", "symbol", "closure", "special", "builtin", "environment", "S4", "language")) & base::length(typeof) == 1L)){ # "language" is the type of object of class "call" # all() without na.rm -> ok because typeof cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": typeof ARGUMENT MUST BE ONE OF THESE VALUE:\n\"logical\", \"integer\", \"double\", \"complex\", \"character\", \"list\", \"expression\", \"name\", \"symbol\", \"closure\", \"special\", \"builtin\", \"environment\", \"S4\", \"language\"") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(neg.values == FALSE & ! typeof %in% c("double", "integer")){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": typeof ARGUMENT CANNOT BE OTHER THAN \"double\" OR \"integer\" IF neg.values ARGUMENT IS SWITCHED TO FALSE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if( ! is.null(mode)){ # all the types are here: https://renenyffenegger.ch/notes/development/languages/R/functions/typeof +if( ! (all(mode %in% c("logical", "numeric", "complex", "character", "list", "expression", "name", "symbol", "function", "environment", "S4", "call")) & base::length(mode) == 1L)){ # all() without na.rm -> ok because mode cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": mode ARGUMENT MUST BE ONE OF THESE VALUE:\n\"logical\", \"numeric\", \"complex\", \"character\", \"list\", \"expression\", \"name\", \"symbol\", \"function\", \"environment\", \"S4\", \"call\"") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(neg.values == FALSE & mode != "numeric"){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": mode ARGUMENT CANNOT BE OTHER THAN \"numeric\" IF neg.values ARGUMENT IS SWITCHED TO FALSE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if( ! is.null(length)){ +if( ! (is.numeric(length) & base::length(length) == 1L & all( ! grepl(length, pattern = "\\.")))){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": length ARGUMENT MUST BE A SINGLE INTEGER VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if( ! (is.logical(prop) & base::length(prop) == 1L)){ # is.na() already checked for prop +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": prop ARGUMENT MUST BE TRUE OR FALSE ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(prop == TRUE){ +if( ! is.null(class)){ +if( ! any(class %in% c("vector", "numeric", "matrix", "array", "data.frame", "table"))){ # no need of na.rm = TRUE for any() because %in% does not output NA +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": class ARGUMENT CANNOT BE OTHER THAN NULL, \"vector\", \"numeric\", \"matrix\", \"array\", \"data.frame\", \"table\" IF prop ARGUMENT IS TRUE") # not integer because prop +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if( ! is.null(mode)){ +if(mode != "numeric"){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": mode ARGUMENT CANNOT BE OTHER THAN NULL OR \"numeric\" IF prop ARGUMENT IS TRUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if( ! is.null(typeof)){ +if(typeof != "double"){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": typeof ARGUMENT CANNOT BE OTHER THAN NULL OR \"double\" IF prop ARGUMENT IS TRUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +} +if( ! (all(base::class(double.as.integer.allowed) == "logical") & base::length(double.as.integer.allowed) == 1L)){ # all() without na.rm -> ok because class() never returns NA +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": THE double.as.integer.allowed ARGUMENT MUST BE TRUE OR FALSE ONLY: ", paste(double.as.integer.allowed, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! (is.logical(all.options.in.data) & base::length(all.options.in.data) == 1L)){ +tempo.cat <- paste0("ERROR IN fun_check()", ifelse(is.null(fun.name), "", paste0(" INSIDE ", fun.name)), ": all.options.in.data ARGUMENT MUST BE A SINGLE LOGICAL VALUE (TRUE OR FALSE ONLY): ", paste(all.options.in.data, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! (all(base::class(na.contain) == "logical") & base::length(na.contain) == 1L)){ # all() without na.rm -> ok because class() never returns NA +tempo.cat <- paste0("ERROR IN fun_check(): THE na.contain ARGUMENT MUST BE TRUE OR FALSE ONLY: ", paste(na.contain, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! (all(base::class(print) == "logical") & base::length(print) == 1L)){ # all() without na.rm -> ok because class() never returns NA +tempo.cat <- paste0("ERROR IN fun_check(): THE print ARGUMENT MUST BE TRUE OR FALSE ONLY: ", paste(print, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# data.name and fun.name tested at the beginning +# end other checkings +# end second round of checking and data preparation +# package checking +# end package checking +# main code +if(is.null(data.name)){ +data.name <- deparse(substitute(data)) +} +problem <- FALSE +text <- paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT") +if(( ! is.null(options)) & (all(base::typeof(data) == "character") | all(base::typeof(data) == "integer") | all(base::typeof(data) == "double"))){ # all() without na.rm -> ok because typeof() never returns NA +if(all(base::typeof(data) == "double")){ +if( ! all(data %% 1 == 0L, na.rm = TRUE)){ +problem <- TRUE +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE SOME OF THESE OPTIONS: ", paste(options, collapse = " "), "\nBUT IS NOT EVEN TYPE CHARACTER OR INTEGER") +} +}else{ +text <- "" +if( ! all(data %in% options)){ # no need of na.rm = TRUE for all() because %in% does not output NA +problem <- TRUE +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE SOME OF THESE OPTIONS: ", paste(options, collapse = " "), "\nTHE PROBLEMATIC ELEMENTS OF ", data.name, " ARE: ", paste(unique(data[ ! (data %in% options)]), collapse = " ")) +} +if(all.options.in.data == TRUE){ +if( ! all(options %in% data)){ # no need of na.rm = TRUE for all() because %in% does not output NA +problem <- TRUE +text <- paste0(ifelse(text == "", "", paste0(text, "\n")), ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE MADE OF ALL THESE OPTIONS: ", paste(options, collapse = " "), "\nTHE MISSING ELEMENTS OF THE options ARGUMENT ARE: ", paste(unique(options[ ! (options %in% data)]), collapse = " ")) +} +} +if( ! is.null(length)){ +if(base::length(data) != length){ +problem <- TRUE +text <- paste0(ifelse(text == "", "", paste0(text, "\n")), ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE LENGTH OF ", data.name, " MUST BE ", length, " AND NOT ", base::length(data)) +} +} +if(text == ""){ +text <- paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT") +} +} +}else if( ! is.null(options)){ +problem <- TRUE +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE SOME OF THESE OPTIONS: ", paste(options, collapse = " "), "\nBUT IS NOT EVEN TYPE CHARACTER OR INTEGER") +} +arg.names <- c("class", "typeof", "mode", "length") +if( ! is.null(class)){ +if(class == "matrix"){ # because of class(matric()) since R4.0.0 +class <- c("matrix", "array") +}else if(class == "factor" & all(base::class(data) %in% c("factor", "ordered"))){ # to deal with ordered factors # all() without na.rm -> ok because class(NA) is "logical" +class <- c("factor", "ordered") +} +} +if(is.null(options)){ +for(i2 in 1:base::length(arg.names)){ +if( ! is.null(get(arg.names[i2], env = sys.nframe(), inherit = FALSE))){ +# script to execute +tempo.script <- ' problem <- TRUE ; if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": THE ", data.name, " OBJECT MUST BE ") ; @@ -443,75 +443,75 @@ text <- paste0(text, " AND ") ; } text <- paste0(text, toupper(arg.names[i2]), " ", if(all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) %in% c("matrix", "array"))){"matrix"}else if(all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) %in% c("factor", "ordered"))){"factor"}else{get(arg.names[i2], env = sys.nframe(), inherit = FALSE)}) ' # no need of na.rm = TRUE for all() because %in% does not output NA - # end script to execute - if(base::typeof(data) == "double" & double.as.integer.allowed == TRUE & ((arg.names[i2] == "class" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "integer")) | (arg.names[i2] == "typeof" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "integer")))){ # no need of na.rm = TRUE for all() because == does not output NA if no NA in left of ==, which is the case for arg.names # typeof(data) == "double" means no factor allowed - if( ! all(data %% 1 == 0L, na.rm = TRUE)){ # to check integers (use %%, meaning the remaining of a division): see the precedent line. isTRUE(all.equal(data%%1, rep(0, length(data)))) not used because we strictly need zero as a result. Warning: na.rm = TRUE required here for all() - eval(parse(text = tempo.script)) # execute tempo.script - } - }else if( ! any(all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) %in% c("vector", "ggplot2"))) & ! all(eval(parse(text = paste0(arg.names[i2], "(data)"))) %in% get(arg.names[i2], env = sys.nframe(), inherit = FALSE))){ # test the four c("class", "typeof", "mode", "length") arguments with their corresponding function. No need of na.rm = TRUE for all() because %in% does not output NA # no need of na.rm = TRUE for all() because %in% does not output NA # no need of na.rm = TRUE for any() because get get(arg.names) does not contain NA - eval(parse(text = tempo.script)) # execute tempo.script - }else if(arg.names[i2] == "class" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "vector") & ! (all(base::class(data) %in% "numeric") | all(base::class(data) %in% "integer") | all(base::class(data) %in% "character") | all(base::class(data) %in% "logical"))){ # test class == "vector". No need of na.rm = TRUE for all() because %in% does not output NA # no need of na.rm = TRUE for all() because == does not output NA if no NA in left of ==, which is the case for arg.names - eval(parse(text = tempo.script)) # execute tempo.script - }else if(arg.names[i2] == "class" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "ggplot2") & ! all(base::class(data) %in% c("gg", "ggplot"))){ # test ggplot object # no need of na.rm = TRUE for all() because == does not output NA if no NA in left of ==, which is the case for arg.names # no need of na.rm = TRUE for all() because %in% does not output NA - eval(parse(text = tempo.script)) # execute tempo.script - } - } - } - } +# end script to execute +if(base::typeof(data) == "double" & double.as.integer.allowed == TRUE & ((arg.names[i2] == "class" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "integer")) | (arg.names[i2] == "typeof" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "integer")))){ # no need of na.rm = TRUE for all() because == does not output NA if no NA in left of ==, which is the case for arg.names # typeof(data) == "double" means no factor allowed +if( ! all(data %% 1 == 0L, na.rm = TRUE)){ # to check integers (use %%, meaning the remaining of a division): see the precedent line. isTRUE(all.equal(data%%1, rep(0, length(data)))) not used because we strictly need zero as a result. Warning: na.rm = TRUE required here for all() +eval(parse(text = tempo.script)) # execute tempo.script +} +}else if( ! any(all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) %in% c("vector", "ggplot2"))) & ! all(eval(parse(text = paste0(arg.names[i2], "(data)"))) %in% get(arg.names[i2], env = sys.nframe(), inherit = FALSE))){ # test the four c("class", "typeof", "mode", "length") arguments with their corresponding function. No need of na.rm = TRUE for all() because %in% does not output NA # no need of na.rm = TRUE for all() because %in% does not output NA # no need of na.rm = TRUE for any() because get get(arg.names) does not contain NA +eval(parse(text = tempo.script)) # execute tempo.script +}else if(arg.names[i2] == "class" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "vector") & ! (all(base::class(data) %in% "numeric") | all(base::class(data) %in% "integer") | all(base::class(data) %in% "character") | all(base::class(data) %in% "logical"))){ # test class == "vector". No need of na.rm = TRUE for all() because %in% does not output NA # no need of na.rm = TRUE for all() because == does not output NA if no NA in left of ==, which is the case for arg.names +eval(parse(text = tempo.script)) # execute tempo.script +}else if(arg.names[i2] == "class" & all(get(arg.names[i2], env = sys.nframe(), inherit = FALSE) == "ggplot2") & ! all(base::class(data) %in% c("gg", "ggplot"))){ # test ggplot object # no need of na.rm = TRUE for all() because == does not output NA if no NA in left of ==, which is the case for arg.names # no need of na.rm = TRUE for all() because %in% does not output NA +eval(parse(text = tempo.script)) # execute tempo.script +} +} +} +} if(prop == TRUE & all(base::typeof(data) == "double")){ # all() without na.rm -> ok because typeof(NA) is "logical" - if(is.null(data) | any(data < 0 | data > 1, na.rm = TRUE)){ # works if data is NULL # Warning: na.rm = TRUE required here for any() # typeof(data) == "double" means no factor allowed - problem <- TRUE - if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") - }else{ - text <- paste0(text, " AND ") - } - text <- paste0(text, "THE ", data.name, " OBJECT MUST BE DECIMAL VALUES BETWEEN 0 AND 1") - } +if(is.null(data) | any(data < 0 | data > 1, na.rm = TRUE)){ # works if data is NULL # Warning: na.rm = TRUE required here for any() # typeof(data) == "double" means no factor allowed +problem <- TRUE +if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") +}else{ +text <- paste0(text, " AND ") +} +text <- paste0(text, "THE ", data.name, " OBJECT MUST BE DECIMAL VALUES BETWEEN 0 AND 1") +} }else if(prop == TRUE){ - problem <- TRUE - if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") - }else{ - text <- paste0(text, " AND ") - } - text <- paste0(text, "THE ", data.name, " OBJECT MUST BE DECIMAL VALUES BETWEEN 0 AND 1") +problem <- TRUE +if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") +}else{ +text <- paste0(text, " AND ") +} +text <- paste0(text, "THE ", data.name, " OBJECT MUST BE DECIMAL VALUES BETWEEN 0 AND 1") } if(all(base::class(data) %in% "expression")){ # no need of na.rm = TRUE for all() because %in% does not output NA - data <- as.character(data) # to evaluate the presence of NA +data <- as.character(data) # to evaluate the presence of NA } if(na.contain == FALSE & (base::mode(data) %in% c("logical", "numeric", "complex", "character", "list"))){ # before it was ! (class(data) %in% c("function", "environment")) - if(any(is.na(data)) == TRUE){ # not on the same line because when data is class envir or function , do not like that # normally no NA with is.na() - problem <- TRUE - if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") - }else{ - text <- paste0(text, " AND ") - } - text <- paste0(text, "THE ", data.name, " OBJECT CONTAINS NA WHILE NOT AUTHORIZED") - } +if(any(is.na(data)) == TRUE){ # not on the same line because when data is class envir or function , do not like that # normally no NA with is.na() +problem <- TRUE +if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") +}else{ +text <- paste0(text, " AND ") +} +text <- paste0(text, "THE ", data.name, " OBJECT CONTAINS NA WHILE NOT AUTHORIZED") +} } if(neg.values == FALSE & all(base::mode(data) %in% "numeric") & ! any(base::class(data) %in% "factor")){ # no need of na.rm = TRUE for all() because %in% does not output NA - if(any(data < 0, na.rm = TRUE)){ # Warning: na.rm = TRUE required here for any() - problem <- TRUE - if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") - }else{ - text <- paste0(text, " AND ") - } - text <- paste0(text, "THE ", data.name, " OBJECT MUST BE MADE OF NON NEGATIVE NUMERIC VALUES") - } +if(any(data < 0, na.rm = TRUE)){ # Warning: na.rm = TRUE required here for any() +problem <- TRUE +if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") +}else{ +text <- paste0(text, " AND ") +} +text <- paste0(text, "THE ", data.name, " OBJECT MUST BE MADE OF NON NEGATIVE NUMERIC VALUES") +} }else if(neg.values == FALSE){ - problem <- TRUE - if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ - text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") - }else{ - text <- paste0(text, " AND ") - } - text <- paste0(text, "THE ", data.name, " OBJECT MUST BE MADE OF NON NEGATIVE VALUES BUT IS ", ifelse(any(base::class(data) %in% "factor"), "A FACTOR", "NOT EVEN MODE NUMERIC")) +problem <- TRUE +if(identical(text, paste0(ifelse(is.null(fun.name), "", paste0("IN ", fun.name, ": ")), "NO PROBLEM DETECTED FOR THE ", data.name, " OBJECT"))){ +text <- paste0(ifelse(is.null(fun.name), "ERROR", paste0("ERROR IN ", fun.name)), ": ") +}else{ +text <- paste0(text, " AND ") +} +text <- paste0(text, "THE ", data.name, " OBJECT MUST BE MADE OF NON NEGATIVE VALUES BUT IS ", ifelse(any(base::class(data) %in% "factor"), "A FACTOR", "NOT EVEN MODE NUMERIC")) } if(print == TRUE & problem == TRUE){ - cat(paste0("\n\n================\n\n", text, "\n\n================\n\n")) +cat(paste0("\n\n================\n\n", text, "\n\n================\n\n")) } # output output <- list(problem = problem, text = text, object.name = data.name) @@ -525,104 +525,104 @@ return(output) fun_secu <- function(pos = 1, name = NULL){ - # AIM - # Verify that variables in the environment defined by the pos parameter are not present in the above environment (following R Scope). This can be used to avoid R scope preference of functions like get() - # ARGUMENTS - # pos: single integer indicating the position of the environment checked (argument n of parent.frame()). Value 1 means one step above the fun_secu() local environment (by default). This means that when fun_secu(pos = 1) is used inside a function A, it checks if variables in the local environment of this function A are also present in above environments (following R Scope). When fun_secu(pos = 1) is used in the Global environment, it checks the objects of this environment - # name: single character string indicating the name of the function checked. If NULL, fun_secu() checks all the variables of the environment indicated by pos, as explained in the pos argument description. If non-null, fun_secu() checks all the variables presents in the local env of the function will be checked in the above envs (which includes the working environment (Global env) - # RETURN - # A character string of the local variables that match variables in the different environments of the R scope, or NULL if no match - # REQUIRED PACKAGES - # None - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_secu() - # fun_secu(pos = 2) - # mean <- 0 ; fun1 <- function(){sd <- 1 ; fun_secu(name = as.character(sys.calls()[[length(sys.calls())]]))} ; fun2 <- function(){cor <- 2 ; fun1()} ; fun1() ; fun2() ; rm(mean) # sys.calls() gives the function name at top stack of the imbricated functions, sys.calls()[[length(sys.calls())]] the name of the just above function. This can also been used for the above function: as.character(sys.call(1)) - # test.pos <- 2 ; mean <- 0 ; fun1 <- function(){sd <- 1 ; fun_secu(pos = test.pos, name = if(length(sys.calls()) >= test.pos){as.character(sys.calls()[[length(sys.calls()) + 1 - test.pos]])}else{search()[ (1:length(search()))[test.pos - length(sys.calls())]]})} ; fun2 <- function(){cor <- 2 ; fun1()} ; fun1() ; fun2() ; rm(mean) # for argument name, here is a way to have the name of the tested environment according to test.pos value - # DEBUGGING - # pos = 1 ; name = NULL # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument primary checking - # arg with no default values - # end arg with no default values - # using fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = pos, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(name)){ - tempo <- fun_check(data = name, class = "vector", typeof = "character", length = 1, fun.name = function.name) ; eval(ee) - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end using fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT JUST BE NA:", paste0(tempo.arg[tempo.log], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <- c( - "pos" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT BE NULL:", paste0(tempo.arg[tempo.log], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # end second round of checking and data preparation - # main code - # match.list <- vector("list", length = (length(sys.calls()) - 1 + length(search()) + ifelse(length(sys.calls()) == 1L, -1, 0))) # match.list is a list of all the environment tested (local of functions and R envs), length(sys.calls()) - 1 to remove the level of the fun_secu() function, sys.calls() giving all the names of the imbricated functions, including fun_secu, ifelse(length(sys.calls()) == 1L, -1, 0) to remove Global env if this one is tested - tempo.name <- rev(as.character(unlist(sys.calls()))) # get names of frames (i.e., enclosed env) - tempo.frame <- rev(sys.frames()) # get frames (i.e., enclosed env) - # dealing with source() - # source() used in the Global env creates three frames above the Global env, which should be removed because not very interesting for variable duplications. Add a <<-(sys.frames()) in this code and source anova_contrasts code to see this. With ls(a[[4]]), we can see the content of each env, which are probably elements of source() - if(any(sapply(tempo.frame, FUN = environmentName) %in% "R_GlobalEnv")){ - global.pos <- which(sapply(tempo.frame, FUN = environmentName) %in% "R_GlobalEnv") - # remove the global env (because already in search(), and all the oabove env - tempo.name <- tempo.name[-c(global.pos:length(tempo.frame))] - tempo.frame <- tempo.frame[-c(global.pos:length(tempo.frame))] - } - # end dealing with source() - # might have a problem if(length(tempo.name) == 0L){ - match.list <- vector("list", length = length(tempo.name) + length(search())) # match.list is a list of all the environment tested (local of functions and R envs), length(sys.calls()) - 1 to remove the level of the fun_secu() function, sys.calls() giving all the names of the imbricated functions, including fun_secu, ifelse(length(sys.calls()) == 1L, -1, 0) to remove Global env if this one is tested - ls.names <- c(tempo.name, search()) # names of the functions + names of the search() environments - ls.input <- c(tempo.frame, as.list(search())) # environements of the functions + names of the search() environments - names(match.list) <- ls.names # - match.list <- match.list[-c(1:(pos + 1))] # because we check only above pos - ls.tested <- ls.input[[pos + 1]] - ls.input <- ls.input[-c(1:(pos + 1))] - for(i1 in 1:length(match.list)){ - if(any(ls(name = ls.input[[i1]], all.names = TRUE) %in% ls(name = ls.tested, all.names = TRUE))){ - match.list[i1] <- list(ls(name = ls.input[[i1]], all.names = TRUE)[ls(name = ls.input[[i1]], all.names = TRUE) %in% ls(name = ls.tested, all.names = TRUE)]) - } - } - if( ! all(sapply(match.list, FUN = is.null))){ - output <- paste0("SOME VARIABLES ", ifelse(is.null(name), "OF THE CHECKED ENVIRONMENT", paste0("OF ", name)), " ARE ALSO PRESENT IN :\n", paste0(names(match.list[ ! sapply(match.list, FUN = is.null)]), ": ", sapply(match.list[ ! sapply(match.list, FUN = is.null)], FUN = paste0, collapse = " "), collapse = "\n")) - }else{ - output <- NULL - } - return(output) +# AIM +# Verify that variables in the environment defined by the pos parameter are not present in the above environment (following R Scope). This can be used to avoid R scope preference of functions like get() +# ARGUMENTS +# pos: single integer indicating the position of the environment checked (argument n of parent.frame()). Value 1 means one step above the fun_secu() local environment (by default). This means that when fun_secu(pos = 1) is used inside a function A, it checks if variables in the local environment of this function A are also present in above environments (following R Scope). When fun_secu(pos = 1) is used in the Global environment, it checks the objects of this environment +# name: single character string indicating the name of the function checked. If NULL, fun_secu() checks all the variables of the environment indicated by pos, as explained in the pos argument description. If non-null, fun_secu() checks all the variables presents in the local env of the function will be checked in the above envs (which includes the working environment (Global env) +# RETURN +# A character string of the local variables that match variables in the different environments of the R scope, or NULL if no match +# REQUIRED PACKAGES +# None +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_secu() +# fun_secu(pos = 2) +# mean <- 0 ; fun1 <- function(){sd <- 1 ; fun_secu(name = as.character(sys.calls()[[length(sys.calls())]]))} ; fun2 <- function(){cor <- 2 ; fun1()} ; fun1() ; fun2() ; rm(mean) # sys.calls() gives the function name at top stack of the imbricated functions, sys.calls()[[length(sys.calls())]] the name of the just above function. This can also been used for the above function: as.character(sys.call(1)) +# test.pos <- 2 ; mean <- 0 ; fun1 <- function(){sd <- 1 ; fun_secu(pos = test.pos, name = if(length(sys.calls()) >= test.pos){as.character(sys.calls()[[length(sys.calls()) + 1 - test.pos]])}else{search()[ (1:length(search()))[test.pos - length(sys.calls())]]})} ; fun2 <- function(){cor <- 2 ; fun1()} ; fun1() ; fun2() ; rm(mean) # for argument name, here is a way to have the name of the tested environment according to test.pos value +# DEBUGGING +# pos = 1 ; name = NULL # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument primary checking +# arg with no default values +# end arg with no default values +# using fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = pos, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(name)){ +tempo <- fun_check(data = name, class = "vector", typeof = "character", length = 1, fun.name = function.name) ; eval(ee) +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end using fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT JUST BE NA:", paste0(tempo.arg[tempo.log], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <- c( +"pos" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT BE NULL:", paste0(tempo.arg[tempo.log], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# end second round of checking and data preparation +# main code +# match.list <- vector("list", length = (length(sys.calls()) - 1 + length(search()) + ifelse(length(sys.calls()) == 1L, -1, 0))) # match.list is a list of all the environment tested (local of functions and R envs), length(sys.calls()) - 1 to remove the level of the fun_secu() function, sys.calls() giving all the names of the imbricated functions, including fun_secu, ifelse(length(sys.calls()) == 1L, -1, 0) to remove Global env if this one is tested +tempo.name <- rev(as.character(unlist(sys.calls()))) # get names of frames (i.e., enclosed env) +tempo.frame <- rev(sys.frames()) # get frames (i.e., enclosed env) +# dealing with source() +# source() used in the Global env creates three frames above the Global env, which should be removed because not very interesting for variable duplications. Add a <<-(sys.frames()) in this code and source anova_contrasts code to see this. With ls(a[[4]]), we can see the content of each env, which are probably elements of source() +if(any(sapply(tempo.frame, FUN = environmentName) %in% "R_GlobalEnv")){ +global.pos <- which(sapply(tempo.frame, FUN = environmentName) %in% "R_GlobalEnv") +# remove the global env (because already in search(), and all the oabove env +tempo.name <- tempo.name[-c(global.pos:length(tempo.frame))] +tempo.frame <- tempo.frame[-c(global.pos:length(tempo.frame))] +} +# end dealing with source() +# might have a problem if(length(tempo.name) == 0L){ +match.list <- vector("list", length = length(tempo.name) + length(search())) # match.list is a list of all the environment tested (local of functions and R envs), length(sys.calls()) - 1 to remove the level of the fun_secu() function, sys.calls() giving all the names of the imbricated functions, including fun_secu, ifelse(length(sys.calls()) == 1L, -1, 0) to remove Global env if this one is tested +ls.names <- c(tempo.name, search()) # names of the functions + names of the search() environments +ls.input <- c(tempo.frame, as.list(search())) # environements of the functions + names of the search() environments +names(match.list) <- ls.names # +match.list <- match.list[-c(1:(pos + 1))] # because we check only above pos +ls.tested <- ls.input[[pos + 1]] +ls.input <- ls.input[-c(1:(pos + 1))] +for(i1 in 1:length(match.list)){ +if(any(ls(name = ls.input[[i1]], all.names = TRUE) %in% ls(name = ls.tested, all.names = TRUE))){ +match.list[i1] <- list(ls(name = ls.input[[i1]], all.names = TRUE)[ls(name = ls.input[[i1]], all.names = TRUE) %in% ls(name = ls.tested, all.names = TRUE)]) +} +} +if( ! all(sapply(match.list, FUN = is.null))){ +output <- paste0("SOME VARIABLES ", ifelse(is.null(name), "OF THE CHECKED ENVIRONMENT", paste0("OF ", name)), " ARE ALSO PRESENT IN :\n", paste0(names(match.list[ ! sapply(match.list, FUN = is.null)]), ": ", sapply(match.list[ ! sapply(match.list, FUN = is.null)], FUN = paste0, collapse = " "), collapse = "\n")) +}else{ +output <- NULL +} +return(output) } @@ -638,273 +638,273 @@ fun_secu <- function(pos = 1, name = NULL){ # -> transferred into the cute package # Do not modify this function in cute_little_R_function anymore. See the cute repo fun_info <- function( - data, - n = NULL, - warn.print = TRUE +data, +n = NULL, +warn.print = TRUE ){ - # AIM - # Provide a broad description of an object - # WARNINGS - # None - # ARGUMENTS - # data: object to analyse - # n: positive integer value indicating the n first number of elements to display per compartment of the output list (i.e., head(..., n)). Write NULL to return all the elements. Does not apply for the $STRUCTURE compartment output - # warn.print: logical. Print potential warnings at the end of the execution? If FALSE the warning messages are added in the output list as an additional compartment (or NULL if no message). - # RETURN - # A list containing information, depending on the class and type of data. The backbone is generally: - # $NAME: name of the object - # $CLASS: class of the object (class() value) - # $TYPE: type of the object (typeof() value) - # $LENGTH: length of the object (length() value) - # $NA.NB: number of NA and NaN (only for type "logical", "integer", "double", "complex", "character" or "list") - # $HEAD: head of the object (head() value) - # $TAIL: tail of the object (tail() value) - # $DIMENSION: dimension (only for object with dimensions) - # $SUMMARY: object summary (summary() value) - # $STRUCTURE: object structure (str() value) - # $WARNING: warning messages (only if the warn.print argument is FALSE) - # If data is made of numerics, provide also: - # $INF.NB: number of Inf and -Inf - # $RANGE: range after removing Inf and NA - # $SUM: sum after removing Inf and NA - # $MEAN: mean after removing Inf and NA - # If data is a 2D object, provide also: - # $ROW_NAMES: row names - # $COL_NAMES: column names - # If data is a data frame, provide also: - # $COLUMN_TYPE: type of each column (typeof() value) - # If data is a list, provide also: - # $COMPARTMENT_NAMES: names of the comprtments - # $COMPARTMENT_TYPE: type of each compartment (typeof() value) - # REQUIRED PACKAGES - # None - # REQUIRED FUNCTIONS FROM THE cute PACKAGE - # fun_check() - # fun_get_message() - # EXAMPLE - # fun_info(data = 1:3) - # see http - # DEBUGGING - # mat1 <- matrix(1:3) ; data = env1 ; n = NULL ; warn.print = TRUE # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - req.function <- c( - "fun_check", - "fun_get_message" - ) - tempo <- NULL - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo <- c(tempo, i1) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # reserved words - # end reserved words - # arg with no default values - mandat.args <- c( - "data" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - if( ! is.null(n)){ - tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = n, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ # normally no NA - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "data", - # "n", # because can be NULL - "warn.print" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){# normally no NA with is.null() - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # code that protects set.seed() in the global environment - # end code that protects set.seed() in the global environment - # warning initiation - ini.warning.length <- options()$warning.length - options(warning.length = 8170) - warn <- NULL - # warn.count <- 0 # not required - # end warning initiation - # other checkings - if( ! is.null(n)){ - if(n < 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT MUST BE A POSITIVE AND NON NULL INTEGER") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(is.finite(n)){ - # warn.count <- warn.count + 1 - tempo.warn <- paste0("SOME COMPARTMENTS CAN BE TRUNCATED (n ARGUMENT IS ", n, ")") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end other checkings - # reserved word checking - # end reserved word checking - # end second round of checking and data preparation - # package checking - # end package checking - # main code - # new environment - env.name <- paste0("env", as.numeric(Sys.time())) - if(exists(env.name, where = -1)){ # verify if still ok when fun_info() is inside a function - tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - assign(env.name, new.env()) - assign("data", data, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # data assigned in a new envir for test - } - # end new environment - data.name <- deparse(substitute(data)) - output <- list("NAME" = data.name) - tempo.try.error <- fun_get_message(data = "class(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - tempo <- list("CLASS" = class(data)) - output <- c(output, tempo) - } - tempo.try.error <- fun_get_message(data = "typeof(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - tempo <- list("TYPE" = typeof(data)) - output <- c(output, tempo) - } - tempo.try.error <- fun_get_message(data = "length(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - tempo <- list("LENGTH" = length(data)) - output <- c(output, tempo) - } - if(all(typeof(data) %in% c("integer", "numeric", "double")) & ! any(class(data) %in% "factor")){ # all() without na.rm -> ok because typeof(NA) is "logical" # any() without na.rm -> ok because class(NA) is "logical" - tempo <- list("INF.NB" = sum(is.infinite(data))) - output <- c(output, tempo) - tempo <- list("RANGE" = range(data[ ! is.infinite(data)], na.rm = TRUE)) - output <- c(output, tempo) - tempo <- list("SUM" = sum(data[ ! is.infinite(data)], na.rm = TRUE)) - output <- c(output, tempo) - tempo <- list("MEAN" = mean(data[ ! is.infinite(data)], na.rm = TRUE)) - output <- c(output, tempo) - } - if(all(typeof(data) %in% c("logical", "integer", "double", "complex", "character", "list"))){ # all() without na.rm -> ok because typeof(NA) is "logical" - tempo.try.error <- fun_get_message(data = "is.na(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - tempo <- list("NA.NB" = sum(is.na(data))) - output <- c(output, tempo) - } - } - tempo.try.error <- fun_get_message(data = "head(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - tempo <- list("HEAD" = head(data)) - output <- c(output, tempo) - tempo <- list("TAIL" = tail(data)) # no reason that tail() does not work if head() works - output <- c(output, tempo) - } - tempo.try.error <- fun_get_message(data = "dim(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - if(length(dim(data)) > 0){ - tempo <- list("DIMENSION" = dim(data)) - if(length(tempo[[1]]) == 2L){ - names(tempo[[1]]) <- c("NROW", "NCOL") - } - output <- c(output, tempo) - } - } - if(all(class(data) == "data.frame") | all(class(data) %in% c("matrix", "array")) | all(class(data) == "table")){ # all() without na.rm -> ok because typeof(NA) is "logical" - if(length(dim(data)) > 1){ # to avoid 1D table - tempo <- list("ROW_NAMES" = dimnames(data)[[1]]) - output <- c(output, tempo) - tempo <- list("COLUM_NAMES" = dimnames(data)[[2]]) - output <- c(output, tempo) - } - } - tempo.try.error <- fun_get_message(data = "summary(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - tempo <- list("SUMMARY" = summary(data)) - output <- c(output, tempo) - } - tempo.try.error <- fun_get_message(data = "noquote(matrix(capture.output(str(data))))", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) - if(is.null(tempo.try.error)){ - tempo <- capture.output(str(data)) - tempo <- list("STRUCTURE" = noquote(matrix(tempo, dimnames = list(rep("", length(tempo)), "")))) # str() print automatically, ls.str() not but does not give the order of the data.frame - output <- c(output, tempo) - } - if(all(class(data) == "data.frame")){ # all() without na.rm -> ok because class(NA) is "logical" - tempo <- list("COLUMN_TYPE" = sapply(data, FUN = "typeof")) - if(any(sapply(data, FUN = "class") %in% "factor")){ # if an ordered factor is present, then sapply(data, FUN = "class") return a list but works with any(sapply(data, FUN = "class") %in% "factor") # any() without na.rm -> ok because class(NA) is "logical" - tempo.class <- sapply(data, FUN = "class") - if(any(unlist(tempo.class) %in% "ordered")){ # any() without na.rm -> ok because class(NA) is "logical" - tempo2 <- sapply(tempo.class, paste, collapse = " ") # paste the "ordered" factor" in "ordered factor" - }else{ - tempo2 <- unlist(tempo.class) - } - tempo[["COLUMN_TYPE"]][grepl(x = tempo2, pattern = "factor")] <- tempo2[grepl(x = tempo2, pattern = "factor")] - } - output <- c(output, tempo) - } - if(all(class(data) == "list")){ # all() without na.rm -> ok because class(NA) is "logical" - tempo <- list("COMPARTMENT_NAMES" = names(data)) - output <- c(output, tempo) - tempo <- list("COMPARTMENT_TYPE" = sapply(data, FUN = "typeof")) - if(any(unlist(sapply(data, FUN = "class")) %in% "factor")){ # if an ordered factor is present, then sapply(data, FUN = "class") return a list but works with any(sapply(data, FUN = "class") %in% "factor") # any() without na.rm -> ok because class(NA) is "logical" - tempo.class <- sapply(data, FUN = "class") - if(any(unlist(tempo.class) %in% "ordered")){ # any() without na.rm -> ok because class(NA) is "logical" - tempo2 <- sapply(tempo.class, paste, collapse = " ") # paste the "ordered" factor" in "ordered factor" - }else{ - tempo2 <- unlist(tempo.class) - } - tempo[["COMPARTMENT_TYPE"]][grepl(x = tempo2, pattern = "factor")] <- tempo2[grepl(x = tempo2, pattern = "factor")] - } - output <- c(output, tempo) - } - if( ! is.null(n)){ - output[names(output) != "STRUCTURE"] <- lapply(X = output[names(output) != "STRUCTURE"], FUN = head, n = n, simplify = FALSE) - } - # output - if(warn.print == FALSE){ - output <- c(output, WARNING = warn) - }else if(warn.print == TRUE & ! is.null(warn)){ - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) - } - on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) - return(output) - # end output - # end main code +# AIM +# Provide a broad description of an object +# WARNINGS +# None +# ARGUMENTS +# data: object to analyse +# n: positive integer value indicating the n first number of elements to display per compartment of the output list (i.e., head(..., n)). Write NULL to return all the elements. Does not apply for the $STRUCTURE compartment output +# warn.print: logical. Print potential warnings at the end of the execution? If FALSE the warning messages are added in the output list as an additional compartment (or NULL if no message). +# RETURN +# A list containing information, depending on the class and type of data. The backbone is generally: +# $NAME: name of the object +# $CLASS: class of the object (class() value) +# $TYPE: type of the object (typeof() value) +# $LENGTH: length of the object (length() value) +# $NA.NB: number of NA and NaN (only for type "logical", "integer", "double", "complex", "character" or "list") +# $HEAD: head of the object (head() value) +# $TAIL: tail of the object (tail() value) +# $DIMENSION: dimension (only for object with dimensions) +# $SUMMARY: object summary (summary() value) +# $STRUCTURE: object structure (str() value) +# $WARNING: warning messages (only if the warn.print argument is FALSE) +# If data is made of numerics, provide also: +# $INF.NB: number of Inf and -Inf +# $RANGE: range after removing Inf and NA +# $SUM: sum after removing Inf and NA +# $MEAN: mean after removing Inf and NA +# If data is a 2D object, provide also: +# $ROW_NAMES: row names +# $COL_NAMES: column names +# If data is a data frame, provide also: +# $COLUMN_TYPE: type of each column (typeof() value) +# If data is a list, provide also: +# $COMPARTMENT_NAMES: names of the comprtments +# $COMPARTMENT_TYPE: type of each compartment (typeof() value) +# REQUIRED PACKAGES +# None +# REQUIRED FUNCTIONS FROM THE cute PACKAGE +# fun_check() +# fun_get_message() +# EXAMPLE +# fun_info(data = 1:3) +# see http +# DEBUGGING +# mat1 <- matrix(1:3) ; data = env1 ; n = NULL ; warn.print = TRUE # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +req.function <- c( +"fun_check", +"fun_get_message" +) +tempo <- NULL +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo <- c(tempo, i1) +} +} +if( ! is.null(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# reserved words +# end reserved words +# arg with no default values +mandat.args <- c( +"data" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +if( ! is.null(n)){ +tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = n, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ # normally no NA +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"data", +# "n", # because can be NULL +"warn.print" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){# normally no NA with is.null() +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# code that protects set.seed() in the global environment +# end code that protects set.seed() in the global environment +# warning initiation +ini.warning.length <- options()$warning.length +options(warning.length = 8170) +warn <- NULL +# warn.count <- 0 # not required +# end warning initiation +# other checkings +if( ! is.null(n)){ +if(n < 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT MUST BE A POSITIVE AND NON NULL INTEGER") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(is.finite(n)){ +# warn.count <- warn.count + 1 +tempo.warn <- paste0("SOME COMPARTMENTS CAN BE TRUNCATED (n ARGUMENT IS ", n, ")") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end other checkings +# reserved word checking +# end reserved word checking +# end second round of checking and data preparation +# package checking +# end package checking +# main code +# new environment +env.name <- paste0("env", as.numeric(Sys.time())) +if(exists(env.name, where = -1)){ # verify if still ok when fun_info() is inside a function +tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +assign(env.name, new.env()) +assign("data", data, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # data assigned in a new envir for test +} +# end new environment +data.name <- deparse(substitute(data)) +output <- list("NAME" = data.name) +tempo.try.error <- fun_get_message(data = "class(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +tempo <- list("CLASS" = class(data)) +output <- c(output, tempo) +} +tempo.try.error <- fun_get_message(data = "typeof(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +tempo <- list("TYPE" = typeof(data)) +output <- c(output, tempo) +} +tempo.try.error <- fun_get_message(data = "length(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +tempo <- list("LENGTH" = length(data)) +output <- c(output, tempo) +} +if(all(typeof(data) %in% c("integer", "numeric", "double")) & ! any(class(data) %in% "factor")){ # all() without na.rm -> ok because typeof(NA) is "logical" # any() without na.rm -> ok because class(NA) is "logical" +tempo <- list("INF.NB" = sum(is.infinite(data))) +output <- c(output, tempo) +tempo <- list("RANGE" = range(data[ ! is.infinite(data)], na.rm = TRUE)) +output <- c(output, tempo) +tempo <- list("SUM" = sum(data[ ! is.infinite(data)], na.rm = TRUE)) +output <- c(output, tempo) +tempo <- list("MEAN" = mean(data[ ! is.infinite(data)], na.rm = TRUE)) +output <- c(output, tempo) +} +if(all(typeof(data) %in% c("logical", "integer", "double", "complex", "character", "list"))){ # all() without na.rm -> ok because typeof(NA) is "logical" +tempo.try.error <- fun_get_message(data = "is.na(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +tempo <- list("NA.NB" = sum(is.na(data))) +output <- c(output, tempo) +} +} +tempo.try.error <- fun_get_message(data = "head(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +tempo <- list("HEAD" = head(data)) +output <- c(output, tempo) +tempo <- list("TAIL" = tail(data)) # no reason that tail() does not work if head() works +output <- c(output, tempo) +} +tempo.try.error <- fun_get_message(data = "dim(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +if(length(dim(data)) > 0){ +tempo <- list("DIMENSION" = dim(data)) +if(length(tempo[[1]]) == 2L){ +names(tempo[[1]]) <- c("NROW", "NCOL") +} +output <- c(output, tempo) +} +} +if(all(class(data) == "data.frame") | all(class(data) %in% c("matrix", "array")) | all(class(data) == "table")){ # all() without na.rm -> ok because typeof(NA) is "logical" +if(length(dim(data)) > 1){ # to avoid 1D table +tempo <- list("ROW_NAMES" = dimnames(data)[[1]]) +output <- c(output, tempo) +tempo <- list("COLUM_NAMES" = dimnames(data)[[2]]) +output <- c(output, tempo) +} +} +tempo.try.error <- fun_get_message(data = "summary(data)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +tempo <- list("SUMMARY" = summary(data)) +output <- c(output, tempo) +} +tempo.try.error <- fun_get_message(data = "noquote(matrix(capture.output(str(data))))", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)) +if(is.null(tempo.try.error)){ +tempo <- capture.output(str(data)) +tempo <- list("STRUCTURE" = noquote(matrix(tempo, dimnames = list(rep("", length(tempo)), "")))) # str() print automatically, ls.str() not but does not give the order of the data.frame +output <- c(output, tempo) +} +if(all(class(data) == "data.frame")){ # all() without na.rm -> ok because class(NA) is "logical" +tempo <- list("COLUMN_TYPE" = sapply(data, FUN = "typeof")) +if(any(sapply(data, FUN = "class") %in% "factor")){ # if an ordered factor is present, then sapply(data, FUN = "class") return a list but works with any(sapply(data, FUN = "class") %in% "factor") # any() without na.rm -> ok because class(NA) is "logical" +tempo.class <- sapply(data, FUN = "class") +if(any(unlist(tempo.class) %in% "ordered")){ # any() without na.rm -> ok because class(NA) is "logical" +tempo2 <- sapply(tempo.class, paste, collapse = " ") # paste the "ordered" factor" in "ordered factor" +}else{ +tempo2 <- unlist(tempo.class) +} +tempo[["COLUMN_TYPE"]][grepl(x = tempo2, pattern = "factor")] <- tempo2[grepl(x = tempo2, pattern = "factor")] +} +output <- c(output, tempo) +} +if(all(class(data) == "list")){ # all() without na.rm -> ok because class(NA) is "logical" +tempo <- list("COMPARTMENT_NAMES" = names(data)) +output <- c(output, tempo) +tempo <- list("COMPARTMENT_TYPE" = sapply(data, FUN = "typeof")) +if(any(unlist(sapply(data, FUN = "class")) %in% "factor")){ # if an ordered factor is present, then sapply(data, FUN = "class") return a list but works with any(sapply(data, FUN = "class") %in% "factor") # any() without na.rm -> ok because class(NA) is "logical" +tempo.class <- sapply(data, FUN = "class") +if(any(unlist(tempo.class) %in% "ordered")){ # any() without na.rm -> ok because class(NA) is "logical" +tempo2 <- sapply(tempo.class, paste, collapse = " ") # paste the "ordered" factor" in "ordered factor" +}else{ +tempo2 <- unlist(tempo.class) +} +tempo[["COMPARTMENT_TYPE"]][grepl(x = tempo2, pattern = "factor")] <- tempo2[grepl(x = tempo2, pattern = "factor")] +} +output <- c(output, tempo) +} +if( ! is.null(n)){ +output[names(output) != "STRUCTURE"] <- lapply(X = output[names(output) != "STRUCTURE"], FUN = head, n = n, simplify = FALSE) +} +# output +if(warn.print == FALSE){ +output <- c(output, WARNING = warn) +}else if(warn.print == TRUE & ! is.null(warn)){ +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) +} +on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) +return(output) +# end output +# end main code } @@ -912,62 +912,62 @@ fun_info <- function( fun_head <- function( - data1, - n = 6, - side = "l" +data1, +n = 6, +side = "l" ){ - # AIM - # as head() but display the left or right head of big 2D objects - # ARGUMENTS - # data1: any object but more dedicated for matrix, data frame or table - # n: as in head() but for for matrix, data frame or table, number of dimension to print (10 means 10 rows and columns) - # side: either "l" or "r" for the left or right side of the 2D object (only for matrix, data frame or table) - # BEWARE: other arguments of head() not used - # RETURN - # the head - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # obs1 = matrix(1:30, ncol = 5, dimnames = list(letters[1:6], LETTERS[1:5])) ; obs1 ; fun_head(obs1, 3) - # DEBUGGING - # data1 = matrix(1:30, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = n, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = side, options = c("l", "r"), length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) - return(head(data1, n)) - }else{ - obs.dim <- dim(data1) - row <- 1:ifelse(obs.dim[1] < n, obs.dim[1], n) - if(side == "l"){ - col <- 1:ifelse(obs.dim[2] < n, obs.dim[2], n) - } - if(side == "r"){ - col <- ifelse(obs.dim[2] < n, 1, obs.dim[2] - n + 1):obs.dim[2] - } - return(data1[row, col]) - } +# AIM +# as head() but display the left or right head of big 2D objects +# ARGUMENTS +# data1: any object but more dedicated for matrix, data frame or table +# n: as in head() but for for matrix, data frame or table, number of dimension to print (10 means 10 rows and columns) +# side: either "l" or "r" for the left or right side of the 2D object (only for matrix, data frame or table) +# BEWARE: other arguments of head() not used +# RETURN +# the head +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# obs1 = matrix(1:30, ncol = 5, dimnames = list(letters[1:6], LETTERS[1:5])) ; obs1 ; fun_head(obs1, 3) +# DEBUGGING +# data1 = matrix(1:30, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = n, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = side, options = c("l", "r"), length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) +return(head(data1, n)) +}else{ +obs.dim <- dim(data1) +row <- 1:ifelse(obs.dim[1] < n, obs.dim[1], n) +if(side == "l"){ +col <- 1:ifelse(obs.dim[2] < n, obs.dim[2], n) +} +if(side == "r"){ +col <- ifelse(obs.dim[2] < n, 1, obs.dim[2] - n + 1):obs.dim[2] +} +return(data1[row, col]) +} } @@ -975,62 +975,62 @@ fun_head <- function( fun_tail <- function( - data1, - n = 6, - side = "l" +data1, +n = 6, +side = "l" ){ - # AIM - # as tail() but display the left or right head of big 2D objects - # ARGUMENTS - # data1: any object but more dedicated for matrix, data frame or table - # n: as in tail() but for for matrix, data frame or table, number of dimension to print (10 means 10 rows and columns) - # side: either "l" or "r" for the left or right side of the 2D object (only for matrix, data frame or table) - # BEWARE: other arguments of tail() not used - # RETURN - # the tail - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # obs1 = matrix(1:30, ncol = 5, dimnames = list(letters[1:6], LETTERS[1:5])) ; obs1 ; fun_tail(obs1, 3, "r") - # DEBUGGING - # data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = n, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = side, options = c("l", "r"), length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) - return(tail(data1, n)) - }else{ - obs.dim <- dim(data1) - row <- ifelse(obs.dim[1] < n, 1, obs.dim[1] - n + 1):obs.dim[1] - if(side == "l"){ - col <- 1:ifelse(obs.dim[2] < n, obs.dim[2], n) - } - if(side == "r"){ - col <- ifelse(obs.dim[2] < n, 1, obs.dim[2] - n + 1):obs.dim[2] - } - return(data1[row, col]) - } +# AIM +# as tail() but display the left or right head of big 2D objects +# ARGUMENTS +# data1: any object but more dedicated for matrix, data frame or table +# n: as in tail() but for for matrix, data frame or table, number of dimension to print (10 means 10 rows and columns) +# side: either "l" or "r" for the left or right side of the 2D object (only for matrix, data frame or table) +# BEWARE: other arguments of tail() not used +# RETURN +# the tail +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# obs1 = matrix(1:30, ncol = 5, dimnames = list(letters[1:6], LETTERS[1:5])) ; obs1 ; fun_tail(obs1, 3, "r") +# DEBUGGING +# data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = n, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = side, options = c("l", "r"), length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) +return(tail(data1, n)) +}else{ +obs.dim <- dim(data1) +row <- ifelse(obs.dim[1] < n, 1, obs.dim[1] - n + 1):obs.dim[1] +if(side == "l"){ +col <- 1:ifelse(obs.dim[2] < n, obs.dim[2], n) +} +if(side == "r"){ +col <- ifelse(obs.dim[2] < n, 1, obs.dim[2] - n + 1):obs.dim[2] +} +return(data1[row, col]) +} } @@ -1038,12365 +1038,12371 @@ fun_tail <- function( fun_comp_1d <- function(data1, data2){ - # AIM - # compare two 1D datasets (vector or factor or 1D table, or 1D matrix or 1D array) of the same class or not. Check and report in a list if the 2 datasets have: - # same class - # common elements - # common element names (except factors) - # common levels (factors only) - # ARGUMENTS - # data1: vector or factor or 1D table, or 1D matrix or 1D array - # data2: vector or factor or 1D table, or 1D matrix or 1D array - # RETURN - # a list containing: - # $same.class: logical. Are class identical? - # $class: class of the 2 datasets (NULL otherwise) - # $same.length: logical. Are number of elements identical? - # $length: number of elements in the 2 datasets (NULL otherwise) - # $same.levels: logical. Are levels identical? NULL if data1 and data2 are not factors - # $levels: levels of the 2 datasets if identical (NULL otherwise or NULL if data1 and data2 are not factors) - # $any.id.levels: logical. Is there any identical levels? (NULL if data1 and data2 are not factors) - # $same.levels.pos1: positions, in data1, of the levels identical in data2 (NULL otherwise or NULL if data1 and data2 are not factors) - # $same.levels.pos2: positions, in data2, of the levels identical in data1 (NULL otherwise or NULL if data1 and data2 are not factors) - # $same.levels.match1: positions, in data2, of the levels that match the levels in data1, as given by match(data1, data2) (NULL otherwise or NULL if data1 and data2 are not factors) - # $same.levels.match2: positions, in data1, of the levels that match the levels in data2, as given by match(data1, data2) (NULL otherwise or NULL if data1 and data2 are not factors) - # $common.levels: common levels between data1 and data2 (can be a subset of $levels or not). NULL if no common levels or if data1 and data2 are not factors - # $same.names: logical. Are element names identical? NULL if data1 and data2 have no names - # $name: name of elements of the 2 datasets if identical (NULL otherwise) - # $any.id.name: logical. Is there any element names identical ? - # $same.names.pos1: positions, in data1, of the element names identical in data2. NULL if no identical names - # $same.names.pos2: positions, in data2, of the elements names identical in data1. NULL if no identical names - # $same.names.match1: positions, in data2, of the names that match the names in data1, as given by match(data1, data2) (NULL otherwise) - # $same.names.match2: positions, in data1, of the names that match the names in data2, as given by match(data1, data2) (NULL otherwise) - # $common.names: common element names between data1 and data2 (can be a subset of $name or not). NULL if no common element names - # $any.id.element: logical. is there any identical elements ? - # $same.elements.pos1: positions, in data1, of the elements identical in data2. NULL if no identical elements - # $same.elements.pos2: positions, in data2, of the elements identical in data1. NULL if no identical elements - # $same.elements.match1: positions, in data2, of the elements that match the elements in data1, as given by match(data1, data2) (NULL otherwise) - # $same.elements.match2: positions, in data1, of the elements that match the elements in data2, as given by match(data1, data2) (NULL otherwise) - # $common.elements: common elements between data1 and data2. NULL if no common elements - # $same.order: logical. Are all elements in the same order? TRUE or FALSE if elements of data1 and data2 are identical but not necessary in the same order. NULL otherwise (different length for instance) - # $order1: order of all elements of data1. NULL if $same.order is FALSE - # $order2: order of all elements of data2. NULL if $same.order is FALSE - # $identical.object: logical. Are objects identical (kind of object, element names, content, including content order)? - # $identical.content: logical. Are content objects identical (identical elements, including order, excluding kind of object and element names)? - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # none - # EXAMPLES - # obs1 = 1:5 ; obs2 = 1:5 ; names(obs1) <- LETTERS[1:5] ; names(obs2) <- LETTERS[1:5] ; fun_comp_1d(obs1, obs2) - # obs1 = 1:5 ; obs2 = 1:5 ; names(obs1) <- LETTERS[1:5] ; fun_comp_1d(obs1, obs2) - # obs1 = 1:5 ; obs2 = 3:6 ; names(obs1) <- LETTERS[1:5] ; names(obs2) <- LETTERS[1:4] ; fun_comp_1d(obs1, obs2) - # obs1 = factor(LETTERS[1:5]) ; obs2 = factor(LETTERS[1:5]) ; fun_comp_1d(obs1, obs2) - # obs1 = factor(LETTERS[1:5]) ; obs2 = factor(LETTERS[10:11]) ; fun_comp_1d(obs1, obs2) - # obs1 = factor(LETTERS[1:5]) ; obs2 = factor(LETTERS[4:7]) ; fun_comp_1d(obs1, obs2) - # obs1 = factor(c(LETTERS[1:4], "E")) ; obs2 = factor(c(LETTERS[1:4], "F")) ; fun_comp_1d(obs1, obs2) - # obs1 = 1:5 ; obs2 = factor(LETTERS[1:5]) ; fun_comp_1d(obs1, obs2) - # obs1 = 1:5 ; obs2 = 1.1:6.1 ; fun_comp_1d(obs1, obs2) - # obs1 = as.table(1:5); obs2 = as.table(1:5) ; fun_comp_1d(obs1, obs2) - # obs1 = as.table(1:5); obs2 = 1:5 ; fun_comp_1d(obs1, obs2) - # DEBUGGING - # data1 = 1:5 ; data2 = 1:5 ; names(data1) <- LETTERS[1:5] ; names(data2) <- LETTERS[1:5] # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # argument checking - if( ! any(class(data1) %in% c("logical", "integer", "numeric", "character", "factor", "table"))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A NON NULL VECTOR, FACTOR OR 1D TABLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(all(class(data1) %in% "table")){ - if(length(dim(data1)) > 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A 1D TABLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! any(class(data2) %in% c("logical", "integer", "numeric", "character", "factor", "table"))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A NON NULL VECTOR, FACTOR OR 1D TABLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(all(class(data2) %in% "table")){ - if(length(dim(data2)) > 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A 1D TABLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status - # end argument checking - # main code - same.class <- FALSE - class <- NULL - same.length <- FALSE - length <- NULL - same.levels <- NULL # not FALSE to deal with no factors - levels <- NULL - any.id.levels <- FALSE - same.levels.pos1 <- NULL - same.levels.pos2 <- NULL - same.levels.match1 <- NULL - same.levels.match2 <- NULL - common.levels <- NULL - same.names <- NULL # not FALSE to deal with absence of name - name <- NULL - any.id.name <- FALSE - same.names.pos1 <- NULL - same.names.pos2 <- NULL - same.names.match1 <- NULL - same.names.match2 <- NULL - common.names <- NULL - any.id.element <- FALSE - same.elements.pos1 <- NULL - same.elements.pos2 <- NULL - same.elements.match1 <- NULL - same.elements.match2 <- NULL - common.elements <- NULL - same.order <- NULL - order1 <- NULL - order2 <- NULL - identical.object <- FALSE - identical.content <- FALSE - if(identical(data1, data2)){ - same.class <- TRUE - class <- class(data1) - same.length <- TRUE - length <- length(data1) - if(any(class(data1) %in% "factor")){ - same.levels <- TRUE - levels <- levels(data1) - any.id.levels <- TRUE - same.levels.pos1 <- 1:length(levels(data1)) - same.levels.pos2 <- 1:length(levels(data2)) - same.levels.match1 <- 1:length(levels(data1)) - same.levels.match2 <- 1:length(levels(data2)) - common.levels <- levels(data1) - } - if( ! is.null(names(data1))){ - same.names <- TRUE - name <- names(data1) - any.id.name <- TRUE - same.names.pos1 <- 1:length(data1) - same.names.pos2 <- 1:length(data2) - same.names.match1 <- 1:length(data1) - same.names.match2 <- 1:length(data2) - common.names <- names(data1) - } - any.id.element <- TRUE - same.elements.pos1 <- 1:length(data1) - same.elements.pos2 <- 1:length(data2) - same.elements.match1 <- 1:length(data1) - same.elements.match2 <- 1:length(data2) - common.elements <- data1 - same.order <- TRUE - order1 <- order(data1) - order2 <- order(data2) - identical.object <- TRUE - identical.content <- TRUE - }else{ - if(identical(class(data1), class(data2))){ - same.class <- TRUE - class <- class(data1) - } - if(identical(length(data1), length(data2))){ - same.length<- TRUE - length <- length(data1) - } - if(any(class(data1) %in% "factor") & any(class(data2) %in% "factor")){ - if(identical(levels(data1), levels(data2))){ - same.levels <- TRUE - levels <- levels(data1) - }else{ - same.levels <- FALSE - } - if(any(levels(data1) %in% levels(data2))){ - any.id.levels <- TRUE - same.levels.pos1 <- which(levels(data1) %in% levels(data2)) - same.levels.match1 <- match(levels(data1), levels(data2)) - } - if(any(levels(data2) %in% levels(data1))){ - any.id.levels <- TRUE - same.levels.pos2 <- which(levels(data2) %in% levels(data1)) - same.levels.match2 <- match(levels(data2), levels(data1)) - } - if(any.id.levels == TRUE){ - common.levels <- unique(c(levels(data1)[same.levels.pos1], levels(data2)[same.levels.pos2])) - } - } - if(any(class(data1) %in% "factor")){ # to compare content - data1 <- as.character(data1) - } - if(any(class(data2) %in% "factor")){ # to compare content - data2 <- as.character(data2) - } - if( ! (is.null(names(data1)) & is.null(names(data2)))){ - if(identical(names(data1), names(data2))){ - same.names <- TRUE - name <- names(data1) - }else{ - same.names <- FALSE - } - if(any(names(data1) %in% names(data2))){ - any.id.name <- TRUE - same.names.pos1 <- which(names(data1) %in% names(data2)) - same.names.match1 <- match(names(data1), names(data2)) - } - if(any(names(data2) %in% names(data1))){ - any.id.name <- TRUE - same.names.pos2 <- which(names(data2) %in% names(data1)) - same.names.match2 <- match(names(data2), names(data1)) - } - if(any.id.name == TRUE){ - common.names <- unique(c(names(data1)[same.names.pos1], names(data2)[same.names.pos2])) - } - } - names(data1) <- NULL # names solved -> to do not be disturbed by names - names(data2) <- NULL # names solved -> to do not be disturbed by names - if(any(data1 %in% data2)){ - any.id.element <- TRUE - same.elements.pos1 <- which(data1 %in% data2) - same.elements.match1 <- match(data1, data2) - } - if(any(data2 %in% data1)){ - any.id.element <- TRUE - same.elements.pos2 <- which(data2 %in% data1) - same.elements.match2 <- match(data2, data1) - } - if(any.id.element == TRUE){ - common.elements <- unique(c(data1[same.elements.pos1], data2[same.elements.pos2])) - } - if(identical(data1, data2)){ - identical.content <- TRUE - same.order <- TRUE - }else if(identical(sort(data1), sort(data2))){ - same.order <- FALSE - order1 <- order(data1) - order2 <- order(data2) - } - } - output <- list(same.class = same.class, class = class, same.length = same.length, length = length, same.levels = same.levels, levels = levels, any.id.levels = any.id.levels, same.levels.pos1 = same.levels.pos1, same.levels.pos2 = same.levels.pos2, same.levels.match1 = same.levels.match1, same.levels.match2 = same.levels.match2, common.levels = common.levels, same.names = same.names, name = name, any.id.name = any.id.name, same.names.pos1 = same.names.pos1, same.names.pos2 = same.names.pos2, same.names.match1 = same.names.match1, same.names.match2 = same.names.match2, common.names = common.names, any.id.element = any.id.element, same.elements.pos1 = same.elements.pos1, same.elements.pos2 = same.elements.pos2, same.elements.match1 = same.elements.match1, same.elements.match2 = same.elements.match2, common.elements = common.elements, same.order = same.order, order1 = order1, order2 = order2, identical.object = identical.object, identical.content = identical.content) - return(output) +# AIM +# compare two 1D datasets (vector or factor or 1D table, or 1D matrix or 1D array) of the same class or not. Check and report in a list if the 2 datasets have: +# same class +# common elements +# common element names (except factors) +# common levels (factors only) +# ARGUMENTS +# data1: vector or factor or 1D table, or 1D matrix or 1D array +# data2: vector or factor or 1D table, or 1D matrix or 1D array +# RETURN +# a list containing: +# $same.class: logical. Are class identical? +# $class: class of the 2 datasets (NULL otherwise) +# $same.length: logical. Are number of elements identical? +# $length: number of elements in the 2 datasets (NULL otherwise) +# $same.levels: logical. Are levels identical? NULL if data1 and data2 are not factors +# $levels: levels of the 2 datasets if identical (NULL otherwise or NULL if data1 and data2 are not factors) +# $any.id.levels: logical. Is there any identical levels? (NULL if data1 and data2 are not factors) +# $same.levels.pos1: positions, in data1, of the levels identical in data2 (NULL otherwise or NULL if data1 and data2 are not factors) +# $same.levels.pos2: positions, in data2, of the levels identical in data1 (NULL otherwise or NULL if data1 and data2 are not factors) +# $same.levels.match1: positions, in data2, of the levels that match the levels in data1, as given by match(data1, data2) (NULL otherwise or NULL if data1 and data2 are not factors) +# $same.levels.match2: positions, in data1, of the levels that match the levels in data2, as given by match(data1, data2) (NULL otherwise or NULL if data1 and data2 are not factors) +# $common.levels: common levels between data1 and data2 (can be a subset of $levels or not). NULL if no common levels or if data1 and data2 are not factors +# $same.names: logical. Are element names identical? NULL if data1 and data2 have no names +# $name: name of elements of the 2 datasets if identical (NULL otherwise) +# $any.id.name: logical. Is there any element names identical ? +# $same.names.pos1: positions, in data1, of the element names identical in data2. NULL if no identical names +# $same.names.pos2: positions, in data2, of the elements names identical in data1. NULL if no identical names +# $same.names.match1: positions, in data2, of the names that match the names in data1, as given by match(data1, data2) (NULL otherwise) +# $same.names.match2: positions, in data1, of the names that match the names in data2, as given by match(data1, data2) (NULL otherwise) +# $common.names: common element names between data1 and data2 (can be a subset of $name or not). NULL if no common element names +# $any.id.element: logical. is there any identical elements ? +# $same.elements.pos1: positions, in data1, of the elements identical in data2. NULL if no identical elements +# $same.elements.pos2: positions, in data2, of the elements identical in data1. NULL if no identical elements +# $same.elements.match1: positions, in data2, of the elements that match the elements in data1, as given by match(data1, data2) (NULL otherwise) +# $same.elements.match2: positions, in data1, of the elements that match the elements in data2, as given by match(data1, data2) (NULL otherwise) +# $common.elements: common elements between data1 and data2. NULL if no common elements +# $same.order: logical. Are all elements in the same order? TRUE or FALSE if elements of data1 and data2 are identical but not necessary in the same order. NULL otherwise (different length for instance) +# $order1: order of all elements of data1. NULL if $same.order is FALSE +# $order2: order of all elements of data2. NULL if $same.order is FALSE +# $identical.object: logical. Are objects identical (kind of object, element names, content, including content order)? +# $identical.content: logical. Are content objects identical (identical elements, including order, excluding kind of object and element names)? +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# none +# EXAMPLES +# obs1 = 1:5 ; obs2 = 1:5 ; names(obs1) <- LETTERS[1:5] ; names(obs2) <- LETTERS[1:5] ; fun_comp_1d(obs1, obs2) +# obs1 = 1:5 ; obs2 = 1:5 ; names(obs1) <- LETTERS[1:5] ; fun_comp_1d(obs1, obs2) +# obs1 = 1:5 ; obs2 = 3:6 ; names(obs1) <- LETTERS[1:5] ; names(obs2) <- LETTERS[1:4] ; fun_comp_1d(obs1, obs2) +# obs1 = factor(LETTERS[1:5]) ; obs2 = factor(LETTERS[1:5]) ; fun_comp_1d(obs1, obs2) +# obs1 = factor(LETTERS[1:5]) ; obs2 = factor(LETTERS[10:11]) ; fun_comp_1d(obs1, obs2) +# obs1 = factor(LETTERS[1:5]) ; obs2 = factor(LETTERS[4:7]) ; fun_comp_1d(obs1, obs2) +# obs1 = factor(c(LETTERS[1:4], "E")) ; obs2 = factor(c(LETTERS[1:4], "F")) ; fun_comp_1d(obs1, obs2) +# obs1 = 1:5 ; obs2 = factor(LETTERS[1:5]) ; fun_comp_1d(obs1, obs2) +# obs1 = 1:5 ; obs2 = 1.1:6.1 ; fun_comp_1d(obs1, obs2) +# obs1 = as.table(1:5); obs2 = as.table(1:5) ; fun_comp_1d(obs1, obs2) +# obs1 = as.table(1:5); obs2 = 1:5 ; fun_comp_1d(obs1, obs2) +# DEBUGGING +# data1 = 1:5 ; data2 = 1:5 ; names(data1) <- LETTERS[1:5] ; names(data2) <- LETTERS[1:5] # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# argument checking +if( ! any(class(data1) %in% c("logical", "integer", "numeric", "character", "factor", "table"))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A NON NULL VECTOR, FACTOR OR 1D TABLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(all(class(data1) %in% "table")){ +if(length(dim(data1)) > 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A 1D TABLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == } - - -######## fun_comp_2d() #### comparison of two 2D datasets (row & col names, dimensions, etc.) - - -fun_comp_2d <- function(data1, data2){ - # AIM - # compare two 2D datasets of the same class or not. Check and report in a list if the 2 datasets have: - # same class - # common row names - # common column names - # same row number - # same column number - # potential identical rows between the 2 datasets - # potential identical columns between the 2 datasets - # WARNINGS - # For data frames: content are compared after conversion of content into characters. This means that the comparison of the content of data frame, either row to row, or column to column, does not take into account the mode in the different columns. This concern the results in $any.id.row, $same.row.pos1, $same.row.pos2, $same.row.match1, $same.row.match2, $any.id.col, $same.row.col1, $same.row.col2, $same.col.match1, $same.col.match2 and $identical.content result - # "TOO BIG FOR EVALUATION" returned in $same.row.pos1, $same.row.pos2, $same.row.match1 and $same.row.match2 when nrow(data1) * nrow(data2) > 1e6 and $any.id.row remains NULL - # "TOO BIG FOR EVALUATION" returned in $same.row.col1, $same.row.col2, $same.col.match1 and $same.col.match2 when ncol(data1) * ncol(data2) > 1e6 and $any.id.col remains NULL - # ARGUMENTS - # data1: matrix, data frame or table - # data2: matrix, data frame or table - # RETURN - # a list containing: - # $same.class: logical. Are class identical ? - # $class: classes of the 2 datasets (NULL otherwise) - # $same.dim: logical. Are dimension identical ? - # $dim: dimension of the 2 datasets (NULL otherwise) - # $same.row.nb: logical. Are number of rows identical ? - # $row.nb: nb of rows of the 2 datasets if identical (NULL otherwise) - # $same.col.nb: logical. Are number of columns identical ? - # $col.nb: nb of columns of the 2 datasets if identical (NULL otherwise) - # $same.row.name: logical. Are row names identical ? NULL if no row names in the two 2D datasets - # $row.name: name of rows of the 2 datasets if identical (NULL otherwise) - # $any.id.row.name: logical. Is there any row names identical ? NULL if no row names in the two 2D datasets - # $same.row.names.pos1: positions, in data1, of the row names identical in data2 - # $same.row.names.pos2: positions, in data2, of the row names identical in data1 - # $same.row.names.match1: positions, in data2, of the row names that match the row names in data1, as given by match(data1, data2) (NULL otherwise) - # $same.row.names.match2: positions, in data1, of the row names that match the row names in data2, as given by match(data1, data2) (NULL otherwise) - # $common.row.names: common row names between data1 and data2 (can be a subset of $name or not). NULL if no common row names - # $same.col.name: logical. Are column names identical ? NULL if no col names in the two 2D datasets - # $col.name: name of columns of the 2 datasets if identical (NULL otherwise) - # $any.id.col.name: logical. Is there any column names identical ? NULL if no col names in the two 2D datasets - # $same.col.names.pos1: positions, in data1, of the column names identical in data2 - # $same.col.names.pos2: positions, in data2, of the column names identical in data1 - # $same.col.names.match1: positions, in data2, of the column names that match the column names in data1, as given by match(data1, data2) (NULL otherwise) - # $same.col.names.match2: positions, in data1, of the column names that match the column names in data2, as given by match(data1, data2) (NULL otherwise) - # $common.col.names: common column names between data1 and data2 (can be a subset of $name or not). NULL if no common column names - # $any.id.row: logical. is there identical rows (not considering row names)? NULL if nrow(data1) * nrow(data2) > 1e10 - # $same.row.pos1: positions, in data1, of the rows identical in data2 (not considering row names). Return "TOO BIG FOR EVALUATION" if nrow(data1) * nrow(data2) > 1e10 - # $same.row.pos2: positions, in data2, of the rows identical in data1 (not considering row names). Return "TOO BIG FOR EVALUATION" if nrow(data1) * nrow(data2) > 1e10 - # $same.row.match1: positions, in data2, of the rows that match the rows in data1, as given by match(data1, data2) (NULL otherwise) - # $same.row.match2: positions, in data1, of the rows that match the rows in data2, as given by match(data1, data2) (NULL otherwise) - # $any.id.col: logical. is there identical columns (not considering column names)? NULL if ncol(data1) * ncol(data2) > 1e10 - # $same.col.pos1: position in data1 of the cols identical in data2 (not considering column names). Return "TOO BIG FOR EVALUATION" if ncol(data1) * ncol(data2) > 1e10 - # $same.col.pos2: position in data2 of the cols identical in data1 (not considering column names). Return "TOO BIG FOR EVALUATION" if ncol(data1) * ncol(data2) > 1e10 - # $same.col.match1: positions, in data2, of the columns that match the columns in data1, as given by match(data1, data2) (NULL otherwise) - # $same.row.match2: positions, in data1, of the columns that match the columns in data2, as given by match(data1, data2) (NULL otherwise) - # $identical.object: logical. Are objects identical (including row & column names)? - # $identical.content: logical. Are content objects identical (identical excluding row & column names)? - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # none - # EXAMPLES - # obs1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = as.data.frame(matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])), stringsAsFactors = TRUE) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) - # obs1 = matrix(101:110, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) - # large matrices - # obs1 = matrix(1:1e6, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; obs2 = matrix(as.integer((1:1e6)+1e6/5), ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; head(obs1) ; head(obs2) ; fun_comp_2d(obs1, obs2) - # WARNING: when comparing content (rows, columns, or total), double and integer data are considered as different -> double(1) != integer(1) - # obs1 = matrix(1:1e6, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; obs2 = matrix((1:1e6)+1e6/5, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; head(obs1) ; head(obs2) ; fun_comp_2d(obs1, obs2) - # Matrices: same row conten tand same row names - # obs1 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4]))) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) - # Matrices: same row content but not same row names -> works: same content is identified - # obs1 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("x", "z", "y"), c(LETTERS[1:2], "k", LETTERS[5:4]))) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) - # obs1 = t(matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5]))) ; obs2 = t(matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4])))) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) - # Data frames: same row content and same row names, not same mode between columns - # obs1 = as.data.frame(matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5]))) ; obs2 = as.data.frame(matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4])))) ; obs1[, 5] <- as.character(obs1[, 5]) ; obs2[, 5] <- as.character(obs2[, 5]) ; obs1 ; obs2 ; str(obs1) ; str(obs2) ; fun_comp_2d(obs1, obs2) - # Data frames: same row content but not same row names -> works: same content is identified - # obs1 = as.data.frame(matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5]))) ; obs2 = as.data.frame(matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("x", "z", "y"), c(LETTERS[1:2], "k", LETTERS[5:4])))) ; obs1[, 5] <- as.character(obs1[, 5]) ; obs2[, 5] <- as.character(obs2[, 5]) ; obs1 ; obs2 ; str(obs1) ; str(obs2) ; fun_comp_2d(obs1, obs2) - # DEBUGGING - # data1 = matrix(1:10, ncol = 5) ; data2 = matrix(1:10, ncol = 5) # for function debugging - # data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging - # data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5) # for function debugging - # data1 = matrix(1:15, byrow = TRUE, ncol = 5, dimnames = list(letters[1:3], LETTERS[1:5])) ; data2 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging - # data1 = matrix(1:15, ncol = 5, dimnames = list(letters[1:3], LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging - # data1 = matrix(1:15, ncol = 5, dimnames = list(paste0("A", letters[1:3]), LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging - # data1 = matrix(1:15, ncol = 5, dimnames = list(letters[1:3], LETTERS[1:5])) ; data2 = matrix(1:12, ncol = 4, dimnames = list(letters[1:3], LETTERS[1:4])) # for function debugging - # data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(101:110, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging - # data1 = data.frame(a = 1:3, b= letters[1:3], row.names = LETTERS[1:3], stringsAsFactors = TRUE) ; data2 = data.frame(A = 1:3, B= letters[1:3], stringsAsFactors = TRUE) # for function debugging - # data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = as.data.frame(matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])), stringsAsFactors = TRUE) # for function debugging - # data1 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4]))) # for function debugging - # data1 = table(Exp1 = c("A", "A", "A", "B", "B", "B"), Exp2 = c("A1", "B1", "A1", "C1", "C1", "B1")) ; data2 = data.frame(A = 1:3, B= letters[1:3], stringsAsFactors = TRUE) # for function debugging - # data1 = matrix(1:1e6, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; data2 = matrix((1:1e6)+1e6/5, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # argument checking - if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A MATRIX, DATA FRAME OR TABLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! (any(class(data2) %in% c("data.frame", "table")) | all(class(data2) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data2) %in% c("matrix", "data.frame", "table")) - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A MATRIX, DATA FRAME OR TABLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status - # end argument checking - # main code - same.class <- NULL - class <- NULL - same.dim <- NULL - dim <- NULL - same.row.nb <- NULL - row.nb <- NULL - same.col.nb <- NULL - col.nb <- NULL - same.row.name <- NULL - row.name <- NULL - any.id.row.name <- NULL - same.row.names.pos1 <- NULL - same.row.names.pos2 <- NULL - same.row.names.match1 <- NULL - same.row.names.match2 <- NULL - common.row.names <- NULL - same.col.name <- NULL - any.id.col.name <- NULL - same.col.names.pos1 <- NULL - same.col.names.pos2 <- NULL - same.col.names.match1 <- NULL - same.col.names.match2 <- NULL - common.col.names <- NULL - col.name <- NULL - any.id.row <- NULL - same.row.pos1 <- NULL - same.row.pos2 <- NULL - same.row.match1 <- NULL - same.row.match2 <- NULL - any.id.col <- NULL - same.col.pos1 <- NULL - same.col.pos2 <- NULL - same.col.match1 <- NULL - same.col.match2 <- NULL - identical.object <- NULL - identical.content <- NULL - if(identical(data1, data2) & (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) - same.class <- TRUE - class <- class(data1) - same.dim <- TRUE - dim <- dim(data1) - same.row.nb <- TRUE - row.nb <- nrow(data1) - same.col.nb <- TRUE - col.nb <- ncol(data1) - same.row.name <- TRUE - row.name <- dimnames(data1)[[1]] - any.id.row.name <- TRUE - same.row.names.pos1 <- 1:row.nb - same.row.names.pos2 <- 1:row.nb - same.row.names.match1 <- 1:row.nb - same.row.names.match2 <- 1:row.nb - common.row.names <- dimnames(data1)[[1]] - same.col.name <- TRUE - col.name <- dimnames(data1)[[2]] - any.id.col.name <- TRUE - same.col.names.pos1 <- 1:col.nb - same.col.names.pos2 <- 1:col.nb - same.col.names.match1 <- 1:col.nb - same.col.names.match2 <- 1:col.nb - common.col.names <- dimnames(data1)[[2]] - any.id.row <- TRUE - same.row.pos1 <- 1:row.nb - same.row.pos2 <- 1:row.nb - same.row.match1 <- 1:row.nb - same.row.match2 <- 1:row.nb - any.id.col <- TRUE - same.col.pos1 <- 1:col.nb - same.col.pos2 <- 1:col.nb - same.col.match1 <- 1:col.nb - same.col.match2 <- 1:col.nb - identical.object <- TRUE - identical.content <- TRUE - }else{ - identical.object <- FALSE - if(all(class(data1) == "table") & length(dim(data1)) == 1L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT IS A 1D TABLE. USE THE fun_comp_1d FUNCTION") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(all(class(data2) == "table") & length(dim(data2)) == 1L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT IS A 1D TABLE. USE THE fun_comp_1d FUNCTION") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! identical(class(data1), class(data2))){ - same.class <- FALSE - }else if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 AND data2 ARGUMENTS MUST BE EITHER MATRIX, DATA FRAME OR TABLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - same.class <- TRUE - class <- class(data1) - } - if( ! identical(dim(data1), dim(data2))){ - same.dim <- FALSE - }else{ - same.dim <- TRUE - dim <- dim(data1) - } - if( ! identical(nrow(data1), nrow(data2))){ - same.row.nb <- FALSE - }else{ - same.row.nb <- TRUE - row.nb <- nrow(data1) - } - if( ! identical(ncol(data1), ncol(data2))){ - same.col.nb <- FALSE - }else{ - same.col.nb <- TRUE - col.nb <- ncol(data1) - } - # row and col names - if(is.null(dimnames(data1)) & is.null(dimnames(data2))){ - same.row.name <- NULL # but already NULL - same.col.name <- NULL # but already NULL - # other row names param remain NULL - }else if((is.null(dimnames(data1)) & ! is.null(dimnames(data2))) | ( ! is.null(dimnames(data1)) & is.null(dimnames(data2)))){ - same.row.name <- FALSE - same.col.name <- FALSE - any.id.row.name <- FALSE - any.id.col.name <- FALSE - # other row names param remain NULL - }else{ - # row names - if(is.null(dimnames(data1)[[1]]) & is.null(dimnames(data2)[[1]])){ - same.row.name <- NULL # but already NULL - # other row names param remain NULL - }else if((is.null(dimnames(data1)[[1]]) & ! is.null(dimnames(data2)[[1]])) | ( ! is.null(dimnames(data1)[[1]]) & is.null(dimnames(data2)[[1]]))){ - same.row.name <- FALSE - any.id.row.name <- FALSE - # other row names param remain NULL - }else if(identical(dimnames(data1)[[1]], dimnames(data2)[[1]])){ - same.row.name <- TRUE - row.name <- dimnames(data1)[[1]] - any.id.row.name <- TRUE - same.row.names.pos1 <- 1:nrow(data1) - same.row.names.pos2 <- 1:nrow(data1) - same.row.names.match1 <- 1:nrow(data1) - same.row.names.match2 <- 1:nrow(data1) - common.row.names <- dimnames(data1)[[1]] - }else{ - same.row.name <- FALSE - any.id.row.name <- FALSE - if(any(dimnames(data1)[[1]] %in% dimnames(data2)[[1]])){ - any.id.row.name <- TRUE - same.row.names.pos1 <- which(dimnames(data1)[[1]] %in% dimnames(data2)[[1]]) - same.row.names.match1 <- match(dimnames(data1)[[1]], dimnames(data2)[[1]]) - } - if(any(dimnames(data2)[[1]] %in% dimnames(data1)[[1]])){ - any.id.row.name <- TRUE - same.row.names.pos2 <- which(dimnames(data2)[[1]] %in% dimnames(data1)[[1]]) - same.row.names.match2 <- match(dimnames(data2)[[1]], dimnames(data1)[[1]]) - } - if(any.id.row.name == TRUE){ - common.row.names <- unique(c(dimnames(data1)[[1]][same.row.names.pos1], dimnames(data2)[[1]][same.row.names.pos2])) - } - } - # col names - if(is.null(dimnames(data1)[[2]]) & is.null(dimnames(data2)[[2]])){ - same.col.name <- NULL # but already NULL - # other col names param remain NULL - }else if((is.null(dimnames(data1)[[2]]) & ! is.null(dimnames(data2)[[2]])) | ( ! is.null(dimnames(data1)[[2]]) & is.null(dimnames(data2)[[2]]))){ - same.col.name <- FALSE - any.id.col.name <- FALSE - # other col names param remain NULL - }else if(identical(dimnames(data1)[[2]], dimnames(data2)[[2]])){ - same.col.name <- TRUE - col.name <- dimnames(data1)[[2]] - any.id.col.name <- TRUE - same.col.names.pos1 <- 1:ncol(data1) - same.col.names.pos2 <- 1:ncol(data1) - same.col.names.match1 <- 1:ncol(data1) - same.col.names.match2 <- 1:ncol(data1) - common.col.names <- dimnames(data1)[[2]] - }else{ - same.col.name <- FALSE - any.id.col.name <- FALSE - if(any(dimnames(data1)[[2]] %in% dimnames(data2)[[2]])){ - any.id.col.name <- TRUE - same.col.names.pos1 <- which(dimnames(data1)[[2]] %in% dimnames(data2)[[2]]) - same.col.names.match1 <- match(dimnames(data1)[[2]], dimnames(data2)[[2]]) - } - if(any(dimnames(data2)[[2]] %in% dimnames(data1)[[2]])){ - any.id.col.name <- TRUE - same.col.names.pos2 <- which(dimnames(data2)[[2]] %in% dimnames(data1)[[2]]) - same.col.names.match2 <- match(dimnames(data2)[[2]], dimnames(data1)[[2]]) - } - if(any.id.col.name == TRUE){ - common.col.names <- unique(c(dimnames(data1)[[2]][same.col.names.pos1], dimnames(data2)[[2]][same.col.names.pos2])) - } - } - } - # identical row and col content - if(all(class(data1) == "table")){ - data1 <- as.data.frame(matrix(data1, ncol = ncol(data1)), stringsAsFactors = FALSE) # conversion of table into data frame to facilitate inter class comparison - }else if(all(class(data1) %in% c("matrix", "array"))){ - data1 <- as.data.frame(data1, stringsAsFactors = FALSE) # conversion of matrix into data frame to facilitate inter class comparison - }else if(all(class(data1) == "data.frame")){ - # data1 <- data.frame(lapply(data1, as.character), stringsAsFactors = FALSE) # conversion of columns into characters - } - if(all(class(data2) == "table")){ - data2 <- as.data.frame(matrix(data2, ncol = ncol(data2)), stringsAsFactors = FALSE) # conversion of table into data frame to facilitate inter class comparison - }else if(all(class(data2) %in% c("matrix", "array"))){ - data2 <- as.data.frame(data2, stringsAsFactors = FALSE) # conversion of matrix into data frame to facilitate inter class comparison - }else if(all(class(data2) == "data.frame")){ - # data2 <- data.frame(lapply(data2, as.character), stringsAsFactors = FALSE) # conversion of columns into characters - } - row.names(data1) <- paste0("A", 1:nrow(data1)) - row.names(data2) <- paste0("A", 1:nrow(data2)) - if(same.col.nb == TRUE){ # because if not the same col nb, the row cannot be identical - if(all(sapply(data1, FUN = typeof) == "integer") & all(sapply(data2, FUN = typeof) == "integer") & as.double(nrow(data1)) * nrow(data2) <= 1e10){ # fast method for integers (thus not data frames). as.double(nrow(data1)) to prevent integer overflow because R is 32 bits for integers - tempo1 <- c(as.data.frame(t(data1), stringsAsFactors = FALSE)) # conversion into list. This work fast with only integers (because 32 bits) - tempo2 <- c(as.data.frame(t(data2), stringsAsFactors = FALSE)) # conversion into list. This work fast with only integers (because 32 bits) - same.row.pos1 <- which(tempo1 %in% tempo2) - same.row.pos2 <- which(tempo2 %in% tempo1) - same.row.match1 <- match(tempo1, tempo2) - same.row.match2 <- match(tempo2, tempo1) - }else if(as.double(nrow(data1)) * nrow(data2) <= 1e6){ # as.double(nrow(data1)) to prevent integer overflow because R is 32 bits for integers - # inactivated because I would like to keep the mode during comparisons - # if(col.nb <= 10){ # if ncol is not to big, the t() should not be that long - # tempo1 <- c(as.data.frame(t(data1), stringsAsFactors = FALSE)) # conversion into list. This work fast with only integers (because 32 bits) - # tempo2 <- c(as.data.frame(t(data2), stringsAsFactors = FALSE)) # conversion into list. - # same.row.pos1 <- which(tempo1 %in% tempo2) - # same.row.pos2 <- which(tempo2 %in% tempo1) - # same.row.match1 <- match(tempo1, tempo2) - # same.row.match2 <- match(tempo2, tempo1) - # }else{ - # very long computation - same.row.pos1 <- logical(length = nrow(data1)) # FALSE by default - same.row.pos1[] <- FALSE # security - same.row.pos2 <- logical(length = nrow(data2)) # FALSE by default - same.row.pos2[] <- FALSE # security - same.row.match1 <- rep(NA, nrow(data1)) - same.row.match2 <- rep(NA, nrow(data2)) - for(i3 in 1:nrow(data1)){ - for(i4 in 1:nrow(data2)){ - tempo1 <- data1[i3, ] - tempo2 <- data2[i4, ] - rownames(tempo1) <- NULL # to have same row and column names - colnames(tempo1) <- NULL # to have same row and column names - rownames(tempo2) <- NULL # to have same row and column names - colnames(tempo2) <- NULL # to have same row and column names - if(identical(tempo1, tempo2)){ - same.row.pos1[i3] <- TRUE - same.row.pos2[i4] <- TRUE - same.row.match1[i3] <- i4 - same.row.match2[i4] <- i3 - } - } - } - same.row.pos1 <- which(same.row.pos1) - same.row.pos2 <- which(same.row.pos2) - # } - }else{ - same.row.pos1 <- "TOO BIG FOR EVALUATION" - same.row.pos2 <- "TOO BIG FOR EVALUATION" - same.row.match1 <- "TOO BIG FOR EVALUATION" - same.row.match2 <- "TOO BIG FOR EVALUATION" - } - - names(same.row.pos1) <- NULL - names(same.row.pos2) <- NULL - if(all(is.na(same.row.pos1))){ - same.row.pos1 <- NULL - }else{ - same.row.pos1 <- same.row.pos1[ ! is.na(same.row.pos1)] - any.id.row <- TRUE - } - if(all(is.na(same.row.pos2))){ - same.row.pos2 <- NULL - }else{ - same.row.pos2 <- same.row.pos2[ ! is.na(same.row.pos2)] - any.id.row <- TRUE - } - if(is.null(same.row.pos1) & is.null(same.row.pos2)){ - any.id.row <- FALSE - }else if(length(same.row.pos1) == 0L & length(same.row.pos2) == 0L){ - any.id.row <- FALSE - }else if(all(same.row.pos1 == "TOO BIG FOR EVALUATION") & all(same.row.pos2 == "TOO BIG FOR EVALUATION")){ - any.id.row <- NULL - } - }else{ - any.id.row <- FALSE - # same.row.pos1 and 2 remain NULL - } - if(same.row.nb == TRUE){ # because if not the same row nb, the col cannot be identical - if(as.double(ncol(data1)) * ncol(data2) <= 1e10){ # comparison of data frame columns is much easier than rows because no need to use t() before converting to list for fast comparison. as.double(ncol(data1)) to prevent integer overflow because R is 32 bits for integers - # if(all(sapply(data1, FUN = typeof) == "integer") & all(sapply(data2, FUN = typeof) == "integer") & as.double(ncol(data1)) * ncol(data2) <= 1e10){ # fast method for integers (thus not data frames). as.double(ncol(data1)) to prevent integer overflow because R is 32 bits for integers - tempo1 <- c(data1) - tempo2 <- c(data2) - same.col.pos1 <- which(tempo1 %in% tempo2) - same.col.pos2 <- which(tempo2 %in% tempo1) - same.col.match1 <- match(tempo1, tempo2) - same.col.match2 <- match(tempo2, tempo1) - # }else if(as.double(ncol(data1)) * ncol(data2) <= 1e6){ # as.double(ncol(data1)) to prevent integer overflow because R is 32 bits for integers - # same.col.pos1 <- logical(length = ncol(data1)) # FALSE by default - # same.col.pos1[] <- FALSE # security - # same.col.pos2 <- logical(length = ncol(data2)) # FALSE by default - # same.col.pos2[] <- FALSE # security - # same.col.match1 <- rep(NA, ncol(data1)) - # same.col.match2 <- rep(NA, ncol(data2)) - # for(i3 in 1:ncol(data1)){ - # for(i4 in 1:ncol(data2)){ - # if(identical(data1[ , i3], data2[ , i4])){ - # same.col.pos1[i3] <- TRUE - # same.col.pos2[i4] <- TRUE - # same.col.match1[i3] <- i4 - # same.col.match2[i4] <- i3 - # } - # } - # } - # same.col.pos1 <- which(same.col.pos1) - # same.col.pos2 <- which(same.col.pos2) - }else{ - same.col.pos1 <- "TOO BIG FOR EVALUATION" - same.col.pos2 <- "TOO BIG FOR EVALUATION" - } - names(same.col.pos1) <- NULL - names(same.col.pos2) <- NULL - if(all(is.na(same.col.pos1))){ - same.col.pos1 <- NULL - }else{ - same.col.pos1 <- same.col.pos1[ ! is.na(same.col.pos1)] - any.id.col <- TRUE - } - if(all(is.na(same.col.pos2))){ - same.col.pos2 <- NULL - }else{ - same.col.pos2 <- same.col.pos2[ ! is.na(same.col.pos2)] - any.id.col <- TRUE - } - if(is.null(same.col.pos1) & is.null(same.col.pos2)){ - any.id.col <- FALSE - }else if(length(same.col.pos1) == 0L & length(same.col.pos2) == 0L){ - any.id.col <- FALSE - }else if(all(same.col.pos1 == "TOO BIG FOR EVALUATION") & all(same.col.pos2 == "TOO BIG FOR EVALUATION")){ - any.id.col <- NULL - } - }else{ - any.id.col <- FALSE - # same.col.pos1 and 2 remain NULL - } - if(same.dim == TRUE){ - names(data1) <- NULL - row.names(data1) <- NULL - names(data2) <- NULL - row.names(data2) <- NULL - if(identical(data1, data2)){ - identical.content <- TRUE - }else{ - identical.content <- FALSE - } - }else{ - identical.content <- FALSE - } - } - output <- list(same.class = same.class, class = class, same.dim = same.dim, dim = dim, same.row.nb = same.row.nb, row.nb = row.nb, same.col.nb = same.col.nb , col.nb = col.nb, same.row.name = same.row.name, row.name = row.name, any.id.row.name = any.id.row.name, same.row.names.pos1 = same.row.names.pos1, same.row.names.pos2 = same.row.names.pos2, same.row.names.match1 = same.row.names.match1, same.row.names.match2 = same.row.names.match2, common.row.names = common.row.names, same.col.name = same.col.name, col.name = col.name,any.id.col.name = any.id.col.name, same.col.names.pos1 = same.col.names.pos1, same.col.names.pos2 = same.col.names.pos2, same.col.names.match1 = same.col.names.match1, same.col.names.match2 = same.col.names.match2, common.col.names = common.col.names, any.id.row = any.id.row, same.row.pos1 = same.row.pos1, same.row.pos2 = same.row.pos2, same.row.match1 = same.row.match1, same.row.match2 = same.row.match2, any.id.col = any.id.col, same.col.pos1 = same.col.pos1, same.col.pos2 = same.col.pos2, same.col.match1 = same.col.match1, same.col.match2 = same.col.match2, identical.object = identical.object, identical.content = identical.content) - return(output) } - - -######## fun_comp_list() #### comparison of two lists - - -fun_comp_list <- function(data1, data2){ - # AIM - # compare two lists. Check and report in a list if the 2 datasets have: - # same length - # common names - # common compartments - # ARGUMENTS - # data1: list - # data2: list - # RETURN - # a list containing: - # $same.length: logical. Are number of elements identical? - # $length: number of elements in the 2 datasets (NULL otherwise) - # $same.names: logical. Are element names identical ? - # $name: name of elements of the 2 datasets if identical (NULL otherwise) - # $any.id.name: logical. Is there any element names identical ? - # $same.names.pos1: positions, in data1, of the element names identical in data2 - # $same.names.pos2: positions, in data2, of the compartment names identical in data1 - # $any.id.compartment: logical. is there any identical compartments ? - # $same.compartment.pos1: positions, in data1, of the compartments identical in data2 - # $same.compartment.pos2: positions, in data2, of the compartments identical in data1 - # $identical.object: logical. Are objects identical (kind of object, compartment names and content)? - # $identical.content: logical. Are content objects identical (identical compartments excluding compartment names)? - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # none - # EXAMPLES - # obs1 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; obs2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; fun_comp_list(obs1, obs2) - # obs1 = list(1:5, LETTERS[1:2]) ; obs2 = list(a = 1:5, b = LETTERS[1:2]) ; fun_comp_list(obs1, obs2) - # obs1 = list(b = 1:5, c = LETTERS[1:2]) ; obs2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; fun_comp_list(obs1, obs2) - # obs1 = list(b = 1:5, c = LETTERS[1:2]) ; obs2 = list(LETTERS[5:9], matrix(1:6), 1:5) ; fun_comp_list(obs1, obs2) - # DEBUGGING - # data1 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; data2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) # for function debugging - # data1 = list(a = 1:5, b = LETTERS[1:2]) ; data2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # argument checking - if( ! any(class(data1) %in% "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! any(class(data2) %in% "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status - # end argument checking - # main code - same.length <- NULL - length <- NULL - same.names <- NULL - name <- NULL - any.id.name <- NULL - same.names.pos1 <- NULL - same.names.pos2 <- NULL - any.id.compartment <- NULL - same.compartment.pos1 <- NULL - same.compartment.pos2 <- NULL - identical.object <- NULL - identical.content <- NULL - if(identical(data1, data2)){ - same.length <- TRUE - length <- length(data1) - if( ! is.null(names(data1))){ - same.names <- TRUE - name <- names(data1) - any.id.name <- TRUE - same.names.pos1 <- 1:length(data1) - same.names.pos2 <- 1:length(data2) - } - any.id.compartment <- TRUE - same.compartment.pos1 <- 1:length(data1) - same.compartment.pos2 <- 1:length(data2) - identical.object <- TRUE - identical.content <- TRUE - }else{ - identical.object <- FALSE - if( ! identical(length(data1), length(data2))){ - same.length<- FALSE - }else{ - same.length<- TRUE - length <- length(data1) - } - if( ! (is.null(names(data1)) & is.null(names(data2)))){ - if( ! identical(names(data1), names(data2))){ - same.names <- FALSE - }else{ - same.names <- TRUE - name <- names(data1) - } - any.id.name <- FALSE - if(any(names(data1) %in% names(data2))){ - any.id.name <- TRUE - same.names.pos1 <- which(names(data1) %in% names(data2)) - } - if(any(names(data2) %in% names(data1))){ - any.id.name <- TRUE - same.names.pos2 <- which(names(data2) %in% names(data1)) - } - } - names(data1) <- NULL - names(data2) <- NULL - any.id.compartment <- FALSE - if(any(data1 %in% data2)){ - any.id.compartment <- TRUE - same.compartment.pos1 <- which(data1 %in% data2) - } - if(any(data2 %in% data1)){ - any.id.compartment <- TRUE - same.compartment.pos2 <- which(data2 %in% data1) - } - if(same.length == TRUE & ! all(is.null(same.compartment.pos1), is.null(same.compartment.pos2))){ - if(identical(same.compartment.pos1, same.compartment.pos2)){ - identical.content <- TRUE - }else{ - identical.content <- FALSE - } - }else{ - identical.content <- FALSE - } - } - output <- list(same.length = same.length, length = length, same.names = same.names, name = name, any.id.name = any.id.name, same.names.pos1 = same.names.pos1, same.names.pos2 = same.names.pos2, any.id.compartment = any.id.compartment, same.compartment.pos1 = same.compartment.pos1, same.compartment.pos2 = same.compartment.pos2, identical.object = identical.object, identical.content = identical.content) - return(output) +if( ! any(class(data2) %in% c("logical", "integer", "numeric", "character", "factor", "table"))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A NON NULL VECTOR, FACTOR OR 1D TABLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(all(class(data2) %in% "table")){ +if(length(dim(data2)) > 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A 1D TABLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == } - - -######## fun_test() #### test combinations of argument values of a function and return errors (and graphs) - - -# add traceback https://stackoverflow.com/questions/47414119/how-to-read-a-traceback-in-r - -fun_test <- function( - fun, - arg, - val, - expect.error = NULL, - parall = FALSE, - thread.nb = NULL, - print.count = 10, - plot.fun = FALSE, - export = FALSE, - res.path = NULL, - lib.path = NULL, - cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" -){ - # AIM - # test combinations of argument values of a function - # WARNINGS - # Limited to 43 arguments with at least 2 values each. The total number of arguments tested can be more if the additional arguments have a single value. The limit is due to nested "for" loops (https://stat.ethz.ch/pipermail/r-help/2008-March/157341.html), but it should not be a problem since the number of tests would be 2^43 > 8e12 - # ARGUMENTS - # fun: character string indicating the name of the function tested (without brackets) - # arg: vector of character strings of arguments of fun. At least arguments that do not have default values must be present in this vector - # val: list with number of compartments equal to length of arg, each compartment containing values of the corresponding argument in arg. Each different value must be in a list or in a vector. For instance, argument 3 in arg is a logical argument (values accepted TRUE, FALSE, NA). Thus, compartment 3 of val can be either list(TRUE, FALSE, NA), or c(TRUE, FALSE, NA). NULL value alone must be written list(NULL) - # expect.error: list of exactly the same structure as val argument, but containing FALSE or TRUE, depending on whether error is expected (TRUE) or not (FALSE) for each corresponding value of val. A message is returned depending on discrepancies between the expected and observed errors. BEWARE: not always possible to write the expected errors for all the combination of argument values. Ignored if NULL - # parall: logical. Force parallelization ? - # thread.nb: numeric value indicating the number of threads to use if ever parallelization is required. If NULL, all the available threads will be used. Ignored if parall is FALSE - # print.count: interger value. Print a working progress message every print.count during loops. BEWARE: can increase substentially the time to complete the process using a small value, like 10 for instance. Use Inf is no loop message desired - # plot.fun: logical. Plot the plotting function tested for each test? - # export: logical. Export the results into a .RData file and into a .txt file? If FALSE, return a list into the console (see below). BEWARE: will be automatically set to TRUE if parall is TRUE. This means that when using parallelization, the results are systematically exported, not returned into the console - # res.path: character string indicating the absolute pathway of folder where the txt results and pdfs, containing all the plots, will be saved. Several txt and pdf, one per thread, if parallelization. Ignored if export is FALSE. Must be specified if parall is TRUE or if export is TRUE - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL - # cute.path: character string indicating the absolute path of the cute.R file. Will be remove when cute will be a package. Ignored if parall is FALSE - # REQUIRED PACKAGES - # lubridate - # parallel if parall arguemtn is TRUE (included in the R installation packages but not automatically loaded) - # pdftools if parall arguemtn is TRUE (included in the R installation packages but not automatically loaded) - # If the tested function is in a package, this package must be imported first (no parallelization) or must be in the classical R package folder indicated by the lib.path argument (parallelization) - # RETURN - # if export is FALSE a list containing: - # $fun: the tested function - # $instruction: the initial instruction - # $sys.info: system and packages info - # $data: a data frame of all the combination tested, containing the following columns: - # the different values tested, named by arguments - # $kind: a vector of character strings indicating the kind of test result: either "ERROR", or "WARNING", or "OK" - # $problem: a logical vector indicating if error or not - # $expected.error: optional logical vector indicating the expected error specified in the expect.error argument - # $message: either NULL if $kind is always "OK", or the messages - # if export is TRUE 1) the same list object into a .RData file, 2) also the $data data frame into a .txt file, and 3) if expect.error is non NULL and if any discrepancy, the $data data frame into a .txt file but containing only the rows with discrepancies between expected and observed errors - # one or several pdf if a plotting function is tested and if the plot.fun argument is TRUE - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_get_message() - # fun_pack() - # EXAMPLES - # fun_test(fun = "unique", arg = c("x", "incomparables"), val = list(x = list(1:10, c(1,1,2,8), NA), incomparable = c(TRUE, FALSE, NA))) - # fun_test(fun = "fun_round", arg = c("data", "dec.nb", "after.lead.zero"), val = list(L1 = list(c(1, 1.0002256, 1.23568), "a", NA), L2 = list(2, c(1,3), NA), L3 = c(TRUE, FALSE, NA))) - # fun_test(fun = "plot", arg = c("x", "y"), val = list(x = list(1:10, 12:13, NA, (1:10)^2), y = list(1:10, NA, NA)), expect.error = list(x = list(FALSE, TRUE, TRUE, FALSE), y = list(FALSE, TRUE, TRUE)), parall = FALSE, thread.nb = NULL, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = NULL) - # fun_test(fun = "plot", arg = c("x", "y"), val = list(x = list(1:10, 12:13, NA, (1:10)^2), y = list(1:10, NA, NA)), parall = FALSE, thread.nb = 4, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = "C:\\Program Files\\R\\R-4.0.2\\library\\") - # set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun_test(fun = "fun_gg_boxplot", arg = c("data1", "y", "categ"), val = list(L1 = list(L1 = obs1), L2 = list(L1 = "Time"), L3 = list(L1 = "Group1"))) - # set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun_test(fun = "fun_gg_boxplot", arg = c("data1", "y", "categ"), val = list(L1 = list(obs1), L2 = "Time", L3 = "Group1"), parall = FALSE, thread.nb = NULL, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = "C:\\Program Files\\R\\R-4.0.2\\library\\") - # library(ggplot2) ; fun_test(fun = "geom_histogram", arg = c("data", "mapping"), val = list(x = list(data.frame(X = "a", stringsAsFactors = TRUE)), y = list(ggplot2::aes(x = X))), parall = FALSE, thread.nb = NULL, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = "C:\\Program Files\\R\\R-4.0.2\\library\\") # BEWARE: ggplot2::geom_histogram does not work - # DEBUGGING - # fun = "unique" ; arg = "x" ; val = list(x = list(1:10, c(1,1,2,8), NA)) ; expect.error = list(x = list(FALSE, FALSE, TRUE)) ; parall = FALSE ; thread.nb = NULL ; plot.fun = FALSE ; export = FALSE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL ; print.count = 1 ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" # for function debugging - # fun = "unique" ; arg = c("x", "incomparables") ; val = list(x = list(1:10, c(1,1,2,8), NA), incomparable = c(TRUE, FALSE, NA)) ; expect.error = NULL ; parall = FALSE ; thread.nb = 2 ; plot.fun = FALSE ; export = TRUE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL ; print.count = 10 ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" # for function debugging - # fun = "plot" ; arg = c("x", "y") ; val = list(x = list(1:10, 12:13, NA), y = list(1:10, NA, NA)) ; expect.error = list(x = list(FALSE, FALSE, TRUE, FALSE), y = list(FALSE, TRUE, TRUE)) ; print.count = 10 ; parall = FALSE ; thread.nb = NULL ; plot.fun = TRUE ; export = TRUE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL # for function debugging - # set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun = "fun_gg_boxplot" ; arg = c("data1", "y", "categ") ; val = list(L1 = list(L1 = obs1), L2 = list(L1 = "Time"), L3 = list(L1 = "Group1")) ; expect.error = NULL ; print.count = 10 ; parall = FALSE ; thread.nb = NULL ; plot.fun = TRUE ; export = TRUE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL # for function debugging - # fun = "unique" ; arg = "x" ; val = list(list(1:3, mean)) ; expect.error = list(TRUE, TRUE) ; parall = FALSE ; thread.nb = NULL ; plot.fun = FALSE ; export = FALSE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL ; print.count = 1 ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - req.function <- c( - "fun_check", - "fun_get_message", - "fun_pack" - ) - tempo <- NULL - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo <- c(tempo, i1) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # reserved words - # end reserved words - # arg with no default values - mandat.args <- c( - "fun", - "arg", - "val" - ) - tempo <- eval(parse(text = paste0("missing(", paste0(mandat.args, collapse = ") | missing("), "))"))) - print(tempo) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = fun, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = arg, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = val, class = "list", fun.name = function.name) ; eval(ee) - if( ! is.null(expect.error)){ - tempo <- fun_check(data = expect.error, class = "list", fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = parall, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(parall == TRUE){ - if( ! is.null(thread.nb)){ - tempo <- fun_check(data = thread.nb, typeof = "integer", double.as.integer.allowed = TRUE, neg.values = FALSE, length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & thread.nb < 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": thread.nb PARAMETER MUST EQUAL OR GREATER THAN 1: ", thread.nb) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - tempo <- fun_check(data = print.count, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = plot.fun, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = export, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(res.path)){ - tempo <- fun_check(data = res.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - } - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = cute.path, class = "vector", typeof = "character", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end using fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # new environment - env.name <- paste0("env", as.numeric(Sys.time())) - if(exists(env.name, where = -1)){ # verify if still ok when fun_info() is inside a function - tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - assign(env.name, new.env()) - assign("data", data, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # data assigned in a new envir for test - } - # end new environment - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT JUST BE NA:", paste0(tempo.arg[tempo.log], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "fun", - "arg", - "val", - # "expect.erro", # because can be NULL - "parall", - # "thread.nb", # because can be NULL - "print.count", - "plot.fun", - "export", - # "res.path", # because can be NULL - # "lib.path", # because can be NULL - "cute.path" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){# normally no NA with is.null() - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # code that protects set.seed() in the global environment - # end code that protects set.seed() in the global environment - # warning initiation - # end warning initiation - # other checkings - if(grepl(x = fun, pattern = "()$")){ # remove () - fun <- sub(x = fun, pattern = "()$", replacement = "") - } - if( ! exists(fun)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CHARACTER STRING IN fun ARGUMENT DOES NOT EXIST IN THE R WORKING ENVIRONMENT: ", paste(fun, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - }else if( ! all(base::class(get(fun)) == "function")){ # here no env = sys.nframe(), inherit = FALSE for get() because fun is a function in the classical scope - tempo.cat <- paste0("ERROR IN ", function.name, ": fun ARGUMENT IS NOT CLASS \"function\" BUT: ", paste(base::class(get(fun)), collapse = "\n"), "\nCHECK IF ANY CREATED OBJECT WOULD HAVE THE NAME OF THE TESTED FUNCTION") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if(tempo$problem == FALSE & base::length(arg) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": arg ARGUMENT CANNOT BE LENGTH 0") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - for(i2 in 1:base::length(val)){ - tempo1 <- fun_check(data = val[[i2]], class = "vector", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = val[[i2]], class = "list", na.contain = TRUE, fun.name = function.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": COMPARTMENT ", i2, " OF val ARGUMENT MUST BE A VECTOR OR A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - }else if(tempo1$problem == FALSE){ # vector split into list compartments - val[[i2]] <- split(x = val[[i2]], f = 1:base::length(val[[i2]])) - } - } - if(base::length(arg) != base::length(val)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": LENGTH OF arg ARGUMENT MUST BE IDENTICAL TO LENGTH OF val ARGUMENT:\nHERE IT IS: ", base::length(arg), " VERSUS ", base::length(val)) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - args <- names(formals(get(fun))) # here no env = sys.nframe(), inherit = FALSE for get() because fun is a function in the classical scope - if( ! all(arg %in% args)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": SOME OF THE STRINGS IN arg ARE NOT ARGUMENTS OF fun\nfun ARGUMENTS: ", paste(args, collapse = " "),"\nPROBLEMATIC STRINGS IN arg: ", paste(arg[ ! arg %in% args], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if(sum(sapply(val, FUN = length) > 1) > 43){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CANNOT TEST MORE THAN 43 ARGUMENTS IF THEY ALL HAVE AT LEAST 2 VALUES EACH\nHERE THE NUMBER IS: ", sum(sapply(val, FUN = length) > 1)) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if( ! is.null(expect.error)){ - if(base::length(val) != base::length(expect.error)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": LENGTH OF val ARGUMENT MUST BE IDENTICAL TO LENGTH OF expect.error ARGUMENT:\nHERE IT IS: ", base::length(val), " VERSUS ", base::length(expect.error)) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - for(i3 in 1:base::length(expect.error)){ - tempo1 <- fun_check(data = expect.error[[i3]], class = "vector", mode = "logical", fun.name = function.name) - tempo2 <- fun_check(data = expect.error[[i3]], class = "list", fun.name = function.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": COMPARTMENT ", i3, " OF expect.error ARGUMENT MUST BE TRUE OR FALSE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - }else if(tempo1$problem == FALSE){ # vector split into list compartments - expect.error[[i3]] <- split(x = expect.error[[i3]], f = 1:base::length(expect.error[[i3]])) - } - } - } - if( ! is.null(res.path)){ - if( ! all(dir.exists(res.path))){ # separation to avoid the problem of tempo$problem == FALSE and res.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE res.path ARGUMENT DOES NOT EXISTS:\n", paste(res.path, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - } - if(parall == TRUE & is.null(res.path)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": res.path ARGUMENT MUST BE SPECIFIED IF parall ARGUMENT IS TRUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if(is.null(res.path) & export == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": res.path ARGUMENT MUST BE SPECIFIED IF export ARGUMENT TRUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if(parall == TRUE & export == FALSE){ - export <- TRUE - tempo.cat <- paste0("WARNING FROM ", function.name, ": export ARGUMENT CONVERTED TO TRUE BECAUSE thread.nb ARGUMENT IS NOT NULL") - warning(paste0("\n", tempo.cat, "\n"), call. = FALSE) - } - if( ! is.null(lib.path)){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - } - if(parall == TRUE){ - if(grepl(x = cute.path, pattern = "^http")){ - tempo.error1 <- any(grepl(x = fun_get_message(data = "source(cute.path)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)), pattern = "^[Ee]rror")) - tempo.error2 <- FALSE - }else{ - tempo.error1 <- FALSE - tempo.error2 <- ! file.exists(cute.path) - } - if(tempo.error1 | tempo.error2){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(grepl(x = cute.path, pattern = "^http"), "URL", "FILE"), " PATH INDICATED IN THE cute.path PARAMETER DOES NOT EXISTS:\n", cute.path) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - } - # end other checkings - # reserved word checking - # end reserved word checking - # end second round of checking and data preparation - # package checking - fun_pack(req.package = c("lubridate"), lib.path = lib.path) - if(parall == TRUE){ - fun_pack(req.package = c("parallel", "pdftools"), lib.path = lib.path) - } - # end package checking - # declaration of special plot functions - sp.plot.fun <- c("fun_gg_scatter", "fun_gg_bar", "fun_gg_boxplot") - # end declaration of special plot functions - # main code - ini.warning.length <- base::options()$warning.length - options(warning.length = 8170) - warn <- NULL - warn.count <- 0 - cat("\nfun_test JOB IGNITION\n") - ini.date <- Sys.time() - ini.time <- as.numeric(ini.date) # time of process begin, converted into seconds - if(export == TRUE){ - res.path <- paste0(res.path, "/fun_test_res_", trunc(ini.time)) - if(dir.exists(res.path)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": FOLDER ALREADY EXISTS\n", res.path, "\nPLEASE RERUN ONCE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - dir.create(res.path) - } - } - total.comp.nb <- prod(sapply(val, FUN = "length")) - cat(paste0("\nTHE TOTAL NUMBER OF TESTS IS: ", total.comp.nb, "\n")) - # creation of the txt instruction that includes several loops - loop.string <- NULL - end.loop.string <- NULL - fun.args <- NULL - fun.args2 <- NULL - error.values <- NULL - arg.values <- "list(" - for(i1 in 1:base::length(arg)){ - if(parall == FALSE){ - if(base::length(val[[i1]]) > 1){ # loop only if more than one value in base::length(val[[i1]]) - loop.string <- paste0(loop.string, "for(i", i1, " in 1:", base::length(val[[i1]]), "){") - end.loop.string <- paste0(end.loop.string, "}") - } - }else{ - loop.string <- "for(i in x){" - end.loop.string <- "}" - } - fun.args <- paste0( - fun.args, - ifelse(i1 == 1L, "", ", "), - arg[i1], - " = val[[", - i1, - "]][[", - if(parall == FALSE){ - if(base::length(val[[i1]]) > 1){ - paste0("i", i1) - }else{ - "1" # a unique element in val[[i1]] - } - }else{ - paste0("i.list[[", i1, "]][i]") - }, - "]]" - ) - fun.args2 <- paste0( - fun.args2, - ifelse(i1 == 1L, "", ", "), - arg[i1], - " = val[[", - i1, - "]][[', ", - if(parall == FALSE){ - if(base::length(val[[i1]]) > 1){ - paste0("i", i1) - }else{ - "1" # a unique element in val[[i1]] - } - }else{ - paste0("i.list[[", i1, "]][i]") - }, - ", ']]" - ) - arg.values <- paste0( - arg.values, - "val[[", i1, "]][[", - if(parall == FALSE){ - if(base::length(val[[i1]]) > 1){ - paste0("i", i1) - }else{ - "1" # a unique element in val[[i1]] - } - }else{ - paste0("i.list[[", i1, "]][i]") - }, - "]]", - ifelse(i1 == base::length(arg), "", ", ") - ) - error.values <- paste0( - error.values, - ifelse(i1 == 1L, "", " | "), - "expect.error[[", i1, "]][[", - if(parall == FALSE){ - if(base::length(expect.error[[i1]]) > 1){ - paste0("i", i1) - }else{ - "1" # a unique element in expect.error[[i1]] - } - }else{ - paste0("i.list[[", i1, "]][i]") - }, - "]]" - ) - } - arg.values <- paste0(arg.values, ")") - fun.test <- paste0(fun, "(", fun.args, ")") - fun.test2 <- paste0("paste0('", fun, "(", fun.args2, ")')") - # plot title for special plot functions - if(plot.fun == TRUE){ - plot.kind <- "classic" - if(fun %in% sp.plot.fun){ - plot.kind <- "special" - if(any(arg %in% "title")){ # this is for the special functions - tempo.match <- regmatches(x = fun.test, m = regexpr(text = fun.test, pattern = "title = .+[,)]")) - tempo.match <- substring(tempo.match , 1, nchar(tempo.match) - 1) - fun.test <- sub(x = fun.test, pattern = tempo.match, replacement = paste0(tempo.match, "\ntempo.title")) - }else{ - fun.test <- sub(x = fun.test, pattern = ")$", replacement = ", title = tempo.title)") - } - } - } - # end plot title for special plot functions - kind <- character() - problem <- logical() - expected.error <- logical() - res <- character() - count <- 0 - print.count.loop <- 0 - plot.count <- 0 - if(base::length(arg) == 1L){ - data <- data.frame() - }else{ # base::length(arg) == 0L already tested above - data <- data.frame(t(vector("character", base::length(arg))), stringsAsFactors = FALSE)[-1, ] # -1 to remove the single row created and to have an empty data frame with base::length(arg) columns - } - code <- paste( - loop.string, ' -count <- count + 1 -print.count.loop <- print.count.loop + 1 -arg.values.print <- eval(parse(text = arg.values)) # recover the list of the i1 compartment -for(j3 in 1:base::length(arg.values.print)){ # WARNING: do not use i1, i2 etc., here because already in loop.string -tempo.capt <- capture.output(tempo.error <- fun_get_message(data = paste0("paste(arg.values.print[[", j3, "]])"), kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE))) # collapsing arg.values sometimes does not work (with function for instance) -if( ! is.null(tempo.error)){ -arg.values.print[[j3]] <- paste0("SPECIAL VALUE OF CLASS ", base::class(arg.values.print[[j3]]), " AND TYPE ", base::typeof(arg.values.print[[j3]])) } +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status +# end argument checking +# main code +same.class <- FALSE +class <- NULL +same.length <- FALSE +length <- NULL +same.levels <- NULL # not FALSE to deal with no factors +levels <- NULL +any.id.levels <- FALSE +same.levels.pos1 <- NULL +same.levels.pos2 <- NULL +same.levels.match1 <- NULL +same.levels.match2 <- NULL +common.levels <- NULL +same.names <- NULL # not FALSE to deal with absence of name +name <- NULL +any.id.name <- FALSE +same.names.pos1 <- NULL +same.names.pos2 <- NULL +same.names.match1 <- NULL +same.names.match2 <- NULL +common.names <- NULL +any.id.element <- FALSE +same.elements.pos1 <- NULL +same.elements.pos2 <- NULL +same.elements.match1 <- NULL +same.elements.match2 <- NULL +common.elements <- NULL +same.order <- NULL +order1 <- NULL +order2 <- NULL +identical.object <- FALSE +identical.content <- FALSE +if(identical(data1, data2)){ +same.class <- TRUE +class <- class(data1) +same.length <- TRUE +length <- length(data1) +if(any(class(data1) %in% "factor")){ +same.levels <- TRUE +levels <- levels(data1) +any.id.levels <- TRUE +same.levels.pos1 <- 1:length(levels(data1)) +same.levels.pos2 <- 1:length(levels(data2)) +same.levels.match1 <- 1:length(levels(data1)) +same.levels.match2 <- 1:length(levels(data2)) +common.levels <- levels(data1) } -data <- rbind(data, as.character(sapply(arg.values.print, FUN = "paste", collapse = " ")), stringsAsFactors = FALSE) # each colum is a test -tempo.capt <- capture.output(tempo.try.error <- fun_get_message(data = eval(parse(text = fun.test2)), kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE))) # data argument needs a character string but eval(parse(text = fun.test2)) provides it (eval parse replace the i1, i2, etc., by the correct values, meaning that only val is required in the env.name environment) -tempo.capt <- capture.output(tempo.try.warning <- fun_get_message(data = eval(parse(text = fun.test2)), kind = "warning", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE), print.no = TRUE)) # data argument needs a character string but eval(parse(text = fun.test2)) provides it (eval parse replace the i1, i2, etc., by the correct values, meaning that only val is required in the env.name environment) -if( ! is.null(expect.error)){ -expected.error <- c(expected.error, eval(parse(text = error.values))) +if( ! is.null(names(data1))){ +same.names <- TRUE +name <- names(data1) +any.id.name <- TRUE +same.names.pos1 <- 1:length(data1) +same.names.pos2 <- 1:length(data2) +same.names.match1 <- 1:length(data1) +same.names.match2 <- 1:length(data2) +common.names <- names(data1) } -if( ! is.null(tempo.try.error)){ -kind <- c(kind, "ERROR") -problem <- c(problem, TRUE) -res <- c(res, tempo.try.error) -}else{ -if( ! is.null(tempo.try.warning)){ -kind <- c(kind, "WARNING") -problem <- c(problem, FALSE) -res <- c(res, tempo.try.warning) +any.id.element <- TRUE +same.elements.pos1 <- 1:length(data1) +same.elements.pos2 <- 1:length(data2) +same.elements.match1 <- 1:length(data1) +same.elements.match2 <- 1:length(data2) +common.elements <- data1 +same.order <- TRUE +order1 <- order(data1) +order2 <- order(data2) +identical.object <- TRUE +identical.content <- TRUE }else{ -kind <- c(kind, "OK") -problem <- c(problem, FALSE) -res <- c(res, "") +if(identical(class(data1), class(data2))){ +same.class <- TRUE +class <- class(data1) } -if(plot.fun == TRUE){ -invisible(dev.set(window.nb)) -plot.count <- plot.count + 1 -tempo.title <- paste0("test_", sprintf(paste0("%0", nchar(total.comp.nb), "d"), ifelse(parall == FALSE, count, x[count]))) -if(plot.kind == "classic"){ -eval(parse(text = fun.test)) -tempo <- fun_post_plot(corner.text = tempo.title) -}else if(plot.kind == "special"){ -eval(parse(text = fun.test)) +if(identical(length(data1), length(data2))){ +same.length<- TRUE +length <- length(data1) +} +if(any(class(data1) %in% "factor") & any(class(data2) %in% "factor")){ +if(identical(levels(data1), levels(data2))){ +same.levels <- TRUE +levels <- levels(data1) }else{ -tempo.cat <- paste0("INTERNAL CODE ERROR 1 IN ", function.name, ": CODE HAS TO BE MODIFIED") -stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +same.levels <- FALSE } +if(any(levels(data1) %in% levels(data2))){ +any.id.levels <- TRUE +same.levels.pos1 <- which(levels(data1) %in% levels(data2)) +same.levels.match1 <- match(levels(data1), levels(data2)) } +if(any(levels(data2) %in% levels(data1))){ +any.id.levels <- TRUE +same.levels.pos2 <- which(levels(data2) %in% levels(data1)) +same.levels.match2 <- match(levels(data2), levels(data1)) } -if(print.count.loop == print.count){ -print.count.loop <- 0 -tempo.time <- as.numeric(Sys.time()) -tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) -final.loop <- (tempo.time - ini.time) / count * ifelse(parall == FALSE, total.comp.nb, base::length(x)) # expected duration in seconds # intra nb.compar loop lapse: time lapse / cycles done * cycles remaining -final.exp <- as.POSIXct(final.loop, origin = ini.date) -cat(paste0(ifelse(parall == FALSE, "\n", paste0("\nIN PROCESS ", process.id, " | ")), "LOOP ", format(count, big.mark=","), " / ", format(ifelse(parall == FALSE, total.comp.nb, base::length(x)), big.mark=","), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) +if(any.id.levels == TRUE){ +common.levels <- unique(c(levels(data1)[same.levels.pos1], levels(data2)[same.levels.pos2])) } -if(count == ifelse(parall == FALSE, total.comp.nb, base::length(x))){ -tempo.time <- as.numeric(Sys.time()) -tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) -cat(paste0(ifelse(parall == FALSE, "\nLOOP PROCESS ENDED | ", paste0("\nPROCESS ", process.id, " ENDED | ")), "LOOP ", format(count, big.mark=","), " / ", format(ifelse(parall == FALSE, total.comp.nb, base::length(x)), big.mark=","), " | TIME SPENT: ", tempo.lapse, "\n\n")) } -', -end.loop.string - ) - # end creation of the txt instruction that includes several loops - if(parall == TRUE){ - # list of i numbers that will be split - i.list <- vector("list", base::length(val)) # positions to split in parallel jobs - for(i2 in 1:base::length(arg)){ - if(i2 == 1L){ - tempo.divisor <- total.comp.nb / base::length(val[[i2]]) - i.list[[i2]] <- rep(1:base::length(val[[i2]]), each = as.integer(tempo.divisor)) - tempo.multi <- base::length(val[[i2]]) - }else{ - tempo.divisor <- tempo.divisor / base::length(val[[i2]]) - i.list[[i2]] <- rep(rep(1:base::length(val[[i2]]), each = as.integer(tempo.divisor)), time = as.integer(tempo.multi)) - tempo.multi <- tempo.multi * base::length(val[[i2]]) - } - } - # end list of i numbers that will be split - tempo.cat <- paste0("PARALLELIZATION INITIATED AT: ", ini.date) - cat(paste0("\n", tempo.cat, "\n")) - tempo.thread.nb = parallel::detectCores(all.tests = FALSE, logical = TRUE) # detect the number of threads - if(tempo.thread.nb < thread.nb){ - thread.nb <- tempo.thread.nb - } - tempo.cat <- paste0("NUMBER OF THREADS USED: ", thread.nb) - cat(paste0("\n ", tempo.cat, "\n")) - Clust <- parallel::makeCluster(thread.nb, outfile = paste0(res.path, "/fun_test_parall_log.txt")) # outfile to print or cat during parallelization (only possible in a file, outfile = "" do not work on windows) - tempo.cat <- paste0("SPLIT OF TEST NUMBERS IN PARALLELISATION:") - cat(paste0("\n ", tempo.cat, "\n")) - cluster.list <- parallel::clusterSplit(Clust, 1:total.comp.nb) # split according to the number of cluster - str(cluster.list) # using print(str()) add a NULL below the result - cat("\n") - paral.output.list <- parallel::clusterApply( # paral.output.list is a list made of thread.nb compartments, each made of n / thread.nb (mat theo column number) compartment. Each compartment receive the corresponding results of fun_permut(), i.e., data (permuted mat1.perm), warning message, cor (final correlation) and count (number of permutations) - cl = Clust, - x = cluster.list, - function.name = function.name, - instruction = instruction, - thread.nb = thread.nb, - print.count = print.count, - total.comp.nb = total.comp.nb, - sp.plot.fun = sp.plot.fun, - i.list = i.list, - fun.tested = fun, - arg.values = arg.values, - fun.test = fun.test, - fun.test2 = fun.test2, - kind = kind, - problem = problem, - res = res, - count = count, - plot.count = plot.count, - data = data, - code = code, - plot.fun = plot.fun, - res.path = res.path, - lib.path = lib.path, - cute.path = cute.path, - fun = function( - x, - function.name, - instruction, - thread.nb, - print.count, - total.comp.nb, - sp.plot.fun, - i.list, - fun.tested, - arg.values, - fun.test, - fun.test2, - kind, - problem, - res, - count, - plot.count, - data, - code, - plot.fun, - res.path, - lib.path, - cute.path - ){ - # check again: very important because another R - process.id <- Sys.getpid() - cat(paste0("\nPROCESS ID ", process.id, " -> TESTS ", x[1], " TO ", x[base::length(x)], "\n")) - source(cute.path, local = .GlobalEnv) - fun_pack(req.package = "lubridate", lib.path = lib.path, load = TRUE) # load = TRUE to be sure that functions are present in the environment. And this prevent to use R.lib.path argument of fun_python_pack() - # end check again: very important because another R - # plot management - if(plot.fun == TRUE){ - pdf(file = paste0(res.path, "/plots_from_fun_test_", x[1], ifelse(base::length(x) == 1L, ".pdf", paste0("-", x[base::length(x)], ".pdf")))) - }else{ - pdf(file = NULL) # send plots into a NULL file, no pdf file created - } - window.nb <- dev.cur() - invisible(dev.set(window.nb)) - # end plot management - # new environment - ini.date <- Sys.time() - ini.time <- as.numeric(ini.date) # time of process begin, converted into - env.name <- paste0("env", ini.time) - if(exists(env.name, where = -1)){ # verify if still ok when fun_test() is inside a function - tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - assign(env.name, new.env()) - assign("val", val, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # var replaced by val - } - # end new environment - print.count.loop <- 0 - suppressMessages(suppressWarnings(eval(parse(text = code)))) - colnames(data) <- arg - if( ! is.null(expect.error)){ - data <- data.frame(data, kind = kind, problem = problem, expected.error = expected.error, message = res, stringsAsFactors = FALSE) - }else{ - data <- data.frame(data, kind = kind, problem = problem, message = res, stringsAsFactors = FALSE) - } - row.names(data) <- paste0("test_", sprintf(paste0("%0", nchar(total.comp.nb), "d"), x)) - sys.info <- sessionInfo() - sys.info$loadedOnly <- sys.info$loadedOnly[order(names(sys.info$loadedOnly))] # sort the packages - invisible(dev.off(window.nb)) - rm(env.name) # optional, because should disappear at the end of the function execution - # output - output <- list(fun = fun, instruction = instruction, sys.info = sys.info) # data = data finally removed from the output list, because everything combined in a RData file at the end - save(output, file = paste0(res.path, "/fun_test_", x[1], ifelse(base::length(x) == 1L, ".RData", paste0("-", x[base::length(x)], ".RData")))) - if(plot.fun == TRUE & plot.count == 0L){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN PROCESS ", process.id, ": NO PDF PLOT BECAUSE ONLY ERRORS REPORTED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - file.remove(paste0(res.path, "/plots_from_fun_test_", x[1], ifelse(base::length(x) == 1L, ".pdf", paste0("-", x[base::length(x)], ".pdf")))) - } - table.out <- as.matrix(data) - # table.out[table.out == ""] <- " " # does not work # because otherwise read.table() converts "" into NA - table.out <- gsub(table.out, pattern = "\n", replacement = " ") - write.table(table.out, file = paste0(res.path, "/table_from_fun_test_", x[1], ifelse(base::length(x) == 1L, ".txt", paste0("-", x[base::length(x)], ".txt"))), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") - } - ) - parallel::stopCluster(Clust) - # files assembly - if(base::length(cluster.list) > 1){ - for(i2 in 1:base::length(cluster.list)){ - tempo.file <- paste0(res.path, "/table_from_fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".txt", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".txt"))) # txt file - tempo <- read.table(file = tempo.file, header = TRUE, stringsAsFactors = FALSE, sep = "\t", row.names = 1, comment.char = "", colClasses = "character") # row.names = 1 (1st column) because now read.table() adds a NA in the header if the header starts by a tabulation, comment.char = "" because colors with #, colClasses = "character" otherwise convert "" (from NULL) into NA - if(file.exists(paste0(res.path, "/plots_from_fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".pdf", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".pdf"))))){ - tempo.pdf <- paste0(res.path, "/plots_from_fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".pdf", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".pdf"))) # pdf file - }else{ - tempo.pdf <- NULL - } - tempo.rdata <- paste0(res.path, "/fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".RData", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".RData"))) # RData file - if(i2 == 1L){ - final.file <- tempo - final.pdf <- tempo.pdf - # new env for RData combining - env.name <- paste0("env", ini.time) - if(exists(env.name, where = -1)){ # verify if still ok when fun_test() is inside a function - tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - # end new env for RData combining - }else{ - assign(env.name, new.env()) - load(tempo.rdata, envir = get(env.name)) - tempo.rdata1 <- tempo.rdata - assign("final.output", get("output", envir = get(env.name)), envir = get(env.name)) - } - }else{ - final.file <- rbind(final.file, tempo, stringsAsFactors = TRUE) - final.pdf <- c(final.pdf, tempo.pdf) - load(tempo.rdata, envir = get(env.name)) - if( ! identical(get("final.output", envir = get(env.name))[c("R.version", "locale", "platform")], get("output", envir = get(env.name))[c("R.version", "locale", "platform")])){ - tempo.cat <- paste0("ERROR IN ", function.name, ": DIFFERENCE BETWEEN OUTPUTS WHILE THEY SHOULD BE IDENTICAL\nPLEASE CHECK\n", tempo.rdata1, "\n", tempo.rdata) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - # add the differences in RData $sysinfo into final.output - tempo.base1 <- sort(get("final.output", envir = get(env.name))$sys.info$basePkgs) - tempo.base2 <- sort(get("output", envir = get(env.name))$sys.info$basePkgs) - tempo.other1 <- names(get("final.output", envir = get(env.name))$sys.info$otherPkgs) - tempo.other2 <- names(get("output", envir = get(env.name))$sys.info$otherPkgs) - tempo.loaded1 <- names(get("final.output", envir = get(env.name))$sys.info$loadedOnly) - tempo.loaded2 <- names(get("output", envir = get(env.name))$sys.info$loadedOnly) - assign("final.output", { - x <- get("final.output", envir = get(env.name)) - y <- get("output", envir = get(env.name)) - x$sys.info$basePkgs <- sort(unique(tempo.base1, tempo.base2)) - if( ! all(tempo.other2 %in% tempo.other1)){ - x$sys.info$otherPkgs <- c(x$sys.info$otherPkgs, y$sys.info$otherPkgs[ ! (tempo.other2 %in% tempo.other1)]) - x$sys.info$otherPkgs <- x$sys.info$otherPkgs[order(names(x$sys.info$otherPkgs))] - } - if( ! all(tempo.loaded2 %in% tempo.loaded1)){ - x$sys.info$loadedOnly <- c(x$sys.info$loadedOnly, y$sys.info$loadedOnly[ ! (tempo.loaded2 %in% tempo.loaded1)]) - x$sys.info$loadedOnly <- x$sys.info$loadedOnly[order(names(x$sys.info$loadedOnly))] - } - x - }, envir = get(env.name)) - # add the differences in RData $sysinfo into final.output - } - } - file.remove(c(tempo.file, tempo.rdata)) - } - # combine pdf and save - if( ! is.null(final.pdf)){ - pdftools::pdf_combine( - input = final.pdf, - output = paste0(res.path, "/plots_from_fun_test_1-", total.comp.nb, ".pdf") - ) - file.remove(final.pdf) - } - # end combine pdf and save - # save RData - assign("output", c(get("final.output", envir = get(env.name)), data = list(final.file)), envir = get(env.name)) - save(output, file = paste0(res.path, "/fun_test__1-", total.comp.nb, ".RData"), envir = get(env.name)) - rm(env.name) # optional, because should disappear at the end of the function execution - # end save RData - # save txt - write.table(final.file, file = paste0(res.path, "/table_from_fun_test_1-", total.comp.nb, ".txt"), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") - # end save txt - if( ! is.null(expect.error)){ - final.file <- final.file[ ! final.file$problem == final.file$expected.error, ] - if(nrow(final.file) == 0L){ - cat(paste0("NO DISCREPANCY BETWEEN EXPECTED AND OBSERVED ERRORS\n\n")) - }else{ - cat(paste0("DISCREPANCIES BETWEEN EXPECTED AND OBSERVED ERRORS (SEE THE discrepancy_table_from_fun_test_1-", total.comp.nb, ".txt FILE)\n\n")) - write.table(final.file, file = paste0(res.path, "/discrepancy_table_from_fun_test_1-", total.comp.nb, ".txt"), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") - } - } - } - # end files assembly - }else{ - # plot management - if(plot.fun == TRUE){ - pdf(file = paste0(res.path, "/plots_from_fun_test_1", ifelse(total.comp.nb == 1L, ".pdf", paste0("-", total.comp.nb, ".pdf")))) - }else{ - pdf(file = NULL) # send plots into a NULL file, no pdf file created - } - window.nb <- dev.cur() - invisible(dev.set(window.nb)) - # end plot management - # new environment - env.name <- paste0("env", ini.time) - if(exists(env.name, where = -1)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - assign(env.name, new.env()) - assign("val", val, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # var replaced by val - } - # end new environment - suppressMessages(suppressWarnings(eval(parse(text = code)))) - colnames(data) <- arg - expect.data <- data.frame() - if( ! is.null(expect.error)){ - data <- data.frame(data, kind = kind, problem = problem, expected.error = expected.error, message = res, stringsAsFactors = FALSE) - }else{ - data <- data.frame(data, kind = kind, problem = problem, message = res, stringsAsFactors = FALSE) - } - row.names(data) <- paste0("test_", sprintf(paste0("%0", nchar(total.comp.nb), "d"), 1:total.comp.nb)) - sys.info <- sessionInfo() - sys.info$loadedOnly <- sys.info$loadedOnly[order(names(sys.info$loadedOnly))] # sort the packages - invisible(dev.off(window.nb)) - rm(env.name) # optional, because should disappear at the end of the function execution - # output - output <- list(fun = fun, instruction = instruction, sys.info = sys.info, data = data) - if(plot.fun == TRUE & plot.count == 0L){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NO PDF PLOT BECAUSE ONLY ERRORS REPORTED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - file.remove(paste0(res.path, "/plots_from_fun_test_1", ifelse(total.comp.nb == 1L, ".pdf", paste0("-", total.comp.nb, ".pdf")))) - } - if( ! is.null(expect.error)){ - expect.data <- output$data[ ! output$data$problem == output$data$expected.error, ] - if(nrow(expect.data) == 0L){ - cat(paste0("NO DISCREPANCY BETWEEN EXPECTED AND OBSERVED ERRORS\n\n")) - }else{ - cat(paste0("DISCREPANCIES BETWEEN EXPECTED AND OBSERVED ERRORS (SEE THE ", if(export == TRUE){paste0("discrepancy_table_from_fun_test_1", ifelse(total.comp.nb == 1L, "", paste0("-", total.comp.nb)), ".txt FILE")}else{"$data RESULT"}, ")\n\n")) - if(export == TRUE){ - expect.data <- as.matrix(expect.data) - expect.data <- gsub(expect.data, pattern = "\n", replacement = " ") - write.table(expect.data, file = paste0(res.path, "/discrepancy_table_from_fun_test_1", ifelse(total.comp.nb == 1L, ".txt", paste0("-", total.comp.nb, ".txt"))), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") - } - } - } - if( ! is.null(warn)){ - base::options(warning.length = 8170) - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) - } - on.exit(exp = base::options(warning.length = ini.warning.length), add = TRUE) - if(export == TRUE){ - save(output, file = paste0(res.path, "/fun_test_1", ifelse(total.comp.nb == 1L, ".RData", paste0("-", total.comp.nb, ".RData")))) - table.out <- as.matrix(output$data) - table.out <- gsub(table.out, pattern = "\n", replacement = " ") - write.table(table.out, file = paste0(res.path, "/table_from_fun_test_1", ifelse(total.comp.nb == 1L, ".txt", paste0("-", total.comp.nb, ".txt"))), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") - }else{ - return(output) - } - } - # after return() ? - end.date <- Sys.time() - end.time <- as.numeric(end.date) - total.lapse <- round(lubridate::seconds_to_period(end.time - ini.time)) - cat(paste0("fun_test JOB END\n\nTIME: ", end.date, "\n\nTOTAL TIME LAPSE: ", total.lapse, "\n\n\n")) +if(any(class(data1) %in% "factor")){ # to compare content +data1 <- as.character(data1) } - - -################ Object modification - - -######## fun_name_change() #### check a vector of character strings and modify any string if present in another vector - - -fun_name_change <- function(data1, data2, added.string = "_modif"){ - # AIM - # this function allow to check if a vector of character strings, like column names of a data frame, has elements present in another vector (vector of reserved words or column names of another data frame before merging) - # ARGUMENTS - # data1: vector of character strings to check and modify - # data2: reference vector of character strings - # added.string: string added at the end of the modified string in data1 if present in data2 - # RETURN - # a list containing - # $data: the modified data1 (in the same order as in the initial data1) - # $ini: the initial elements before modification. NULL if no modification - # $post: the modified elements in the same order as in ini. NULL if no modification - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # obs1 <- c("A", "B", "C", "D") ; obs2 <- c("A", "C") ; fun_name_change(obs1, obs2) - # obs1 <- c("A", "B", "C", "C_modif1", "D") ; obs2 <- c("A", "A_modif1", "C") ; fun_name_change(obs1, obs2) # the function checks that the new names are neither in obs1 nor in obs2 (increment the number after the added string) - # DEBUGGING - # data1 = c("A", "B", "C", "D") ; data2 <- c("A", "C") ; added.string = "_modif" # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data1, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = data2, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = added.string, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - ini <- NULL - post <- NULL - if(any(data1 %in% data2)){ - tempo.names <- data1[data1 %in% data2] - ini <- NULL - post <- NULL - for(i2 in 1:length(tempo.names)){ - count <- 0 - tempo <- tempo.names[i2] - while(any(tempo %in% data2) | any(tempo %in% data1)){ - count <- count + 1 - tempo <- paste0(tempo.names[i2], "_modif", count) - } - data1[data1 %in% tempo.names[i2]] <- paste0(tempo.names[i2], "_modif", count) - if(count != 0){ - ini <- c(ini, tempo.names[i2]) - post <- c(post, paste0(tempo.names[i2], "_modif", count)) - } - } - data <- data1 - }else{ - data <- data1 - } - output <- list(data = data, ini = ini, post = post) - return(output) +if(any(class(data2) %in% "factor")){ # to compare content +data2 <- as.character(data2) } - - -######## fun_df_remod() #### remodeling a data frame to have column name as a qualitative values and vice-versa - - -fun_df_remod <- function( - data, - quanti.col.name = "quanti", - quali.col.name = "quali" -){ - # AIM - # if the data frame is made of n numeric columns, a new data frame is created, with the 1st column gathering all the numeric values, and the 2nd column being the name of the columns of the initial data frame. If row names were present in the initial data frame, then a new ini_rowname column is added with the names of the rows - - - # If the data frame is made of one numeric column and one character or factor column, a new data frame is created, with the new columns corresponding to the split numeric values (according to the character column). NA are added a the end of each column to have the same number of rows. BEWARE: in such data frame, rows are not individuals. This means that in the example below, values 10 and 20 are associated on the same row but that means nothing in term of association - - - - # ARGUMENTS - # data: data frame to convert - # quanti.col.name: optional name for the quanti column of the new data frame - # quali.col.name: optional name for the quali column of the new data frame - # RETURN - # the modified data frame - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # obs <- data.frame(col1 = (1:4)*10, col2 = c("A", "B", "A", "A"), stringsAsFactors = TRUE) ; obs ; fun_df_remod(obs) - # obs <- data.frame(col1 = (1:4)*10, col2 = 5:8, stringsAsFactors = TRUE) ; obs ; fun_df_remod(obs, quanti.col.name = "quanti", quali.col.name = "quali") - # obs <- data.frame(col1 = (1:4)*10, col2 = 5:8, stringsAsFactors = TRUE) ; rownames(obs) <- paste0("row", 1:4) ; obs ; fun_df_remod(obs, quanti.col.name = "quanti", quali.col.name = "quali") - # DEBUGGING - # data = data.frame(a = 1:3, b = 4:6, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging - # data = data.frame(a = 1:3, b = 4:6, c = 11:13, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging - # data = data.frame(a = 1:3, b = letters[1:3], stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging - # data = data.frame(a = 1:3, b = letters[1:3], stringsAsFactors = TRUE) ; quanti.col.name = "TEST" ; quali.col.name = "quali" # for function debugging - # data = data.frame(b = letters[1:3], a = 1:3, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging - # data = data.frame(b = c("e", "e", "h"), a = 1:3, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - # argument checking without fun_check() - if( ! any(class(data) %in% "data.frame")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data ARGUMENT MUST BE A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end argument checking without fun_check() - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = quanti.col.name, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = quali.col.name, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - tempo.factor <- unlist(lapply(data, class)) - for(i in 1:length(tempo.factor)){ # convert factor columns as character - if(all(tempo.factor[i] == "factor")){ - data[, i] <- as.character(data[, i]) - } - } - tempo.factor <- unlist(lapply(data, mode)) - if(length(data) == 2L){ - if( ! ((base::mode(data[, 1]) == "character" & base::mode(data[, 2]) == "numeric") | base::mode(data[, 2]) == "character" & base::mode(data[, 1]) == "numeric" | base::mode(data[, 2]) == "numeric" & base::mode(data[, 1]) == "numeric") ){ - tempo.cat <- paste0("ERROR IN ", function.name, ": IF data ARGUMENT IS A DATA FRAME MADE OF 2 COLUMNS, EITHER A COLUMN MUST BE NUMERIC AND THE OTHER CHARACTER, OR THE TWO COLUMNS MUST BE NUMERIC") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if((base::mode(data[, 1]) == "character" | base::mode(data[, 2]) == "character") & (quanti.col.name != "quanti" | quali.col.name != "quali")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": IMPROPER quanti.col.name OR quali.col.name RESETTINGS. THESE ARGUMENTS ARE RESERVED FOR DATA FRAMES MADE OF n NUMERIC COLUMNS ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else{ - if( ! all(tempo.factor %in% "numeric")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": IF data ARGUMENT IS A DATA FRAME MADE OF ONE COLUMN, OR MORE THAN 2 COLUMNS, THESE COLUMNS MUST BE NUMERIC") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(( ! any(tempo.factor %in% "character")) & is.null(names(data))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": NUMERIC DATA FRAME in the data ARGUMENT MUST HAVE COLUMN NAMES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(all(tempo.factor %in% "numeric")){ # transfo 1 - quanti <- NULL - for(i in 1:length(data)){ - quanti <-c(quanti, data[, i]) - } - quali <- rep(names(data), each = nrow(data)) - output.data <- data.frame(quanti, quali, stringsAsFactors = TRUE, check.names = FALSE) - names(output.data) <- c(quanti.col.name, quali.col.name) - # add the ini_rowname column - ini.rownames <- rownames(data) - tempo.data <- data - rownames(tempo.data) <- NULL - null.rownames <- (tempo.data) - if( ! identical(ini.rownames, null.rownames)){ - ini_rowname <- rep(ini.rownames, times = ncol(data)) - output.data <- cbind(output.data, ini_rowname, stringsAsFactors = TRUE) - } - }else{ # transfo 2 - if(class(data[, 1]) == "character"){ - data <- cbind(data[2], data[1], stringsAsFactors = TRUE) - } - nc.max <- max(table(data[, 2])) # effectif maximum des classes - nb.na <- nc.max - table(data[,2]) # nombre de NA à ajouter pour réaliser la data frame - tempo<-split(data[, 1], data[, 2]) - for(i in 1:length(tempo)){tempo[[i]] <- append(tempo[[i]], rep(NA, nb.na[i]))} # des NA doivent être ajoutés lorsque les effectifs sont différents entre les classes. C'est uniquement pour que chaque colonne ait le même nombre de lignes - output.data<-data.frame(tempo, stringsAsFactors = TRUE, check.names = FALSE) - } - return(output.data) +if( ! (is.null(names(data1)) & is.null(names(data2)))){ +if(identical(names(data1), names(data2))){ +same.names <- TRUE +name <- names(data1) +}else{ +same.names <- FALSE } - - - - -######## fun_round() #### rounding number if decimal present - - -fun_round <- function(data, dec.nb = 2, after.lead.zero = TRUE){ - # AIM - # round a vector of values, if decimal, with the desired number of decimal digits after the decimal leading zeros - # WARNINGS - # Work well with numbers as character strings, but not always with numerical numbers because of the floating point - # Numeric values are really truncated from a part of their decimal digits, whatever options(digits) settings - # See ?.Machine or https://stackoverflow.com/questions/5173692/how-to-return-number-of-decimal-places-in-r, with the interexting formula: abs(x - round(x)) > .Machine$double.eps^0.5 - # ARGUMENTS - # data: a vector of numbers (numeric or character mode) - # dec.nb: number of required decimal digits - # after.lead.zero: logical. If FALSE, rounding is performed for all the decimal numbers, whatever the leading zeros (e.g., 0.123 -> 0.12 and 0.00128 -> 0.00). If TRUE, dec.nb are taken after the leading zeros (e.g., 0.123 -> 0.12 and 0.00128 -> 0.0013) - # RETURN - # the modified vector - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, 10, 100.001, 333.0001254, 12312.1235), dec.nb = 2, after.lead.zero = FALSE), "\n\n") ; options(digits = ini.options) - # ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, 10, 100.001, 333.0001254, 12312.1235), dec.nb = 2, after.lead.zero = TRUE), "\n\n") ; options(digits = ini.options) - # ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, "10", "100.001", "333.0001254", "12312.1235"), dec.nb = 2, after.lead.zero = FALSE), "\n\n") ; options(digits = ini.options) - # ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, "10", "100.001", "333.0001254", "12312.1235"), dec.nb = 2, after.lead.zero = TRUE), "\n\n") ; options(digits = ini.options) - # DEBUGGING - # data = data = c(10, 100.001, 333.0001254, 12312.1235) ; dec.nb = 2 ; after.lead.zero = FALSE # # for function debugging - # data = data = c("10", "100.001", "333.0001254", "12312.1235") ; dec.nb = 2 ; after.lead.zero = TRUE # # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - # argument checking without fun_check() - if( ! (all(typeof(data) == "character") | all(typeof(data) == "double") | all(typeof(data) == "integer"))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A VECTOR OF NUMBERS (IN NUMERIC OR CHARACTER MODE)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end argument checking without fun_check() - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data, class = "vector", na.contain = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = dec.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = after.lead.zero, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - tempo <- grepl(x = data, pattern = "\\.") # detection of decimal numbers - ini.mode <- base::mode(data) - data <- as.character(data) # to really truncate decimal digits - for(i in 1:length(data)){ # scan all the numbers of the vector - if(tempo[i] == TRUE){ # means decimal number - if(after.lead.zero == TRUE){ - zero.pos <- unlist(gregexpr(text=data[i], pattern = 0)) # recover all the position of the zeros in the number. -1 if no zeros (do not record the leading and trailing zeros) - }else{ - zero.pos <- -1 # -1 as if no zero - } - dot.pos <- unlist(gregexpr(text=data[i], pattern = "\\.")) # recover all the position of the zeros in the number - digit.pos <- unlist(gregexpr(text=data[i], pattern = "[[:digit:]]")) # recover all the position of the digits in the number - dec.pos <- digit.pos[digit.pos > dot.pos] - count <- 0 - while((dot.pos + count + 1) %in% zero.pos & (dot.pos + count + 1) <= max(dec.pos) & (count + dec.nb) < length(dec.pos)){ # count the number of leading zeros in the decimal part - count <- count + 1 - } - data[i] <- formatC(as.numeric(data[i]), digits = (count + dec.nb), format = "f") - } - } - if(ini.mode != "character"){ - data <- as.numeric(data) - } - return(data) +if(any(names(data1) %in% names(data2))){ +any.id.name <- TRUE +same.names.pos1 <- which(names(data1) %in% names(data2)) +same.names.match1 <- match(names(data1), names(data2)) } - - -######## fun_mat_rotate() #### 90° clockwise matrix rotation - - -fun_mat_rotate <- function(data){ - # AIM - # 90° clockwise matrix rotation - # applied twice, the function provide the mirror matrix, according to vertical and horizontal symmetry - # ARGUMENTS - # data: matrix (matrix class) - # RETURN - # the modified matrix - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # obs <- matrix(1:10, ncol = 1) ; obs ; fun_mat_rotate(obs) - # obs <- matrix(LETTERS[1:10], ncol = 5) ; obs ; fun_mat_rotate(obs) - # DEBUGGING - # data = matrix(1:10, ncol = 1) - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data, class = "matrix", fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - for (i in 1:ncol(data)){data[,i] <- rev(data[,i])} - data <- t(data) - return(data) +if(any(names(data2) %in% names(data1))){ +any.id.name <- TRUE +same.names.pos2 <- which(names(data2) %in% names(data1)) +same.names.match2 <- match(names(data2), names(data1)) } - - -######## fun_mat_num2color() #### convert a numeric matrix into hexadecimal color matrix - - -fun_mat_num2color <- function( - mat1, - mat.hsv.h = TRUE, - notch = 1, - s = 1, - v = 1, - forced.color = NULL -){ - # AIM - # convert a matrix made of numbers into a hexadecimal matrix for rgb colorization - # ARGUMENTS: - # mat1: matrix 1 of non negative numerical values that has to be colored (matrix class). NA allowed - # mat.hsv.h: logical. Is mat1 the h of hsv colors ? (if TRUE, mat1 must be between zero and 1). If FALSE, mat1 must be made of positive integer values without 0 - # notch: single value between 0 and 1 to shift the successive colors on the hsv circle by + notch - # s: s argument of hsv(). Must be between 0 and 1 - # v: v argument of hsv(). Must be between 0 and 1 - # forced.color: Must be NULL or hexadecimal color code or name given by colors(). The first minimal values of mat1 will be these colors. All the color of mat1 can be forced using this argument - # RETURN - # a list containing: - # $mat1.name: name of mat1 - # $colored.mat: colors of mat1 in hexa - # $problem: logical. Is any colors of forced.color overlap the colors designed by the function. NULL if forced.color = NULL - # $text.problem: text when overlapping colors. NULL if forced.color = NULL or problem == FALSE - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # mat1 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; dimnames(mat1) <- list(LETTERS[1:4], letters[1:2]) ; fun_mat_num2color(mat1, mat.hsv.h = FALSE, notch = 1, s = 1, v = 1, forced.color = NULL) - # DEBUGGING - # mat1 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; dimnames(mat1) <- list(LETTERS[1:4], letters[1:2]); mat.hsv.h = FALSE ; notch = 1 ; s = 1 ; v = 1 ; forced.color = c(hsv(1,1,1), hsv(0,0,0)) # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = mat1, mode = "numeric", class = "matrix", na.contain = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = mat.hsv.h, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = notch, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = s, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = v, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # argument checking without fun_check() - if(mat.hsv.h == TRUE & fun_check(data = mat1, mode = "numeric", prop = TRUE)$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat1 ARGUMENT MUST BE A MATRIX OF PROPORTIONS SINCE THE mat.hsv.h ARGUMENT IS SET TO TRUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(forced.color)){ - tempo <- fun_check(data = forced.color, class = "character") - if(any(tempo$problem == TRUE)){ - paste0("\n\n================\n\n", paste(tempo$text[tempo$problem], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! all(forced.color %in% colors() | grepl(pattern = "^#", forced.color))){ # check that all strings of forced.color start by # - tempo.cat <- paste0("ERROR IN ", function.name, ": forced.color ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end argument checking without fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - problem <- NULL - text.problem <- NULL - mat1.name <- deparse(substitute(mat1)) - # change the scale of the plotted matrix - if(mat.hsv.h == TRUE){ - if(any(min(mat1, na.rm = TRUE) < 0 | max(mat1, na.rm = TRUE) > 1, na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat1 MUST BE MADE OF VALUES BETWEEN 0 AND 1 BECAUSE mat.hsv.h ARGUMENT SET TO TRUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else{ - if(any(mat1 - floor(mat1) > 0, na.rm = TRUE) | any(mat1 == 0L, na.rm = TRUE)){ # no need of isTRUE(all.equal()) because we do not require approx here but strictly 0, thus == is ok - tempo.cat <- paste0("ERROR IN ", function.name, ": mat1 MUST BE MADE OF INTEGER VALUES WITHOUT 0 BECAUSE mat.hsv.h ARGUMENT SET TO FALSE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - mat1 <- mat1 / max(mat1, na.rm = TRUE) - } - } - if(notch != 1){ - different.color <- unique(as.vector(mat1)) - different.color <- different.color[ ! is.na(different.color)] - tempo.different.color <- different.color + c(0, cumsum(rep(notch, length(different.color) - 1))) - tempo.different.color <- tempo.different.color - floor(tempo.different.color) - if(any(duplicated(tempo.different.color) == TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": DUPLICATED VALUES AFTER USING notch (", paste(tempo.different.color[duplicated(tempo.different.color)], collapse = " "), "). TRY ANOTHER notch VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(length(different.color) != length(tempo.different.color)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": LENGTH OF different.color (", paste(different.color, collapse = " "), ") DIFFERENT FROM LENGTH OF tempo.different.color (", paste(tempo.different.color, collapse = " "), ")") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - for(i in 1:length(different.color)){ - mat1[mat1 == different.color[i]] <- tempo.different.color[i] # no need of isTRUE(all.equal()) because different.color comes from mat1 - } - } - } - if( ! is.null(forced.color)){ - hexa.values.to.change <- hsv(unique(sort(mat1))[1:length(forced.color)], s, v) - } - mat1[ ! is.na(mat1)] <- hsv(mat1[ ! is.na(mat1)], s, v) - if( ! is.null(forced.color)){ - if(any(forced.color %in% mat1, na.rm = TRUE)){ - problem <- TRUE - text.problem <- paste0("THE FOLLOWING COLORS WHERE INTRODUCED USING forced.color BUT WHERE ALREADY PRESENT IN THE COLORED MATRIX :", paste(forced.color[forced.color %in% mat1], collapse = " ")) - }else{ - problem <- FALSE - } - for(i in 1:length(hexa.values.to.change)){ - if( ! any(mat1 == hexa.values.to.change[i], na.rm = TRUE)){# no need of isTRUE(all.equal()) because character - tempo.cat <- paste0("ERROR IN ", function.name, ": THE ", hexa.values.to.change[i], " VALUE FROM hexa.values.to.change IS NOT REPRESENTED IN mat1 : ", paste(unique(as.vector(mat1)), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - mat1[which(mat1 == hexa.values.to.change[i])] <- forced.color[i] # no need of isTRUE(all.equal()) because character - } - } - } - output <- list(mat1.name = mat1.name, colored.mat = mat1, problem = problem, text.problem = text.problem) - return(output) +if(any.id.name == TRUE){ +common.names <- unique(c(names(data1)[same.names.pos1], names(data2)[same.names.pos2])) } - - -######## fun_mat_op() #### assemble several matrices with operation - - -fun_mat_op <- function(mat.list, kind.of.operation = "+"){ - # AIM - # assemble several matrices of same dimensions by performing by case operation. For instance add the value of all the case 1 (row1 & column1) of the matrices and put it in the case 1 of a new matrix M, add the value of all the case 2 (row2 & column1) of the matrices and put it in the case 2 of a new matrix M, etc. - - # c: case - # i: row number - # j: column number - # k: matrix number - # z: number of matrices - # ARGUMENTS: - # mat.list: list of matrices - # kind.of.operation: either "+" (by case addition), "-" (by case subtraction) or "*" (by case multiplication) - # RETURN - # the assembled matrix, with row and/or column names only if all the matrices have identical row/column names - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_comp_2d() - # EXAMPLES - # mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; fun_mat_op(mat.list = list(mat1, mat2), kind.of.operation = "+") - # mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; fun_mat_op(mat.list = list(mat1, mat2), kind.of.operation = "*") - # mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(LETTERS[1:4], c(NA, NA))) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; fun_mat_op(mat.list = list(mat1, mat2), kind.of.operation = "-") - # mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(c("A1", "A2", "A3", "A4"), letters[1:2])) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; mat3 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; fun_mat_op(mat.list = list(mat1, mat2, mat3), kind.of.operation = "+") - # DEBUGGING - # mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; mat.list = list(mat1, mat2) ; kind.of.operation = "+" # for function debugging - # mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(LETTERS[1:4], c(NA, NA))) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; mat.list = list(mat1, mat2) ; kind.of.operation = "*" # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_comp_2d() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = mat.list, class = "list", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = kind.of.operation, options = c("+", "-", "*"), length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # argument checking without fun_check() - if(length(mat.list) < 2){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat.list ARGUMENT MUST BE A LIST CONTAINING AT LEAST 2 MATRICES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - for(i1 in 1:length(mat.list)){ - tempo <- fun_check(data = mat.list[[i1]], class = "matrix", mode = "numeric", na.contain = TRUE) - if(tempo$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ELEMENT ", i1, " OF mat.list ARGUMENT MUST BE A NUMERIC MATRIX") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - ident.row.names <- TRUE - ident.col.names <- TRUE - for(i1 in 2:length(mat.list)){ - tempo <- fun_comp_2d(data1 = mat.list[[1]], data2 = mat.list[[i1]]) - if(tempo$same.dim == FALSE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": MATRIX ", i1, " OF mat.list ARGUMENT MUST HAVE THE SAME DIMENSION (", paste(dim(mat.list[[i1]]), collapse = " "), ") THAN THE MATRIX 1 IN mat.list (", paste(dim(mat.list[[1]]), collapse = " "), ")") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(tempo$same.row.name)){ - if(tempo$same.row.name != TRUE){ # != TRUE to deal with NA - ident.row.names <- FALSE - } - } - if( ! is.null(tempo$same.col.name)){ - if(tempo$same.col.name != TRUE){ # != TRUE to deal with NA - ident.col.names <- FALSE - } - } - } - # end argument checking without fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - output <- mat.list[[1]] - for(i1 in 2:length(mat.list)){ - output <- get(kind.of.operation)(output, mat.list[[i1]]) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - } - dimnames(output) <- NULL - if(ident.row.names == TRUE){ - rownames(output) <- rownames(mat.list[[1]]) - } - if(ident.col.names == TRUE){ - colnames(output) <- colnames(mat.list[[1]]) - } - return(output) } - - -######## fun_mat_inv() #### return the inverse of a square matrix - - -fun_mat_inv <- function(mat){ - # AIM - # return the inverse of a square matrix when solve() cannot - # ARGUMENTS: - # mat: a square numeric matrix without NULL, NA, Inf or single case (dimension 1, 1) of 0 - # RETURN - # the inversed matrix - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # mat1 = matrix(c(1,1,1,2,1,5,9,8,9), ncol = 3) ; fun_mat_inv(mat = mat1) # use solve() - # mat1 = matrix(c(0,0,0,0,0,0,0,0,0), ncol = 3) ; fun_mat_inv(mat = mat1) # use the trick - # mat1 = matrix(c(1,1,1,2,Inf,5,9,8,9), ncol = 3) ; fun_mat_inv(mat = mat1) - # mat1 = matrix(c(1,1,1,2,NA,5,9,8,9), ncol = 3) ; fun_mat_inv(mat = mat1) - # mat1 = matrix(c(1,2), ncol = 1) ; fun_mat_inv(mat = mat1) - # mat1 = matrix(0, ncol = 1) ; fun_mat_inv(mat = mat1) - # mat1 = matrix(2, ncol = 1) ; fun_mat_inv(mat = mat1) - # DEBUGGING - # mat = matrix(c(1,1,1,2,1,5,9,8,9), ncol = 3) # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = mat, class = "matrix", mode = "numeric", fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # argument checking without fun_check() - if(ncol(mat) != nrow(mat)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A SQUARE MATRIX") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(mat %in% c(Inf, -Inf, NA))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A MATRIX WITHOUT Inf, -Inf OR NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(all(mat == 0L) & ncol(mat) == 1L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT CANNOT BE A SQUARE MATRIX MADE OF A SINGLE CASE OF 0") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end argument checking without fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - if(any(grepl(x = try(solve(mat), silent = TRUE)[], pattern = "[Ee]rror"))){ - tempo <- svd(mat) - val.critique <- which(tempo$d < 10^-8) - Diag.mod <- diag(1 / tempo$d) - for(i in val.critique){ - Diag.mod[i, i] <- 0 - } - return(tempo$v %*% Diag.mod %*% t(tempo$u)) - }else{ - return(solve(mat)) - } +names(data1) <- NULL # names solved -> to do not be disturbed by names +names(data2) <- NULL # names solved -> to do not be disturbed by names +if(any(data1 %in% data2)){ +any.id.element <- TRUE +same.elements.pos1 <- which(data1 %in% data2) +same.elements.match1 <- match(data1, data2) } - - -######## fun_mat_fill() #### fill the empty half part of a symmetric square matrix - - -fun_mat_fill <- function(mat, empty.cell.string = 0, warn.print = FALSE){ - # AIM - # detect the empty half part of a symmetric square matrix (either topleft, topright, bottomleft or bottomright) - # fill this empty half part using the other symmetric half part of the matrix - # WARNINGS - # a plot verification using fun_gg_heatmap() is recommanded - # ARGUMENTS: - # mat: a numeric or character square matrix with the half part (according to the grand diagonal) filled with NA (any kind of matrix), "0" (character matrix) or 0 (numeric matrix) exclusively (not a mix of 0 and NA in the empty part) - # empty.cell.string: a numeric, character or NA (no quotes) indicating what empty cells are filled with - # warn.print: logical. Print warnings at the end of the execution? No print if no warning messages - # RETURN - # a list containing: - # $mat: the filled matrix - # $warn: the warning messages. Use cat() for proper display. NULL if no warning - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # mat1 = matrix(c(1,NA,NA,NA, 0,2,NA,NA, NA,3,4,NA, 5,6,7,8), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = NA, warn.print = TRUE) # bottomleft example - # mat1 = matrix(c(1,1,1,2, 0,2,3,0, NA,3,0,0, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = NA, warn.print = TRUE) # error example - # mat1 = matrix(c(1,1,1,2, 0,2,3,0, NA,3,0,0, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = 0, warn.print = TRUE) # bottomright example - # mat1 = matrix(c(1,1,1,2, "a",2,3,NA, "a","a",0,0, "a","a","a",0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = "a", warn.print = TRUE) # topright example - # mat1 = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,NA, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = 0, warn.print = TRUE) # topleft example - # mat1 = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,0, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = 0, warn.print = TRUE) # error example - # DEBUGGING - # mat = matrix(c(1,NA,NA,NA, 0,2,NA,NA, NA,3,4,NA, 5,6,7,8), ncol = 4) ; empty.cell.string = NA ; warn.print = TRUE # for function debugging - # mat = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,NA, 5,0,0,0), ncol = 4) ; empty.cell.string = 0 ; warn.print = TRUE # for function debugging # topleft example - # mat = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,NA, 5,0,0,0), ncol = 4) ; empty.cell.string = NA ; warn.print = TRUE # for function debugging # topleft example - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = mat, class = "matrix", na.contain = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = empty.cell.string, class = "vector", na.contain = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # argument checking without fun_check() - if(ncol(mat) != nrow(mat)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A SQUARE MATRIX") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! (base::mode(mat) %in% c("numeric", "character"))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A NUMERIC OR CHARACTER MATRIX") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(nrow(mat) == 1L & ncol(mat) == 1L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT CANNOT BE A SQUARE MATRIX MADE OF A SINGLE CASE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(ifelse(is.na(empty.cell.string), ! any(is.na(mat)), ! any(mat == empty.cell.string, na.rm = TRUE))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MATRIX MUST HAVE CELLS WITH THE EMPTY STRING SPECIFIED IN empty.cell.string ARGUMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end argument checking without fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - list.diag <- vector("list", length = nrow(mat) - 1) - for(i1 in 1:(nrow(mat) - 1)){ - list.diag[[i1]] <- numeric(length = nrow(mat) - i1) # list made of zero - } - sector <- c("topleft", "topright", "bottomright", "bottomleft") - diag.scan <-c( # same order as sector. Recover each diag from center to corner - "mat[as.matrix(as.data.frame(list(1:(nrow(mat) - i2), (ncol(mat) -i2):1), stringsAsFactors = TRUE))]", # topleft part - "mat[as.matrix(as.data.frame(list(1:(nrow(mat) - i2), (1:ncol(mat))[-(1:i2)]), stringsAsFactors = TRUE))]", # topright part - "mat[as.matrix(as.data.frame(list((1 + i2):nrow(mat), ncol(mat):(1 + i2)), stringsAsFactors = TRUE))]", # bottomright part - "mat[as.matrix(as.data.frame(list((1 + i2):nrow(mat), 1:(ncol(mat) -i2)), stringsAsFactors = TRUE))]" # bottomleft part - ) - # empty part detection - empty.sector <- NULL - full.sector <- NULL - ini.warning.length <- options()$warning.length - options(warning.length = 8170) - warn <- NULL - warn.count <- 0 - for(i1 in 1:length(sector)){ - tempo.list.diag <- list.diag - for(i2 in 1:(nrow(mat) - 1)){ - tempo.list.diag[[i2]] <- eval(parse(text = diag.scan[i1])) - if(ifelse(is.na(empty.cell.string), ! all(is.na(tempo.list.diag[[i2]])), ! (all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = TRUE) & ! (is.na(all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = FALSE)))))){ # I had to add this ! (is.na(all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = FALSE))) because all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = FALSE) gives NA and not FALSE if one NA in tempo.list.diag[[i2]] -> not good for if() - full.sector <- c(full.sector, sector[i1]) - break - } - } - if(i2 == nrow(mat) - 1){ - if(all(unlist(lapply(tempo.list.diag, FUN = function(x){if(is.na(empty.cell.string)){is.na(x)}else{x == empty.cell.string}})), na.rm = TRUE)){ - empty.sector <- c(empty.sector, sector[i1]) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") EMPTY SECTOR DETECTED ON THE ", toupper(sector[i1]), " CORNER, FULL OF ", empty.cell.string) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE ", toupper(sector[i1]), " SECTOR, DETECTED AS EMPTY, IS NOT? DIFFERENT VALUES IN THIS SECTOR:\n", paste(names(table(unlist(tempo.list.diag), useNA = "ifany")), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - } - # end empty part detection - if(length(empty.sector) == 0L){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") ACCORDING TO empty.cell.string ARGUMENT (", empty.cell.string, "), mat ARGUMENT MATRIX HAS ZERO EMPTY HALF PART") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - if(length(empty.sector) > 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ACCORDING TO empty.cell.string ARGUMENT (", empty.cell.string, "), mat ARGUMENT MATRIX HAS MORE THAN ONE EMPTY HALF PART (ACCORDING TO THE GRAND DIAGONAL): ", paste(empty.sector, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(any(full.sector %in% empty.sector, na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE FUNCTION HAS DETECTED EMPTY AND NON EMPTY HALF PART IN THE SAME SECTOR: ", paste(full.sector[full.sector %in% empty.sector], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(length(empty.sector) + length(full.sector)!= 4){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE FUNCTION HAS DETECTED MORE OR LESS SECTORS THAN 4:\nHALF SECTORS:", paste(empty.sector, collapse = " "), "\nFULL SECTORS:", paste(full.sector, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") ", toupper(empty.sector), " SECTOR HAS BEEN COMPLETED TO BECOME SYMMETRICAL") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # matrix filling - for(i2 in 1:(nrow(mat) - 1)){ - if(empty.sector == "topleft"){ - eval(parse(text = paste0(diag.scan[1], " <- ", diag.scan[3]))) - }else if(empty.sector == "topright"){ - eval(parse(text = paste0(diag.scan[2], " <- ", diag.scan[4]))) - }else if(empty.sector == "bottomright"){ - eval(parse(text = paste0(diag.scan[3], " <- ", diag.scan[1]))) - }else if(empty.sector == "bottomleft"){ - eval(parse(text = paste0(diag.scan[4], " <- ", diag.scan[2]))) - } - } - # end matrix filling - } - if(warn.print == TRUE & ! is.null(warn)){ - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) - } - on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) - return(list(mat = mat, warn = warn)) +if(any(data2 %in% data1)){ +any.id.element <- TRUE +same.elements.pos2 <- which(data2 %in% data1) +same.elements.match2 <- match(data2, data1) } - - -######## fun_permut() #### progressively breaks a vector order - - -fun_permut <- function( - data1, - data2 = NULL, - n = NULL, - seed = NULL, - print.count = 10, - text.print = "", - cor.method = "spearman", - cor.limit = 0.2, - warn.print = FALSE, - lib.path = NULL -){ - # AIM - # reorder the elements of the data1 vector by flipping 2 randomly selected consecutive positions either: - # 1) n times (when n is precised) or - # 2) until the correlation between data1 and data2 decreases down to the cor.limit (0.2 by default). See cor.limit below to deal with negative correlations - # Example of consecutive position flipping: ABCD -> BACD -> BADC, etc. - # designed for discrete values, but worls also for continuous values - # WARNINGS - # see # https://www.r-bloggers.com/strategies-to-speedup-r-code/ for code speedup - # the random switch of non consecutive positions (ABCD -> DBCA for instance) does not work very well as the correlation is quickly obtained but the initial vector structure is mainly kept (no much order). Ths code would be: pos <- ini.pos[1:2] ; pos <- sample.int(n = n , size = 2, replace = FALSE) ; tempo.pos[pos] <- tempo.pos[rev(pos)] - # ARGUMENTS - # data1: a vector of at least 2 elements. Must be numeric if data2 is specified - # data2: a numeric vector of same length as data1 - # n: number of times "flipping 2 randomly selected consecutive positions". Ignored if data2 is specified - # seed: integer number used by set.seed(). Write NULL if random result is required, an integer otherwise. BEWARE: if not NULL, fun_permut() will systematically return the same result when the other parameters keep the same settings - # print.count: interger value. Print a working progress message every print.count during loops. BEWARE: can increase substentially the time to complete the process using a small value, like 10 for instance. Use Inf is no loop message desired - # text.print: optional message to add to the working progress message every print.count loop - # cor.method: correlation method. Either "pearson", "kendall" or "spearman". Ignored if data2 is not specified - # cor.limit: a correlation limit (between 0 and 1). Ignored if data2 is not specified. Compute the correlation between data1 and data2, permute the data1 values, and stop the permutation process when the correlation between data1 and data2 decreases down below the cor limit value (0.2 by default). If cor(data1, data2) is negative, then -cor.limit is used and the process stops until the correlation between data1 and data2 increases up over cor.limit (-0.2 by default). BEWARE: write a positive cor.limit even if cor(data1, data2) is known to be negative. The function will automatically uses -cor.limit. If the initial correlation is already below cor.limit (positive correlation) or over -cor.limit (negative correlation), then the data1 value positions are completely randomized (correlation between data1 and data2 is expected to be 0) - # warn.print: logical. Print warnings at the end of the execution? No print if no warning messages - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL - # RETURN - # a list containing: - # $data: the modified vector - # $warn: potential warning messages (in case of negative correlation when data2 is specified). NULL if non warning message - # $cor: a spearman correlation between the initial positions (1:length(data1) and the final positions if data2 is not specified and the final correlation between data1 and data2 otherwise, according to cor.method - # $count: the number of loops used - # REQUIRED PACKAGES - # lubridate - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_pack() - # fun_round() - # EXAMPLES - # example (1) showing that for loop, used in fun_permut(), is faster than while loop - # ini.time <- as.numeric(Sys.time()) ; count <- 0 ; for(i0 in 1:1e9){count <- count + 1} ; tempo.time <- as.numeric(Sys.time()) ; tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) ; tempo.lapse - # example (2) showing that for loop, used in fun_permut(), is faster than while loop - # ini.time <- as.numeric(Sys.time()) ; count <- 0 ; while(count < 1e9){count <- count + 1} ; tempo.time <- as.numeric(Sys.time()) ; tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) ; tempo.lapse - # fun_permut(data1 = LETTERS[1:5], data2 = NULL, n = 100, seed = 1, print.count = 10, text.print = "CPU NB 4") - # fun_permut(data1 = 101:110, data2 = 21:30, seed = 1, print.count = 1e4, text.print = "", cor.method = "spearman", cor.limit = 0.2) - # a way to use the cor.limit argument just considering data1 - # obs1 <- 101:110 ; fun_permut(data1 = obs1, data2 = obs1, seed = 1, print.count = 10, cor.method = "spearman", cor.limit = 0.2) - # fun_permut(data1 = 1:1e3, data2 = 1e3:1, seed = 1, print.count = 1e6, text.print = "", cor.method = "spearman", cor.limit = 0.7) - # fun_permut(data1 = 1:1e2, data2 = 1e2:1, seed = 1, print.count = 1e3, cor.limit = 0.5) - # fun_permut(data1 = c(0,0,0,0,0), n = 5, data2 = NULL, seed = 1, print.count = 1e3, cor.limit = 0.5) - # DEBUGGING - # data1 = LETTERS[1:5] ; data2 = NULL ; n = 1e6 ; seed = NULL ; print.count = 1e3 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.2 ; warn.print = TRUE ; lib.path = NULL - # data1 = LETTERS[1:5] ; data2 = NULL ; n = 10 ; seed = 22 ; print.count = 10 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.2 ; warn.print = TRUE ; lib.path = NULL - # data1 = 101:110 ; data2 = 21:30 ; n = 10 ; seed = 22 ; print.count = 10 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.2 ; warn.print = TRUE ; lib.path = NULL - # data1 = 1:1e3 ; data2 = 1e3:1 ; n = 20 ; seed = 22 ; print.count = 1e6 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.5 ; warn.print = TRUE ; lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_pack", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_round", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data1, class = "vector", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & length(data1) < 2){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A VECTOR OF MINIMUM LENGTH 2. HERE IT IS: ", length(data1)) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(data2)){ - tempo <- fun_check(data = data1, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) - if(tempo$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data1 MUST BE A NUMERIC VECTOR IF data2 ARGUMENT IS SPECIFIED") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = data2, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) - if(length(data1) != length(data2)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data1 AND data2 MUST BE VECTOR OF SAME LENGTH. HERE IT IS ", length(data1)," AND ", length(data2)) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else if(is.null(n)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT CANNOT BE NULL IF data2 ARGUMENT IS NULL") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(n)){ - tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - } - if( ! is.null(seed)){ - tempo <- fun_check(data = seed, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = TRUE, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = print.count, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = text.print, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = cor.method, options = c("pearson", "kendall", "spearman"), length =1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = cor.limit, class = "vector", mode = "numeric", prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # package checking - fun_pack(req.package = "lubridate", lib.path = lib.path) - # end package checking - # main code - # code that protects set.seed() in the global environment - # see also Protocol 100-rev0 Parallelization in R.docx - if(exists(".Random.seed", envir = .GlobalEnv)){ # if .Random.seed does not exists, it means that no random operation has been performed yet in any R environment - tempo.random.seed <- .Random.seed - on.exit(assign(".Random.seed", tempo.random.seed, env = .GlobalEnv)) - }else{ - on.exit(set.seed(NULL)) # inactivate seeding -> return to complete randomness - } - set.seed(seed) - # end code that protects set.seed() in the global environment - ini.date <- Sys.time() # time of process begin, converted into seconds - ini.time <- as.numeric(ini.date) # time of process begin, converted into seconds - ini.pos <- 1:length(data1) # positions of data1 before permutation loops - tempo.pos <- ini.pos # positions of data1 that will be modified during loops - # pos.selec.seq <- ini.pos[-length(data1)] # selection of 1 position in initial position, without the last because always up permutation (pos -> pos+1 & pos+1 -> pos) - pos.selec.seq.max <- length(ini.pos) - 1 # max position (used by sample.int() function). See below for - 1 - ini.warning.length <- options()$warning.length - options(warning.length = 8170) - warn <- NULL - warn.count <- 0 - count <- 0 - round <- 0 - BREAK <- FALSE - tempo.cor <- 0 - if(is.null(data2)){ - if(length(table(data1)) == 1L){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NO PERMUTATION PERFORMED BECAUSE data1 ARGUMENT SEEMS TO BE MADE OF IDENTICAL ELEMENTS: ", names(table(data1))) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # - }else{ - if(print.count > n){ - print.count <- n - } - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP OF ", n, " LOOPS INITIATED | LOOP COUNT: ", format(count, big.mark=","))) - print.count.loop <- logical(length = print.count) - print.count.loop[length(print.count.loop)] <- TRUE # not this to avoid long vector, but not forget to reset during printing: print.count.loop[(1:trunc(n / print.count) * print.count)] <- TRUE # counter to speedup - count.loop <- 0 - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - tempo.date.loop <- Sys.time() - tempo.time.loop <- as.numeric(tempo.date.loop) - for(i3 in 1:n){ - count.loop <- count.loop + 1 - pos2 <- pos[count.loop] # selection of 1 position - tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - if(print.count.loop[count.loop]){ - count.loop <- 0 - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) - final.loop <- (tempo.time - tempo.time.loop) / i3 * n # expected duration in seconds - final.exp <- as.POSIXct(final.loop, origin = tempo.date.loop) - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP ", i3, " / ", n, " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) - } - } - count <- count + n # out of the loop to speedup - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP ENDED | LOOP COUNT: ", format(count, big.mark=","))) - cat("\n\n") - } - }else{ - if(length(table(data1)) == 1L){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NO PERMUTATION PERFORMED BECAUSE data1 ARGUMENT SEEMS TO BE MADE OF IDENTICAL ELEMENTS: ", names(table(data1))) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # - tempo.cor <- 1 - }else if(length(table(data2)) == 1L){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NO PERMUTATION PERFORMED BECAUSE data2 ARGUMENT SEEMS TO BE MADE OF IDENTICAL ELEMENTS: ", names(table(data2))) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # - tempo.cor <- 1 - }else{ - cor.ini <- cor(x = data1, y = data2, use = "pairwise.complete.obs", method = cor.method) - tempo.cor <- cor.ini # correlation that will be modified during loops - neg.cor <- FALSE - if(tempo.cor < 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") INITIAL ", toupper(cor.method), " CORRELATION BETWEEN data1 AND data2 HAS BEEN DETECTED AS NEGATIVE: ", tempo.cor, ". THE LOOP STEPS WILL BE PERFORMED USING POSITIVE CORRELATIONS BUT THE FINAL CORRELATION WILL BE NEGATIVE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # - neg.cor <- TRUE - tempo.cor <- abs(tempo.cor) - cor.ini <- abs(cor.ini) - } - if(tempo.cor < cor.limit){ # randomize directly all the position to be close to correlation zero - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") INITIAL ABSOLUTE VALUE OF THE ", toupper(cor.method), " CORRELATION ", fun_round(tempo.cor), " BETWEEN data1 AND data2 HAS BEEN DETECTED AS BELOW THE CORRELATION LIMIT PARAMETER ", cor.limit, "\nTHE data1 SEQUENCE HAS BEEN COMPLETELY RANDOMIZED TO CORRESPOND TO CORRELATION ZERO") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # - for(i4 in 1:5){ # done 5 times to be sure of the complete randomness - tempo.pos <- sample(x = tempo.pos, size = length(tempo.pos), replace = FALSE) - } - count <- count + 5 # out of the loop to speedup - }else{ - # smallest correlation decrease - count <- count + 1 # 1 and not 0 because already 1 performed just below - pos <- sample.int(n = pos.selec.seq.max , size = 1, replace = TRUE) # selection of 1 position # pos.selec.seq.max because selection of 1 position in initial position, without the last because always up permutation (pos -> pos+1 & pos+1 -> pos) - tempo.pos[c(pos + 1, pos)] <- tempo.pos[c(pos, pos + 1)] - tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) - smallest.cor.dec <- cor.ini - tempo.cor - # end smallest correlation decrease - # going out of tempo.cor == cor.ini - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "CORRELATION DECREASE AFTER A SINGLE PERMUTATION: ", fun_round(smallest.cor.dec, 4))) - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FIRST WHILE LOOP STEP -> GOING OUT FROM EQUALITY | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) - print.count.loop <- logical(length = print.count) - print.count.loop[length(print.count.loop)] <- TRUE # counter to speedup - count.loop <- 0 # - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - tempo.date.loop <- Sys.time() - tempo.time.loop <- as.numeric(tempo.date.loop) - while(tempo.cor == cor.ini){ # to be out of equality between tempo.cor and cor.ini at the beginning (only valid for very long vector) - count <- count + 1 - count.loop <- count.loop + 1 - pos2 <- pos[count.loop] - tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) - if(print.count.loop[count.loop]){ - count.loop <- 0 - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FIRST WHILE LOOP STEP", format(count.loop, big.mark=","), " / ? | COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TIME SPENT: ", tempo.lapse)) - } - } - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FIRST WHILE LOOP STEP END | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TOTAL SPENT TIME: ", tempo.lapse)) - if(tempo.cor < cor.limit){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE FIRST FOR & WHILE LOOP STEPS HAVE BEEN TOO FAR AND SUBSEQUENT LOOP STEPS WILL NOT RUN") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # end going out of tempo.cor == cor.ini - # estimation of the average correlation decrease per loop on x loops and for loop execution - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "WHILE/FOR LOOPS INITIATION | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) - count.est <- 1e5 - first.round <- TRUE - GOBACK <- FALSE - while(tempo.cor > cor.limit){ - round <- round + 1 - # estimation step - if(first.round == TRUE){ - first.round <- FALSE - cor.dec.per.loop <- numeric(length = 5) - loop.nb.est <- Inf - cor.est.ini <- tempo.cor - cor.est <- numeric(length = 5) - for(i6 in 1:5){ # connected to cor.dec.per.loop - tempo.pos.est <- tempo.pos - pos <- sample.int(n = pos.selec.seq.max , size = count.est, replace = TRUE) # selection of n position - for(i7 in 1:count.est){ - pos2 <- pos[i7] # selection of 1 position - tempo.pos.est[c(pos2 + 1, pos2)] <- tempo.pos.est[c(pos2, pos2 + 1)] - } - tempo.cor.est <- abs(cor(x = data1[tempo.pos.est], y = data2, use = "pairwise.complete.obs", method = cor.method)) - cor.est[i6] <- tempo.cor.est - tempo.cor.dec.per.loop <- (cor.est.ini - tempo.cor.est) / count.est # correlation decrease per loop - if(is.na(tempo.cor.dec.per.loop) | ! is.finite(tempo.cor.dec.per.loop)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2\ncor.est.ini: ", cor.est.ini, "\ntempo.cor.est: ", tempo.cor.est) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - cor.dec.per.loop[i6] <- tempo.cor.dec.per.loop - } - cor.est <- cor.est[which.max(cor.dec.per.loop)] # max to avoid to go to far with for loop (tempo.cor below tempo.limit) - cor.dec.per.loop <- max(cor.dec.per.loop, na.rm = TRUE) # max to avoid to go to far with for loop (tempo.cor below tempo.limit) - loop.nb.est <- round((tempo.cor - cor.limit) / cor.dec.per.loop) - }else{ - if(GOBACK == TRUE){ - loop.nb.est <- round(loop.nb.est / 2) - }else{ - cor.dec.per.loop <- (cor.ini - tempo.cor) / count - loop.nb.est <- round((tempo.cor - cor.limit) / cor.dec.per.loop) - } - } - # end estimation step - # loop step - if(is.na(loop.nb.est) | ! is.finite(loop.nb.est)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 1\nloop.nb.est: ", loop.nb.est, "\ncor.ini: ", cor.ini, "\ntempo.cor: ", tempo.cor, "\ncor.limit: ", cor.limit, "\ncor.dec.per.loop: ", cor.dec.per.loop) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(loop.nb.est > 1e4){ # below -> leave the while loop - tempo.pos.secu <- tempo.pos - count.secu <- count - tempo.cor.secu <- tempo.cor - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "INITIAL SETTINGS BEFORE ROUND: ", round, " | LOOP COUNT: ", format(count, big.mark=","), " | GO BACK: ", GOBACK, " | LOOP NUMBER ESTIMATION: ", format(loop.nb.est, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) - print.count.loop <- logical(length = print.count) - print.count.loop[length(print.count.loop)] <- TRUE # not this to avoid long vector, but not forget to reset during printing: print.count.loop[(1:trunc(n / print.count) * print.count)] <- TRUE # counter to speedup - count.loop <- 0 - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - tempo.date.loop <- Sys.time() - tempo.time.loop <- as.numeric(tempo.date.loop) - for(i6 in 1:loop.nb.est){ - count.loop <- count.loop + 1 - pos2 <- pos[count.loop] # selection of 1 position - tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - if(print.count.loop[count.loop]){ - count.loop <- 0 - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) - final.loop <- (tempo.time - tempo.time.loop) / i6 * loop.nb.est # expected duration in seconds # intra nb.compar loop lapse: time lapse / cycles done * cycles remaining - final.exp <- as.POSIXct(final.loop, origin = tempo.date.loop) - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP | ROUND ", round, " | LOOP: ", format(i6, big.mark=","), " / ", format(loop.nb.est, big.mark=","), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) - } - } - count <- count + loop.nb.est # out of the loop to speedup - tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) - if(tempo.cor > tempo.cor.secu | ((tempo.cor - cor.limit) < 0 & abs(tempo.cor - cor.limit) > smallest.cor.dec * round(log10(max(ini.pos, na.rm = TRUE))))){ - GOBACK <- TRUE - tempo.pos <- tempo.pos.secu - count <- count.secu - tempo.cor <- tempo.cor.secu - }else{ - GOBACK <- FALSE - } - }else{ - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FINAL WHILE LOOP | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) - print.count.loop <- logical(length = print.count) - print.count.loop[length(print.count.loop)] <- TRUE # counter to speedup - count.loop <- 0 # - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - tempo.cor.loop <- tempo.cor - tempo.date.loop <- Sys.time() - tempo.time.loop <- as.numeric(tempo.date.loop) - while(tempo.cor > cor.limit){ - count <- count + 1 - count.loop <- count.loop + 1 - pos2 <- pos[count.loop] - tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] - tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) - if(print.count.loop[count.loop]){ - count.loop <- 0 - pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) - final.loop <- (tempo.time - tempo.time.loop) / (tempo.cor.loop - tempo.cor) * (tempo.cor - cor.limit) # expected duration in seconds # tempo.cor.loop - tempo.cor always positive and tempo.cor decreases progressively starting from tempo.cor.loop - final.exp <- as.POSIXct(final.loop, origin = tempo.date.loop) - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "WHILE LOOP | LOOP NB: ", format(count.loop, big.mark=","), " | COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) - } - } - } - } - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) - cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "WHILE/FOR LOOPS END | LOOP COUNT: ", format(count, big.mark=","), " | NB OF ROUNDS: ", round, " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TOTAL SPENT TIME: ", tempo.lapse)) - } - tempo.cor <- ifelse(neg.cor == TRUE, -tempo.cor, tempo.cor) - } - } - cat("\n\n") - if(warn.print == TRUE & ! is.null(warn)){ - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE), add = TRUE) - } - on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) - output <- list(data = data1[tempo.pos], warn = warn, cor = if(is.null(data2)){cor(ini.pos, tempo.pos, method = "spearman")}else{tempo.cor}, count = count) - return(output) +if(any.id.element == TRUE){ +common.elements <- unique(c(data1[same.elements.pos1], data2[same.elements.pos2])) } - - -######## fun_slide() #### return a computation made on a vector using a sliding window - - -fun_slide <- function( - data, - window.size, - step, - from = NULL, - to = NULL, - fun, - args = NULL, - boundary = "left", - parall = FALSE, - thread.nb = NULL, - print.count = 100, - res.path = NULL, - lib.path = NULL, - verbose = TRUE, - cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" -){ - # AIM - # return a computation made on a vector using a sliding window - # WARNINGS - # The function uses two strategies, depending on the amout of memory required which depends on the data, window.size and step arguments. The first one uses lapply(), is generally fast but requires lots of memory. The second one uses a parallelized loop. The choice between the two strategies is automatic if parall argument is FALSE, and is forced toward parallelization if parall argument is TRUE - # The parall argument forces the parallelization, which is convenient when the data argument is big, because the lapply() function is sometimes slower than the parallelization - # Always use the env argument when fun_slide() is used inside functions - # ARGUMENTS - # data: vector, matrix, table or array of numeric values (mode must be numeric). Inf not allowed. NA will be removed before computation - # window.size: single numeric value indicating the width of the window sliding across data (in the same unit as data value) - # step: single numeric value indicating the step between each window (in the same unit as data value). Cannot be larger than window.size - # from: value of the left boundary of the first sliding window. If NULL, min(data) is used. The first window will strictly have from or min(data) as left boundary - # to: value of the right boundary of the last sliding window. If NULL, max(data) is used. Warning: (1) the final last window will not necessary have to|max(data) as right boundary. In fact the last window will be the one that contains to|max(data) for the first time, i.e., min[from|min(data) + window.size + n * step >= to|max(data)]; (2) In fact, the >= in min[from|min(data) + window.size + n * step >= to|max(data)] depends on the boundary argument (>= for "right" and > for "left"); (3) to have the rule (1) but for the center of the last window, use to argument as to = to|max(data) + window.size / 2 - # fun: function or character string (without brackets) indicating the name of the function to apply in each window. Example: fun = "mean", or fun = mean - # args: character string of additional arguments of fun (separated by a comma between the quotes). Example args = "na.rm = TRUE" for fun = mean. Ignored if NULL - # boundary: either "left" or "right". Indicates if the sliding window includes values equal to left boundary and exclude values equal to right boundary ("left") or the opposite ("right") - # parall: logical. Force parallelization ? - # thread.nb: numeric value indicating the number of threads to use if ever parallelization is required. If NULL, all the available threads will be used. Ignored if parall is FALSE - # print.count: interger value. Print a working progress message every print.count during loops. BEWARE: can increase substentially the time to complete the process using a small value, like 10 for instance. Use Inf is no loop message desired - # res.path: character string indicating the absolute pathway where the parallelization log file will be created if parallelization is used. If NULL, will be created in the R current directory - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL - # verbose: logical. Display messages? - # cute.path: character string indicating the absolute path of the cute.R file. Will be remove when cute will be a package. Ignored if parall is FALSE - # RETURN - # a data frame containing - #$left : the left boundary of each window (in the unit of the data argument) - #$right : the right boundary of each window (in the unit of data argument) - #$center : the center of each window (in the unit of data argument) - #$value : the computed value by the fun argument in each window) - # REQUIRED PACKAGES - # lubridate - # parallel if parall argument is TRUE (included in the R installation packages but not automatically loaded) - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_get_message - # fun_pack() - # EXAMPLES - # fun_slide(data = c(1:10, 100:110, 500), window.size = 5, step = 2, fun = length, boundary = "left") - # fun_slide(data = c(1:10, 100:110, 500), window.size = 5, step = 2, fun = length, boundary = "right") # effect of boundary argument - # fun_slide(data = c(1:10, 100:110, 500), window.size = 5, step = 2, fun = length, boundary = "left", parall = TRUE) # effect of parall argument - # DEBUGGING - # data = c(1:10, 100:110, 500) ; window.size = 5 ; step = 2 ; from = NULL ; to = NULL ; fun = length ; args = NULL ; boundary = "left" ; parall = FALSE ; thread.nb = NULL ; print.count = 100 ; res.path = NULL ; lib.path = NULL ; verbose = TRUE ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" - # data = lag.pos; window.size = window.size; step = step; fun = length; from = min(a$pos); to = max(a$pos) - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - req.function <- c( - "fun_check", - "fun_get_message", - "fun_pack" - ) - tempo <- NULL - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo <- c(tempo, i1) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # reserved words - # end reserved words - # arg with no default values - mandat.args <- c( - "data", - "window.size", - "step", - "fun" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data, mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = window.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = step, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(from)){ - tempo <- fun_check(data = from, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - } - if( ! is.null(to)){ - tempo <- fun_check(data = to, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - } - tempo1 <- fun_check(data = fun, class = "vector", mode = "character", length = 1, fun.name = function.name) - tempo2 <- fun_check(data = fun, class = "function", length = 1, fun.name = function.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": fun ARGUMENT MUST BE A FUNCTION OR A CHARACTER STRING OF THE NAME OF A FUNCTION") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(args)){ - tempo <- fun_check(data = args, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = boundary, options = c("left", "right"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = parall, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(parall == TRUE){ - if( ! is.null(thread.nb)){ - tempo <- fun_check(data = thread.nb, typeof = "integer", double.as.integer.allowed = TRUE, neg.values = FALSE, length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & thread.nb < 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": thread.nb PARAMETER MUST EQUAL OR GREATER THAN 1: ", thread.nb) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - tempo <- fun_check(data = print.count, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - if( ! is.null(res.path)){ - tempo <- fun_check(data = res.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - } - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = verbose, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = cute.path, class = "vector", typeof = "character", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end using fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # new environment - env.name <- paste0("env", as.numeric(Sys.time())) - if(exists(env.name, where = -1)){ # verify if still ok when fun_info() is inside a function - tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - assign(env.name, new.env()) - } - # end new environment - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT JUST BE NA:", paste0(tempo.arg[tempo.log], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "data", - "window.size", - "step", - # "from", # because can be NULL - # "to", # because can be NULL - "fun", - # "args", # because can be NULL - "boundary", - "parall", - # "thread.nb", # because can be NULL - "print.count", - # "res.path", # because can be NULL - # "lib.path", # because can be NULL - "verbose", - "cute.path" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){# normally no NA with is.null() - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # code that protects set.seed() in the global environment - # end code that protects set.seed() in the global environment - # warning initiation - # end warning initiation - # other checkings - if(length(data) == 0){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT CANNOT BE LENGTH 0") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if(any( ! is.finite(data))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT CANNOT CONTAIN Inf VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if(step > window.size){ - tempo.cat <- paste0("ERROR IN ", function.name, ": step ARGUMENT MUST BE LOWER THAN window.size ARGUMENT\nstep: ", paste(step, collapse = " "), "\nwindow.size: ", paste(window.size, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - if( ! is.null(res.path)){ - if( ! all(dir.exists(res.path))){ # separation to avoid the problem of tempo$problem == FALSE and res.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE res.path ARGUMENT DOES NOT EXISTS:\n", paste(res.path, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - }else{ - res.path <- getwd() # working directory - } - if( ! is.null(lib.path)){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) - } - } - if(grepl(x = cute.path, pattern = "^http")){ - tempo.error1 <- any(grepl(x = fun_get_message(data = "source(cute.path)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)), pattern = "^[Ee]rror")) - tempo.error2 <- FALSE - }else{ - tempo.error1 <- FALSE - tempo.error2 <- ! file.exists(cute.path) - } - if(tempo.error1 | tempo.error2){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(grepl(x = cute.path, pattern = "^http"), "URL", "FILE"), " PATH INDICATED IN THE cute.path PARAMETER DOES NOT EXISTS:\n", cute.path) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - # end other checkings - # reserved word checking - # end reserved word checking - # end second round of checking and data preparation - # package checking - fun_pack(req.package = c("lubridate"), lib.path = lib.path) - fun_pack(req.package = c("parallel"), lib.path = lib.path) - # end package checking - # main code - if(verbose == TRUE){ - cat("\nfun_slide JOB IGNITION\n") - } - ini.date <- Sys.time() - ini.time <- as.numeric(ini.date) # time of process begin, converted into seconds - fun <- match.fun(fun) # make fun <- get(fun) is fun is a function name written as character string of length 1 - if(boundary == "left"){ - left <- ">=" - right <- "<" - right.last.wind <- ">" - }else if(boundary == "right"){ - left <- ">" - right <- "<=" - right.last.wind <- ">=" - }else{ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - data <- as.vector(data) - data <- sort(data, na.last = NA) # NA removed - wind <- data.frame(left = seq(from = if(is.null(from)){min(data, na.rm = TRUE)}else{from}, to = if(is.null(to)){max(data, na.rm = TRUE)}else{to}, by = step), stringsAsFactors = TRUE) - wind <- data.frame(wind, right = wind$left + window.size, stringsAsFactors = TRUE) - wind <- data.frame(wind, center = (wind$left + wind$right) / 2, stringsAsFactors = TRUE) - if(all(wind$right < if(is.null(to)){max(data, na.rm = TRUE)}else{to})){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 2") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # The 3 next lines is for the rule of to argument with center (see to argument description) - # if(any(wind$center > max(data, na.rm = TRUE))){ - # wind <- wind[ ! wind$center > max(data, na.rm = TRUE),] - # } - if(sum(get(right.last.wind)(wind$right, if(is.null(to)){max(data, na.rm = TRUE)}else{to}), na.rm = TRUE) > 1){ # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - tempo.log <- get(right.last.wind)(wind$right, if(is.null(to)){max(data, na.rm = TRUE)}else{to}) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - tempo.log[min(which(tempo.log), na.rm = TRUE)] <- FALSE # convert the first left boundary that goes above max(data, na.rm = TRUE) to FALSE to keep it (the next ones will be removed) - wind <- wind[ ! tempo.log,] - } - - # test if lapply can be used - if(parall == FALSE){ - assign("wind", wind, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # wind assigned in a new envir for test - assign("data", data, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # data assigned in a new envir for test - tempo.message <- fun_get_message(data="lapply(X = wind$left, Y = data, FUN = function(X, Y){res <- get(left)(Y, X) ; return(res)})", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE), print.no = FALSE) # no env = sys.nframe(), inherit = FALSE in get(left) because look for function in the classical scope - # rm(env.name) # optional, because should disappear at the end of the function execution - }else{ - tempo.message <- "ERROR" # with this, force the parallelization by default - } - # end test if lapply can be used - if( ! any(grepl(x = tempo.message, pattern = "ERROR.*"))){ - left.log <- lapply(X = wind$left, Y = data, FUN = function(X, Y){ - res <- get(left)(Y, X) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - return(res) - }) - right.log <- lapply(X = wind$right, Y = data, FUN = function(X, Y){ - res <- get(right)(Y, X) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - return(res) - }) - log <- mapply(FUN = "&", left.log, right.log, SIMPLIFY = FALSE) - output <- eval(parse(text = paste0("sapply(lapply(log, FUN = function(X){(data[X])}), FUN = fun", if( ! is.null(args)){paste0(", ", args)}, ")"))) # take the values of the data vector according to log (list of logical, each compartment of length(data)) and apply fun with args of fun - if(length(output) != nrow(wind)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 3") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - output <- data.frame(wind, value = output, stringsAsFactors = TRUE) - } - }else{ - if(verbose == TRUE){ - tempo.cat <- paste0("PARALLELIZATION INITIATED AT: ", ini.date) - cat(paste0("\n", tempo.cat, "\n")) - } - tempo.thread.nb = parallel::detectCores(all.tests = FALSE, logical = TRUE) # detect the number of threads - if( ! is.null(thread.nb)){ - if(tempo.thread.nb < thread.nb){ - thread.nb <- tempo.thread.nb - if(verbose == TRUE){ - tempo.cat <- paste0("ONLY: ", tempo.thread.nb, " THREADS AVAILABLE") - cat(paste0("\n", tempo.cat, "\n")) - } - } - }else{ - thread.nb <- tempo.thread.nb - } - if(verbose == TRUE){ - tempo.cat <- paste0("NUMBER OF THREADS USED: ", thread.nb) - cat(paste0("\n ", tempo.cat, "\n")) - } - Clust <- parallel::makeCluster(thread.nb, outfile = paste0(res.path, "/fun_slide_parall_log.txt")) # outfile to print or cat during parallelization (only possible in a file, outfile = "" do not work on windows) - cluster.list <- parallel::clusterSplit(Clust, 1:nrow(wind)) # split according to the number of cluster - if(verbose == TRUE){ - tempo.cat <- paste0("SPLIT OF TEST NUMBERS IN PARALLELISATION:") - cat(paste0("\n ", tempo.cat, "\n")) - str(cluster.list) # using print(str()) add a NULL below the result - cat("\n") - } - paral.output.list <- parallel::clusterApply( # - cl = Clust, - x = cluster.list, - function.name = function.name, - data = data, - FUN = fun, # because fun argument of clusterApply - args = args, - thread.nb = thread.nb, - print.count = print.count, - wind = wind, - left = left, - right = right, - res.path = res.path, - lib.path = lib.path, - verbose = verbose, - cute.path = cute.path, - fun = function( - x, - function.name, - data, - FUN, - args, - thread.nb, - print.count, - wind, - left, - right, - res.path, - lib.path, - verbose, - cute.path - ){ - # check again: very important because another R - process.id <- Sys.getpid() - cat(paste0("\nPROCESS ID ", process.id, " -> TESTS ", x[1], " TO ", x[length(x)], "\n")) - source(cute.path, local = .GlobalEnv) - fun_pack(req.package = "lubridate", lib.path = lib.path, load = TRUE) # load = TRUE to be sure that functions are present in the environment. And this prevent to use R.lib.path argument of fun_python_pack() - # end check again: very important because another R - ini.date <- Sys.time() - ini.time <- as.numeric(ini.date) # time of process begin, converted into - output <- NULL - print.count.loop <- 0 - for(i4 in 1:length(x)){ - print.count.loop <- print.count.loop + 1 - log <- get(left)(data, wind$left[x[i4]]) & get(right)(data, wind$right[x[i4]]) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - output <- c(output, eval(parse(text = paste0("FUN(data[log]", if( ! is.null(args)){paste0(", ", args)}, ")")))) - if(verbose == TRUE){ - if(print.count.loop == print.count){ - print.count.loop <- 0 - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) - final.loop <- (tempo.time - ini.time) / i4 * length(x) # expected duration in seconds # intra nb.compar loop lapse: time lapse / cycles done * cycles remaining - final.exp <- as.POSIXct(final.loop, origin = ini.date) - cat(paste0("\nIN PROCESS ", process.id, " | LOOP ", format(i4, big.mark=","), " / ", format(length(x), big.mark=","), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) - } - if(i4 == length(x)){ - tempo.time <- as.numeric(Sys.time()) - tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) - cat(paste0("\nPROCESS ", process.id, " ENDED | LOOP ", format(i4, big.mark=","), " / ", format(length(x), big.mark=","), " | TIME SPENT: ", tempo.lapse, "\n\n")) - } - } - } - wind <- wind[x, ] - if(length(output) != nrow(wind)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 4") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - output <- data.frame(wind, value = output, stringsAsFactors = TRUE) - return(output) - } - } - ) - parallel::stopCluster(Clust) - # result assembly - output <- data.frame() - for(i2 in 1:length(paral.output.list)){ # compartment relatives to each parallelization - output <- rbind(output, paral.output.list[[i2]], stringsAsFactors = TRUE) - } - # end result assembly - if(nrow(output) != nrow(wind)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 5\nlength(output): ", length(output), "\nnrow(wind): ", nrow(wind)) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - output <- output[order(output$left), ] - } - } - if(verbose == TRUE){ - end.date <- Sys.time() - end.time <- as.numeric(end.date) - total.lapse <- round(lubridate::seconds_to_period(end.time - ini.time)) - cat(paste0("\nfun_slide JOB END\n\nTIME: ", end.date, "\n\nTOTAL TIME LAPSE: ", total.lapse, "\n\n\n")) - } - return(output) +if(identical(data1, data2)){ +identical.content <- TRUE +same.order <- TRUE +}else if(identical(sort(data1), sort(data2))){ +same.order <- FALSE +order1 <- order(data1) +order2 <- order(data2) +} +} +output <- list(same.class = same.class, class = class, same.length = same.length, length = length, same.levels = same.levels, levels = levels, any.id.levels = any.id.levels, same.levels.pos1 = same.levels.pos1, same.levels.pos2 = same.levels.pos2, same.levels.match1 = same.levels.match1, same.levels.match2 = same.levels.match2, common.levels = common.levels, same.names = same.names, name = name, any.id.name = any.id.name, same.names.pos1 = same.names.pos1, same.names.pos2 = same.names.pos2, same.names.match1 = same.names.match1, same.names.match2 = same.names.match2, common.names = common.names, any.id.element = any.id.element, same.elements.pos1 = same.elements.pos1, same.elements.pos2 = same.elements.pos2, same.elements.match1 = same.elements.match1, same.elements.match2 = same.elements.match2, common.elements = common.elements, same.order = same.order, order1 = order1, order2 = order2, identical.object = identical.object, identical.content = identical.content) +return(output) } +######## fun_comp_2d() #### comparison of two 2D datasets (row & col names, dimensions, etc.) -######## fun_codon2aa() #### convert codon to amino acid using standard genetic code - +fun_comp_2d <- function(data1, data2){ +# AIM +# compare two 2D datasets of the same class or not. Check and report in a list if the 2 datasets have: +# same class +# common row names +# common column names +# same row number +# same column number +# potential identical rows between the 2 datasets +# potential identical columns between the 2 datasets +# WARNINGS +# For data frames: content are compared after conversion of content into characters. This means that the comparison of the content of data frame, either row to row, or column to column, does not take into account the mode in the different columns. This concern the results in $any.id.row, $same.row.pos1, $same.row.pos2, $same.row.match1, $same.row.match2, $any.id.col, $same.row.col1, $same.row.col2, $same.col.match1, $same.col.match2 and $identical.content result +# "TOO BIG FOR EVALUATION" returned in $same.row.pos1, $same.row.pos2, $same.row.match1 and $same.row.match2 when nrow(data1) * nrow(data2) > 1e6 and $any.id.row remains NULL +# "TOO BIG FOR EVALUATION" returned in $same.row.col1, $same.row.col2, $same.col.match1 and $same.col.match2 when ncol(data1) * ncol(data2) > 1e6 and $any.id.col remains NULL +# ARGUMENTS +# data1: matrix, data frame or table +# data2: matrix, data frame or table +# RETURN +# a list containing: +# $same.class: logical. Are class identical ? +# $class: classes of the 2 datasets (NULL otherwise) +# $same.dim: logical. Are dimension identical ? +# $dim: dimension of the 2 datasets (NULL otherwise) +# $same.row.nb: logical. Are number of rows identical ? +# $row.nb: nb of rows of the 2 datasets if identical (NULL otherwise) +# $same.col.nb: logical. Are number of columns identical ? +# $col.nb: nb of columns of the 2 datasets if identical (NULL otherwise) +# $same.row.name: logical. Are row names identical ? NULL if no row names in the two 2D datasets +# $row.name: name of rows of the 2 datasets if identical (NULL otherwise) +# $any.id.row.name: logical. Is there any row names identical ? NULL if no row names in the two 2D datasets +# $same.row.names.pos1: positions, in data1, of the row names identical in data2 +# $same.row.names.pos2: positions, in data2, of the row names identical in data1 +# $same.row.names.match1: positions, in data2, of the row names that match the row names in data1, as given by match(data1, data2) (NULL otherwise) +# $same.row.names.match2: positions, in data1, of the row names that match the row names in data2, as given by match(data1, data2) (NULL otherwise) +# $common.row.names: common row names between data1 and data2 (can be a subset of $name or not). NULL if no common row names +# $same.col.name: logical. Are column names identical ? NULL if no col names in the two 2D datasets +# $col.name: name of columns of the 2 datasets if identical (NULL otherwise) +# $any.id.col.name: logical. Is there any column names identical ? NULL if no col names in the two 2D datasets +# $same.col.names.pos1: positions, in data1, of the column names identical in data2 +# $same.col.names.pos2: positions, in data2, of the column names identical in data1 +# $same.col.names.match1: positions, in data2, of the column names that match the column names in data1, as given by match(data1, data2) (NULL otherwise) +# $same.col.names.match2: positions, in data1, of the column names that match the column names in data2, as given by match(data1, data2) (NULL otherwise) +# $common.col.names: common column names between data1 and data2 (can be a subset of $name or not). NULL if no common column names +# $any.id.row: logical. is there identical rows (not considering row names)? NULL if nrow(data1) * nrow(data2) > 1e10 +# $same.row.pos1: positions, in data1, of the rows identical in data2 (not considering row names). Return "TOO BIG FOR EVALUATION" if nrow(data1) * nrow(data2) > 1e10 +# $same.row.pos2: positions, in data2, of the rows identical in data1 (not considering row names). Return "TOO BIG FOR EVALUATION" if nrow(data1) * nrow(data2) > 1e10 +# $same.row.match1: positions, in data2, of the rows that match the rows in data1, as given by match(data1, data2) (NULL otherwise) +# $same.row.match2: positions, in data1, of the rows that match the rows in data2, as given by match(data1, data2) (NULL otherwise) +# $any.id.col: logical. is there identical columns (not considering column names)? NULL if ncol(data1) * ncol(data2) > 1e10 +# $same.col.pos1: position in data1 of the cols identical in data2 (not considering column names). Return "TOO BIG FOR EVALUATION" if ncol(data1) * ncol(data2) > 1e10 +# $same.col.pos2: position in data2 of the cols identical in data1 (not considering column names). Return "TOO BIG FOR EVALUATION" if ncol(data1) * ncol(data2) > 1e10 +# $same.col.match1: positions, in data2, of the columns that match the columns in data1, as given by match(data1, data2) (NULL otherwise) +# $same.row.match2: positions, in data1, of the columns that match the columns in data2, as given by match(data1, data2) (NULL otherwise) +# $identical.object: logical. Are objects identical (including row & column names)? +# $identical.content: logical. Are content objects identical (identical excluding row & column names)? +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# none +# EXAMPLES +# obs1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = as.data.frame(matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])), stringsAsFactors = TRUE) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) +# obs1 = matrix(101:110, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) +# large matrices +# obs1 = matrix(1:1e6, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; obs2 = matrix(as.integer((1:1e6)+1e6/5), ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; head(obs1) ; head(obs2) ; fun_comp_2d(obs1, obs2) +# WARNING: when comparing content (rows, columns, or total), double and integer data are considered as different -> double(1) != integer(1) +# obs1 = matrix(1:1e6, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; obs2 = matrix((1:1e6)+1e6/5, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; head(obs1) ; head(obs2) ; fun_comp_2d(obs1, obs2) +# Matrices: same row conten tand same row names +# obs1 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4]))) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) +# Matrices: same row content but not same row names -> works: same content is identified +# obs1 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; obs2 = matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("x", "z", "y"), c(LETTERS[1:2], "k", LETTERS[5:4]))) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) +# obs1 = t(matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5]))) ; obs2 = t(matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4])))) ; obs1 ; obs2 ; fun_comp_2d(obs1, obs2) +# Data frames: same row content and same row names, not same mode between columns +# obs1 = as.data.frame(matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5]))) ; obs2 = as.data.frame(matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4])))) ; obs1[, 5] <- as.character(obs1[, 5]) ; obs2[, 5] <- as.character(obs2[, 5]) ; obs1 ; obs2 ; str(obs1) ; str(obs2) ; fun_comp_2d(obs1, obs2) +# Data frames: same row content but not same row names -> works: same content is identified +# obs1 = as.data.frame(matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5]))) ; obs2 = as.data.frame(matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("x", "z", "y"), c(LETTERS[1:2], "k", LETTERS[5:4])))) ; obs1[, 5] <- as.character(obs1[, 5]) ; obs2[, 5] <- as.character(obs2[, 5]) ; obs1 ; obs2 ; str(obs1) ; str(obs2) ; fun_comp_2d(obs1, obs2) +# DEBUGGING +# data1 = matrix(1:10, ncol = 5) ; data2 = matrix(1:10, ncol = 5) # for function debugging +# data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging +# data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5) # for function debugging +# data1 = matrix(1:15, byrow = TRUE, ncol = 5, dimnames = list(letters[1:3], LETTERS[1:5])) ; data2 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging +# data1 = matrix(1:15, ncol = 5, dimnames = list(letters[1:3], LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging +# data1 = matrix(1:15, ncol = 5, dimnames = list(paste0("A", letters[1:3]), LETTERS[1:5])) ; data2 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging +# data1 = matrix(1:15, ncol = 5, dimnames = list(letters[1:3], LETTERS[1:5])) ; data2 = matrix(1:12, ncol = 4, dimnames = list(letters[1:3], LETTERS[1:4])) # for function debugging +# data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(101:110, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) # for function debugging +# data1 = data.frame(a = 1:3, b= letters[1:3], row.names = LETTERS[1:3], stringsAsFactors = TRUE) ; data2 = data.frame(A = 1:3, B= letters[1:3], stringsAsFactors = TRUE) # for function debugging +# data1 = matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = as.data.frame(matrix(1:10, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])), stringsAsFactors = TRUE) # for function debugging +# data1 = matrix(1:10, byrow = TRUE, ncol = 5, dimnames = list(letters[1:2], LETTERS[1:5])) ; data2 = matrix(c(1:5, 101:105, 6:10), byrow = TRUE, ncol = 5, dimnames = list(c("a", "z", "b"), c(LETTERS[1:2], "k", LETTERS[5:4]))) # for function debugging +# data1 = table(Exp1 = c("A", "A", "A", "B", "B", "B"), Exp2 = c("A1", "B1", "A1", "C1", "C1", "B1")) ; data2 = data.frame(A = 1:3, B= letters[1:3], stringsAsFactors = TRUE) # for function debugging +# data1 = matrix(1:1e6, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) ; data2 = matrix((1:1e6)+1e6/5, ncol = 5, dimnames = list(NULL, LETTERS[1:5])) +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# argument checking +if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A MATRIX, DATA FRAME OR TABLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! (any(class(data2) %in% c("data.frame", "table")) | all(class(data2) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data2) %in% c("matrix", "data.frame", "table")) +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A MATRIX, DATA FRAME OR TABLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status +# end argument checking +# main code +same.class <- NULL +class <- NULL +same.dim <- NULL +dim <- NULL +same.row.nb <- NULL +row.nb <- NULL +same.col.nb <- NULL +col.nb <- NULL +same.row.name <- NULL +row.name <- NULL +any.id.row.name <- NULL +same.row.names.pos1 <- NULL +same.row.names.pos2 <- NULL +same.row.names.match1 <- NULL +same.row.names.match2 <- NULL +common.row.names <- NULL +same.col.name <- NULL +any.id.col.name <- NULL +same.col.names.pos1 <- NULL +same.col.names.pos2 <- NULL +same.col.names.match1 <- NULL +same.col.names.match2 <- NULL +common.col.names <- NULL +col.name <- NULL +any.id.row <- NULL +same.row.pos1 <- NULL +same.row.pos2 <- NULL +same.row.match1 <- NULL +same.row.match2 <- NULL +any.id.col <- NULL +same.col.pos1 <- NULL +same.col.pos2 <- NULL +same.col.match1 <- NULL +same.col.match2 <- NULL +identical.object <- NULL +identical.content <- NULL +if(identical(data1, data2) & (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) +same.class <- TRUE +class <- class(data1) +same.dim <- TRUE +dim <- dim(data1) +same.row.nb <- TRUE +row.nb <- nrow(data1) +same.col.nb <- TRUE +col.nb <- ncol(data1) +same.row.name <- TRUE +row.name <- dimnames(data1)[[1]] +any.id.row.name <- TRUE +same.row.names.pos1 <- 1:row.nb +same.row.names.pos2 <- 1:row.nb +same.row.names.match1 <- 1:row.nb +same.row.names.match2 <- 1:row.nb +common.row.names <- dimnames(data1)[[1]] +same.col.name <- TRUE +col.name <- dimnames(data1)[[2]] +any.id.col.name <- TRUE +same.col.names.pos1 <- 1:col.nb +same.col.names.pos2 <- 1:col.nb +same.col.names.match1 <- 1:col.nb +same.col.names.match2 <- 1:col.nb +common.col.names <- dimnames(data1)[[2]] +any.id.row <- TRUE +same.row.pos1 <- 1:row.nb +same.row.pos2 <- 1:row.nb +same.row.match1 <- 1:row.nb +same.row.match2 <- 1:row.nb +any.id.col <- TRUE +same.col.pos1 <- 1:col.nb +same.col.pos2 <- 1:col.nb +same.col.match1 <- 1:col.nb +same.col.match2 <- 1:col.nb +identical.object <- TRUE +identical.content <- TRUE +}else{ +identical.object <- FALSE +if(all(class(data1) == "table") & length(dim(data1)) == 1L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT IS A 1D TABLE. USE THE fun_comp_1d FUNCTION") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(all(class(data2) == "table") & length(dim(data2)) == 1L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT IS A 1D TABLE. USE THE fun_comp_1d FUNCTION") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! identical(class(data1), class(data2))){ +same.class <- FALSE +}else if( ! (any(class(data1) %in% c("data.frame", "table")) | all(class(data1) %in% c("matrix", "array")))){ # before R4.0.0, it was ! any(class(data1) %in% c("matrix", "data.frame", "table")) +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 AND data2 ARGUMENTS MUST BE EITHER MATRIX, DATA FRAME OR TABLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +same.class <- TRUE +class <- class(data1) +} +if( ! identical(dim(data1), dim(data2))){ +same.dim <- FALSE +}else{ +same.dim <- TRUE +dim <- dim(data1) +} +if( ! identical(nrow(data1), nrow(data2))){ +same.row.nb <- FALSE +}else{ +same.row.nb <- TRUE +row.nb <- nrow(data1) +} +if( ! identical(ncol(data1), ncol(data2))){ +same.col.nb <- FALSE +}else{ +same.col.nb <- TRUE +col.nb <- ncol(data1) +} +# row and col names +if(is.null(dimnames(data1)) & is.null(dimnames(data2))){ +same.row.name <- NULL # but already NULL +same.col.name <- NULL # but already NULL +# other row names param remain NULL +}else if((is.null(dimnames(data1)) & ! is.null(dimnames(data2))) | ( ! is.null(dimnames(data1)) & is.null(dimnames(data2)))){ +same.row.name <- FALSE +same.col.name <- FALSE +any.id.row.name <- FALSE +any.id.col.name <- FALSE +# other row names param remain NULL +}else{ +# row names +if(is.null(dimnames(data1)[[1]]) & is.null(dimnames(data2)[[1]])){ +same.row.name <- NULL # but already NULL +# other row names param remain NULL +}else if((is.null(dimnames(data1)[[1]]) & ! is.null(dimnames(data2)[[1]])) | ( ! is.null(dimnames(data1)[[1]]) & is.null(dimnames(data2)[[1]]))){ +same.row.name <- FALSE +any.id.row.name <- FALSE +# other row names param remain NULL +}else if(identical(dimnames(data1)[[1]], dimnames(data2)[[1]])){ +same.row.name <- TRUE +row.name <- dimnames(data1)[[1]] +any.id.row.name <- TRUE +same.row.names.pos1 <- 1:nrow(data1) +same.row.names.pos2 <- 1:nrow(data1) +same.row.names.match1 <- 1:nrow(data1) +same.row.names.match2 <- 1:nrow(data1) +common.row.names <- dimnames(data1)[[1]] +}else{ +same.row.name <- FALSE +any.id.row.name <- FALSE +if(any(dimnames(data1)[[1]] %in% dimnames(data2)[[1]])){ +any.id.row.name <- TRUE +same.row.names.pos1 <- which(dimnames(data1)[[1]] %in% dimnames(data2)[[1]]) +same.row.names.match1 <- match(dimnames(data1)[[1]], dimnames(data2)[[1]]) +} +if(any(dimnames(data2)[[1]] %in% dimnames(data1)[[1]])){ +any.id.row.name <- TRUE +same.row.names.pos2 <- which(dimnames(data2)[[1]] %in% dimnames(data1)[[1]]) +same.row.names.match2 <- match(dimnames(data2)[[1]], dimnames(data1)[[1]]) +} +if(any.id.row.name == TRUE){ +common.row.names <- unique(c(dimnames(data1)[[1]][same.row.names.pos1], dimnames(data2)[[1]][same.row.names.pos2])) +} +} +# col names +if(is.null(dimnames(data1)[[2]]) & is.null(dimnames(data2)[[2]])){ +same.col.name <- NULL # but already NULL +# other col names param remain NULL +}else if((is.null(dimnames(data1)[[2]]) & ! is.null(dimnames(data2)[[2]])) | ( ! is.null(dimnames(data1)[[2]]) & is.null(dimnames(data2)[[2]]))){ +same.col.name <- FALSE +any.id.col.name <- FALSE +# other col names param remain NULL +}else if(identical(dimnames(data1)[[2]], dimnames(data2)[[2]])){ +same.col.name <- TRUE +col.name <- dimnames(data1)[[2]] +any.id.col.name <- TRUE +same.col.names.pos1 <- 1:ncol(data1) +same.col.names.pos2 <- 1:ncol(data1) +same.col.names.match1 <- 1:ncol(data1) +same.col.names.match2 <- 1:ncol(data1) +common.col.names <- dimnames(data1)[[2]] +}else{ +same.col.name <- FALSE +any.id.col.name <- FALSE +if(any(dimnames(data1)[[2]] %in% dimnames(data2)[[2]])){ +any.id.col.name <- TRUE +same.col.names.pos1 <- which(dimnames(data1)[[2]] %in% dimnames(data2)[[2]]) +same.col.names.match1 <- match(dimnames(data1)[[2]], dimnames(data2)[[2]]) +} +if(any(dimnames(data2)[[2]] %in% dimnames(data1)[[2]])){ +any.id.col.name <- TRUE +same.col.names.pos2 <- which(dimnames(data2)[[2]] %in% dimnames(data1)[[2]]) +same.col.names.match2 <- match(dimnames(data2)[[2]], dimnames(data1)[[2]]) +} +if(any.id.col.name == TRUE){ +common.col.names <- unique(c(dimnames(data1)[[2]][same.col.names.pos1], dimnames(data2)[[2]][same.col.names.pos2])) +} +} +} +# identical row and col content +if(all(class(data1) == "table")){ +data1 <- as.data.frame(matrix(data1, ncol = ncol(data1)), stringsAsFactors = FALSE) # conversion of table into data frame to facilitate inter class comparison +}else if(all(class(data1) %in% c("matrix", "array"))){ +data1 <- as.data.frame(data1, stringsAsFactors = FALSE) # conversion of matrix into data frame to facilitate inter class comparison +}else if(all(class(data1) == "data.frame")){ +# data1 <- data.frame(lapply(data1, as.character), stringsAsFactors = FALSE) # conversion of columns into characters +} +if(all(class(data2) == "table")){ +data2 <- as.data.frame(matrix(data2, ncol = ncol(data2)), stringsAsFactors = FALSE) # conversion of table into data frame to facilitate inter class comparison +}else if(all(class(data2) %in% c("matrix", "array"))){ +data2 <- as.data.frame(data2, stringsAsFactors = FALSE) # conversion of matrix into data frame to facilitate inter class comparison +}else if(all(class(data2) == "data.frame")){ +# data2 <- data.frame(lapply(data2, as.character), stringsAsFactors = FALSE) # conversion of columns into characters +} +row.names(data1) <- paste0("A", 1:nrow(data1)) +row.names(data2) <- paste0("A", 1:nrow(data2)) +if(same.col.nb == TRUE){ # because if not the same col nb, the row cannot be identical +if(all(sapply(data1, FUN = typeof) == "integer") & all(sapply(data2, FUN = typeof) == "integer") & as.double(nrow(data1)) * nrow(data2) <= 1e10){ # fast method for integers (thus not data frames). as.double(nrow(data1)) to prevent integer overflow because R is 32 bits for integers +tempo1 <- c(as.data.frame(t(data1), stringsAsFactors = FALSE)) # conversion into list. This work fast with only integers (because 32 bits) +tempo2 <- c(as.data.frame(t(data2), stringsAsFactors = FALSE)) # conversion into list. This work fast with only integers (because 32 bits) +same.row.pos1 <- which(tempo1 %in% tempo2) +same.row.pos2 <- which(tempo2 %in% tempo1) +same.row.match1 <- match(tempo1, tempo2) +same.row.match2 <- match(tempo2, tempo1) +}else if(as.double(nrow(data1)) * nrow(data2) <= 1e6){ # as.double(nrow(data1)) to prevent integer overflow because R is 32 bits for integers +# inactivated because I would like to keep the mode during comparisons +# if(col.nb <= 10){ # if ncol is not to big, the t() should not be that long +# tempo1 <- c(as.data.frame(t(data1), stringsAsFactors = FALSE)) # conversion into list. This work fast with only integers (because 32 bits) +# tempo2 <- c(as.data.frame(t(data2), stringsAsFactors = FALSE)) # conversion into list. +# same.row.pos1 <- which(tempo1 %in% tempo2) +# same.row.pos2 <- which(tempo2 %in% tempo1) +# same.row.match1 <- match(tempo1, tempo2) +# same.row.match2 <- match(tempo2, tempo1) +# }else{ +# very long computation +same.row.pos1 <- logical(length = nrow(data1)) # FALSE by default +same.row.pos1[] <- FALSE # security +same.row.pos2 <- logical(length = nrow(data2)) # FALSE by default +same.row.pos2[] <- FALSE # security +same.row.match1 <- rep(NA, nrow(data1)) +same.row.match2 <- rep(NA, nrow(data2)) +for(i3 in 1:nrow(data1)){ +for(i4 in 1:nrow(data2)){ +tempo1 <- data1[i3, ] +tempo2 <- data2[i4, ] +rownames(tempo1) <- NULL # to have same row and column names +colnames(tempo1) <- NULL # to have same row and column names +rownames(tempo2) <- NULL # to have same row and column names +colnames(tempo2) <- NULL # to have same row and column names +if(identical(tempo1, tempo2)){ +same.row.pos1[i3] <- TRUE +same.row.pos2[i4] <- TRUE +same.row.match1[i3] <- i4 +same.row.match2[i4] <- i3 +} +} +} +same.row.pos1 <- which(same.row.pos1) +same.row.pos2 <- which(same.row.pos2) +# } +}else{ +same.row.pos1 <- "TOO BIG FOR EVALUATION" +same.row.pos2 <- "TOO BIG FOR EVALUATION" +same.row.match1 <- "TOO BIG FOR EVALUATION" +same.row.match2 <- "TOO BIG FOR EVALUATION" +} -fun_codon2aa <- function( - data, - display = FALSE -){ - # AIM - # Convert codon to amino acid using standard genetic code indicated in https://en.wikipedia.org/wiki/DNA_and_RNA_codon_tables - # WARNINGS - # None - # ARGUMENTS - # data: single caracter string of three characters, or vector of three caracters, indicating the DNA codon (only "A", "T", "G" and "C" allowed). Case insensitive. Omitted if display argument is TRUE - # display: logical. Display the whole genetic table? if TRUE, override data - # RETURN - # The 1 letter uppercase amino acid of the submitted codon or the whole table if display argument is TRUE - # REQUIRED PACKAGES - # None - # REQUIRED FUNCTIONS FROM THE cute PACKAGE - # fun_check() - # EXAMPLE - # fun_codon2aa(data = "ATC", display = TRUE) - # see http - # DEBUGGING - # data = "atg" ; display = FALSE - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - req.function <- c( - "fun_check" - ) - tempo <- NULL - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo <- c(tempo, i1) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # reserved words - # end reserved words - # arg with no default values - mandat.args <- c( - "data" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data, class = "vector", typeof = "character", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = display, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ # normally no NA - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "data", - "display" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){# normally no NA with is.null() - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # code that protects set.seed() in the global environment - # end code that protects set.seed() in the global environment - # warning initiation - # end warning initiation - # other checkings - if(length(data) == 1L){ - data <- unlist(strsplit(data, split = "")) - }else if(length(data) != 3L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A STRING OF THREE CHARACTERS OR A VECTOR OF THREE CHARACTERS, MADE OF \"A\", \"C\", \"G\", \"T\" ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! all(toupper(data) %in% c("A", "C", "G","T"))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A STRING OF THREE CHARACTERS OR A VECTOR OF THREE CHARACTERS, MADE OF \"A\", \"C\", \"G\", \"T\" ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end other checkings - # reserved word checking - # end reserved word checking - # end second round of checking and data preparation - # package checking - # end package checking - # main code - # standard genetic code - sgc <- array( - c( - "F", "L", "I", "V", - "S", "P", "T", "A", - "Y", "H", "N", "D", - "C", "R", "S", "G", - - "F", "L", "I", "V", - "S", "P", "T", "A", - "Y", "H", "N", "D", - "C", "R", "S", "G", - - "L", "L", "I", "V", - "S", "P", "T", "A", - "stop", "Q", "K", "E", - "stop", "R", "R", "G", - - "L", "L", "M", "V", - "S", "P", "T", "A", - "stop", "Q", "K", "E", - "W", "R", "R", "G" - ), - dim = c(4, 4, 4), - dimnames = list( - first = c("T", "C", "A", "G"), - second = c("T", "C", "A", "G"), - third = c("T", "C", "A", "G") - ) - ) - # end standard genetic code - if(display == TRUE){ - output <- sgc - }else{ - data <- toupper(data) - output <- eval(parse(text = paste0("sgc['", paste0(data, collapse = "','"), "']"))) - } - return(output) +names(same.row.pos1) <- NULL +names(same.row.pos2) <- NULL +if(all(is.na(same.row.pos1))){ +same.row.pos1 <- NULL +}else{ +same.row.pos1 <- same.row.pos1[ ! is.na(same.row.pos1)] +any.id.row <- TRUE +} +if(all(is.na(same.row.pos2))){ +same.row.pos2 <- NULL +}else{ +same.row.pos2 <- same.row.pos2[ ! is.na(same.row.pos2)] +any.id.row <- TRUE +} +if(is.null(same.row.pos1) & is.null(same.row.pos2)){ +any.id.row <- FALSE +}else if(length(same.row.pos1) == 0L & length(same.row.pos2) == 0L){ +any.id.row <- FALSE +}else if(all(same.row.pos1 == "TOO BIG FOR EVALUATION") & all(same.row.pos2 == "TOO BIG FOR EVALUATION")){ +any.id.row <- NULL +} +}else{ +any.id.row <- FALSE +# same.row.pos1 and 2 remain NULL +} +if(same.row.nb == TRUE){ # because if not the same row nb, the col cannot be identical +if(as.double(ncol(data1)) * ncol(data2) <= 1e10){ # comparison of data frame columns is much easier than rows because no need to use t() before converting to list for fast comparison. as.double(ncol(data1)) to prevent integer overflow because R is 32 bits for integers +# if(all(sapply(data1, FUN = typeof) == "integer") & all(sapply(data2, FUN = typeof) == "integer") & as.double(ncol(data1)) * ncol(data2) <= 1e10){ # fast method for integers (thus not data frames). as.double(ncol(data1)) to prevent integer overflow because R is 32 bits for integers +tempo1 <- c(data1) +tempo2 <- c(data2) +same.col.pos1 <- which(tempo1 %in% tempo2) +same.col.pos2 <- which(tempo2 %in% tempo1) +same.col.match1 <- match(tempo1, tempo2) +same.col.match2 <- match(tempo2, tempo1) +# }else if(as.double(ncol(data1)) * ncol(data2) <= 1e6){ # as.double(ncol(data1)) to prevent integer overflow because R is 32 bits for integers +# same.col.pos1 <- logical(length = ncol(data1)) # FALSE by default +# same.col.pos1[] <- FALSE # security +# same.col.pos2 <- logical(length = ncol(data2)) # FALSE by default +# same.col.pos2[] <- FALSE # security +# same.col.match1 <- rep(NA, ncol(data1)) +# same.col.match2 <- rep(NA, ncol(data2)) +# for(i3 in 1:ncol(data1)){ +# for(i4 in 1:ncol(data2)){ +# if(identical(data1[ , i3], data2[ , i4])){ +# same.col.pos1[i3] <- TRUE +# same.col.pos2[i4] <- TRUE +# same.col.match1[i3] <- i4 +# same.col.match2[i4] <- i3 +# } +# } +# } +# same.col.pos1 <- which(same.col.pos1) +# same.col.pos2 <- which(same.col.pos2) +}else{ +same.col.pos1 <- "TOO BIG FOR EVALUATION" +same.col.pos2 <- "TOO BIG FOR EVALUATION" +} +names(same.col.pos1) <- NULL +names(same.col.pos2) <- NULL +if(all(is.na(same.col.pos1))){ +same.col.pos1 <- NULL +}else{ +same.col.pos1 <- same.col.pos1[ ! is.na(same.col.pos1)] +any.id.col <- TRUE +} +if(all(is.na(same.col.pos2))){ +same.col.pos2 <- NULL +}else{ +same.col.pos2 <- same.col.pos2[ ! is.na(same.col.pos2)] +any.id.col <- TRUE +} +if(is.null(same.col.pos1) & is.null(same.col.pos2)){ +any.id.col <- FALSE +}else if(length(same.col.pos1) == 0L & length(same.col.pos2) == 0L){ +any.id.col <- FALSE +}else if(all(same.col.pos1 == "TOO BIG FOR EVALUATION") & all(same.col.pos2 == "TOO BIG FOR EVALUATION")){ +any.id.col <- NULL +} +}else{ +any.id.col <- FALSE +# same.col.pos1 and 2 remain NULL +} +if(same.dim == TRUE){ +names(data1) <- NULL +row.names(data1) <- NULL +names(data2) <- NULL +row.names(data2) <- NULL +if(identical(data1, data2)){ +identical.content <- TRUE +}else{ +identical.content <- FALSE +} +}else{ +identical.content <- FALSE +} +} +output <- list(same.class = same.class, class = class, same.dim = same.dim, dim = dim, same.row.nb = same.row.nb, row.nb = row.nb, same.col.nb = same.col.nb , col.nb = col.nb, same.row.name = same.row.name, row.name = row.name, any.id.row.name = any.id.row.name, same.row.names.pos1 = same.row.names.pos1, same.row.names.pos2 = same.row.names.pos2, same.row.names.match1 = same.row.names.match1, same.row.names.match2 = same.row.names.match2, common.row.names = common.row.names, same.col.name = same.col.name, col.name = col.name,any.id.col.name = any.id.col.name, same.col.names.pos1 = same.col.names.pos1, same.col.names.pos2 = same.col.names.pos2, same.col.names.match1 = same.col.names.match1, same.col.names.match2 = same.col.names.match2, common.col.names = common.col.names, any.id.row = any.id.row, same.row.pos1 = same.row.pos1, same.row.pos2 = same.row.pos2, same.row.match1 = same.row.match1, same.row.match2 = same.row.match2, any.id.col = any.id.col, same.col.pos1 = same.col.pos1, same.col.pos2 = same.col.pos2, same.col.match1 = same.col.match1, same.col.match2 = same.col.match2, identical.object = identical.object, identical.content = identical.content) +return(output) } -######## fun_codon_finder() #### gives the codon number and position in the codon of nucleotid positions +######## fun_comp_list() #### comparison of two lists -fun_codon_finder <- function( - pos, - begin, - end -){ - # AIM - # gives the codon number and position in the codon of nucleotid positions - # WARNINGS - # Only for coding sequences (no introns): ((end - begin) + 1) / 3 must be an integer (i.e., modulo zero) - # Negatives positions allowed but this implies that one base has the position 0 in the sequence - # ARGUMENTS - # pos: vector of integers indicating the positions of nucleotids in a sequence. Must be between begin and end arguments - # begin: single integer indicating the position of the first base of the coding sequence - # end: single indicating the position of the last base of the coding sequence - # RETURN - # a data frame with column names: - # pos: values of the pos argument - # codon_nb: the codon number in the CDS encompassing the pos value - # codon_pos: the position of pos in the codon (either 1, 2 or 3) - # codon_begin: the first base position of the codon - # codon_end: the last base position of the codon - # REQUIRED PACKAGES - # None - # REQUIRED FUNCTIONS FROM THE cute PACKAGE - # fun_check() - # EXAMPLE - # fun_codon_finder(c(5, 6, 8, 10), begin = 5, end = 10) - # fun_codon_finder(c(0, 5, 6, 8, 10), begin = -2, end = 12) - # see http - # DEBUGGING - # pos = c(5, 6, 8, 10) ; begin = 5 ; end = 10 - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - req.function <- c( - "fun_check" - ) - tempo <- NULL - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo <- c(tempo, i1) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # reserved words - # end reserved words - # arg with no default values - mandat.args <- c( - "pos", - "begin", - "end" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = pos, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = begin, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = end, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ # normally no NA - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "pos", - "begin", - "end" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){# normally no NA with is.null() - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # code that protects set.seed() in the global environment - # end code that protects set.seed() in the global environment - # warning initiation - # end warning initiation - # other checkings - if(begin >= end){ - tempo.cat <- paste0("ERROR IN ", function.name, ": end ARGUMENT MUST BE STRICTLY GREATER THAN begin ARGUMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if((end - begin + 1) %% 3 != 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ((end - begin) + 1) / 3 MUST BE AN INTEGER (I.E., MODULO ZERO)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(pos < begin | pos > end)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": pos ARGUMENT VALUES MUST BE BETWEEN begin AND end ARGUMENT VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end other checkings - # reserved word checking - # end reserved word checking - # end second round of checking and data preparation - # package checking - # end package checking - # main code - first <- seq.int(from = begin, to = end, by = 3) - last <- seq.int(from = begin + 2, to = end, by = 3) - tempo <- lapply(X = pos, FUN = function(x = X){ - tempo.log <- x >= first & x <= last - if(sum(tempo.log, na.rm = TRUE) != 1){ # check that 1 possible TRUE - tempo.cat <- paste0("ERROR IN ", function.name, ": INTERNAL ERROR. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - codon_nb <- which(tempo.log) - codon_pos <- as.integer((x - (begin + (codon_nb - 1) * 3) + 1)) - codon_begin <- as.integer(first[tempo.log]) - codon_end <- as.integer(last[tempo.log]) - } - return(data.frame(codon_nb = codon_nb, codon_pos = codon_pos, codon_begin = codon_begin, codon_end = codon_end)) - }) - tempo <- do.call("rbind", tempo) - output <- data.frame(pos = as.integer(pos), tempo) - return(output) +fun_comp_list <- function(data1, data2){ +# AIM +# compare two lists. Check and report in a list if the 2 datasets have: +# same length +# common names +# common compartments +# ARGUMENTS +# data1: list +# data2: list +# RETURN +# a list containing: +# $same.length: logical. Are number of elements identical? +# $length: number of elements in the 2 datasets (NULL otherwise) +# $same.names: logical. Are element names identical ? +# $name: name of elements of the 2 datasets if identical (NULL otherwise) +# $any.id.name: logical. Is there any element names identical ? +# $same.names.pos1: positions, in data1, of the element names identical in data2 +# $same.names.pos2: positions, in data2, of the compartment names identical in data1 +# $any.id.compartment: logical. is there any identical compartments ? +# $same.compartment.pos1: positions, in data1, of the compartments identical in data2 +# $same.compartment.pos2: positions, in data2, of the compartments identical in data1 +# $identical.object: logical. Are objects identical (kind of object, compartment names and content)? +# $identical.content: logical. Are content objects identical (identical compartments excluding compartment names)? +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# none +# EXAMPLES +# obs1 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; obs2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; fun_comp_list(obs1, obs2) +# obs1 = list(1:5, LETTERS[1:2]) ; obs2 = list(a = 1:5, b = LETTERS[1:2]) ; fun_comp_list(obs1, obs2) +# obs1 = list(b = 1:5, c = LETTERS[1:2]) ; obs2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; fun_comp_list(obs1, obs2) +# obs1 = list(b = 1:5, c = LETTERS[1:2]) ; obs2 = list(LETTERS[5:9], matrix(1:6), 1:5) ; fun_comp_list(obs1, obs2) +# DEBUGGING +# data1 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) ; data2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) # for function debugging +# data1 = list(a = 1:5, b = LETTERS[1:2]) ; data2 = list(a = 1:5, b = LETTERS[1:2], d = matrix(1:6)) # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# argument checking +if( ! any(class(data1) %in% "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! any(class(data2) %in% "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) # activate this line and use the function to check arguments status +# end argument checking +# main code +same.length <- NULL +length <- NULL +same.names <- NULL +name <- NULL +any.id.name <- NULL +same.names.pos1 <- NULL +same.names.pos2 <- NULL +any.id.compartment <- NULL +same.compartment.pos1 <- NULL +same.compartment.pos2 <- NULL +identical.object <- NULL +identical.content <- NULL +if(identical(data1, data2)){ +same.length <- TRUE +length <- length(data1) +if( ! is.null(names(data1))){ +same.names <- TRUE +name <- names(data1) +any.id.name <- TRUE +same.names.pos1 <- 1:length(data1) +same.names.pos2 <- 1:length(data2) +} +any.id.compartment <- TRUE +same.compartment.pos1 <- 1:length(data1) +same.compartment.pos2 <- 1:length(data2) +identical.object <- TRUE +identical.content <- TRUE +}else{ +identical.object <- FALSE +if( ! identical(length(data1), length(data2))){ +same.length<- FALSE +}else{ +same.length<- TRUE +length <- length(data1) +} +if( ! (is.null(names(data1)) & is.null(names(data2)))){ +if( ! identical(names(data1), names(data2))){ +same.names <- FALSE +}else{ +same.names <- TRUE +name <- names(data1) +} +any.id.name <- FALSE +if(any(names(data1) %in% names(data2))){ +any.id.name <- TRUE +same.names.pos1 <- which(names(data1) %in% names(data2)) +} +if(any(names(data2) %in% names(data1))){ +any.id.name <- TRUE +same.names.pos2 <- which(names(data2) %in% names(data1)) +} +} +names(data1) <- NULL +names(data2) <- NULL +any.id.compartment <- FALSE +if(any(data1 %in% data2)){ +any.id.compartment <- TRUE +same.compartment.pos1 <- which(data1 %in% data2) +} +if(any(data2 %in% data1)){ +any.id.compartment <- TRUE +same.compartment.pos2 <- which(data2 %in% data1) +} +if(same.length == TRUE & ! all(is.null(same.compartment.pos1), is.null(same.compartment.pos2))){ +if(identical(same.compartment.pos1, same.compartment.pos2)){ +identical.content <- TRUE +}else{ +identical.content <- FALSE +} +}else{ +identical.content <- FALSE +} +} +output <- list(same.length = same.length, length = length, same.names = same.names, name = name, any.id.name = any.id.name, same.names.pos1 = same.names.pos1, same.names.pos2 = same.names.pos2, any.id.compartment = any.id.compartment, same.compartment.pos1 = same.compartment.pos1, same.compartment.pos2 = same.compartment.pos2, identical.object = identical.object, identical.content = identical.content) +return(output) } -################ Graphics management - - -# this order can be used: -# fun_width() -# fun_open() -# fun_prior_plot() # not for ggplot2 -# plot() or any other plotting -# fun_post_plot() if fun_prior_plot() has been used # not for ggplot2 -# fun_close() - +######## fun_test() #### test combinations of argument values of a function and return errors (and graphs) -######## fun_width() #### window width depending on classes to plot +# add traceback https://stackoverflow.com/questions/47414119/how-to-read-a-traceback-in-r -fun_width <- function( - class.nb, - inches.per.class.nb = 1, - ini.window.width = 7, - inch.left.space, - inch.right.space, - boundarie.space = 0.5 +fun_test <- function( +fun, +arg, +val, +expect.error = NULL, +parall = FALSE, +thread.nb = NULL, +print.count = 10, +plot.fun = FALSE, +export = FALSE, +res.path = NULL, +lib.path = NULL, +cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" ){ - # AIM - # rescale the width of a window to open depending on the number of classes to plot - # can be used for height, considering that it is as if it was a width - # this order can be used: - # fun_width() - # fun_open() - # fun_prior_plot() # not for ggplot2 - # plot() or any other plotting - # fun_post_plot() if fun_prior_plot() has been used # not for ggplot2 - # fun_close() - # ARGUMENTS - # class.nb: number of class to plot - # inches.per.class.nb: number of inches per unit of class.nb. 2 means 2 inches for each boxplot for instance - # ini.window.width:initial window width in inches - # inch.left.space: left horizontal margin of the figure region (in inches) - # inch.right.space: right horizontal margin of the figure region (in inches) - # boundarie.space: space between the right and left limits of the plotting region and the plot (0.5 means half a class width) - # RETURN - # the new window width in inches - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_width(class.nb = 10, inches.per.class.nb = 0.2, ini.window.width = 7, inch.left.space = 1, inch.right.space = 1, boundarie.space = 0.5) - # DEBUGGING - # class.nb = 10 ; inches.per.class.nb = 0.2 ; ini.window.width = 7 ; inch.left.space = 1 ; inch.right.space = 1 ; boundarie.space = 0.5 # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = class.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = inches.per.class.nb, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = ini.window.width, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = inch.left.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = inch.right.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = boundarie.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - range.max <- class.nb + boundarie.space # the max range of the future plot - range.min <- boundarie.space # the min range of the future plot - window.width <- inch.left.space + inch.right.space + inches.per.class.nb * (range.max - range.min) - return(window.width) +# AIM +# test combinations of argument values of a function +# WARNINGS +# Limited to 43 arguments with at least 2 values each. The total number of arguments tested can be more if the additional arguments have a single value. The limit is due to nested "for" loops (https://stat.ethz.ch/pipermail/r-help/2008-March/157341.html), but it should not be a problem since the number of tests would be 2^43 > 8e12 +# ARGUMENTS +# fun: character string indicating the name of the function tested (without brackets) +# arg: vector of character strings of arguments of fun. At least arguments that do not have default values must be present in this vector +# val: list with number of compartments equal to length of arg, each compartment containing values of the corresponding argument in arg. Each different value must be in a list or in a vector. For instance, argument 3 in arg is a logical argument (values accepted TRUE, FALSE, NA). Thus, compartment 3 of val can be either list(TRUE, FALSE, NA), or c(TRUE, FALSE, NA). NULL value alone must be written list(NULL) +# expect.error: list of exactly the same structure as val argument, but containing FALSE or TRUE, depending on whether error is expected (TRUE) or not (FALSE) for each corresponding value of val. A message is returned depending on discrepancies between the expected and observed errors. BEWARE: not always possible to write the expected errors for all the combination of argument values. Ignored if NULL +# parall: logical. Force parallelization ? +# thread.nb: numeric value indicating the number of threads to use if ever parallelization is required. If NULL, all the available threads will be used. Ignored if parall is FALSE +# print.count: interger value. Print a working progress message every print.count during loops. BEWARE: can increase substentially the time to complete the process using a small value, like 10 for instance. Use Inf is no loop message desired +# plot.fun: logical. Plot the plotting function tested for each test? +# export: logical. Export the results into a .RData file and into a .txt file? If FALSE, return a list into the console (see below). BEWARE: will be automatically set to TRUE if parall is TRUE. This means that when using parallelization, the results are systematically exported, not returned into the console +# res.path: character string indicating the absolute pathway of folder where the txt results and pdfs, containing all the plots, will be saved. Several txt and pdf, one per thread, if parallelization. Ignored if export is FALSE. Must be specified if parall is TRUE or if export is TRUE +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL +# cute.path: character string indicating the absolute path of the cute.R file. Will be remove when cute will be a package. Ignored if parall is FALSE +# REQUIRED PACKAGES +# lubridate +# parallel if parall arguemtn is TRUE (included in the R installation packages but not automatically loaded) +# pdftools if parall arguemtn is TRUE (included in the R installation packages but not automatically loaded) +# If the tested function is in a package, this package must be imported first (no parallelization) or must be in the classical R package folder indicated by the lib.path argument (parallelization) +# RETURN +# if export is FALSE a list containing: +# $fun: the tested function +# $instruction: the initial instruction +# $sys.info: system and packages info +# $data: a data frame of all the combination tested, containing the following columns: +# the different values tested, named by arguments +# $kind: a vector of character strings indicating the kind of test result: either "ERROR", or "WARNING", or "OK" +# $problem: a logical vector indicating if error or not +# $expected.error: optional logical vector indicating the expected error specified in the expect.error argument +# $message: either NULL if $kind is always "OK", or the messages +# if export is TRUE 1) the same list object into a .RData file, 2) also the $data data frame into a .txt file, and 3) if expect.error is non NULL and if any discrepancy, the $data data frame into a .txt file but containing only the rows with discrepancies between expected and observed errors +# one or several pdf if a plotting function is tested and if the plot.fun argument is TRUE +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_get_message() +# fun_pack() +# EXAMPLES +# fun_test(fun = "unique", arg = c("x", "incomparables"), val = list(x = list(1:10, c(1,1,2,8), NA), incomparable = c(TRUE, FALSE, NA))) +# fun_test(fun = "fun_round", arg = c("data", "dec.nb", "after.lead.zero"), val = list(L1 = list(c(1, 1.0002256, 1.23568), "a", NA), L2 = list(2, c(1,3), NA), L3 = c(TRUE, FALSE, NA))) +# fun_test(fun = "plot", arg = c("x", "y"), val = list(x = list(1:10, 12:13, NA, (1:10)^2), y = list(1:10, NA, NA)), expect.error = list(x = list(FALSE, TRUE, TRUE, FALSE), y = list(FALSE, TRUE, TRUE)), parall = FALSE, thread.nb = NULL, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = NULL) +# fun_test(fun = "plot", arg = c("x", "y"), val = list(x = list(1:10, 12:13, NA, (1:10)^2), y = list(1:10, NA, NA)), parall = FALSE, thread.nb = 4, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = "C:\\Program Files\\R\\R-4.0.2\\library\\") +# set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun_test(fun = "fun_gg_boxplot", arg = c("data1", "y", "categ"), val = list(L1 = list(L1 = obs1), L2 = list(L1 = "Time"), L3 = list(L1 = "Group1"))) +# set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun_test(fun = "fun_gg_boxplot", arg = c("data1", "y", "categ"), val = list(L1 = list(obs1), L2 = "Time", L3 = "Group1"), parall = FALSE, thread.nb = NULL, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = "C:\\Program Files\\R\\R-4.0.2\\library\\") +# library(ggplot2) ; fun_test(fun = "geom_histogram", arg = c("data", "mapping"), val = list(x = list(data.frame(X = "a", stringsAsFactors = TRUE)), y = list(ggplot2::aes(x = X))), parall = FALSE, thread.nb = NULL, plot.fun = TRUE, res.path = "C:\\Users\\Gael\\Desktop\\", lib.path = "C:\\Program Files\\R\\R-4.0.2\\library\\") # BEWARE: ggplot2::geom_histogram does not work +# DEBUGGING +# fun = "unique" ; arg = "x" ; val = list(x = list(1:10, c(1,1,2,8), NA)) ; expect.error = list(x = list(FALSE, FALSE, TRUE)) ; parall = FALSE ; thread.nb = NULL ; plot.fun = FALSE ; export = FALSE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL ; print.count = 1 ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" # for function debugging +# fun = "unique" ; arg = c("x", "incomparables") ; val = list(x = list(1:10, c(1,1,2,8), NA), incomparable = c(TRUE, FALSE, NA)) ; expect.error = NULL ; parall = FALSE ; thread.nb = 2 ; plot.fun = FALSE ; export = TRUE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL ; print.count = 10 ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" # for function debugging +# fun = "plot" ; arg = c("x", "y") ; val = list(x = list(1:10, 12:13, NA), y = list(1:10, NA, NA)) ; expect.error = list(x = list(FALSE, FALSE, TRUE, FALSE), y = list(FALSE, TRUE, TRUE)) ; print.count = 10 ; parall = FALSE ; thread.nb = NULL ; plot.fun = TRUE ; export = TRUE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL # for function debugging +# set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun = "fun_gg_boxplot" ; arg = c("data1", "y", "categ") ; val = list(L1 = list(L1 = obs1), L2 = list(L1 = "Time"), L3 = list(L1 = "Group1")) ; expect.error = NULL ; print.count = 10 ; parall = FALSE ; thread.nb = NULL ; plot.fun = TRUE ; export = TRUE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL # for function debugging +# fun = "unique" ; arg = "x" ; val = list(list(1:3, mean)) ; expect.error = list(TRUE, TRUE) ; parall = FALSE ; thread.nb = NULL ; plot.fun = FALSE ; export = FALSE ; res.path = "C:\\Users\\Gael\\Desktop\\" ; lib.path = NULL ; print.count = 1 ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +req.function <- c( +"fun_check", +"fun_get_message", +"fun_pack" +) +tempo <- NULL +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo <- c(tempo, i1) } - - -######## fun_open() #### open a GUI or pdf graphic window - - -fun_open <- function( - pdf = TRUE, - pdf.path = "working.dir", - pdf.name = "graph", - width = 7, - height = 7, - paper = "special", - pdf.overwrite = FALSE, - rescale = "fixed", - remove.read.only = TRUE, - return.output = FALSE -){ - # AIM - # open a pdf or screen (GUI) graphic window and return initial graphic parameters - # this order can be used: - # fun_width() - # fun_open() - # fun_prior_plot() # not for ggplot2 - # plot() or any other plotting - # fun_post_plot() if fun_prior_plot() has been used # not for ggplot2 - # fun_close() - # WARNINGS - # On Linux, use pdf = TRUE, if (GUI) graphic window is not always available, meaning that X is not installed (clusters for instance). Use X11() in R to test if available - # ARGUMENTS: - # pdf: logical. Use pdf display? If FALSE, a GUI is opened - # pdf.path: where the pdf is saved (do not terminate by / or \\). Write "working.dir" if working directory is required (default). Ignored if pdf == FALSE - # pdf.name: name of the pdf file containing the graphs (the .pdf extension is added by the function, if not detected in the name end). Ignored if pdf == FALSE - # width: width of the window (in inches) - # height: height of the window (in inches) - # paper: paper argument of the pdf function (paper format). Only used for pdf(). Either "a4", "letter", "legal", "us", "executive", "a4r", "USr" or "special". If "special", means that the paper dimension will be width and height. With another paper format, if width or height is over the size of the paper, width or height will be modified such that the plot is adjusted to the paper dimension (see $dim in the returned list below to see the modified dimensions). Ignored if pdf == FALSE - # pdf.overwrite: logical. Existing pdf can be overwritten? . Ignored if pdf == FALSE - # rescale: kind of GUI. Either "R", "fit", or "fixed". Ignored on Mac and Linux OS. See ?windows for details - # remove.read.only: logical. remove the read only (R.O.) graphical parameters? If TRUE, the graphical parameters are returned without the R.O. parameters. The returned $ini.par list can be used to set the par() of a new graphical device. If FALSE, graphical parameters are returned with the R.O. parameters, which provides information like text dimension (see ?par() ). The returned $ini.par list can be used to set the par() of a new graphical device, but generate a warning message. Ignored if return.output == FALSE. - # return.output: logical. Return output ? If TRUE the output list is displayed - # RETURN - # a list containing: - # $pdf.loc: path of the pdf created - # $ini.par: initial par() parameters - # $zone.ini: initial window spliting - # $dim: dimension of the graphical device (in inches) - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_open(pdf = FALSE, pdf.path = "C:/Users/Gael/Desktop", pdf.name = "graph", width = 7, height = 7, paper = "special", pdf.overwrite = FALSE, return.output = TRUE) - # DEBUGGING - # pdf = TRUE ; pdf.path = "C:/Users/Gael/Desktop" ; pdf.name = "graphs" ; width = 7 ; height = 7 ; paper = "special" ; pdf.overwrite = FALSE ; rescale = "fixed" ; remove.read.only = TRUE ; return.output = TRUE # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = pdf, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = pdf.path, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = pdf.name, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = width, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = height, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = paper, options = c("a4", "letter", "legal", "us", "executive", "a4r", "USr", "special", "A4", "LETTER", "LEGAL", "US"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data =pdf.overwrite, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = rescale, options = c("R", "fit", "fixed"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = remove.read.only, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return.output, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - if(pdf.path == "working.dir"){ - pdf.path <- getwd() - }else{ - if(grepl(x = pdf.path, pattern = ".+/$")){ - pdf.path <- sub(x = pdf.path, pattern = "/$", replacement = "") # remove the last / - }else if(grepl(x = pdf.path, pattern = ".+[\\]$")){ # or ".+\\\\$" # cannot be ".+\$" because \$ does not exist contrary to \n - pdf.path <- sub(x = pdf.path, pattern = "[\\]$", replacement = "") # remove the last / - } - if(dir.exists(pdf.path) == FALSE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\npdf.path ARGUMENT DOES NOT CORRESPOND TO EXISTING DIRECTORY\n", pdf.path) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # par.ini recovery - # cannot use pdf(file = NULL), because some small differences between pdf() and other devices. For instance, differences with windows() for par()$fin, par()$pin and par()$plt - if(Sys.info()["sysname"] == "Windows"){ # Note that .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows - open.fail <- NULL - grDevices::windows() - ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - invisible(dev.off()) # close the new window - }else if(Sys.info()["sysname"] == "Linux"){ - if(pdf == TRUE){# cannot use pdf(file = NULL), because some small differences between pdf() and other devices. For instance, differences with windows() for par()$fin, par()$pin and par()$plt - if(exists(".Random.seed", envir = .GlobalEnv)){ # if .Random.seed does not exists, it means that no random operation has been performed yet in any R environment - tempo.random.seed <- .Random.seed - on.exit(assign(".Random.seed", tempo.random.seed, env = .GlobalEnv)) - }else{ - on.exit(set.seed(NULL)) # inactivate seeding -> return to complete randomness - } - set.seed(NULL) - tempo.code <- sample(x = 1:1e7, size = 1) - while(file.exists(paste0(pdf.path, "/recover_ini_par", tempo.code, ".pdf")) == TRUE){ - tempo.code <- tempo.code + 1 - } - grDevices::pdf(width = width, height = height, file=paste0(pdf.path, "/recover_ini_par", tempo.code, ".pdf"), paper = paper) - ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - invisible(dev.off()) # close the pdf window - file.remove(paste0(pdf.path, "/recover_ini_par", tempo.code, ".pdf")) # remove the pdf file - }else{ - # test if X11 can be opened - if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nTHIS FUNCTION CANNOT BE USED ON LINUX IF A Rplots.pdf FILE ALREADY EXISTS HERE\n", getwd()) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - open.fail <- suppressWarnings(try(grDevices::X11(), silent = TRUE))[] # try to open a X11 window. If open.fail == NULL, no problem, meaning that the X11 window is opened. If open.fail != NULL, a pdf can be opened here paste0(getwd(), "/Rplots.pdf") - if(is.null(open.fail)){ - ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - invisible(dev.off()) # close the new window - }else if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ - file.remove(paste0(getwd(), "/Rplots.pdf")) # remove the pdf file - tempo.cat <- ("ERROR IN fun_open()\nTHIS FUNCTION CANNOT OPEN GUI ON LINUX OR NON MACOS UNIX SYSTEM\nTO OVERCOME THIS, EITHER SET THE X GRAPHIC INTERFACE OF THE SYSTEM OR SET THE pdf ARGUMENT OF THE fun_open() FUNCTION TO TRUE AND RERUN") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - } - }else{ - open.fail <- NULL - grDevices::quartz() - ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - invisible(dev.off()) # close the new window - } - # end par.ini recovery - zone.ini <- matrix(1, ncol=1) # to recover the initial parameters for next figure region when device region split into several figure regions - if(pdf == TRUE){ - if(grepl(x = pdf.name, pattern = "\\.pdf$")){ - pdf.name <- sub(x = pdf.name, pattern = "\\.pdf$", replacement = "") # remove the last .pdf - } - pdf.loc <- paste0(pdf.path, "/", pdf.name, ".pdf") - if(file.exists(pdf.loc) == TRUE & pdf.overwrite == FALSE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\npdf.loc FILE ALREADY EXISTS AND CANNOT BE OVERWRITTEN DUE TO pdf.overwrite ARGUMENT SET TO TRUE\n", pdf.loc) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - grDevices::pdf(width = width, height = height, file=pdf.loc, paper = paper) - } - }else if(pdf == FALSE){ - pdf.loc <- NULL - if(Sys.info()["sysname"] == "Windows"){ # .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows - grDevices::windows(width = width, height = height, rescale = rescale) - }else if(Sys.info()["sysname"] == "Linux"){ - if( ! is.null(open.fail)){ - tempo.cat <- "ERROR IN fun_open()\nTHIS FUNCTION CANNOT OPEN GUI ON LINUX OR NON MACOS UNIX SYSTEM\nTO OVERCOME THIS, EITHER SET THE X GRAPHIC INTERFACE OF THE SYSTEM OR SET THE pdf ARGUMENT OF THE fun_open() FUNCTION TO TRUE AND RERUN" - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - grDevices::X11(width = width, height = height) - } - }else{ - grDevices::quartz(width = width, height = height) - } - } - if(return.output == TRUE){ - output <- list(pdf.loc = pdf.loc, ini.par = ini.par, zone.ini = zone.ini, dim = dev.size()) - return(output) - } } - - -######## fun_prior_plot() #### set graph param before plotting (erase axes for instance) - - -fun_prior_plot <- function( - param.reinitial = FALSE, - xlog.scale = FALSE, - ylog.scale = FALSE, - remove.label = TRUE, - remove.x.axis = TRUE, - remove.y.axis = TRUE, - std.x.range = TRUE, - std.y.range = TRUE, - down.space = 1, - left.space = 1, - up.space = 1, - right.space = 1, - orient = 1, - dist.legend = 3.5, - tick.length = 0.5, - box.type = "n", - amplif.label = 1, - amplif.axis = 1, - display.extend = FALSE, - return.par = FALSE -){ - # AIM - # very convenient to erase the axes for post plot axis redrawing using fun_post_plot() - # reinitialize and set the graphic parameters before plotting - # CANNOT be used if no graphic device already opened - # ARGUMENTS - # param.reinitial: reinitialize graphic parameters before applying the new ones, as defined by the other arguments? Either TRUE or FALSE - # xlog.scale: Log scale for the x-axis? Either TRUE or FALSE. If TRUE, erases the x-axis, except legend, for further drawing by fun_post_plot()(xlog argument of par()) - # ylog.scale: Log scale for the y-axis? Either TRUE or FALSE. If TRUE, erases the y-axis, except legend, for further drawing by fun_post_plot()(ylog argument of par()) - # remove.label: remove labels (axis legend) of the two axes? Either TRUE or FALSE (ann argument of par()) - # remove.x.axis: remove x-axis except legend? Either TRUE or FALSE (control the xaxt argument of par()). Automately set to TRUE if xlog.scale == TRUE - # remove.y.axis: remove y-axis except legend? Either TRUE or FALSE (control the yaxt argument of par()). Automately set to TRUE if ylog.scale == TRUE - # std.x.range: standard range on the x-axis? TRUE (no range extend) or FALSE (4% range extend). Controls xaxs argument of par() (TRUE is xaxs = "i", FALSE is xaxs = "r") - # std.y.range: standard range on the y-axis? TRUE (no range extend) or FALSE (4% range extend). Controls yaxs argument of par() (TRUE is yaxs = "i", FALSE is yaxs = "r") - # down.space: lower vertical margin (in inches, mai argument of par()) - # left.space: left horizontal margin (in inches, mai argument of par()) - # up.space: upper vertical margin between plot region and grapical window (in inches, mai argument of par()) - # right.space: right horizontal margin (in inches, mai argument of par()) - # orient: scale number orientation (las argument of par()). 0, always parallel to the axis; 1, always horizontal; 2, always perpendicular to the axis; 3, always vertical - # dist.legend: numeric value that moves axis legends away in inches (first number of mgp argument of par() but in inches thus / 0.2) - # tick.length: length of the ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc. 0 means no tick - # box.type: bty argument of par(). Either "o", "l", "7", "c", "u", "]", the resulting box resembles the corresponding upper case letter. A value of "n" suppresses the box - # amplif.label: increase or decrease the size of the text in legends - # amplif.axis: increase or decrease the size of the scale numbers in axis - # display.extend: extend display beyond plotting region? Either TRUE or FALSE (xpd argument of par() without NA) - # return.par: return graphic parameter modification? - # RETURN - # return graphic parameter modification - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_prior_plot(param.reinitial = FALSE, xlog.scale = FALSE, ylog.scale = FALSE, remove.label = TRUE, remove.x.axis = TRUE, remove.y.axis = TRUE, std.x.range = TRUE, std.y.range = TRUE, down.space = 1, left.space = 1, up.space = 1, right.space = 1, orient = 1, dist.legend = 4.5, tick.length = 0.5, box.type = "n", amplif.label = 1, amplif.axis = 1, display.extend = FALSE, return.par = FALSE) - # DEBUGGING - # param.reinitial = FALSE ; xlog.scale = FALSE ; ylog.scale = FALSE ; remove.label = TRUE ; remove.x.axis = TRUE ; remove.y.axis = TRUE ; std.x.range = TRUE ; std.y.range = TRUE ; down.space = 1 ; left.space = 1 ; up.space = 1 ; right.space = 1 ; orient = 1 ; dist.legend = 4.5 ; tick.length = 0.5 ; box.type = "n" ; amplif.label = 1 ; amplif.axis = 1 ; display.extend = FALSE ; return.par = FALSE # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = param.reinitial, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = xlog.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = ylog.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = remove.label, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = remove.x.axis, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = remove.y.axis, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = std.x.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = std.y.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = down.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = left.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = up.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = right.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = orient, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.type, options = c("o", "l", "7", "c", "u", "]", "n"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = amplif.label, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = amplif.axis, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = display.extend, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return.par, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - if(is.null(dev.list())){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THIS FUNCTION CANNOT BE USED IF NO GRAPHIC DEVICE ALREADY OPENED (dev.list() IS CURRENTLY NULL)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # par.ini recovery - # cannot use pdf(file = NULL), because some small differences between pdf() and other devices. For instance, differences with windows() for par()$fin, par()$pin and par()$plt - if(param.reinitial == TRUE){ - if( ! all(names(dev.cur()) == "null device")){ - active.wind.nb <- dev.cur() - }else{ - active.wind.nb <- 0 - } - if(Sys.info()["sysname"] == "Windows"){ # Note that .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows - grDevices::windows() - ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - invisible(dev.off()) # close the new window - }else if(Sys.info()["sysname"] == "Linux"){ - if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THIS FUNCTION CANNOT BE USED ON LINUX WITH param.reinitial SET TO TRUE IF A Rplots.pdf FILE ALREADY EXISTS HERE: ", getwd()) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - open.fail <- suppressWarnings(try(grDevices::X11(), silent = TRUE))[] # try to open a X11 window. If open.fail == NULL, no problem, meaning that the X11 window is opened. If open.fail != NULL, a pdf can be opened here paste0(getwd(), "/Rplots.pdf") - if(is.null(open.fail)){ - ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - invisible(dev.off()) # close the new window - }else if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ - ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - invisible(dev.off()) # close the new window - file.remove(paste0(getwd(), "/Rplots.pdf")) # remove the pdf file - }else{ - tempo.cat <- ("ERROR IN fun_prior_plot()\nTHIS FUNCTION CANNOT OPEN GUI ON LINUX OR NON MACOS UNIX SYSTEM\nTO OVERCOME THIS, PLEASE USE A PDF GRAPHIC INTERFACE AND RERUN") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - }else{ # macOS - grDevices::quartz() - ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened) - invisible(dev.off()) # close the new window - } - if( ! all(names(dev.cur()) == "null device")){ - invisible(dev.set(active.wind.nb)) # go back to the active window if exists - par(ini.par) # apply the initial par to current window - } - } - # end par.ini recovery - if(remove.x.axis == TRUE){ - par(xaxt = "n") # suppress the y-axis label - }else{ - par(xaxt = "s") - } - if(remove.y.axis == TRUE){ - par(yaxt = "n") # suppress the y-axis label - }else{ - par(yaxt = "s") - } - if(std.x.range == TRUE){ - par(xaxs = "i") - }else{ - par(xaxs = "r") - } - if(std.y.range == TRUE){ - par(yaxs = "i") - }else{ - par(yaxs = "r") - } - par(mai = c(down.space, left.space, up.space, right.space), ann = ! remove.label, las = orient, mgp = c(dist.legend/0.2, 1, 0), xpd = display.extend, bty= box.type, cex.lab = amplif.label, cex.axis = amplif.axis) - par(tcl = -par()$mgp[2] * tick.length) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) - if(xlog.scale == TRUE){ - par(xaxt = "n", xlog = TRUE) # suppress the x-axis label - }else{ - par(xlog = FALSE) - } - if(ylog.scale == TRUE){ - par(yaxt = "n", ylog = TRUE) # suppress the y-axis label - }else{ - par(ylog = FALSE) - } - if(return.par == TRUE){ - tempo.par <- par() - return(tempo.par) - } +if( ! is.null(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# reserved words +# end reserved words +# arg with no default values +mandat.args <- c( +"fun", +"arg", +"val" +) +tempo <- eval(parse(text = paste0("missing(", paste0(mandat.args, collapse = ") | missing("), "))"))) +print(tempo) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = fun, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = arg, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = val, class = "list", fun.name = function.name) ; eval(ee) +if( ! is.null(expect.error)){ +tempo <- fun_check(data = expect.error, class = "list", fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = parall, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(parall == TRUE){ +if( ! is.null(thread.nb)){ +tempo <- fun_check(data = thread.nb, typeof = "integer", double.as.integer.allowed = TRUE, neg.values = FALSE, length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & thread.nb < 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": thread.nb PARAMETER MUST EQUAL OR GREATER THAN 1: ", thread.nb) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +tempo <- fun_check(data = print.count, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = plot.fun, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = export, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(res.path)){ +tempo <- fun_check(data = res.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +} +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = cute.path, class = "vector", typeof = "character", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end using fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# new environment +env.name <- paste0("env", as.numeric(Sys.time())) +if(exists(env.name, where = -1)){ # verify if still ok when fun_info() is inside a function +tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +assign(env.name, new.env()) +assign("data", data, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # data assigned in a new envir for test +} +# end new environment +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT JUST BE NA:", paste0(tempo.arg[tempo.log], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"fun", +"arg", +"val", +# "expect.erro", # because can be NULL +"parall", +# "thread.nb", # because can be NULL +"print.count", +"plot.fun", +"export", +# "res.path", # because can be NULL +# "lib.path", # because can be NULL +"cute.path" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){# normally no NA with is.null() +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# code that protects set.seed() in the global environment +# end code that protects set.seed() in the global environment +# warning initiation +# end warning initiation +# other checkings +if(grepl(x = fun, pattern = "()$")){ # remove () +fun <- sub(x = fun, pattern = "()$", replacement = "") +} +if( ! exists(fun)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CHARACTER STRING IN fun ARGUMENT DOES NOT EXIST IN THE R WORKING ENVIRONMENT: ", paste(fun, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +}else if( ! all(base::class(get(fun)) == "function")){ # here no env = sys.nframe(), inherit = FALSE for get() because fun is a function in the classical scope +tempo.cat <- paste0("ERROR IN ", function.name, ": fun ARGUMENT IS NOT CLASS \"function\" BUT: ", paste(base::class(get(fun)), collapse = "\n"), "\nCHECK IF ANY CREATED OBJECT WOULD HAVE THE NAME OF THE TESTED FUNCTION") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if(tempo$problem == FALSE & base::length(arg) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": arg ARGUMENT CANNOT BE LENGTH 0") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +for(i2 in 1:base::length(val)){ +tempo1 <- fun_check(data = val[[i2]], class = "vector", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = val[[i2]], class = "list", na.contain = TRUE, fun.name = function.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": COMPARTMENT ", i2, " OF val ARGUMENT MUST BE A VECTOR OR A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +}else if(tempo1$problem == FALSE){ # vector split into list compartments +val[[i2]] <- split(x = val[[i2]], f = 1:base::length(val[[i2]])) +} +} +if(base::length(arg) != base::length(val)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": LENGTH OF arg ARGUMENT MUST BE IDENTICAL TO LENGTH OF val ARGUMENT:\nHERE IT IS: ", base::length(arg), " VERSUS ", base::length(val)) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +args <- names(formals(get(fun))) # here no env = sys.nframe(), inherit = FALSE for get() because fun is a function in the classical scope +if( ! all(arg %in% args)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": SOME OF THE STRINGS IN arg ARE NOT ARGUMENTS OF fun\nfun ARGUMENTS: ", paste(args, collapse = " "),"\nPROBLEMATIC STRINGS IN arg: ", paste(arg[ ! arg %in% args], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if(sum(sapply(val, FUN = length) > 1) > 43){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CANNOT TEST MORE THAN 43 ARGUMENTS IF THEY ALL HAVE AT LEAST 2 VALUES EACH\nHERE THE NUMBER IS: ", sum(sapply(val, FUN = length) > 1)) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if( ! is.null(expect.error)){ +if(base::length(val) != base::length(expect.error)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": LENGTH OF val ARGUMENT MUST BE IDENTICAL TO LENGTH OF expect.error ARGUMENT:\nHERE IT IS: ", base::length(val), " VERSUS ", base::length(expect.error)) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +for(i3 in 1:base::length(expect.error)){ +tempo1 <- fun_check(data = expect.error[[i3]], class = "vector", mode = "logical", fun.name = function.name) +tempo2 <- fun_check(data = expect.error[[i3]], class = "list", fun.name = function.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": COMPARTMENT ", i3, " OF expect.error ARGUMENT MUST BE TRUE OR FALSE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +}else if(tempo1$problem == FALSE){ # vector split into list compartments +expect.error[[i3]] <- split(x = expect.error[[i3]], f = 1:base::length(expect.error[[i3]])) +} +} +} +if( ! is.null(res.path)){ +if( ! all(dir.exists(res.path))){ # separation to avoid the problem of tempo$problem == FALSE and res.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE res.path ARGUMENT DOES NOT EXISTS:\n", paste(res.path, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +} +if(parall == TRUE & is.null(res.path)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": res.path ARGUMENT MUST BE SPECIFIED IF parall ARGUMENT IS TRUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if(is.null(res.path) & export == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": res.path ARGUMENT MUST BE SPECIFIED IF export ARGUMENT TRUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if(parall == TRUE & export == FALSE){ +export <- TRUE +tempo.cat <- paste0("WARNING FROM ", function.name, ": export ARGUMENT CONVERTED TO TRUE BECAUSE thread.nb ARGUMENT IS NOT NULL") +warning(paste0("\n", tempo.cat, "\n"), call. = FALSE) +} +if( ! is.null(lib.path)){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +} +if(parall == TRUE){ +if(grepl(x = cute.path, pattern = "^http")){ +tempo.error1 <- any(grepl(x = fun_get_message(data = "source(cute.path)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)), pattern = "^[Ee]rror")) +tempo.error2 <- FALSE +}else{ +tempo.error1 <- FALSE +tempo.error2 <- ! file.exists(cute.path) +} +if(tempo.error1 | tempo.error2){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(grepl(x = cute.path, pattern = "^http"), "URL", "FILE"), " PATH INDICATED IN THE cute.path PARAMETER DOES NOT EXISTS:\n", cute.path) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +} +# end other checkings +# reserved word checking +# end reserved word checking +# end second round of checking and data preparation +# package checking +fun_pack(req.package = c("lubridate"), lib.path = lib.path) +if(parall == TRUE){ +fun_pack(req.package = c("parallel", "pdftools"), lib.path = lib.path) +} +# end package checking +# declaration of special plot functions +sp.plot.fun <- c("fun_gg_scatter", "fun_gg_bar", "fun_gg_boxplot") +# end declaration of special plot functions +# main code +ini.warning.length <- base::options()$warning.length +options(warning.length = 8170) +warn <- NULL +warn.count <- 0 +cat("\nfun_test JOB IGNITION\n") +ini.date <- Sys.time() +ini.time <- as.numeric(ini.date) # time of process begin, converted into seconds +if(export == TRUE){ +res.path <- paste0(res.path, "/fun_test_res_", trunc(ini.time)) +if(dir.exists(res.path)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": FOLDER ALREADY EXISTS\n", res.path, "\nPLEASE RERUN ONCE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +dir.create(res.path) +} +} +total.comp.nb <- prod(sapply(val, FUN = "length")) +cat(paste0("\nTHE TOTAL NUMBER OF TESTS IS: ", total.comp.nb, "\n")) +# creation of the txt instruction that includes several loops +loop.string <- NULL +end.loop.string <- NULL +fun.args <- NULL +fun.args2 <- NULL +error.values <- NULL +arg.values <- "list(" +for(i1 in 1:base::length(arg)){ +if(parall == FALSE){ +if(base::length(val[[i1]]) > 1){ # loop only if more than one value in base::length(val[[i1]]) +loop.string <- paste0(loop.string, "for(i", i1, " in 1:", base::length(val[[i1]]), "){") +end.loop.string <- paste0(end.loop.string, "}") +} +}else{ +loop.string <- "for(i in x){" +end.loop.string <- "}" +} +fun.args <- paste0( +fun.args, +ifelse(i1 == 1L, "", ", "), +arg[i1], +" = val[[", +i1, +"]][[", +if(parall == FALSE){ +if(base::length(val[[i1]]) > 1){ +paste0("i", i1) +}else{ +"1" # a unique element in val[[i1]] +} +}else{ +paste0("i.list[[", i1, "]][i]") +}, +"]]" +) +fun.args2 <- paste0( +fun.args2, +ifelse(i1 == 1L, "", ", "), +arg[i1], +" = val[[", +i1, +"]][[', ", +if(parall == FALSE){ +if(base::length(val[[i1]]) > 1){ +paste0("i", i1) +}else{ +"1" # a unique element in val[[i1]] +} +}else{ +paste0("i.list[[", i1, "]][i]") +}, +", ']]" +) +arg.values <- paste0( +arg.values, +"val[[", i1, "]][[", +if(parall == FALSE){ +if(base::length(val[[i1]]) > 1){ +paste0("i", i1) +}else{ +"1" # a unique element in val[[i1]] +} +}else{ +paste0("i.list[[", i1, "]][i]") +}, +"]]", +ifelse(i1 == base::length(arg), "", ", ") +) +error.values <- paste0( +error.values, +ifelse(i1 == 1L, "", " | "), +"expect.error[[", i1, "]][[", +if(parall == FALSE){ +if(base::length(expect.error[[i1]]) > 1){ +paste0("i", i1) +}else{ +"1" # a unique element in expect.error[[i1]] +} +}else{ +paste0("i.list[[", i1, "]][i]") +}, +"]]" +) +} +arg.values <- paste0(arg.values, ")") +fun.test <- paste0(fun, "(", fun.args, ")") +fun.test2 <- paste0("paste0('", fun, "(", fun.args2, ")')") +# plot title for special plot functions +if(plot.fun == TRUE){ +plot.kind <- "classic" +if(fun %in% sp.plot.fun){ +plot.kind <- "special" +if(any(arg %in% "title")){ # this is for the special functions +tempo.match <- regmatches(x = fun.test, m = regexpr(text = fun.test, pattern = "title = .+[,)]")) +tempo.match <- substring(tempo.match , 1, nchar(tempo.match) - 1) +fun.test <- sub(x = fun.test, pattern = tempo.match, replacement = paste0(tempo.match, "\ntempo.title")) +}else{ +fun.test <- sub(x = fun.test, pattern = ")$", replacement = ", title = tempo.title)") +} +} +} +# end plot title for special plot functions +kind <- character() +problem <- logical() +expected.error <- logical() +res <- character() +count <- 0 +print.count.loop <- 0 +plot.count <- 0 +if(base::length(arg) == 1L){ +data <- data.frame() +}else{ # base::length(arg) == 0L already tested above +data <- data.frame(t(vector("character", base::length(arg))), stringsAsFactors = FALSE)[-1, ] # -1 to remove the single row created and to have an empty data frame with base::length(arg) columns +} +code <- paste( +loop.string, ' +count <- count + 1 +print.count.loop <- print.count.loop + 1 +arg.values.print <- eval(parse(text = arg.values)) # recover the list of the i1 compartment +for(j3 in 1:base::length(arg.values.print)){ # WARNING: do not use i1, i2 etc., here because already in loop.string +tempo.capt <- capture.output(tempo.error <- fun_get_message(data = paste0("paste(arg.values.print[[", j3, "]])"), kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE))) # collapsing arg.values sometimes does not work (with function for instance) +if( ! is.null(tempo.error)){ +arg.values.print[[j3]] <- paste0("SPECIAL VALUE OF CLASS ", base::class(arg.values.print[[j3]]), " AND TYPE ", base::typeof(arg.values.print[[j3]])) +} +} +data <- rbind(data, as.character(sapply(arg.values.print, FUN = "paste", collapse = " ")), stringsAsFactors = FALSE) # each colum is a test +tempo.capt <- capture.output(tempo.try.error <- fun_get_message(data = eval(parse(text = fun.test2)), kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE))) # data argument needs a character string but eval(parse(text = fun.test2)) provides it (eval parse replace the i1, i2, etc., by the correct values, meaning that only val is required in the env.name environment) +tempo.capt <- capture.output(tempo.try.warning <- fun_get_message(data = eval(parse(text = fun.test2)), kind = "warning", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE), print.no = TRUE)) # data argument needs a character string but eval(parse(text = fun.test2)) provides it (eval parse replace the i1, i2, etc., by the correct values, meaning that only val is required in the env.name environment) +if( ! is.null(expect.error)){ +expected.error <- c(expected.error, eval(parse(text = error.values))) +} +if( ! is.null(tempo.try.error)){ +kind <- c(kind, "ERROR") +problem <- c(problem, TRUE) +res <- c(res, tempo.try.error) +}else{ +if( ! is.null(tempo.try.warning)){ +kind <- c(kind, "WARNING") +problem <- c(problem, FALSE) +res <- c(res, tempo.try.warning) +}else{ +kind <- c(kind, "OK") +problem <- c(problem, FALSE) +res <- c(res, "") +} +if(plot.fun == TRUE){ +invisible(dev.set(window.nb)) +plot.count <- plot.count + 1 +tempo.title <- paste0("test_", sprintf(paste0("%0", nchar(total.comp.nb), "d"), ifelse(parall == FALSE, count, x[count]))) +if(plot.kind == "classic"){ +eval(parse(text = fun.test)) +tempo <- fun_post_plot(corner.text = tempo.title) +}else if(plot.kind == "special"){ +eval(parse(text = fun.test)) +}else{ +tempo.cat <- paste0("INTERNAL CODE ERROR 1 IN ", function.name, ": CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +} +if(print.count.loop == print.count){ +print.count.loop <- 0 +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) +final.loop <- (tempo.time - ini.time) / count * ifelse(parall == FALSE, total.comp.nb, base::length(x)) # expected duration in seconds # intra nb.compar loop lapse: time lapse / cycles done * cycles remaining +final.exp <- as.POSIXct(final.loop, origin = ini.date) +cat(paste0(ifelse(parall == FALSE, "\n", paste0("\nIN PROCESS ", process.id, " | ")), "LOOP ", format(count, big.mark=","), " / ", format(ifelse(parall == FALSE, total.comp.nb, base::length(x)), big.mark=","), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) +} +if(count == ifelse(parall == FALSE, total.comp.nb, base::length(x))){ +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) +cat(paste0(ifelse(parall == FALSE, "\nLOOP PROCESS ENDED | ", paste0("\nPROCESS ", process.id, " ENDED | ")), "LOOP ", format(count, big.mark=","), " / ", format(ifelse(parall == FALSE, total.comp.nb, base::length(x)), big.mark=","), " | TIME SPENT: ", tempo.lapse, "\n\n")) +} +', +end.loop.string +) +# end creation of the txt instruction that includes several loops +if(parall == TRUE){ +# list of i numbers that will be split +i.list <- vector("list", base::length(val)) # positions to split in parallel jobs +for(i2 in 1:base::length(arg)){ +if(i2 == 1L){ +tempo.divisor <- total.comp.nb / base::length(val[[i2]]) +i.list[[i2]] <- rep(1:base::length(val[[i2]]), each = as.integer(tempo.divisor)) +tempo.multi <- base::length(val[[i2]]) +}else{ +tempo.divisor <- tempo.divisor / base::length(val[[i2]]) +i.list[[i2]] <- rep(rep(1:base::length(val[[i2]]), each = as.integer(tempo.divisor)), time = as.integer(tempo.multi)) +tempo.multi <- tempo.multi * base::length(val[[i2]]) +} +} +# end list of i numbers that will be split +tempo.cat <- paste0("PARALLELIZATION INITIATED AT: ", ini.date) +cat(paste0("\n", tempo.cat, "\n")) +tempo.thread.nb = parallel::detectCores(all.tests = FALSE, logical = TRUE) # detect the number of threads +if(tempo.thread.nb < thread.nb){ +thread.nb <- tempo.thread.nb +} +tempo.cat <- paste0("NUMBER OF THREADS USED: ", thread.nb) +cat(paste0("\n ", tempo.cat, "\n")) +Clust <- parallel::makeCluster(thread.nb, outfile = paste0(res.path, "/fun_test_parall_log.txt")) # outfile to print or cat during parallelization (only possible in a file, outfile = "" do not work on windows) +tempo.cat <- paste0("SPLIT OF TEST NUMBERS IN PARALLELISATION:") +cat(paste0("\n ", tempo.cat, "\n")) +cluster.list <- parallel::clusterSplit(Clust, 1:total.comp.nb) # split according to the number of cluster +str(cluster.list) # using print(str()) add a NULL below the result +cat("\n") +paral.output.list <- parallel::clusterApply( # paral.output.list is a list made of thread.nb compartments, each made of n / thread.nb (mat theo column number) compartment. Each compartment receive the corresponding results of fun_permut(), i.e., data (permuted mat1.perm), warning message, cor (final correlation) and count (number of permutations) +cl = Clust, +x = cluster.list, +function.name = function.name, +instruction = instruction, +thread.nb = thread.nb, +print.count = print.count, +total.comp.nb = total.comp.nb, +sp.plot.fun = sp.plot.fun, +i.list = i.list, +fun.tested = fun, +arg.values = arg.values, +fun.test = fun.test, +fun.test2 = fun.test2, +kind = kind, +problem = problem, +res = res, +count = count, +plot.count = plot.count, +data = data, +code = code, +plot.fun = plot.fun, +res.path = res.path, +lib.path = lib.path, +cute.path = cute.path, +fun = function( +x, +function.name, +instruction, +thread.nb, +print.count, +total.comp.nb, +sp.plot.fun, +i.list, +fun.tested, +arg.values, +fun.test, +fun.test2, +kind, +problem, +res, +count, +plot.count, +data, +code, +plot.fun, +res.path, +lib.path, +cute.path +){ +# check again: very important because another R +process.id <- Sys.getpid() +cat(paste0("\nPROCESS ID ", process.id, " -> TESTS ", x[1], " TO ", x[base::length(x)], "\n")) +source(cute.path, local = .GlobalEnv) +fun_pack(req.package = "lubridate", lib.path = lib.path, load = TRUE) # load = TRUE to be sure that functions are present in the environment. And this prevent to use R.lib.path argument of fun_python_pack() +# end check again: very important because another R +# plot management +if(plot.fun == TRUE){ +pdf(file = paste0(res.path, "/plots_from_fun_test_", x[1], ifelse(base::length(x) == 1L, ".pdf", paste0("-", x[base::length(x)], ".pdf")))) +}else{ +pdf(file = NULL) # send plots into a NULL file, no pdf file created +} +window.nb <- dev.cur() +invisible(dev.set(window.nb)) +# end plot management +# new environment +ini.date <- Sys.time() +ini.time <- as.numeric(ini.date) # time of process begin, converted into +env.name <- paste0("env", ini.time) +if(exists(env.name, where = -1)){ # verify if still ok when fun_test() is inside a function +tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +assign(env.name, new.env()) +assign("val", val, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # var replaced by val +} +# end new environment +print.count.loop <- 0 +suppressMessages(suppressWarnings(eval(parse(text = code)))) +colnames(data) <- arg +if( ! is.null(expect.error)){ +data <- data.frame(data, kind = kind, problem = problem, expected.error = expected.error, message = res, stringsAsFactors = FALSE) +}else{ +data <- data.frame(data, kind = kind, problem = problem, message = res, stringsAsFactors = FALSE) +} +row.names(data) <- paste0("test_", sprintf(paste0("%0", nchar(total.comp.nb), "d"), x)) +sys.info <- sessionInfo() +sys.info$loadedOnly <- sys.info$loadedOnly[order(names(sys.info$loadedOnly))] # sort the packages +invisible(dev.off(window.nb)) +rm(env.name) # optional, because should disappear at the end of the function execution +# output +output <- list(fun = fun, instruction = instruction, sys.info = sys.info) # data = data finally removed from the output list, because everything combined in a RData file at the end +save(output, file = paste0(res.path, "/fun_test_", x[1], ifelse(base::length(x) == 1L, ".RData", paste0("-", x[base::length(x)], ".RData")))) +if(plot.fun == TRUE & plot.count == 0L){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN PROCESS ", process.id, ": NO PDF PLOT BECAUSE ONLY ERRORS REPORTED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +file.remove(paste0(res.path, "/plots_from_fun_test_", x[1], ifelse(base::length(x) == 1L, ".pdf", paste0("-", x[base::length(x)], ".pdf")))) +} +table.out <- as.matrix(data) +# table.out[table.out == ""] <- " " # does not work # because otherwise read.table() converts "" into NA +table.out <- gsub(table.out, pattern = "\n", replacement = " ") +write.table(table.out, file = paste0(res.path, "/table_from_fun_test_", x[1], ifelse(base::length(x) == 1L, ".txt", paste0("-", x[base::length(x)], ".txt"))), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") +} +) +parallel::stopCluster(Clust) +# files assembly +if(base::length(cluster.list) > 1){ +for(i2 in 1:base::length(cluster.list)){ +tempo.file <- paste0(res.path, "/table_from_fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".txt", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".txt"))) # txt file +tempo <- read.table(file = tempo.file, header = TRUE, stringsAsFactors = FALSE, sep = "\t", row.names = 1, comment.char = "", colClasses = "character") # row.names = 1 (1st column) because now read.table() adds a NA in the header if the header starts by a tabulation, comment.char = "" because colors with #, colClasses = "character" otherwise convert "" (from NULL) into NA +if(file.exists(paste0(res.path, "/plots_from_fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".pdf", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".pdf"))))){ +tempo.pdf <- paste0(res.path, "/plots_from_fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".pdf", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".pdf"))) # pdf file +}else{ +tempo.pdf <- NULL +} +tempo.rdata <- paste0(res.path, "/fun_test_", min(cluster.list[[i2]], na.rm = TRUE), ifelse(base::length(cluster.list[[i2]]) == 1L, ".RData", paste0("-", max(cluster.list[[i2]], na.rm = TRUE), ".RData"))) # RData file +if(i2 == 1L){ +final.file <- tempo +final.pdf <- tempo.pdf +# new env for RData combining +env.name <- paste0("env", ini.time) +if(exists(env.name, where = -1)){ # verify if still ok when fun_test() is inside a function +tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +# end new env for RData combining +}else{ +assign(env.name, new.env()) +load(tempo.rdata, envir = get(env.name)) +tempo.rdata1 <- tempo.rdata +assign("final.output", get("output", envir = get(env.name)), envir = get(env.name)) +} +}else{ +final.file <- rbind(final.file, tempo, stringsAsFactors = TRUE) +final.pdf <- c(final.pdf, tempo.pdf) +load(tempo.rdata, envir = get(env.name)) +if( ! identical(get("final.output", envir = get(env.name))[c("R.version", "locale", "platform")], get("output", envir = get(env.name))[c("R.version", "locale", "platform")])){ +tempo.cat <- paste0("ERROR IN ", function.name, ": DIFFERENCE BETWEEN OUTPUTS WHILE THEY SHOULD BE IDENTICAL\nPLEASE CHECK\n", tempo.rdata1, "\n", tempo.rdata) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +# add the differences in RData $sysinfo into final.output +tempo.base1 <- sort(get("final.output", envir = get(env.name))$sys.info$basePkgs) +tempo.base2 <- sort(get("output", envir = get(env.name))$sys.info$basePkgs) +tempo.other1 <- names(get("final.output", envir = get(env.name))$sys.info$otherPkgs) +tempo.other2 <- names(get("output", envir = get(env.name))$sys.info$otherPkgs) +tempo.loaded1 <- names(get("final.output", envir = get(env.name))$sys.info$loadedOnly) +tempo.loaded2 <- names(get("output", envir = get(env.name))$sys.info$loadedOnly) +assign("final.output", { +x <- get("final.output", envir = get(env.name)) +y <- get("output", envir = get(env.name)) +x$sys.info$basePkgs <- sort(unique(tempo.base1, tempo.base2)) +if( ! all(tempo.other2 %in% tempo.other1)){ +x$sys.info$otherPkgs <- c(x$sys.info$otherPkgs, y$sys.info$otherPkgs[ ! (tempo.other2 %in% tempo.other1)]) +x$sys.info$otherPkgs <- x$sys.info$otherPkgs[order(names(x$sys.info$otherPkgs))] +} +if( ! all(tempo.loaded2 %in% tempo.loaded1)){ +x$sys.info$loadedOnly <- c(x$sys.info$loadedOnly, y$sys.info$loadedOnly[ ! (tempo.loaded2 %in% tempo.loaded1)]) +x$sys.info$loadedOnly <- x$sys.info$loadedOnly[order(names(x$sys.info$loadedOnly))] +} +x +}, envir = get(env.name)) +# add the differences in RData $sysinfo into final.output +} +} +file.remove(c(tempo.file, tempo.rdata)) +} +# combine pdf and save +if( ! is.null(final.pdf)){ +pdftools::pdf_combine( +input = final.pdf, +output = paste0(res.path, "/plots_from_fun_test_1-", total.comp.nb, ".pdf") +) +file.remove(final.pdf) +} +# end combine pdf and save +# save RData +assign("output", c(get("final.output", envir = get(env.name)), data = list(final.file)), envir = get(env.name)) +save(output, file = paste0(res.path, "/fun_test__1-", total.comp.nb, ".RData"), envir = get(env.name)) +rm(env.name) # optional, because should disappear at the end of the function execution +# end save RData +# save txt +write.table(final.file, file = paste0(res.path, "/table_from_fun_test_1-", total.comp.nb, ".txt"), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") +# end save txt +if( ! is.null(expect.error)){ +final.file <- final.file[ ! final.file$problem == final.file$expected.error, ] +if(nrow(final.file) == 0L){ +cat(paste0("NO DISCREPANCY BETWEEN EXPECTED AND OBSERVED ERRORS\n\n")) +}else{ +cat(paste0("DISCREPANCIES BETWEEN EXPECTED AND OBSERVED ERRORS (SEE THE discrepancy_table_from_fun_test_1-", total.comp.nb, ".txt FILE)\n\n")) +write.table(final.file, file = paste0(res.path, "/discrepancy_table_from_fun_test_1-", total.comp.nb, ".txt"), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") +} +} +} +# end files assembly +}else{ +# plot management +if(plot.fun == TRUE){ +pdf(file = paste0(res.path, "/plots_from_fun_test_1", ifelse(total.comp.nb == 1L, ".pdf", paste0("-", total.comp.nb, ".pdf")))) +}else{ +pdf(file = NULL) # send plots into a NULL file, no pdf file created +} +window.nb <- dev.cur() +invisible(dev.set(window.nb)) +# end plot management +# new environment +env.name <- paste0("env", ini.time) +if(exists(env.name, where = -1)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +assign(env.name, new.env()) +assign("val", val, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # var replaced by val +} +# end new environment +suppressMessages(suppressWarnings(eval(parse(text = code)))) +colnames(data) <- arg +expect.data <- data.frame() +if( ! is.null(expect.error)){ +data <- data.frame(data, kind = kind, problem = problem, expected.error = expected.error, message = res, stringsAsFactors = FALSE) +}else{ +data <- data.frame(data, kind = kind, problem = problem, message = res, stringsAsFactors = FALSE) +} +row.names(data) <- paste0("test_", sprintf(paste0("%0", nchar(total.comp.nb), "d"), 1:total.comp.nb)) +sys.info <- sessionInfo() +sys.info$loadedOnly <- sys.info$loadedOnly[order(names(sys.info$loadedOnly))] # sort the packages +invisible(dev.off(window.nb)) +rm(env.name) # optional, because should disappear at the end of the function execution +# output +output <- list(fun = fun, instruction = instruction, sys.info = sys.info, data = data) +if(plot.fun == TRUE & plot.count == 0L){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NO PDF PLOT BECAUSE ONLY ERRORS REPORTED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +file.remove(paste0(res.path, "/plots_from_fun_test_1", ifelse(total.comp.nb == 1L, ".pdf", paste0("-", total.comp.nb, ".pdf")))) +} +if( ! is.null(expect.error)){ +expect.data <- output$data[ ! output$data$problem == output$data$expected.error, ] +if(nrow(expect.data) == 0L){ +cat(paste0("NO DISCREPANCY BETWEEN EXPECTED AND OBSERVED ERRORS\n\n")) +}else{ +cat(paste0("DISCREPANCIES BETWEEN EXPECTED AND OBSERVED ERRORS (SEE THE ", if(export == TRUE){paste0("discrepancy_table_from_fun_test_1", ifelse(total.comp.nb == 1L, "", paste0("-", total.comp.nb)), ".txt FILE")}else{"$data RESULT"}, ")\n\n")) +if(export == TRUE){ +expect.data <- as.matrix(expect.data) +expect.data <- gsub(expect.data, pattern = "\n", replacement = " ") +write.table(expect.data, file = paste0(res.path, "/discrepancy_table_from_fun_test_1", ifelse(total.comp.nb == 1L, ".txt", paste0("-", total.comp.nb, ".txt"))), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") +} +} +} +if( ! is.null(warn)){ +base::options(warning.length = 8170) +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) +} +on.exit(exp = base::options(warning.length = ini.warning.length), add = TRUE) +if(export == TRUE){ +save(output, file = paste0(res.path, "/fun_test_1", ifelse(total.comp.nb == 1L, ".RData", paste0("-", total.comp.nb, ".RData")))) +table.out <- as.matrix(output$data) +table.out <- gsub(table.out, pattern = "\n", replacement = " ") +write.table(table.out, file = paste0(res.path, "/table_from_fun_test_1", ifelse(total.comp.nb == 1L, ".txt", paste0("-", total.comp.nb, ".txt"))), row.names = TRUE, col.names = NA, append = FALSE, quote = FALSE, sep = "\t", eol = "\n", na = "") +}else{ +return(output) +} +} +# after return() ? +end.date <- Sys.time() +end.time <- as.numeric(end.date) +total.lapse <- round(lubridate::seconds_to_period(end.time - ini.time)) +cat(paste0("fun_test JOB END\n\nTIME: ", end.date, "\n\nTOTAL TIME LAPSE: ", total.lapse, "\n\n\n")) +} + + +################ Object modification + + +######## fun_name_change() #### check a vector of character strings and modify any string if present in another vector + + +fun_name_change <- function(data1, data2, added.string = "_modif"){ +# AIM +# this function allow to check if a vector of character strings, like column names of a data frame, has elements present in another vector (vector of reserved words or column names of another data frame before merging) +# ARGUMENTS +# data1: vector of character strings to check and modify +# data2: reference vector of character strings +# added.string: string added at the end of the modified string in data1 if present in data2 +# RETURN +# a list containing +# $data: the modified data1 (in the same order as in the initial data1) +# $ini: the initial elements before modification. NULL if no modification +# $post: the modified elements in the same order as in ini. NULL if no modification +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# obs1 <- c("A", "B", "C", "D") ; obs2 <- c("A", "C") ; fun_name_change(obs1, obs2) +# obs1 <- c("A", "B", "C", "C_modif1", "D") ; obs2 <- c("A", "A_modif1", "C") ; fun_name_change(obs1, obs2) # the function checks that the new names are neither in obs1 nor in obs2 (increment the number after the added string) +# DEBUGGING +# data1 = c("A", "B", "C", "D") ; data2 <- c("A", "C") ; added.string = "_modif" # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data1, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = data2, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = added.string, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +ini <- NULL +post <- NULL +if(any(data1 %in% data2)){ +tempo.names <- data1[data1 %in% data2] +ini <- NULL +post <- NULL +for(i2 in 1:length(tempo.names)){ +count <- 0 +tempo <- tempo.names[i2] +while(any(tempo %in% data2) | any(tempo %in% data1)){ +count <- count + 1 +tempo <- paste0(tempo.names[i2], "_modif", count) +} +data1[data1 %in% tempo.names[i2]] <- paste0(tempo.names[i2], "_modif", count) +if(count != 0){ +ini <- c(ini, tempo.names[i2]) +post <- c(post, paste0(tempo.names[i2], "_modif", count)) +} +} +data <- data1 +}else{ +data <- data1 +} +output <- list(data = data, ini = ini, post = post) +return(output) +} + + +######## fun_df_remod() #### remodeling a data frame to have column name as a qualitative values and vice-versa + + +fun_df_remod <- function( +data, +quanti.col.name = "quanti", +quali.col.name = "quali" +){ +# AIM +# if the data frame is made of n numeric columns, a new data frame is created, with the 1st column gathering all the numeric values, and the 2nd column being the name of the columns of the initial data frame. If row names were present in the initial data frame, then a new ini_rowname column is added with the names of the rows + + +# If the data frame is made of one numeric column and one character or factor column, a new data frame is created, with the new columns corresponding to the split numeric values (according to the character column). NA are added a the end of each column to have the same number of rows. BEWARE: in such data frame, rows are not individuals. This means that in the example below, values 10 and 20 are associated on the same row but that means nothing in term of association + + + +# ARGUMENTS +# data: data frame to convert +# quanti.col.name: optional name for the quanti column of the new data frame +# quali.col.name: optional name for the quali column of the new data frame +# RETURN +# the modified data frame +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# obs <- data.frame(col1 = (1:4)*10, col2 = c("A", "B", "A", "A"), stringsAsFactors = TRUE) ; obs ; fun_df_remod(obs) +# obs <- data.frame(col1 = (1:4)*10, col2 = 5:8, stringsAsFactors = TRUE) ; obs ; fun_df_remod(obs, quanti.col.name = "quanti", quali.col.name = "quali") +# obs <- data.frame(col1 = (1:4)*10, col2 = 5:8, stringsAsFactors = TRUE) ; rownames(obs) <- paste0("row", 1:4) ; obs ; fun_df_remod(obs, quanti.col.name = "quanti", quali.col.name = "quali") +# DEBUGGING +# data = data.frame(a = 1:3, b = 4:6, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging +# data = data.frame(a = 1:3, b = 4:6, c = 11:13, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging +# data = data.frame(a = 1:3, b = letters[1:3], stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging +# data = data.frame(a = 1:3, b = letters[1:3], stringsAsFactors = TRUE) ; quanti.col.name = "TEST" ; quali.col.name = "quali" # for function debugging +# data = data.frame(b = letters[1:3], a = 1:3, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging +# data = data.frame(b = c("e", "e", "h"), a = 1:3, stringsAsFactors = TRUE) ; quanti.col.name = "quanti" ; quali.col.name = "quali" # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +# argument checking without fun_check() +if( ! any(class(data) %in% "data.frame")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data ARGUMENT MUST BE A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end argument checking without fun_check() +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = quanti.col.name, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = quali.col.name, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +tempo.factor <- unlist(lapply(data, class)) +for(i in 1:length(tempo.factor)){ # convert factor columns as character +if(all(tempo.factor[i] == "factor")){ +data[, i] <- as.character(data[, i]) +} +} +tempo.factor <- unlist(lapply(data, mode)) +if(length(data) == 2L){ +if( ! ((base::mode(data[, 1]) == "character" & base::mode(data[, 2]) == "numeric") | base::mode(data[, 2]) == "character" & base::mode(data[, 1]) == "numeric" | base::mode(data[, 2]) == "numeric" & base::mode(data[, 1]) == "numeric") ){ +tempo.cat <- paste0("ERROR IN ", function.name, ": IF data ARGUMENT IS A DATA FRAME MADE OF 2 COLUMNS, EITHER A COLUMN MUST BE NUMERIC AND THE OTHER CHARACTER, OR THE TWO COLUMNS MUST BE NUMERIC") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if((base::mode(data[, 1]) == "character" | base::mode(data[, 2]) == "character") & (quanti.col.name != "quanti" | quali.col.name != "quali")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": IMPROPER quanti.col.name OR quali.col.name RESETTINGS. THESE ARGUMENTS ARE RESERVED FOR DATA FRAMES MADE OF n NUMERIC COLUMNS ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else{ +if( ! all(tempo.factor %in% "numeric")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": IF data ARGUMENT IS A DATA FRAME MADE OF ONE COLUMN, OR MORE THAN 2 COLUMNS, THESE COLUMNS MUST BE NUMERIC") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(( ! any(tempo.factor %in% "character")) & is.null(names(data))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": NUMERIC DATA FRAME in the data ARGUMENT MUST HAVE COLUMN NAMES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(all(tempo.factor %in% "numeric")){ # transfo 1 +quanti <- NULL +for(i in 1:length(data)){ +quanti <-c(quanti, data[, i]) +} +quali <- rep(names(data), each = nrow(data)) +output.data <- data.frame(quanti, quali, stringsAsFactors = TRUE, check.names = FALSE) +names(output.data) <- c(quanti.col.name, quali.col.name) +# add the ini_rowname column +ini.rownames <- rownames(data) +tempo.data <- data +rownames(tempo.data) <- NULL +null.rownames <- (tempo.data) +if( ! identical(ini.rownames, null.rownames)){ +ini_rowname <- rep(ini.rownames, times = ncol(data)) +output.data <- cbind(output.data, ini_rowname, stringsAsFactors = TRUE) +} +}else{ # transfo 2 +if(class(data[, 1]) == "character"){ +data <- cbind(data[2], data[1], stringsAsFactors = TRUE) +} +nc.max <- max(table(data[, 2])) # effectif maximum des classes +nb.na <- nc.max - table(data[,2]) # nombre de NA à ajouter pour réaliser la data frame +tempo<-split(data[, 1], data[, 2]) +for(i in 1:length(tempo)){tempo[[i]] <- append(tempo[[i]], rep(NA, nb.na[i]))} # des NA doivent être ajoutés lorsque les effectifs sont différents entre les classes. C'est uniquement pour que chaque colonne ait le même nombre de lignes +output.data<-data.frame(tempo, stringsAsFactors = TRUE, check.names = FALSE) +} +return(output.data) +} + + + + +######## fun_round() #### rounding number if decimal present + + +fun_round <- function(data, dec.nb = 2, after.lead.zero = TRUE){ +# AIM +# round a vector of values, if decimal, with the desired number of decimal digits after the decimal leading zeros +# WARNINGS +# Work well with numbers as character strings, but not always with numerical numbers because of the floating point +# Numeric values are really truncated from a part of their decimal digits, whatever options(digits) settings +# See ?.Machine or https://stackoverflow.com/questions/5173692/how-to-return-number-of-decimal-places-in-r, with the interexting formula: abs(x - round(x)) > .Machine$double.eps^0.5 +# ARGUMENTS +# data: a vector of numbers (numeric or character mode) +# dec.nb: number of required decimal digits +# after.lead.zero: logical. If FALSE, rounding is performed for all the decimal numbers, whatever the leading zeros (e.g., 0.123 -> 0.12 and 0.00128 -> 0.00). If TRUE, dec.nb are taken after the leading zeros (e.g., 0.123 -> 0.12 and 0.00128 -> 0.0013) +# RETURN +# the modified vector +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, 10, 100.001, 333.0001254, 12312.1235), dec.nb = 2, after.lead.zero = FALSE), "\n\n") ; options(digits = ini.options) +# ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, 10, 100.001, 333.0001254, 12312.1235), dec.nb = 2, after.lead.zero = TRUE), "\n\n") ; options(digits = ini.options) +# ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, "10", "100.001", "333.0001254", "12312.1235"), dec.nb = 2, after.lead.zero = FALSE), "\n\n") ; options(digits = ini.options) +# ini.options <- options()$digits ; options(digits = 8) ; cat(fun_round(data = c(NA, "10", "100.001", "333.0001254", "12312.1235"), dec.nb = 2, after.lead.zero = TRUE), "\n\n") ; options(digits = ini.options) +# DEBUGGING +# data = data = c(10, 100.001, 333.0001254, 12312.1235) ; dec.nb = 2 ; after.lead.zero = FALSE # # for function debugging +# data = data = c("10", "100.001", "333.0001254", "12312.1235") ; dec.nb = 2 ; after.lead.zero = TRUE # # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +# argument checking without fun_check() +if( ! (all(typeof(data) == "character") | all(typeof(data) == "double") | all(typeof(data) == "integer"))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A VECTOR OF NUMBERS (IN NUMERIC OR CHARACTER MODE)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end argument checking without fun_check() +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data, class = "vector", na.contain = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = dec.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = after.lead.zero, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +tempo <- grepl(x = data, pattern = "\\.") # detection of decimal numbers +ini.mode <- base::mode(data) +data <- as.character(data) # to really truncate decimal digits +for(i in 1:length(data)){ # scan all the numbers of the vector +if(tempo[i] == TRUE){ # means decimal number +if(after.lead.zero == TRUE){ +zero.pos <- unlist(gregexpr(text=data[i], pattern = 0)) # recover all the position of the zeros in the number. -1 if no zeros (do not record the leading and trailing zeros) +}else{ +zero.pos <- -1 # -1 as if no zero +} +dot.pos <- unlist(gregexpr(text=data[i], pattern = "\\.")) # recover all the position of the zeros in the number +digit.pos <- unlist(gregexpr(text=data[i], pattern = "[[:digit:]]")) # recover all the position of the digits in the number +dec.pos <- digit.pos[digit.pos > dot.pos] +count <- 0 +while((dot.pos + count + 1) %in% zero.pos & (dot.pos + count + 1) <= max(dec.pos) & (count + dec.nb) < length(dec.pos)){ # count the number of leading zeros in the decimal part +count <- count + 1 +} +data[i] <- formatC(as.numeric(data[i]), digits = (count + dec.nb), format = "f") +} +} +if(ini.mode != "character"){ +data <- as.numeric(data) +} +return(data) +} + + +######## fun_mat_rotate() #### 90° clockwise matrix rotation + + +fun_mat_rotate <- function(data){ +# AIM +# 90° clockwise matrix rotation +# applied twice, the function provide the mirror matrix, according to vertical and horizontal symmetry +# ARGUMENTS +# data: matrix (matrix class) +# RETURN +# the modified matrix +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# obs <- matrix(1:10, ncol = 1) ; obs ; fun_mat_rotate(obs) +# obs <- matrix(LETTERS[1:10], ncol = 5) ; obs ; fun_mat_rotate(obs) +# DEBUGGING +# data = matrix(1:10, ncol = 1) +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data, class = "matrix", fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +for (i in 1:ncol(data)){data[,i] <- rev(data[,i])} +data <- t(data) +return(data) +} + + +######## fun_mat_num2color() #### convert a numeric matrix into hexadecimal color matrix + + +fun_mat_num2color <- function( +mat1, +mat.hsv.h = TRUE, +notch = 1, +s = 1, +v = 1, +forced.color = NULL +){ +# AIM +# convert a matrix made of numbers into a hexadecimal matrix for rgb colorization +# ARGUMENTS: +# mat1: matrix 1 of non negative numerical values that has to be colored (matrix class). NA allowed +# mat.hsv.h: logical. Is mat1 the h of hsv colors ? (if TRUE, mat1 must be between zero and 1). If FALSE, mat1 must be made of positive integer values without 0 +# notch: single value between 0 and 1 to shift the successive colors on the hsv circle by + notch +# s: s argument of hsv(). Must be between 0 and 1 +# v: v argument of hsv(). Must be between 0 and 1 +# forced.color: Must be NULL or hexadecimal color code or name given by colors(). The first minimal values of mat1 will be these colors. All the color of mat1 can be forced using this argument +# RETURN +# a list containing: +# $mat1.name: name of mat1 +# $colored.mat: colors of mat1 in hexa +# $problem: logical. Is any colors of forced.color overlap the colors designed by the function. NULL if forced.color = NULL +# $text.problem: text when overlapping colors. NULL if forced.color = NULL or problem == FALSE +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# mat1 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; dimnames(mat1) <- list(LETTERS[1:4], letters[1:2]) ; fun_mat_num2color(mat1, mat.hsv.h = FALSE, notch = 1, s = 1, v = 1, forced.color = NULL) +# DEBUGGING +# mat1 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; dimnames(mat1) <- list(LETTERS[1:4], letters[1:2]); mat.hsv.h = FALSE ; notch = 1 ; s = 1 ; v = 1 ; forced.color = c(hsv(1,1,1), hsv(0,0,0)) # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = mat1, mode = "numeric", class = "matrix", na.contain = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = mat.hsv.h, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = notch, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = s, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = v, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# argument checking without fun_check() +if(mat.hsv.h == TRUE & fun_check(data = mat1, mode = "numeric", prop = TRUE)$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat1 ARGUMENT MUST BE A MATRIX OF PROPORTIONS SINCE THE mat.hsv.h ARGUMENT IS SET TO TRUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(forced.color)){ +tempo <- fun_check(data = forced.color, class = "character") +if(any(tempo$problem == TRUE)){ +paste0("\n\n================\n\n", paste(tempo$text[tempo$problem], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! all(forced.color %in% colors() | grepl(pattern = "^#", forced.color))){ # check that all strings of forced.color start by # +tempo.cat <- paste0("ERROR IN ", function.name, ": forced.color ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end argument checking without fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +problem <- NULL +text.problem <- NULL +mat1.name <- deparse(substitute(mat1)) +# change the scale of the plotted matrix +if(mat.hsv.h == TRUE){ +if(any(min(mat1, na.rm = TRUE) < 0 | max(mat1, na.rm = TRUE) > 1, na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat1 MUST BE MADE OF VALUES BETWEEN 0 AND 1 BECAUSE mat.hsv.h ARGUMENT SET TO TRUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else{ +if(any(mat1 - floor(mat1) > 0, na.rm = TRUE) | any(mat1 == 0L, na.rm = TRUE)){ # no need of isTRUE(all.equal()) because we do not require approx here but strictly 0, thus == is ok +tempo.cat <- paste0("ERROR IN ", function.name, ": mat1 MUST BE MADE OF INTEGER VALUES WITHOUT 0 BECAUSE mat.hsv.h ARGUMENT SET TO FALSE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +mat1 <- mat1 / max(mat1, na.rm = TRUE) +} +} +if(notch != 1){ +different.color <- unique(as.vector(mat1)) +different.color <- different.color[ ! is.na(different.color)] +tempo.different.color <- different.color + c(0, cumsum(rep(notch, length(different.color) - 1))) +tempo.different.color <- tempo.different.color - floor(tempo.different.color) +if(any(duplicated(tempo.different.color) == TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": DUPLICATED VALUES AFTER USING notch (", paste(tempo.different.color[duplicated(tempo.different.color)], collapse = " "), "). TRY ANOTHER notch VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(length(different.color) != length(tempo.different.color)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": LENGTH OF different.color (", paste(different.color, collapse = " "), ") DIFFERENT FROM LENGTH OF tempo.different.color (", paste(tempo.different.color, collapse = " "), ")") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +for(i in 1:length(different.color)){ +mat1[mat1 == different.color[i]] <- tempo.different.color[i] # no need of isTRUE(all.equal()) because different.color comes from mat1 +} +} +} +if( ! is.null(forced.color)){ +hexa.values.to.change <- hsv(unique(sort(mat1))[1:length(forced.color)], s, v) +} +mat1[ ! is.na(mat1)] <- hsv(mat1[ ! is.na(mat1)], s, v) +if( ! is.null(forced.color)){ +if(any(forced.color %in% mat1, na.rm = TRUE)){ +problem <- TRUE +text.problem <- paste0("THE FOLLOWING COLORS WHERE INTRODUCED USING forced.color BUT WHERE ALREADY PRESENT IN THE COLORED MATRIX :", paste(forced.color[forced.color %in% mat1], collapse = " ")) +}else{ +problem <- FALSE +} +for(i in 1:length(hexa.values.to.change)){ +if( ! any(mat1 == hexa.values.to.change[i], na.rm = TRUE)){# no need of isTRUE(all.equal()) because character +tempo.cat <- paste0("ERROR IN ", function.name, ": THE ", hexa.values.to.change[i], " VALUE FROM hexa.values.to.change IS NOT REPRESENTED IN mat1 : ", paste(unique(as.vector(mat1)), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +mat1[which(mat1 == hexa.values.to.change[i])] <- forced.color[i] # no need of isTRUE(all.equal()) because character +} +} +} +output <- list(mat1.name = mat1.name, colored.mat = mat1, problem = problem, text.problem = text.problem) +return(output) +} + + +######## fun_mat_op() #### assemble several matrices with operation + + +fun_mat_op <- function(mat.list, kind.of.operation = "+"){ +# AIM +# assemble several matrices of same dimensions by performing by case operation. For instance add the value of all the case 1 (row1 & column1) of the matrices and put it in the case 1 of a new matrix M, add the value of all the case 2 (row2 & column1) of the matrices and put it in the case 2 of a new matrix M, etc. + +# c: case +# i: row number +# j: column number +# k: matrix number +# z: number of matrices +# ARGUMENTS: +# mat.list: list of matrices +# kind.of.operation: either "+" (by case addition), "-" (by case subtraction) or "*" (by case multiplication) +# RETURN +# the assembled matrix, with row and/or column names only if all the matrices have identical row/column names +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_comp_2d() +# EXAMPLES +# mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; fun_mat_op(mat.list = list(mat1, mat2), kind.of.operation = "+") +# mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; fun_mat_op(mat.list = list(mat1, mat2), kind.of.operation = "*") +# mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(LETTERS[1:4], c(NA, NA))) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; fun_mat_op(mat.list = list(mat1, mat2), kind.of.operation = "-") +# mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(c("A1", "A2", "A3", "A4"), letters[1:2])) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; mat3 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; fun_mat_op(mat.list = list(mat1, mat2, mat3), kind.of.operation = "+") +# DEBUGGING +# mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2) ; mat.list = list(mat1, mat2) ; kind.of.operation = "+" # for function debugging +# mat1 = matrix(c(1,1,1,2,1,5,9,8), ncol = 2, dimnames = list(LETTERS[1:4], c(NA, NA))) ; mat2 = matrix(c(1,1,1,2,1,5,9,NA), ncol = 2, dimnames = list(LETTERS[1:4], letters[1:2])) ; mat.list = list(mat1, mat2) ; kind.of.operation = "*" # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_comp_2d() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = mat.list, class = "list", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = kind.of.operation, options = c("+", "-", "*"), length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# argument checking without fun_check() +if(length(mat.list) < 2){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat.list ARGUMENT MUST BE A LIST CONTAINING AT LEAST 2 MATRICES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +for(i1 in 1:length(mat.list)){ +tempo <- fun_check(data = mat.list[[i1]], class = "matrix", mode = "numeric", na.contain = TRUE) +if(tempo$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ELEMENT ", i1, " OF mat.list ARGUMENT MUST BE A NUMERIC MATRIX") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +ident.row.names <- TRUE +ident.col.names <- TRUE +for(i1 in 2:length(mat.list)){ +tempo <- fun_comp_2d(data1 = mat.list[[1]], data2 = mat.list[[i1]]) +if(tempo$same.dim == FALSE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": MATRIX ", i1, " OF mat.list ARGUMENT MUST HAVE THE SAME DIMENSION (", paste(dim(mat.list[[i1]]), collapse = " "), ") THAN THE MATRIX 1 IN mat.list (", paste(dim(mat.list[[1]]), collapse = " "), ")") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(tempo$same.row.name)){ +if(tempo$same.row.name != TRUE){ # != TRUE to deal with NA +ident.row.names <- FALSE +} +} +if( ! is.null(tempo$same.col.name)){ +if(tempo$same.col.name != TRUE){ # != TRUE to deal with NA +ident.col.names <- FALSE +} +} +} +# end argument checking without fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +output <- mat.list[[1]] +for(i1 in 2:length(mat.list)){ +output <- get(kind.of.operation)(output, mat.list[[i1]]) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +} +dimnames(output) <- NULL +if(ident.row.names == TRUE){ +rownames(output) <- rownames(mat.list[[1]]) +} +if(ident.col.names == TRUE){ +colnames(output) <- colnames(mat.list[[1]]) +} +return(output) +} + + +######## fun_mat_inv() #### return the inverse of a square matrix + + +fun_mat_inv <- function(mat){ +# AIM +# return the inverse of a square matrix when solve() cannot +# ARGUMENTS: +# mat: a square numeric matrix without NULL, NA, Inf or single case (dimension 1, 1) of 0 +# RETURN +# the inversed matrix +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# mat1 = matrix(c(1,1,1,2,1,5,9,8,9), ncol = 3) ; fun_mat_inv(mat = mat1) # use solve() +# mat1 = matrix(c(0,0,0,0,0,0,0,0,0), ncol = 3) ; fun_mat_inv(mat = mat1) # use the trick +# mat1 = matrix(c(1,1,1,2,Inf,5,9,8,9), ncol = 3) ; fun_mat_inv(mat = mat1) +# mat1 = matrix(c(1,1,1,2,NA,5,9,8,9), ncol = 3) ; fun_mat_inv(mat = mat1) +# mat1 = matrix(c(1,2), ncol = 1) ; fun_mat_inv(mat = mat1) +# mat1 = matrix(0, ncol = 1) ; fun_mat_inv(mat = mat1) +# mat1 = matrix(2, ncol = 1) ; fun_mat_inv(mat = mat1) +# DEBUGGING +# mat = matrix(c(1,1,1,2,1,5,9,8,9), ncol = 3) # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = mat, class = "matrix", mode = "numeric", fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# argument checking without fun_check() +if(ncol(mat) != nrow(mat)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A SQUARE MATRIX") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(mat %in% c(Inf, -Inf, NA))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A MATRIX WITHOUT Inf, -Inf OR NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(all(mat == 0L) & ncol(mat) == 1L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT CANNOT BE A SQUARE MATRIX MADE OF A SINGLE CASE OF 0") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end argument checking without fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +if(any(grepl(x = try(solve(mat), silent = TRUE)[], pattern = "[Ee]rror"))){ +tempo <- svd(mat) +val.critique <- which(tempo$d < 10^-8) +Diag.mod <- diag(1 / tempo$d) +for(i in val.critique){ +Diag.mod[i, i] <- 0 +} +return(tempo$v %*% Diag.mod %*% t(tempo$u)) +}else{ +return(solve(mat)) +} +} + + +######## fun_mat_fill() #### fill the empty half part of a symmetric square matrix + + +fun_mat_fill <- function(mat, empty.cell.string = 0, warn.print = FALSE){ +# AIM +# detect the empty half part of a symmetric square matrix (either topleft, topright, bottomleft or bottomright) +# fill this empty half part using the other symmetric half part of the matrix +# WARNINGS +# a plot verification using fun_gg_heatmap() is recommanded +# ARGUMENTS: +# mat: a numeric or character square matrix with the half part (according to the grand diagonal) filled with NA (any kind of matrix), "0" (character matrix) or 0 (numeric matrix) exclusively (not a mix of 0 and NA in the empty part) +# empty.cell.string: a numeric, character or NA (no quotes) indicating what empty cells are filled with +# warn.print: logical. Print warnings at the end of the execution? No print if no warning messages +# RETURN +# a list containing: +# $mat: the filled matrix +# $warn: the warning messages. Use cat() for proper display. NULL if no warning +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# mat1 = matrix(c(1,NA,NA,NA, 0,2,NA,NA, NA,3,4,NA, 5,6,7,8), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = NA, warn.print = TRUE) # bottomleft example +# mat1 = matrix(c(1,1,1,2, 0,2,3,0, NA,3,0,0, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = NA, warn.print = TRUE) # error example +# mat1 = matrix(c(1,1,1,2, 0,2,3,0, NA,3,0,0, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = 0, warn.print = TRUE) # bottomright example +# mat1 = matrix(c(1,1,1,2, "a",2,3,NA, "a","a",0,0, "a","a","a",0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = "a", warn.print = TRUE) # topright example +# mat1 = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,NA, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = 0, warn.print = TRUE) # topleft example +# mat1 = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,0, 5,0,0,0), ncol = 4) ; mat1 ; fun_mat_fill(mat = mat1, empty.cell.string = 0, warn.print = TRUE) # error example +# DEBUGGING +# mat = matrix(c(1,NA,NA,NA, 0,2,NA,NA, NA,3,4,NA, 5,6,7,8), ncol = 4) ; empty.cell.string = NA ; warn.print = TRUE # for function debugging +# mat = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,NA, 5,0,0,0), ncol = 4) ; empty.cell.string = 0 ; warn.print = TRUE # for function debugging # topleft example +# mat = matrix(c(0,0,0,2, 0,0,3,0, 0,3,0,NA, 5,0,0,0), ncol = 4) ; empty.cell.string = NA ; warn.print = TRUE # for function debugging # topleft example +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = mat, class = "matrix", na.contain = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = empty.cell.string, class = "vector", na.contain = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# argument checking without fun_check() +if(ncol(mat) != nrow(mat)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A SQUARE MATRIX") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! (base::mode(mat) %in% c("numeric", "character"))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MUST BE A NUMERIC OR CHARACTER MATRIX") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(nrow(mat) == 1L & ncol(mat) == 1L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT CANNOT BE A SQUARE MATRIX MADE OF A SINGLE CASE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(ifelse(is.na(empty.cell.string), ! any(is.na(mat)), ! any(mat == empty.cell.string, na.rm = TRUE))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": mat ARGUMENT MATRIX MUST HAVE CELLS WITH THE EMPTY STRING SPECIFIED IN empty.cell.string ARGUMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end argument checking without fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +list.diag <- vector("list", length = nrow(mat) - 1) +for(i1 in 1:(nrow(mat) - 1)){ +list.diag[[i1]] <- numeric(length = nrow(mat) - i1) # list made of zero +} +sector <- c("topleft", "topright", "bottomright", "bottomleft") +diag.scan <-c( # same order as sector. Recover each diag from center to corner +"mat[as.matrix(as.data.frame(list(1:(nrow(mat) - i2), (ncol(mat) -i2):1), stringsAsFactors = TRUE))]", # topleft part +"mat[as.matrix(as.data.frame(list(1:(nrow(mat) - i2), (1:ncol(mat))[-(1:i2)]), stringsAsFactors = TRUE))]", # topright part +"mat[as.matrix(as.data.frame(list((1 + i2):nrow(mat), ncol(mat):(1 + i2)), stringsAsFactors = TRUE))]", # bottomright part +"mat[as.matrix(as.data.frame(list((1 + i2):nrow(mat), 1:(ncol(mat) -i2)), stringsAsFactors = TRUE))]" # bottomleft part +) +# empty part detection +empty.sector <- NULL +full.sector <- NULL +ini.warning.length <- options()$warning.length +options(warning.length = 8170) +warn <- NULL +warn.count <- 0 +for(i1 in 1:length(sector)){ +tempo.list.diag <- list.diag +for(i2 in 1:(nrow(mat) - 1)){ +tempo.list.diag[[i2]] <- eval(parse(text = diag.scan[i1])) +if(ifelse(is.na(empty.cell.string), ! all(is.na(tempo.list.diag[[i2]])), ! (all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = TRUE) & ! (is.na(all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = FALSE)))))){ # I had to add this ! (is.na(all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = FALSE))) because all(tempo.list.diag[[i2]] == empty.cell.string, na.rm = FALSE) gives NA and not FALSE if one NA in tempo.list.diag[[i2]] -> not good for if() +full.sector <- c(full.sector, sector[i1]) +break +} +} +if(i2 == nrow(mat) - 1){ +if(all(unlist(lapply(tempo.list.diag, FUN = function(x){if(is.na(empty.cell.string)){is.na(x)}else{x == empty.cell.string}})), na.rm = TRUE)){ +empty.sector <- c(empty.sector, sector[i1]) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") EMPTY SECTOR DETECTED ON THE ", toupper(sector[i1]), " CORNER, FULL OF ", empty.cell.string) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE ", toupper(sector[i1]), " SECTOR, DETECTED AS EMPTY, IS NOT? DIFFERENT VALUES IN THIS SECTOR:\n", paste(names(table(unlist(tempo.list.diag), useNA = "ifany")), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +} +# end empty part detection +if(length(empty.sector) == 0L){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") ACCORDING TO empty.cell.string ARGUMENT (", empty.cell.string, "), mat ARGUMENT MATRIX HAS ZERO EMPTY HALF PART") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +if(length(empty.sector) > 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ACCORDING TO empty.cell.string ARGUMENT (", empty.cell.string, "), mat ARGUMENT MATRIX HAS MORE THAN ONE EMPTY HALF PART (ACCORDING TO THE GRAND DIAGONAL): ", paste(empty.sector, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(any(full.sector %in% empty.sector, na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE FUNCTION HAS DETECTED EMPTY AND NON EMPTY HALF PART IN THE SAME SECTOR: ", paste(full.sector[full.sector %in% empty.sector], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(length(empty.sector) + length(full.sector)!= 4){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE FUNCTION HAS DETECTED MORE OR LESS SECTORS THAN 4:\nHALF SECTORS:", paste(empty.sector, collapse = " "), "\nFULL SECTORS:", paste(full.sector, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") ", toupper(empty.sector), " SECTOR HAS BEEN COMPLETED TO BECOME SYMMETRICAL") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# matrix filling +for(i2 in 1:(nrow(mat) - 1)){ +if(empty.sector == "topleft"){ +eval(parse(text = paste0(diag.scan[1], " <- ", diag.scan[3]))) +}else if(empty.sector == "topright"){ +eval(parse(text = paste0(diag.scan[2], " <- ", diag.scan[4]))) +}else if(empty.sector == "bottomright"){ +eval(parse(text = paste0(diag.scan[3], " <- ", diag.scan[1]))) +}else if(empty.sector == "bottomleft"){ +eval(parse(text = paste0(diag.scan[4], " <- ", diag.scan[2]))) +} +} +# end matrix filling +} +if(warn.print == TRUE & ! is.null(warn)){ +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) +} +on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) +return(list(mat = mat, warn = warn)) +} + + +######## fun_permut() #### progressively breaks a vector order + + +fun_permut <- function( +data1, +data2 = NULL, +n = NULL, +seed = NULL, +print.count = 10, +text.print = "", +cor.method = "spearman", +cor.limit = 0.2, +warn.print = FALSE, +lib.path = NULL +){ +# AIM +# reorder the elements of the data1 vector by flipping 2 randomly selected consecutive positions either: +# 1) n times (when n is precised) or +# 2) until the correlation between data1 and data2 decreases down to the cor.limit (0.2 by default). See cor.limit below to deal with negative correlations +# Example of consecutive position flipping: ABCD -> BACD -> BADC, etc. +# designed for discrete values, but worls also for continuous values +# WARNINGS +# see # https://www.r-bloggers.com/strategies-to-speedup-r-code/ for code speedup +# the random switch of non consecutive positions (ABCD -> DBCA for instance) does not work very well as the correlation is quickly obtained but the initial vector structure is mainly kept (no much order). Ths code would be: pos <- ini.pos[1:2] ; pos <- sample.int(n = n , size = 2, replace = FALSE) ; tempo.pos[pos] <- tempo.pos[rev(pos)] +# ARGUMENTS +# data1: a vector of at least 2 elements. Must be numeric if data2 is specified +# data2: a numeric vector of same length as data1 +# n: number of times "flipping 2 randomly selected consecutive positions". Ignored if data2 is specified +# seed: integer number used by set.seed(). Write NULL if random result is required, an integer otherwise. BEWARE: if not NULL, fun_permut() will systematically return the same result when the other parameters keep the same settings +# print.count: interger value. Print a working progress message every print.count during loops. BEWARE: can increase substentially the time to complete the process using a small value, like 10 for instance. Use Inf is no loop message desired +# text.print: optional message to add to the working progress message every print.count loop +# cor.method: correlation method. Either "pearson", "kendall" or "spearman". Ignored if data2 is not specified +# cor.limit: a correlation limit (between 0 and 1). Ignored if data2 is not specified. Compute the correlation between data1 and data2, permute the data1 values, and stop the permutation process when the correlation between data1 and data2 decreases down below the cor limit value (0.2 by default). If cor(data1, data2) is negative, then -cor.limit is used and the process stops until the correlation between data1 and data2 increases up over cor.limit (-0.2 by default). BEWARE: write a positive cor.limit even if cor(data1, data2) is known to be negative. The function will automatically uses -cor.limit. If the initial correlation is already below cor.limit (positive correlation) or over -cor.limit (negative correlation), then the data1 value positions are completely randomized (correlation between data1 and data2 is expected to be 0) +# warn.print: logical. Print warnings at the end of the execution? No print if no warning messages +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL +# RETURN +# a list containing: +# $data: the modified vector +# $warn: potential warning messages (in case of negative correlation when data2 is specified). NULL if non warning message +# $cor: a spearman correlation between the initial positions (1:length(data1) and the final positions if data2 is not specified and the final correlation between data1 and data2 otherwise, according to cor.method +# $count: the number of loops used +# REQUIRED PACKAGES +# lubridate +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_pack() +# fun_round() +# EXAMPLES +# example (1) showing that for loop, used in fun_permut(), is faster than while loop +# ini.time <- as.numeric(Sys.time()) ; count <- 0 ; for(i0 in 1:1e9){count <- count + 1} ; tempo.time <- as.numeric(Sys.time()) ; tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) ; tempo.lapse +# example (2) showing that for loop, used in fun_permut(), is faster than while loop +# ini.time <- as.numeric(Sys.time()) ; count <- 0 ; while(count < 1e9){count <- count + 1} ; tempo.time <- as.numeric(Sys.time()) ; tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) ; tempo.lapse +# fun_permut(data1 = LETTERS[1:5], data2 = NULL, n = 100, seed = 1, print.count = 10, text.print = "CPU NB 4") +# fun_permut(data1 = 101:110, data2 = 21:30, seed = 1, print.count = 1e4, text.print = "", cor.method = "spearman", cor.limit = 0.2) +# a way to use the cor.limit argument just considering data1 +# obs1 <- 101:110 ; fun_permut(data1 = obs1, data2 = obs1, seed = 1, print.count = 10, cor.method = "spearman", cor.limit = 0.2) +# fun_permut(data1 = 1:1e3, data2 = 1e3:1, seed = 1, print.count = 1e6, text.print = "", cor.method = "spearman", cor.limit = 0.7) +# fun_permut(data1 = 1:1e2, data2 = 1e2:1, seed = 1, print.count = 1e3, cor.limit = 0.5) +# fun_permut(data1 = c(0,0,0,0,0), n = 5, data2 = NULL, seed = 1, print.count = 1e3, cor.limit = 0.5) +# DEBUGGING +# data1 = LETTERS[1:5] ; data2 = NULL ; n = 1e6 ; seed = NULL ; print.count = 1e3 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.2 ; warn.print = TRUE ; lib.path = NULL +# data1 = LETTERS[1:5] ; data2 = NULL ; n = 10 ; seed = 22 ; print.count = 10 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.2 ; warn.print = TRUE ; lib.path = NULL +# data1 = 101:110 ; data2 = 21:30 ; n = 10 ; seed = 22 ; print.count = 10 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.2 ; warn.print = TRUE ; lib.path = NULL +# data1 = 1:1e3 ; data2 = 1e3:1 ; n = 20 ; seed = 22 ; print.count = 1e6 ; text.print = "" ; cor.method = "spearman" ; cor.limit = 0.5 ; warn.print = TRUE ; lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_pack", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_round", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data1, class = "vector", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & length(data1) < 2){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A VECTOR OF MINIMUM LENGTH 2. HERE IT IS: ", length(data1)) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(data2)){ +tempo <- fun_check(data = data1, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) +if(tempo$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data1 MUST BE A NUMERIC VECTOR IF data2 ARGUMENT IS SPECIFIED") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = data2, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) +if(length(data1) != length(data2)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data1 AND data2 MUST BE VECTOR OF SAME LENGTH. HERE IT IS ", length(data1)," AND ", length(data2)) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else if(is.null(n)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT CANNOT BE NULL IF data2 ARGUMENT IS NULL") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(n)){ +tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +} +if( ! is.null(seed)){ +tempo <- fun_check(data = seed, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = TRUE, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = print.count, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = text.print, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = cor.method, options = c("pearson", "kendall", "spearman"), length =1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = cor.limit, class = "vector", mode = "numeric", prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# package checking +fun_pack(req.package = "lubridate", lib.path = lib.path) +# end package checking +# main code +# code that protects set.seed() in the global environment +# see also Protocol 100-rev0 Parallelization in R.docx +if(exists(".Random.seed", envir = .GlobalEnv)){ # if .Random.seed does not exists, it means that no random operation has been performed yet in any R environment +tempo.random.seed <- .Random.seed +on.exit(assign(".Random.seed", tempo.random.seed, env = .GlobalEnv)) +}else{ +on.exit(set.seed(NULL)) # inactivate seeding -> return to complete randomness +} +set.seed(seed) +# end code that protects set.seed() in the global environment +ini.date <- Sys.time() # time of process begin, converted into seconds +ini.time <- as.numeric(ini.date) # time of process begin, converted into seconds +ini.pos <- 1:length(data1) # positions of data1 before permutation loops +tempo.pos <- ini.pos # positions of data1 that will be modified during loops +# pos.selec.seq <- ini.pos[-length(data1)] # selection of 1 position in initial position, without the last because always up permutation (pos -> pos+1 & pos+1 -> pos) +pos.selec.seq.max <- length(ini.pos) - 1 # max position (used by sample.int() function). See below for - 1 +ini.warning.length <- options()$warning.length +options(warning.length = 8170) +warn <- NULL +warn.count <- 0 +count <- 0 +round <- 0 +BREAK <- FALSE +tempo.cor <- 0 +if(is.null(data2)){ +if(length(table(data1)) == 1L){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NO PERMUTATION PERFORMED BECAUSE data1 ARGUMENT SEEMS TO BE MADE OF IDENTICAL ELEMENTS: ", names(table(data1))) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # +}else{ +if(print.count > n){ +print.count <- n +} +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP OF ", n, " LOOPS INITIATED | LOOP COUNT: ", format(count, big.mark=","))) +print.count.loop <- logical(length = print.count) +print.count.loop[length(print.count.loop)] <- TRUE # not this to avoid long vector, but not forget to reset during printing: print.count.loop[(1:trunc(n / print.count) * print.count)] <- TRUE # counter to speedup +count.loop <- 0 +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +tempo.date.loop <- Sys.time() +tempo.time.loop <- as.numeric(tempo.date.loop) +for(i3 in 1:n){ +count.loop <- count.loop + 1 +pos2 <- pos[count.loop] # selection of 1 position +tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +if(print.count.loop[count.loop]){ +count.loop <- 0 +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) +final.loop <- (tempo.time - tempo.time.loop) / i3 * n # expected duration in seconds +final.exp <- as.POSIXct(final.loop, origin = tempo.date.loop) +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP ", i3, " / ", n, " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) +} +} +count <- count + n # out of the loop to speedup +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP ENDED | LOOP COUNT: ", format(count, big.mark=","))) +cat("\n\n") +} +}else{ +if(length(table(data1)) == 1L){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NO PERMUTATION PERFORMED BECAUSE data1 ARGUMENT SEEMS TO BE MADE OF IDENTICAL ELEMENTS: ", names(table(data1))) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # +tempo.cor <- 1 +}else if(length(table(data2)) == 1L){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NO PERMUTATION PERFORMED BECAUSE data2 ARGUMENT SEEMS TO BE MADE OF IDENTICAL ELEMENTS: ", names(table(data2))) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # +tempo.cor <- 1 +}else{ +cor.ini <- cor(x = data1, y = data2, use = "pairwise.complete.obs", method = cor.method) +tempo.cor <- cor.ini # correlation that will be modified during loops +neg.cor <- FALSE +if(tempo.cor < 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") INITIAL ", toupper(cor.method), " CORRELATION BETWEEN data1 AND data2 HAS BEEN DETECTED AS NEGATIVE: ", tempo.cor, ". THE LOOP STEPS WILL BE PERFORMED USING POSITIVE CORRELATIONS BUT THE FINAL CORRELATION WILL BE NEGATIVE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # +neg.cor <- TRUE +tempo.cor <- abs(tempo.cor) +cor.ini <- abs(cor.ini) +} +if(tempo.cor < cor.limit){ # randomize directly all the position to be close to correlation zero +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") INITIAL ABSOLUTE VALUE OF THE ", toupper(cor.method), " CORRELATION ", fun_round(tempo.cor), " BETWEEN data1 AND data2 HAS BEEN DETECTED AS BELOW THE CORRELATION LIMIT PARAMETER ", cor.limit, "\nTHE data1 SEQUENCE HAS BEEN COMPLETELY RANDOMIZED TO CORRESPOND TO CORRELATION ZERO") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) # +for(i4 in 1:5){ # done 5 times to be sure of the complete randomness +tempo.pos <- sample(x = tempo.pos, size = length(tempo.pos), replace = FALSE) +} +count <- count + 5 # out of the loop to speedup +}else{ +# smallest correlation decrease +count <- count + 1 # 1 and not 0 because already 1 performed just below +pos <- sample.int(n = pos.selec.seq.max , size = 1, replace = TRUE) # selection of 1 position # pos.selec.seq.max because selection of 1 position in initial position, without the last because always up permutation (pos -> pos+1 & pos+1 -> pos) +tempo.pos[c(pos + 1, pos)] <- tempo.pos[c(pos, pos + 1)] +tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) +smallest.cor.dec <- cor.ini - tempo.cor +# end smallest correlation decrease +# going out of tempo.cor == cor.ini +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "CORRELATION DECREASE AFTER A SINGLE PERMUTATION: ", fun_round(smallest.cor.dec, 4))) +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FIRST WHILE LOOP STEP -> GOING OUT FROM EQUALITY | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) +print.count.loop <- logical(length = print.count) +print.count.loop[length(print.count.loop)] <- TRUE # counter to speedup +count.loop <- 0 # +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +tempo.date.loop <- Sys.time() +tempo.time.loop <- as.numeric(tempo.date.loop) +while(tempo.cor == cor.ini){ # to be out of equality between tempo.cor and cor.ini at the beginning (only valid for very long vector) +count <- count + 1 +count.loop <- count.loop + 1 +pos2 <- pos[count.loop] +tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) +if(print.count.loop[count.loop]){ +count.loop <- 0 +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FIRST WHILE LOOP STEP", format(count.loop, big.mark=","), " / ? | COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TIME SPENT: ", tempo.lapse)) +} +} +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FIRST WHILE LOOP STEP END | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TOTAL SPENT TIME: ", tempo.lapse)) +if(tempo.cor < cor.limit){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE FIRST FOR & WHILE LOOP STEPS HAVE BEEN TOO FAR AND SUBSEQUENT LOOP STEPS WILL NOT RUN") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# end going out of tempo.cor == cor.ini +# estimation of the average correlation decrease per loop on x loops and for loop execution +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "WHILE/FOR LOOPS INITIATION | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) +count.est <- 1e5 +first.round <- TRUE +GOBACK <- FALSE +while(tempo.cor > cor.limit){ +round <- round + 1 +# estimation step +if(first.round == TRUE){ +first.round <- FALSE +cor.dec.per.loop <- numeric(length = 5) +loop.nb.est <- Inf +cor.est.ini <- tempo.cor +cor.est <- numeric(length = 5) +for(i6 in 1:5){ # connected to cor.dec.per.loop +tempo.pos.est <- tempo.pos +pos <- sample.int(n = pos.selec.seq.max , size = count.est, replace = TRUE) # selection of n position +for(i7 in 1:count.est){ +pos2 <- pos[i7] # selection of 1 position +tempo.pos.est[c(pos2 + 1, pos2)] <- tempo.pos.est[c(pos2, pos2 + 1)] +} +tempo.cor.est <- abs(cor(x = data1[tempo.pos.est], y = data2, use = "pairwise.complete.obs", method = cor.method)) +cor.est[i6] <- tempo.cor.est +tempo.cor.dec.per.loop <- (cor.est.ini - tempo.cor.est) / count.est # correlation decrease per loop +if(is.na(tempo.cor.dec.per.loop) | ! is.finite(tempo.cor.dec.per.loop)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2\ncor.est.ini: ", cor.est.ini, "\ntempo.cor.est: ", tempo.cor.est) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +cor.dec.per.loop[i6] <- tempo.cor.dec.per.loop +} +cor.est <- cor.est[which.max(cor.dec.per.loop)] # max to avoid to go to far with for loop (tempo.cor below tempo.limit) +cor.dec.per.loop <- max(cor.dec.per.loop, na.rm = TRUE) # max to avoid to go to far with for loop (tempo.cor below tempo.limit) +loop.nb.est <- round((tempo.cor - cor.limit) / cor.dec.per.loop) +}else{ +if(GOBACK == TRUE){ +loop.nb.est <- round(loop.nb.est / 2) +}else{ +cor.dec.per.loop <- (cor.ini - tempo.cor) / count +loop.nb.est <- round((tempo.cor - cor.limit) / cor.dec.per.loop) +} +} +# end estimation step +# loop step +if(is.na(loop.nb.est) | ! is.finite(loop.nb.est)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 1\nloop.nb.est: ", loop.nb.est, "\ncor.ini: ", cor.ini, "\ntempo.cor: ", tempo.cor, "\ncor.limit: ", cor.limit, "\ncor.dec.per.loop: ", cor.dec.per.loop) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(loop.nb.est > 1e4){ # below -> leave the while loop +tempo.pos.secu <- tempo.pos +count.secu <- count +tempo.cor.secu <- tempo.cor +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "INITIAL SETTINGS BEFORE ROUND: ", round, " | LOOP COUNT: ", format(count, big.mark=","), " | GO BACK: ", GOBACK, " | LOOP NUMBER ESTIMATION: ", format(loop.nb.est, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) +print.count.loop <- logical(length = print.count) +print.count.loop[length(print.count.loop)] <- TRUE # not this to avoid long vector, but not forget to reset during printing: print.count.loop[(1:trunc(n / print.count) * print.count)] <- TRUE # counter to speedup +count.loop <- 0 +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +tempo.date.loop <- Sys.time() +tempo.time.loop <- as.numeric(tempo.date.loop) +for(i6 in 1:loop.nb.est){ +count.loop <- count.loop + 1 +pos2 <- pos[count.loop] # selection of 1 position +tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +if(print.count.loop[count.loop]){ +count.loop <- 0 +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) +final.loop <- (tempo.time - tempo.time.loop) / i6 * loop.nb.est # expected duration in seconds # intra nb.compar loop lapse: time lapse / cycles done * cycles remaining +final.exp <- as.POSIXct(final.loop, origin = tempo.date.loop) +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FOR LOOP | ROUND ", round, " | LOOP: ", format(i6, big.mark=","), " / ", format(loop.nb.est, big.mark=","), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) +} +} +count <- count + loop.nb.est # out of the loop to speedup +tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) +if(tempo.cor > tempo.cor.secu | ((tempo.cor - cor.limit) < 0 & abs(tempo.cor - cor.limit) > smallest.cor.dec * round(log10(max(ini.pos, na.rm = TRUE))))){ +GOBACK <- TRUE +tempo.pos <- tempo.pos.secu +count <- count.secu +tempo.cor <- tempo.cor.secu +}else{ +GOBACK <- FALSE +} +}else{ +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "FINAL WHILE LOOP | LOOP COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4))) +print.count.loop <- logical(length = print.count) +print.count.loop[length(print.count.loop)] <- TRUE # counter to speedup +count.loop <- 0 # +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # selection of random positions. BEWARE: n = pos.selec.seq.max because already - 1 (see above) but is connected to tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +tempo.cor.loop <- tempo.cor +tempo.date.loop <- Sys.time() +tempo.time.loop <- as.numeric(tempo.date.loop) +while(tempo.cor > cor.limit){ +count <- count + 1 +count.loop <- count.loop + 1 +pos2 <- pos[count.loop] +tempo.pos[c(pos2 + 1, pos2)] <- tempo.pos[c(pos2, pos2 + 1)] +tempo.cor <- abs(cor(x = data1[tempo.pos], y = data2, use = "pairwise.complete.obs", method = cor.method)) +if(print.count.loop[count.loop]){ +count.loop <- 0 +pos <- sample.int(n = pos.selec.seq.max , size = print.count, replace = TRUE) # BEWARE: never forget to resample here +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - tempo.time.loop)) +final.loop <- (tempo.time - tempo.time.loop) / (tempo.cor.loop - tempo.cor) * (tempo.cor - cor.limit) # expected duration in seconds # tempo.cor.loop - tempo.cor always positive and tempo.cor decreases progressively starting from tempo.cor.loop +final.exp <- as.POSIXct(final.loop, origin = tempo.date.loop) +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "WHILE LOOP | LOOP NB: ", format(count.loop, big.mark=","), " | COUNT: ", format(count, big.mark=","), " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) +} +} +} +} +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) +cat(paste0("\n", ifelse(text.print == "", "", paste0(text.print, " | ")), "WHILE/FOR LOOPS END | LOOP COUNT: ", format(count, big.mark=","), " | NB OF ROUNDS: ", round, " | CORRELATION LIMIT: ", fun_round(cor.limit, 4), " | ABS TEMPO CORRELATION: ", fun_round(tempo.cor, 4), " | TOTAL SPENT TIME: ", tempo.lapse)) +} +tempo.cor <- ifelse(neg.cor == TRUE, -tempo.cor, tempo.cor) +} +} +cat("\n\n") +if(warn.print == TRUE & ! is.null(warn)){ +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE), add = TRUE) +} +on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) +output <- list(data = data1[tempo.pos], warn = warn, cor = if(is.null(data2)){cor(ini.pos, tempo.pos, method = "spearman")}else{tempo.cor}, count = count) +return(output) +} + + +######## fun_slide() #### return a computation made on a vector using a sliding window + + +fun_slide <- function( +data, +window.size, +step, +from = NULL, +to = NULL, +fun, +args = NULL, +boundary = "left", +parall = FALSE, +thread.nb = NULL, +print.count = 100, +res.path = NULL, +lib.path = NULL, +verbose = TRUE, +cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" +){ +# AIM +# return a computation made on a vector using a sliding window +# WARNINGS +# The function uses two strategies, depending on the amout of memory required which depends on the data, window.size and step arguments. The first one uses lapply(), is generally fast but requires lots of memory. The second one uses a parallelized loop. The choice between the two strategies is automatic if parall argument is FALSE, and is forced toward parallelization if parall argument is TRUE +# The parall argument forces the parallelization, which is convenient when the data argument is big, because the lapply() function is sometimes slower than the parallelization +# Always use the env argument when fun_slide() is used inside functions +# ARGUMENTS +# data: vector, matrix, table or array of numeric values (mode must be numeric). Inf not allowed. NA will be removed before computation +# window.size: single numeric value indicating the width of the window sliding across data (in the same unit as data value) +# step: single numeric value indicating the step between each window (in the same unit as data value). Cannot be larger than window.size +# from: value of the left boundary of the first sliding window. If NULL, min(data) is used. The first window will strictly have from or min(data) as left boundary +# to: value of the right boundary of the last sliding window. If NULL, max(data) is used. Warning: (1) the final last window will not necessary have to|max(data) as right boundary. In fact the last window will be the one that contains to|max(data) for the first time, i.e., min[from|min(data) + window.size + n * step >= to|max(data)]; (2) In fact, the >= in min[from|min(data) + window.size + n * step >= to|max(data)] depends on the boundary argument (>= for "right" and > for "left"); (3) to have the rule (1) but for the center of the last window, use to argument as to = to|max(data) + window.size / 2 +# fun: function or character string (without brackets) indicating the name of the function to apply in each window. Example: fun = "mean", or fun = mean +# args: character string of additional arguments of fun (separated by a comma between the quotes). Example args = "na.rm = TRUE" for fun = mean. Ignored if NULL +# boundary: either "left" or "right". Indicates if the sliding window includes values equal to left boundary and exclude values equal to right boundary ("left") or the opposite ("right") +# parall: logical. Force parallelization ? +# thread.nb: numeric value indicating the number of threads to use if ever parallelization is required. If NULL, all the available threads will be used. Ignored if parall is FALSE +# print.count: interger value. Print a working progress message every print.count during loops. BEWARE: can increase substentially the time to complete the process using a small value, like 10 for instance. Use Inf is no loop message desired +# res.path: character string indicating the absolute pathway where the parallelization log file will be created if parallelization is used. If NULL, will be created in the R current directory +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL +# verbose: logical. Display messages? +# cute.path: character string indicating the absolute path of the cute.R file. Will be remove when cute will be a package. Ignored if parall is FALSE +# RETURN +# a data frame containing +#$left : the left boundary of each window (in the unit of the data argument) +#$right : the right boundary of each window (in the unit of data argument) +#$center : the center of each window (in the unit of data argument) +#$value : the computed value by the fun argument in each window) +# REQUIRED PACKAGES +# lubridate +# parallel if parall argument is TRUE (included in the R installation packages but not automatically loaded) +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_get_message +# fun_pack() +# EXAMPLES +# fun_slide(data = c(1:10, 100:110, 500), window.size = 5, step = 2, fun = length, boundary = "left") +# fun_slide(data = c(1:10, 100:110, 500), window.size = 5, step = 2, fun = length, boundary = "right") # effect of boundary argument +# fun_slide(data = c(1:10, 100:110, 500), window.size = 5, step = 2, fun = length, boundary = "left", parall = TRUE) # effect of parall argument +# DEBUGGING +# data = c(1:10, 100:110, 500) ; window.size = 5 ; step = 2 ; from = NULL ; to = NULL ; fun = length ; args = NULL ; boundary = "left" ; parall = FALSE ; thread.nb = NULL ; print.count = 100 ; res.path = NULL ; lib.path = NULL ; verbose = TRUE ; cute.path = "C:\\Users\\Gael\\Documents\\Git_projects\\cute_little_R_functions\\cute_little_R_functions.R" +# data = lag.pos; window.size = window.size; step = step; fun = length; from = min(a$pos); to = max(a$pos) +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +req.function <- c( +"fun_check", +"fun_get_message", +"fun_pack" +) +tempo <- NULL +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo <- c(tempo, i1) +} +} +if( ! is.null(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# reserved words +# end reserved words +# arg with no default values +mandat.args <- c( +"data", +"window.size", +"step", +"fun" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data, mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = window.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = step, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(from)){ +tempo <- fun_check(data = from, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +} +if( ! is.null(to)){ +tempo <- fun_check(data = to, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +} +tempo1 <- fun_check(data = fun, class = "vector", mode = "character", length = 1, fun.name = function.name) +tempo2 <- fun_check(data = fun, class = "function", length = 1, fun.name = function.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": fun ARGUMENT MUST BE A FUNCTION OR A CHARACTER STRING OF THE NAME OF A FUNCTION") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(args)){ +tempo <- fun_check(data = args, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = boundary, options = c("left", "right"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = parall, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(parall == TRUE){ +if( ! is.null(thread.nb)){ +tempo <- fun_check(data = thread.nb, typeof = "integer", double.as.integer.allowed = TRUE, neg.values = FALSE, length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & thread.nb < 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": thread.nb PARAMETER MUST EQUAL OR GREATER THAN 1: ", thread.nb) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +tempo <- fun_check(data = print.count, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +if( ! is.null(res.path)){ +tempo <- fun_check(data = res.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +} +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = verbose, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = cute.path, class = "vector", typeof = "character", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end using fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# new environment +env.name <- paste0("env", as.numeric(Sys.time())) +if(exists(env.name, where = -1)){ # verify if still ok when fun_info() is inside a function +tempo.cat <- paste0("ERROR IN ", function.name, ": ENVIRONMENT env.name ALREADY EXISTS. PLEASE RERUN ONCE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +assign(env.name, new.env()) +} +# end new environment +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS", "THIS ARGUMENT"), " CANNOT JUST BE NA:", paste0(tempo.arg[tempo.log], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"data", +"window.size", +"step", +# "from", # because can be NULL +# "to", # because can be NULL +"fun", +# "args", # because can be NULL +"boundary", +"parall", +# "thread.nb", # because can be NULL +"print.count", +# "res.path", # because can be NULL +# "lib.path", # because can be NULL +"verbose", +"cute.path" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){# normally no NA with is.null() +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# code that protects set.seed() in the global environment +# end code that protects set.seed() in the global environment +# warning initiation +# end warning initiation +# other checkings +if(length(data) == 0){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT CANNOT BE LENGTH 0") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if(any( ! is.finite(data))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT CANNOT CONTAIN Inf VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if(step > window.size){ +tempo.cat <- paste0("ERROR IN ", function.name, ": step ARGUMENT MUST BE LOWER THAN window.size ARGUMENT\nstep: ", paste(step, collapse = " "), "\nwindow.size: ", paste(window.size, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +if( ! is.null(res.path)){ +if( ! all(dir.exists(res.path))){ # separation to avoid the problem of tempo$problem == FALSE and res.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE res.path ARGUMENT DOES NOT EXISTS:\n", paste(res.path, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +}else{ +res.path <- getwd() # working directory +} +if( ! is.null(lib.path)){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) +} +} +if(grepl(x = cute.path, pattern = "^http")){ +tempo.error1 <- any(grepl(x = fun_get_message(data = "source(cute.path)", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE)), pattern = "^[Ee]rror")) +tempo.error2 <- FALSE +}else{ +tempo.error1 <- FALSE +tempo.error2 <- ! file.exists(cute.path) +} +if(tempo.error1 | tempo.error2){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(grepl(x = cute.path, pattern = "^http"), "URL", "FILE"), " PATH INDICATED IN THE cute.path PARAMETER DOES NOT EXISTS:\n", cute.path) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +# end other checkings +# reserved word checking +# end reserved word checking +# end second round of checking and data preparation +# package checking +fun_pack(req.package = c("lubridate"), lib.path = lib.path) +fun_pack(req.package = c("parallel"), lib.path = lib.path) +# end package checking +# main code +if(verbose == TRUE){ +cat("\nfun_slide JOB IGNITION\n") +} +ini.date <- Sys.time() +ini.time <- as.numeric(ini.date) # time of process begin, converted into seconds +fun <- match.fun(fun) # make fun <- get(fun) is fun is a function name written as character string of length 1 +if(boundary == "left"){ +left <- ">=" +right <- "<" +right.last.wind <- ">" +}else if(boundary == "right"){ +left <- ">" +right <- "<=" +right.last.wind <- ">=" +}else{ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +data <- as.vector(data) +data <- sort(data, na.last = NA) # NA removed +wind <- data.frame(left = seq(from = if(is.null(from)){min(data, na.rm = TRUE)}else{from}, to = if(is.null(to)){max(data, na.rm = TRUE)}else{to}, by = step), stringsAsFactors = TRUE) +wind <- data.frame(wind, right = wind$left + window.size, stringsAsFactors = TRUE) +wind <- data.frame(wind, center = (wind$left + wind$right) / 2, stringsAsFactors = TRUE) +if(all(wind$right < if(is.null(to)){max(data, na.rm = TRUE)}else{to})){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 2") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# The 3 next lines is for the rule of to argument with center (see to argument description) +# if(any(wind$center > max(data, na.rm = TRUE))){ +# wind <- wind[ ! wind$center > max(data, na.rm = TRUE),] +# } +if(sum(get(right.last.wind)(wind$right, if(is.null(to)){max(data, na.rm = TRUE)}else{to}), na.rm = TRUE) > 1){ # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +tempo.log <- get(right.last.wind)(wind$right, if(is.null(to)){max(data, na.rm = TRUE)}else{to}) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +tempo.log[min(which(tempo.log), na.rm = TRUE)] <- FALSE # convert the first left boundary that goes above max(data, na.rm = TRUE) to FALSE to keep it (the next ones will be removed) +wind <- wind[ ! tempo.log,] +} + +# test if lapply can be used +if(parall == FALSE){ +assign("wind", wind, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # wind assigned in a new envir for test +assign("data", data, envir = get(env.name, env = sys.nframe(), inherit = FALSE)) # data assigned in a new envir for test +tempo.message <- fun_get_message(data="lapply(X = wind$left, Y = data, FUN = function(X, Y){res <- get(left)(Y, X) ; return(res)})", kind = "error", header = FALSE, env = get(env.name, env = sys.nframe(), inherit = FALSE), print.no = FALSE) # no env = sys.nframe(), inherit = FALSE in get(left) because look for function in the classical scope +# rm(env.name) # optional, because should disappear at the end of the function execution +}else{ +tempo.message <- "ERROR" # with this, force the parallelization by default +} +# end test if lapply can be used +if( ! any(grepl(x = tempo.message, pattern = "ERROR.*"))){ +left.log <- lapply(X = wind$left, Y = data, FUN = function(X, Y){ +res <- get(left)(Y, X) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +return(res) +}) +right.log <- lapply(X = wind$right, Y = data, FUN = function(X, Y){ +res <- get(right)(Y, X) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +return(res) +}) +log <- mapply(FUN = "&", left.log, right.log, SIMPLIFY = FALSE) +output <- eval(parse(text = paste0("sapply(lapply(log, FUN = function(X){(data[X])}), FUN = fun", if( ! is.null(args)){paste0(", ", args)}, ")"))) # take the values of the data vector according to log (list of logical, each compartment of length(data)) and apply fun with args of fun +if(length(output) != nrow(wind)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 3") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +output <- data.frame(wind, value = output, stringsAsFactors = TRUE) +} +}else{ +if(verbose == TRUE){ +tempo.cat <- paste0("PARALLELIZATION INITIATED AT: ", ini.date) +cat(paste0("\n", tempo.cat, "\n")) +} +tempo.thread.nb = parallel::detectCores(all.tests = FALSE, logical = TRUE) # detect the number of threads +if( ! is.null(thread.nb)){ +if(tempo.thread.nb < thread.nb){ +thread.nb <- tempo.thread.nb +if(verbose == TRUE){ +tempo.cat <- paste0("ONLY: ", tempo.thread.nb, " THREADS AVAILABLE") +cat(paste0("\n", tempo.cat, "\n")) +} +} +}else{ +thread.nb <- tempo.thread.nb +} +if(verbose == TRUE){ +tempo.cat <- paste0("NUMBER OF THREADS USED: ", thread.nb) +cat(paste0("\n ", tempo.cat, "\n")) +} +Clust <- parallel::makeCluster(thread.nb, outfile = paste0(res.path, "/fun_slide_parall_log.txt")) # outfile to print or cat during parallelization (only possible in a file, outfile = "" do not work on windows) +cluster.list <- parallel::clusterSplit(Clust, 1:nrow(wind)) # split according to the number of cluster +if(verbose == TRUE){ +tempo.cat <- paste0("SPLIT OF TEST NUMBERS IN PARALLELISATION:") +cat(paste0("\n ", tempo.cat, "\n")) +str(cluster.list) # using print(str()) add a NULL below the result +cat("\n") +} +paral.output.list <- parallel::clusterApply( # +cl = Clust, +x = cluster.list, +function.name = function.name, +data = data, +FUN = fun, # because fun argument of clusterApply +args = args, +thread.nb = thread.nb, +print.count = print.count, +wind = wind, +left = left, +right = right, +res.path = res.path, +lib.path = lib.path, +verbose = verbose, +cute.path = cute.path, +fun = function( +x, +function.name, +data, +FUN, +args, +thread.nb, +print.count, +wind, +left, +right, +res.path, +lib.path, +verbose, +cute.path +){ +# check again: very important because another R +process.id <- Sys.getpid() +cat(paste0("\nPROCESS ID ", process.id, " -> TESTS ", x[1], " TO ", x[length(x)], "\n")) +source(cute.path, local = .GlobalEnv) +fun_pack(req.package = "lubridate", lib.path = lib.path, load = TRUE) # load = TRUE to be sure that functions are present in the environment. And this prevent to use R.lib.path argument of fun_python_pack() +# end check again: very important because another R +ini.date <- Sys.time() +ini.time <- as.numeric(ini.date) # time of process begin, converted into +output <- NULL +print.count.loop <- 0 +for(i4 in 1:length(x)){ +print.count.loop <- print.count.loop + 1 +log <- get(left)(data, wind$left[x[i4]]) & get(right)(data, wind$right[x[i4]]) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +output <- c(output, eval(parse(text = paste0("FUN(data[log]", if( ! is.null(args)){paste0(", ", args)}, ")")))) +if(verbose == TRUE){ +if(print.count.loop == print.count){ +print.count.loop <- 0 +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) +final.loop <- (tempo.time - ini.time) / i4 * length(x) # expected duration in seconds # intra nb.compar loop lapse: time lapse / cycles done * cycles remaining +final.exp <- as.POSIXct(final.loop, origin = ini.date) +cat(paste0("\nIN PROCESS ", process.id, " | LOOP ", format(i4, big.mark=","), " / ", format(length(x), big.mark=","), " | TIME SPENT: ", tempo.lapse, " | EXPECTED END: ", final.exp)) +} +if(i4 == length(x)){ +tempo.time <- as.numeric(Sys.time()) +tempo.lapse <- round(lubridate::seconds_to_period(tempo.time - ini.time)) +cat(paste0("\nPROCESS ", process.id, " ENDED | LOOP ", format(i4, big.mark=","), " / ", format(length(x), big.mark=","), " | TIME SPENT: ", tempo.lapse, "\n\n")) +} +} +} +wind <- wind[x, ] +if(length(output) != nrow(wind)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 4") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +output <- data.frame(wind, value = output, stringsAsFactors = TRUE) +return(output) +} +} +) +parallel::stopCluster(Clust) +# result assembly +output <- data.frame() +for(i2 in 1:length(paral.output.list)){ # compartment relatives to each parallelization +output <- rbind(output, paral.output.list[[i2]], stringsAsFactors = TRUE) +} +# end result assembly +if(nrow(output) != nrow(wind)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 5\nlength(output): ", length(output), "\nnrow(wind): ", nrow(wind)) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +output <- output[order(output$left), ] +} +} +if(verbose == TRUE){ +end.date <- Sys.time() +end.time <- as.numeric(end.date) +total.lapse <- round(lubridate::seconds_to_period(end.time - ini.time)) +cat(paste0("\nfun_slide JOB END\n\nTIME: ", end.date, "\n\nTOTAL TIME LAPSE: ", total.lapse, "\n\n\n")) +} +return(output) +} + + + + +######## fun_codon2aa() #### convert codon to amino acid using standard genetic code + + +fun_codon2aa <- function( +data, +display = FALSE +){ +# AIM +# Convert codon to amino acid using standard genetic code indicated in https://en.wikipedia.org/wiki/DNA_and_RNA_codon_tables +# WARNINGS +# None +# ARGUMENTS +# data: single caracter string of three characters, or vector of three caracters, indicating the DNA codon (only "A", "T", "G" and "C" allowed). Case insensitive. Omitted if display argument is TRUE +# display: logical. Display the whole genetic table? if TRUE, override data +# RETURN +# The 1 letter uppercase amino acid of the submitted codon or the whole table if display argument is TRUE +# REQUIRED PACKAGES +# None +# REQUIRED FUNCTIONS FROM THE cute PACKAGE +# fun_check() +# EXAMPLE +# fun_codon2aa(data = "ATC", display = TRUE) +# see http +# DEBUGGING +# data = "atg" ; display = FALSE +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +req.function <- c( +"fun_check" +) +tempo <- NULL +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo <- c(tempo, i1) +} +} +if( ! is.null(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# reserved words +# end reserved words +# arg with no default values +mandat.args <- c( +"data" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data, class = "vector", typeof = "character", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = display, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ # normally no NA +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"data", +"display" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){# normally no NA with is.null() +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# code that protects set.seed() in the global environment +# end code that protects set.seed() in the global environment +# warning initiation +# end warning initiation +# other checkings +if(length(data) == 1L){ +data <- unlist(strsplit(data, split = "")) +}else if(length(data) != 3L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A STRING OF THREE CHARACTERS OR A VECTOR OF THREE CHARACTERS, MADE OF \"A\", \"C\", \"G\", \"T\" ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! all(toupper(data) %in% c("A", "C", "G","T"))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A STRING OF THREE CHARACTERS OR A VECTOR OF THREE CHARACTERS, MADE OF \"A\", \"C\", \"G\", \"T\" ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end other checkings +# reserved word checking +# end reserved word checking +# end second round of checking and data preparation +# package checking +# end package checking +# main code +# standard genetic code +sgc <- array( +c( +"F", "L", "I", "V", +"S", "P", "T", "A", +"Y", "H", "N", "D", +"C", "R", "S", "G", + +"F", "L", "I", "V", +"S", "P", "T", "A", +"Y", "H", "N", "D", +"C", "R", "S", "G", + +"L", "L", "I", "V", +"S", "P", "T", "A", +"stop", "Q", "K", "E", +"stop", "R", "R", "G", + +"L", "L", "M", "V", +"S", "P", "T", "A", +"stop", "Q", "K", "E", +"W", "R", "R", "G" +), +dim = c(4, 4, 4), +dimnames = list( +first = c("T", "C", "A", "G"), +second = c("T", "C", "A", "G"), +third = c("T", "C", "A", "G") +) +) +# end standard genetic code +if(display == TRUE){ +output <- sgc +}else{ +data <- toupper(data) +output <- eval(parse(text = paste0("sgc['", paste0(data, collapse = "','"), "']"))) +} +return(output) +} + + +######## fun_codon_finder() #### gives the codon number and position in the codon of nucleotid positions + + +fun_codon_finder <- function( +pos, +begin, +end +){ +# AIM +# gives the codon number and position in the codon of nucleotid positions +# WARNINGS +# Only for coding sequences (no introns): ((end - begin) + 1) / 3 must be an integer (i.e., modulo zero) +# Negatives positions allowed but this implies that one base has the position 0 in the sequence +# ARGUMENTS +# pos: vector of integers indicating the positions of nucleotids in a sequence. Must be between begin and end arguments +# begin: single integer indicating the position of the first base of the coding sequence +# end: single indicating the position of the last base of the coding sequence +# RETURN +# a data frame with column names: +# pos: values of the pos argument +# codon_nb: the codon number in the CDS encompassing the pos value +# codon_pos: the position of pos in the codon (either 1, 2 or 3) +# codon_begin: the first base position of the codon +# codon_end: the last base position of the codon +# REQUIRED PACKAGES +# None +# REQUIRED FUNCTIONS FROM THE cute PACKAGE +# fun_check() +# EXAMPLE +# fun_codon_finder(c(5, 6, 8, 10), begin = 5, end = 10) +# fun_codon_finder(c(0, 5, 6, 8, 10), begin = -2, end = 12) +# see http +# DEBUGGING +# pos = c(5, 6, 8, 10) ; begin = 5 ; end = 10 +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +req.function <- c( +"fun_check" +) +tempo <- NULL +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo <- c(tempo, i1) +} +} +if( ! is.null(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# reserved words +# end reserved words +# arg with no default values +mandat.args <- c( +"pos", +"begin", +"end" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = pos, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = begin, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = end, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ # normally no NA +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"pos", +"begin", +"end" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){# normally no NA with is.null() +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# code that protects set.seed() in the global environment +# end code that protects set.seed() in the global environment +# warning initiation +# end warning initiation +# other checkings +if(begin >= end){ +tempo.cat <- paste0("ERROR IN ", function.name, ": end ARGUMENT MUST BE STRICTLY GREATER THAN begin ARGUMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if((end - begin + 1) %% 3 != 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ((end - begin) + 1) / 3 MUST BE AN INTEGER (I.E., MODULO ZERO)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(pos < begin | pos > end)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": pos ARGUMENT VALUES MUST BE BETWEEN begin AND end ARGUMENT VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end other checkings +# reserved word checking +# end reserved word checking +# end second round of checking and data preparation +# package checking +# end package checking +# main code +first <- seq.int(from = begin, to = end, by = 3) +last <- seq.int(from = begin + 2, to = end, by = 3) +tempo <- lapply(X = pos, FUN = function(x = X){ +tempo.log <- x >= first & x <= last +if(sum(tempo.log, na.rm = TRUE) != 1){ # check that 1 possible TRUE +tempo.cat <- paste0("ERROR IN ", function.name, ": INTERNAL ERROR. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +codon_nb <- which(tempo.log) +codon_pos <- as.integer((x - (begin + (codon_nb - 1) * 3) + 1)) +codon_begin <- as.integer(first[tempo.log]) +codon_end <- as.integer(last[tempo.log]) +} +return(data.frame(codon_nb = codon_nb, codon_pos = codon_pos, codon_begin = codon_begin, codon_end = codon_end)) +}) +tempo <- do.call("rbind", tempo) +output <- data.frame(pos = as.integer(pos), tempo) +return(output) +} + + +################ Graphics management + + +# this order can be used: +# fun_width() +# fun_open() +# fun_prior_plot() # not for ggplot2 +# plot() or any other plotting +# fun_post_plot() if fun_prior_plot() has been used # not for ggplot2 +# fun_close() + + +######## fun_width() #### window width depending on classes to plot + + +fun_width <- function( +class.nb, +inches.per.class.nb = 1, +ini.window.width = 7, +inch.left.space, +inch.right.space, +boundarie.space = 0.5 +){ +# AIM +# rescale the width of a window to open depending on the number of classes to plot +# can be used for height, considering that it is as if it was a width +# this order can be used: +# fun_width() +# fun_open() +# fun_prior_plot() # not for ggplot2 +# plot() or any other plotting +# fun_post_plot() if fun_prior_plot() has been used # not for ggplot2 +# fun_close() +# ARGUMENTS +# class.nb: number of class to plot +# inches.per.class.nb: number of inches per unit of class.nb. 2 means 2 inches for each boxplot for instance +# ini.window.width:initial window width in inches +# inch.left.space: left horizontal margin of the figure region (in inches) +# inch.right.space: right horizontal margin of the figure region (in inches) +# boundarie.space: space between the right and left limits of the plotting region and the plot (0.5 means half a class width) +# RETURN +# the new window width in inches +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_width(class.nb = 10, inches.per.class.nb = 0.2, ini.window.width = 7, inch.left.space = 1, inch.right.space = 1, boundarie.space = 0.5) +# DEBUGGING +# class.nb = 10 ; inches.per.class.nb = 0.2 ; ini.window.width = 7 ; inch.left.space = 1 ; inch.right.space = 1 ; boundarie.space = 0.5 # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = class.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = inches.per.class.nb, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = ini.window.width, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = inch.left.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = inch.right.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = boundarie.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +range.max <- class.nb + boundarie.space # the max range of the future plot +range.min <- boundarie.space # the min range of the future plot +window.width <- inch.left.space + inch.right.space + inches.per.class.nb * (range.max - range.min) +return(window.width) +} + + +######## fun_open() #### open a GUI or pdf graphic window + + +fun_open <- function( +pdf = TRUE, +pdf.path = "working.dir", +pdf.name = "graph", +width = 7, +height = 7, +paper = "special", +pdf.overwrite = FALSE, +rescale = "fixed", +remove.read.only = TRUE, +return.output = FALSE +){ +# AIM +# open a pdf or screen (GUI) graphic window and return initial graphic parameters +# this order can be used: +# fun_width() +# fun_open() +# fun_prior_plot() # not for ggplot2 +# plot() or any other plotting +# fun_post_plot() if fun_prior_plot() has been used # not for ggplot2 +# fun_close() +# WARNINGS +# On Linux, use pdf = TRUE, if (GUI) graphic window is not always available, meaning that X is not installed (clusters for instance). Use X11() in R to test if available +# ARGUMENTS: +# pdf: logical. Use pdf display? If FALSE, a GUI is opened +# pdf.path: where the pdf is saved (do not terminate by / or \\). Write "working.dir" if working directory is required (default). Ignored if pdf == FALSE +# pdf.name: name of the pdf file containing the graphs (the .pdf extension is added by the function, if not detected in the name end). Ignored if pdf == FALSE +# width: width of the window (in inches) +# height: height of the window (in inches) +# paper: paper argument of the pdf function (paper format). Only used for pdf(). Either "a4", "letter", "legal", "us", "executive", "a4r", "USr" or "special". If "special", means that the paper dimension will be width and height. With another paper format, if width or height is over the size of the paper, width or height will be modified such that the plot is adjusted to the paper dimension (see $dim in the returned list below to see the modified dimensions). Ignored if pdf == FALSE +# pdf.overwrite: logical. Existing pdf can be overwritten? . Ignored if pdf == FALSE +# rescale: kind of GUI. Either "R", "fit", or "fixed". Ignored on Mac and Linux OS. See ?windows for details +# remove.read.only: logical. remove the read only (R.O.) graphical parameters? If TRUE, the graphical parameters are returned without the R.O. parameters. The returned $ini.par list can be used to set the par() of a new graphical device. If FALSE, graphical parameters are returned with the R.O. parameters, which provides information like text dimension (see ?par() ). The returned $ini.par list can be used to set the par() of a new graphical device, but generate a warning message. Ignored if return.output == FALSE. +# return.output: logical. Return output ? If TRUE the output list is displayed +# RETURN +# a list containing: +# $pdf.loc: path of the pdf created +# $ini.par: initial par() parameters +# $zone.ini: initial window spliting +# $dim: dimension of the graphical device (in inches) +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_open(pdf = FALSE, pdf.path = "C:/Users/Gael/Desktop", pdf.name = "graph", width = 7, height = 7, paper = "special", pdf.overwrite = FALSE, return.output = TRUE) +# DEBUGGING +# pdf = TRUE ; pdf.path = "C:/Users/Gael/Desktop" ; pdf.name = "graphs" ; width = 7 ; height = 7 ; paper = "special" ; pdf.overwrite = FALSE ; rescale = "fixed" ; remove.read.only = TRUE ; return.output = TRUE # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = pdf, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = pdf.path, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = pdf.name, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = width, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = height, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = paper, options = c("a4", "letter", "legal", "us", "executive", "a4r", "USr", "special", "A4", "LETTER", "LEGAL", "US"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data =pdf.overwrite, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = rescale, options = c("R", "fit", "fixed"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = remove.read.only, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return.output, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +if(pdf.path == "working.dir"){ +pdf.path <- getwd() +}else{ +if(grepl(x = pdf.path, pattern = ".+/$")){ +pdf.path <- sub(x = pdf.path, pattern = "/$", replacement = "") # remove the last / +}else if(grepl(x = pdf.path, pattern = ".+[\\]$")){ # or ".+\\\\$" # cannot be ".+\$" because \$ does not exist contrary to \n +pdf.path <- sub(x = pdf.path, pattern = "[\\]$", replacement = "") # remove the last / +} +if(dir.exists(pdf.path) == FALSE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\npdf.path ARGUMENT DOES NOT CORRESPOND TO EXISTING DIRECTORY\n", pdf.path) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# par.ini recovery +# cannot use pdf(file = NULL), because some small differences between pdf() and other devices. For instance, differences with windows() for par()$fin, par()$pin and par()$plt +if(Sys.info()["sysname"] == "Windows"){ # Note that .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows +open.fail <- NULL +grDevices::windows() +ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +invisible(dev.off()) # close the new window +}else if(Sys.info()["sysname"] == "Linux"){ +if(pdf == TRUE){# cannot use pdf(file = NULL), because some small differences between pdf() and other devices. For instance, differences with windows() for par()$fin, par()$pin and par()$plt +if(exists(".Random.seed", envir = .GlobalEnv)){ # if .Random.seed does not exists, it means that no random operation has been performed yet in any R environment +tempo.random.seed <- .Random.seed +on.exit(assign(".Random.seed", tempo.random.seed, env = .GlobalEnv)) +}else{ +on.exit(set.seed(NULL)) # inactivate seeding -> return to complete randomness +} +set.seed(NULL) +tempo.code <- sample(x = 1:1e7, size = 1) +while(file.exists(paste0(pdf.path, "/recover_ini_par", tempo.code, ".pdf")) == TRUE){ +tempo.code <- tempo.code + 1 +} +grDevices::pdf(width = width, height = height, file=paste0(pdf.path, "/recover_ini_par", tempo.code, ".pdf"), paper = paper) +ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +invisible(dev.off()) # close the pdf window +file.remove(paste0(pdf.path, "/recover_ini_par", tempo.code, ".pdf")) # remove the pdf file +}else{ +# test if X11 can be opened +if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nTHIS FUNCTION CANNOT BE USED ON LINUX IF A Rplots.pdf FILE ALREADY EXISTS HERE\n", getwd()) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +open.fail <- suppressWarnings(try(grDevices::X11(), silent = TRUE))[] # try to open a X11 window. If open.fail == NULL, no problem, meaning that the X11 window is opened. If open.fail != NULL, a pdf can be opened here paste0(getwd(), "/Rplots.pdf") +if(is.null(open.fail)){ +ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +invisible(dev.off()) # close the new window +}else if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ +file.remove(paste0(getwd(), "/Rplots.pdf")) # remove the pdf file +tempo.cat <- ("ERROR IN fun_open()\nTHIS FUNCTION CANNOT OPEN GUI ON LINUX OR NON MACOS UNIX SYSTEM\nTO OVERCOME THIS, EITHER SET THE X GRAPHIC INTERFACE OF THE SYSTEM OR SET THE pdf ARGUMENT OF THE fun_open() FUNCTION TO TRUE AND RERUN") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +} +}else{ +open.fail <- NULL +grDevices::quartz() +ini.par <- par(no.readonly = remove.read.only) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +invisible(dev.off()) # close the new window +} +# end par.ini recovery +zone.ini <- matrix(1, ncol=1) # to recover the initial parameters for next figure region when device region split into several figure regions +if(pdf == TRUE){ +if(grepl(x = pdf.name, pattern = "\\.pdf$")){ +pdf.name <- sub(x = pdf.name, pattern = "\\.pdf$", replacement = "") # remove the last .pdf +} +pdf.loc <- paste0(pdf.path, "/", pdf.name, ".pdf") +if(file.exists(pdf.loc) == TRUE & pdf.overwrite == FALSE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\npdf.loc FILE ALREADY EXISTS AND CANNOT BE OVERWRITTEN DUE TO pdf.overwrite ARGUMENT SET TO TRUE\n", pdf.loc) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +grDevices::pdf(width = width, height = height, file=pdf.loc, paper = paper) +} +}else if(pdf == FALSE){ +pdf.loc <- NULL +if(Sys.info()["sysname"] == "Windows"){ # .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows +grDevices::windows(width = width, height = height, rescale = rescale) +}else if(Sys.info()["sysname"] == "Linux"){ +if( ! is.null(open.fail)){ +tempo.cat <- "ERROR IN fun_open()\nTHIS FUNCTION CANNOT OPEN GUI ON LINUX OR NON MACOS UNIX SYSTEM\nTO OVERCOME THIS, EITHER SET THE X GRAPHIC INTERFACE OF THE SYSTEM OR SET THE pdf ARGUMENT OF THE fun_open() FUNCTION TO TRUE AND RERUN" +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +grDevices::X11(width = width, height = height) +} +}else{ +grDevices::quartz(width = width, height = height) +} +} +if(return.output == TRUE){ +output <- list(pdf.loc = pdf.loc, ini.par = ini.par, zone.ini = zone.ini, dim = dev.size()) +return(output) +} +} + + +######## fun_prior_plot() #### set graph param before plotting (erase axes for instance) + + +fun_prior_plot <- function( +param.reinitial = FALSE, +xlog.scale = FALSE, +ylog.scale = FALSE, +remove.label = TRUE, +remove.x.axis = TRUE, +remove.y.axis = TRUE, +std.x.range = TRUE, +std.y.range = TRUE, +down.space = 1, +left.space = 1, +up.space = 1, +right.space = 1, +orient = 1, +dist.legend = 3.5, +tick.length = 0.5, +box.type = "n", +amplif.label = 1, +amplif.axis = 1, +display.extend = FALSE, +return.par = FALSE +){ +# AIM +# very convenient to erase the axes for post plot axis redrawing using fun_post_plot() +# reinitialize and set the graphic parameters before plotting +# CANNOT be used if no graphic device already opened +# ARGUMENTS +# param.reinitial: reinitialize graphic parameters before applying the new ones, as defined by the other arguments? Either TRUE or FALSE +# xlog.scale: Log scale for the x-axis? Either TRUE or FALSE. If TRUE, erases the x-axis, except legend, for further drawing by fun_post_plot()(xlog argument of par()) +# ylog.scale: Log scale for the y-axis? Either TRUE or FALSE. If TRUE, erases the y-axis, except legend, for further drawing by fun_post_plot()(ylog argument of par()) +# remove.label: remove labels (axis legend) of the two axes? Either TRUE or FALSE (ann argument of par()) +# remove.x.axis: remove x-axis except legend? Either TRUE or FALSE (control the xaxt argument of par()). Automately set to TRUE if xlog.scale == TRUE +# remove.y.axis: remove y-axis except legend? Either TRUE or FALSE (control the yaxt argument of par()). Automately set to TRUE if ylog.scale == TRUE +# std.x.range: standard range on the x-axis? TRUE (no range extend) or FALSE (4% range extend). Controls xaxs argument of par() (TRUE is xaxs = "i", FALSE is xaxs = "r") +# std.y.range: standard range on the y-axis? TRUE (no range extend) or FALSE (4% range extend). Controls yaxs argument of par() (TRUE is yaxs = "i", FALSE is yaxs = "r") +# down.space: lower vertical margin (in inches, mai argument of par()) +# left.space: left horizontal margin (in inches, mai argument of par()) +# up.space: upper vertical margin between plot region and grapical window (in inches, mai argument of par()) +# right.space: right horizontal margin (in inches, mai argument of par()) +# orient: scale number orientation (las argument of par()). 0, always parallel to the axis; 1, always horizontal; 2, always perpendicular to the axis; 3, always vertical +# dist.legend: numeric value that moves axis legends away in inches (first number of mgp argument of par() but in inches thus / 0.2) +# tick.length: length of the ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc. 0 means no tick +# box.type: bty argument of par(). Either "o", "l", "7", "c", "u", "]", the resulting box resembles the corresponding upper case letter. A value of "n" suppresses the box +# amplif.label: increase or decrease the size of the text in legends +# amplif.axis: increase or decrease the size of the scale numbers in axis +# display.extend: extend display beyond plotting region? Either TRUE or FALSE (xpd argument of par() without NA) +# return.par: return graphic parameter modification? +# RETURN +# return graphic parameter modification +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_prior_plot(param.reinitial = FALSE, xlog.scale = FALSE, ylog.scale = FALSE, remove.label = TRUE, remove.x.axis = TRUE, remove.y.axis = TRUE, std.x.range = TRUE, std.y.range = TRUE, down.space = 1, left.space = 1, up.space = 1, right.space = 1, orient = 1, dist.legend = 4.5, tick.length = 0.5, box.type = "n", amplif.label = 1, amplif.axis = 1, display.extend = FALSE, return.par = FALSE) +# DEBUGGING +# param.reinitial = FALSE ; xlog.scale = FALSE ; ylog.scale = FALSE ; remove.label = TRUE ; remove.x.axis = TRUE ; remove.y.axis = TRUE ; std.x.range = TRUE ; std.y.range = TRUE ; down.space = 1 ; left.space = 1 ; up.space = 1 ; right.space = 1 ; orient = 1 ; dist.legend = 4.5 ; tick.length = 0.5 ; box.type = "n" ; amplif.label = 1 ; amplif.axis = 1 ; display.extend = FALSE ; return.par = FALSE # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = param.reinitial, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = xlog.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = ylog.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = remove.label, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = remove.x.axis, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = remove.y.axis, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = std.x.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = std.y.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = down.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = left.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = up.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = right.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = orient, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.type, options = c("o", "l", "7", "c", "u", "]", "n"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = amplif.label, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = amplif.axis, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = display.extend, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return.par, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +if(is.null(dev.list())){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THIS FUNCTION CANNOT BE USED IF NO GRAPHIC DEVICE ALREADY OPENED (dev.list() IS CURRENTLY NULL)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# par.ini recovery +# cannot use pdf(file = NULL), because some small differences between pdf() and other devices. For instance, differences with windows() for par()$fin, par()$pin and par()$plt +if(param.reinitial == TRUE){ +if( ! all(names(dev.cur()) == "null device")){ +active.wind.nb <- dev.cur() +}else{ +active.wind.nb <- 0 +} +if(Sys.info()["sysname"] == "Windows"){ # Note that .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows +grDevices::windows() +ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +invisible(dev.off()) # close the new window +}else if(Sys.info()["sysname"] == "Linux"){ +if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THIS FUNCTION CANNOT BE USED ON LINUX WITH param.reinitial SET TO TRUE IF A Rplots.pdf FILE ALREADY EXISTS HERE: ", getwd()) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +open.fail <- suppressWarnings(try(grDevices::X11(), silent = TRUE))[] # try to open a X11 window. If open.fail == NULL, no problem, meaning that the X11 window is opened. If open.fail != NULL, a pdf can be opened here paste0(getwd(), "/Rplots.pdf") +if(is.null(open.fail)){ +ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +invisible(dev.off()) # close the new window +}else if(file.exists(paste0(getwd(), "/Rplots.pdf"))){ +ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +invisible(dev.off()) # close the new window +file.remove(paste0(getwd(), "/Rplots.pdf")) # remove the pdf file +}else{ +tempo.cat <- ("ERROR IN fun_prior_plot()\nTHIS FUNCTION CANNOT OPEN GUI ON LINUX OR NON MACOS UNIX SYSTEM\nTO OVERCOME THIS, PLEASE USE A PDF GRAPHIC INTERFACE AND RERUN") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +}else{ # macOS +grDevices::quartz() +ini.par <- par(no.readonly = FALSE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened) +invisible(dev.off()) # close the new window +} +if( ! all(names(dev.cur()) == "null device")){ +invisible(dev.set(active.wind.nb)) # go back to the active window if exists +par(ini.par) # apply the initial par to current window +} +} +# end par.ini recovery +if(remove.x.axis == TRUE){ +par(xaxt = "n") # suppress the y-axis label +}else{ +par(xaxt = "s") +} +if(remove.y.axis == TRUE){ +par(yaxt = "n") # suppress the y-axis label +}else{ +par(yaxt = "s") +} +if(std.x.range == TRUE){ +par(xaxs = "i") +}else{ +par(xaxs = "r") +} +if(std.y.range == TRUE){ +par(yaxs = "i") +}else{ +par(yaxs = "r") +} +par(mai = c(down.space, left.space, up.space, right.space), ann = ! remove.label, las = orient, mgp = c(dist.legend/0.2, 1, 0), xpd = display.extend, bty= box.type, cex.lab = amplif.label, cex.axis = amplif.axis) +par(tcl = -par()$mgp[2] * tick.length) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) +if(xlog.scale == TRUE){ +par(xaxt = "n", xlog = TRUE) # suppress the x-axis label +}else{ +par(xlog = FALSE) +} +if(ylog.scale == TRUE){ +par(yaxt = "n", ylog = TRUE) # suppress the y-axis label +}else{ +par(ylog = FALSE) +} +if(return.par == TRUE){ +tempo.par <- par() +return(tempo.par) +} +} + + +######## fun_scale() #### select nice label numbers when setting number of ticks on an axis + + + + + +fun_scale <- function(n, lim, kind = "approx", lib.path = NULL){ +# AIM +# attempt to select nice scale numbers when setting n ticks on a lim axis range +# ARGUMENTS +# n: desired number of main ticks on the axis (integer above 0) +# lim: vector of 2 numbers indicating the limit range of the axis. Order of the 2 values matters (for inverted axis). Can be log transformed values +# kind: either "approx" (approximative), "strict" (strict) or "strict.cl" (strict clean). If "approx", use the scales::trans_breaks() function to provide an easy to read scale of approximately n ticks spanning the range of the lim argument. If "strict", cut the range of the lim argument into n + 1 equidistant part and return the n numbers at each boundary. This often generates numbers uneasy to read. If "strict.cl", provide an easy to read scale of exactly n ticks, but sometimes not completely spanning the range of the lim argument +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL +# RETURN +# a vector of numbers +# REQUIRED PACKAGES +# if kind = "approx": +# ggplot2 +# scales +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_round() +# EXAMPLES +# approximate number of main ticks +# ymin = 2 ; ymax = 3.101 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "approx") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) +# strict number of main ticks +# ymin = 2 ; ymax = 3.101 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "strict") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) +# strict "clean" number of main ticks +# ymin = 2 ; ymax = 3.101 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "strict.cl") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) +# approximate number of main ticks, scale inversion +# ymin = 3.101 ; ymax = 2 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "approx") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) +# DEBUGGING +# n = 9 ; lim = c(2, 3.101) ; kind = "approx" ; lib.path = NULL # for function debugging +# n = 10 ; lim = c(1e-4, 1e6) ; kind = "approx" ; lib.path = NULL # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# end initial argument checking +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_round", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_round() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & isTRUE(all.equal(n, 0))){ # isTRUE(all.equal(n, 0)) equivalent to n == 0 but deals with floats (approx ok) +tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT MUST BE A NON NULL AND POSITIVE INTEGER") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) # +} +tempo <- fun_check(data = lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & all(diff(lim) == 0L)){ # isTRUE(all.equal(diff(lim), rep(0, length(diff(lim))))) not used because we strictly need zero as a result +tempo.cat <- paste0("ERROR IN ", function.name, ": lim ARGUMENT HAS A NULL RANGE (2 IDENTICAL VALUES)") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & any(lim %in% c(Inf, -Inf))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = kind, options = c("approx", "strict", "strict.cl"), length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +lim.rank <- rank(lim) # to deal with inverted axis +lim <- sort(lim) +if(kind == "approx"){ +# package checking +fun_pack(req.package = c("ggplot2"), lib.path = lib.path) +fun_pack(req.package = c("scales"), lib.path = lib.path) +# end package checking +output <- ggplot2::ggplot_build(ggplot2::ggplot() + ggplot2::scale_y_continuous( +breaks = scales::trans_breaks( +trans = "identity", +inv = "identity", +n = n +), +limits = lim +))$layout$panel_params[[1]]$y$breaks # pretty() alone is not appropriate: tempo.pret <- pretty(seq(lim[1] ,lim[2], length.out = n)) ; tempo.pret[tempo.pret > = lim[1] & tempo.pret < = lim[2]]. # in ggplot 3.3.0, tempo.coord$y.major_source replaced by tempo.coord$y$breaks +if( ! is.null(attributes(output))){ # layout$panel_params[[1]]$y$breaks can be characters (labels of the axis). In that case, it has attributes that corresponds to positions +output <- unlist(attributes(output)) +} +output <- output[ ! is.na(output)] +}else if(kind == "strict"){ +output <- fun_round(seq(lim[1] ,lim[2], length.out = n), 2) +}else if(kind == "strict.cl"){ +tempo.range <- diff(sort(lim)) +tempo.max <- max(lim) +tempo.min <- min(lim) +mid <- tempo.min + (tempo.range/2) # middle of axis +tempo.inter <- tempo.range / (n + 1) # current interval between two ticks, between 0 and Inf +if(tempo.inter == 0L){ # isTRUE(all.equal(tempo.inter, rep(0, length(tempo.inter)))) not used because we strictly need zero as a result +tempo.cat <- paste0("ERROR IN ", function.name, ": THE INTERVAL BETWEEN TWO TICKS OF THE SCALE IS NULL. MODIFY THE lim OR n ARGUMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +log10.abs.lim <- 200 +log10.range <- (-log10.abs.lim):log10.abs.lim +log10.vec <- 10^log10.range +round.vec <- c(5, 4, 3, 2.5, 2, 1.25, 1) +dec.table <- outer(log10.vec, round.vec) # table containing the scale units (row: power of ten from -201 to +199, column: the 5, 2.5, 2, 1.25, 1 notches + + + +# recover the number of leading zeros in tempo.inter +ini.scipen <- options()$scipen +options(scipen = -1000) # force scientific format +if(any(grepl(pattern = "\\+", x = tempo.inter))){ # tempo.inter > 1 +power10.exp <- as.integer(substring(text = tempo.inter, first = (regexpr(pattern = "\\+", text = tempo.inter) + 1))) # recover the power of 10. Example recover 08 from 1e+08 +mantisse <- as.numeric(substr(x = tempo.inter, start = 1, stop = (regexpr(pattern = "\\+", text = tempo.inter) - 2))) # recover the mantisse. Example recover 1.22 from 1.22e+08 +}else if(any(grepl(pattern = "\\-", x = tempo.inter))){ # tempo.inter < 1 +power10.exp <- as.integer(substring(text = tempo.inter, first = (regexpr(pattern = "\\-", text = tempo.inter)))) # recover the power of 10. Example recover 08 from 1e+08 +mantisse <- as.numeric(substr(x = tempo.inter, start = 1, stop = (regexpr(pattern = "\\-", text = tempo.inter) - 2))) # recover the mantisse. Example recover 1.22 from 1.22e+08 +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +tempo.scale <- dec.table[log10.range == power10.exp, ] +# new interval +inter.select <- NULL +for(i1 in 1:length(tempo.scale)){ +tempo.first.tick <- trunc((tempo.min + tempo.scale[i1]) / tempo.scale[i1]) * (tempo.scale[i1]) # this would be use to have a number not multiple of tempo.scale[i1]: ceiling(tempo.min) + tempo.scale[i1] * 10^power10.exp +tempo.last.tick <- tempo.first.tick + tempo.scale[i1] * (n - 1) +if((tempo.first.tick >= tempo.min) & (tempo.last.tick <= tempo.max)){ +inter.select <- tempo.scale[i1] +break() +} +} +if(is.null(inter.select)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +options(scipen = ini.scipen) # restore the initial scientific penalty +# end new interval +# centering the new scale +tempo.mid <- trunc((mid + (-1:1) * inter.select) / inter.select) * inter.select # tempo middle tick closest to the middle axis +mid.tick <- tempo.mid[which.min(abs(tempo.mid - mid))] +if(isTRUE(all.equal(n, rep(1, length(n))))){ # isTRUE(all.equal(n, rep(1, length(n)))) is similar to n == 1L but deals with float +output <- mid.tick +}else if(isTRUE(all.equal(n, rep(2, length(n))))){ # isTRUE(all.equal(n, rep(0, length(n)))) is similar to n == 2L but deals with float +output <- mid.tick +tempo.min.dist <- mid.tick - inter.select - tempo.min +tempo.max.dist <- tempo.max - mid.tick + inter.select +if(tempo.min.dist <= tempo.max.dist){ # distance between lowest tick and bottom axis <= distance between highest tick and top axis. If yes, extra tick but at the top, otherwise at the bottom +output <- c(mid.tick, mid.tick + inter.select) +}else{ +output <- c(mid.tick - inter.select, mid.tick) +} +}else if((n / 2 - trunc(n / 2)) > 0.1){ # > 0.1 to avoid floating point. Because result can only be 0 or 0.5. Thus, > 0.1 means odd number +output <- c(mid.tick - (trunc(n / 2):1) * inter.select, mid.tick, mid.tick + (1:trunc(n / 2)) * inter.select) +}else if((n / 2 - trunc(n / 2)) < 0.1){ # < 0.1 to avoid floating point. Because result can only be 0 or 0.5. Thus, < 0.1 means even number +tempo.min.dist <- mid.tick - trunc(n / 2) * inter.select - tempo.min +tempo.max.dist <- tempo.max - mid.tick + trunc(n / 2) * inter.select +if(tempo.min.dist <= tempo.max.dist){ # distance between lowest tick and bottom axis <= distance between highest tick and top axis. If yes, extra tick but at the bottom, otherwise at the top +output <- c(mid.tick - ((trunc(n / 2) - 1):1) * inter.select, mid.tick, mid.tick + (1:trunc(n / 2)) * inter.select) +}else{ +output <- c(mid.tick - (trunc(n / 2):1) * inter.select, mid.tick, mid.tick + (1:(trunc(n / 2) - 1)) * inter.select) +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 3") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end centering the new scale +# last check +if(min(output) < tempo.min){ +output <- c(output[-1], max(output) + inter.select) # remove the lowest tick and add a tick at the top +}else if( max(output) > tempo.max){ +output <- c(min(output) - inter.select, output[-length(output)]) +} +if(min(output) < tempo.min | max(output) > tempo.max){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 4") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(is.na(output))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 5 (NA GENERATION)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end last check +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 6") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(diff(lim.rank) < 0){ +output <- rev(output) +} +return(output) +} + + +######## fun_inter_ticks() #### define coordinates of secondary ticks + + +fun_inter_ticks <- function( +lim, +log = "log10", +breaks = NULL, +n = NULL, +warn.print = TRUE +){ +# AIM +# define coordinates and values of secondary ticks +# ARGUMENTS +# lim: vector of 2 numbers indicating the limit range of the axis. Order of the 2 values matters (for inverted axis). If log argument is "log2" or "log10", values in lim must be already log transformed. Thus, negative or zero values are allowed +# log: either "log2" (values in the lim argument are log2 transformed) or "log10" (values in the lim argument are log10 transformed), or "no" +# breaks: mandatory vector of numbers indicating the main ticks values/positions when log argument is "no". Ignored when log argument is "log2" or "log10" +# n: number of secondary ticks between each main tick when log argument is "no". Ignored when log argument is "log2" or "log10" +# warn.print: logical. Print potential warning messages at the end of the execution? If FALSE, warning messages are never printed, but can still be recovered in the returned list +# RETURN +# a list containing +# $log: value of the log argument used +# $coordinates: the coordinates of the secondary ticks on the axis, between the lim values +# $values: the corresponding values associated to each coordinate (with log scale, 2^$values or 10^$values is equivalent to the labels of the axis) +# $warn: the potential warning messages. Use cat() for proper display. NULL if no warning +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# no log scale +# fun_inter_ticks(lim = c(-4,4), log = "no", breaks = c(-2, 0, 2), n = 3) +# fun_inter_ticks(lim = c(10, 0), log = "no", breaks = c(10, 8, 6, 4, 2, 0), n = 4) +# log2 +# fun_inter_ticks(lim = c(-4,4), log = "log2") +# log10 +# fun_inter_ticks(lim = c(-2,3), log = "log10") +# DEBUGGING +# lim = c(2, 3.101) ; log = "no" ; breaks = NULL ; n = NULL ; warn.print = TRUE # for function debugging +# lim = c(0, 26.5) ; log = "no" ; breaks = c(0, 10, 20) ; n = 3 # for function debugging +# lim = c(10, 0); log = "no"; breaks = c(10, 8, 6, 4, 2, 0); n = 4 # for function debugging +# lim = c(-10, -20); log = "no"; breaks = c(-20, -15, -10); n = 4 # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +req.function <- c( +"fun_check" +) +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED ", i1, "() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end required function checking +# argument primary checking +# arg with no default values +mandat.args <- c( +"lim" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# using fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(breaks)){ +tempo <- fun_check(data = breaks, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) +} +if( ! is.null(n)){ +tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = warn.print, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end using fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# management of NA +if(any(is.na(lim)) | any(is.na(log)) | any(is.na(breaks)) | any(is.na(n)) | any(is.na(warn.print))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nNO ARGUMENT CAN HAVE NA VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA +# management of NULL +if(is.null(lim) | is.null(log)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nTHESE ARGUMENTS\nlim\nlog\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL +if(all(diff(lim) == 0L)){ # isTRUE(all.equal(diff(lim), rep(0, length(diff(lim))))) not used because we strictly need zero as a result +tempo.cat <- paste0("ERROR IN ", function.name, "\nlim ARGUMENT HAS A NULL RANGE (2 IDENTICAL VALUES): ", paste(lim, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(any(lim %in% c(Inf, -Inf))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nlim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(log == "no" & is.null(breaks)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nbreaks ARGUMENT CANNOT BE NULL IF log ARGUMENT IS \"no\"") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(breaks)){ +if(length(breaks) < 2){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nbreaks ARGUMENT MUST HAVE 2 VALUES AT LEAST (OTHERWISE, INTER TICK POSITIONS CANNOT BE COMPUTED): ", paste(breaks, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! isTRUE(all.equal(diff(sort(breaks)), rep(diff(sort(breaks))[1], length(diff(sort(breaks))))))){ # isTRUE(all.equal(n, 0)) equivalent to n == 0 but deals with floats (approx ok) +tempo.cat <- paste0("ERROR IN ", function.name, "\nbreaks ARGUMENT MUST HAVE EQUIDISTANT VALUES (OTHERWISE, EQUAL NUMBER OF INTER TICK BETWEEN MAIN TICKS CANNOT BE COMPUTED): ", paste(breaks, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if( ! is.null(n)){ +if(n <= 0){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nn ARGUMENT MUST BE A POSITIVE AND NON NULL INTEGER: ", paste(n, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end second round of checking and data preparation +# main code +ini.warning.length <- options()$warning.length +options(warning.length = 8170) +warn <- NULL +warn.count <- 0 +lim.rank <- rank(lim) # to deal with inverse axis +if(log != "no"){ +ini.scipen <- options()$scipen +options(scipen = -1000) # force scientific format +power10.exp <- as.integer(substring(text = 10^lim, first = (regexpr(pattern = "\\+|\\-", text = 10^lim)))) # recover the power of 10, i.e., integer part of lim. Example recover 08 from 1e+08. Works for log2 +# mantisse <- as.numeric(substr(x = 10^lim, start = 1, stop = (regexpr(pattern = "\\+|\\-", text = 10^lim) - 2))) # recover the mantisse. Example recover 1.22 from 1.22e+08 +options(scipen = ini.scipen) # restore the initial scientific penalty +tick.pos <- unique(as.vector(outer(2:10, ifelse(log == "log2", 2, 10)^((power10.exp[1] - ifelse(diff(lim.rank) > 0, 1, -1)):(power10.exp[2] + ifelse(diff(lim.rank) > 0, 1, -1)))))) # use log10(2:10) even if log2: it is to get log values between 0 and 1 +tick.pos <- sort(tick.pos, decreasing = ifelse(diff(lim.rank) > 0, FALSE, TRUE)) +if(log == "log2"){ +tick.values <- tick.pos[tick.pos >= min(2^lim) & tick.pos <= max(2^lim)] +tick.pos <- log2(tick.values) +}else if(log == "log10"){ +tick.values <- tick.pos[tick.pos >= min(10^lim) & tick.pos <= max(10^lim)] +tick.pos <- log10(tick.values) +} +}else{ +# if(length(breaks) > 1){ # not required because already checked above +breaks.rank <- rank(c(breaks[1], breaks[length(breaks)])) +if(diff(breaks.rank) != diff(lim.rank)){ +breaks <- sort(breaks, decreasing = ifelse(diff(lim.rank) < 0, TRUE, FALSE)) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") VALUES IN breaks ARGUMENT NOT IN THE SAME ORDER AS IN lim ARGUMENT -> VALUES REORDERED AS IN lim: ", paste(breaks, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +breaks.rank <- rank(c(breaks[1], breaks[length(breaks)])) +} +# } +main.tick.dist <- mean(diff(breaks), na.rm = TRUE) +tick.dist <- main.tick.dist / (n + 1) +tempo.extra.margin <- max(abs(diff(breaks)), na.rm = TRUE) +tick.pos <- seq( +if(diff(breaks.rank) > 0){breaks[1] - tempo.extra.margin}else{breaks[1] + tempo.extra.margin}, +if(diff(breaks.rank) > 0){breaks[length(breaks)] + tempo.extra.margin}else{breaks[length(breaks)] - tempo.extra.margin}, +by = tick.dist +) +tick.pos <- tick.pos[tick.pos >= min(lim) & tick.pos <= max(lim)] +tick.values <- tick.pos +} +if(any(is.na(tick.pos) | ! is.finite(tick.pos))){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, ": NA or Inf GENERATED FOR THE INTER TICK POSITIONS: ", paste(tick.pos, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(tick.pos) == 0L){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NO INTER TICKS COMPUTED BETWEEN THE LIMITS INDICATED: ", paste(lim, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +output <- list(log = log, coordinates = tick.pos, values = tick.values, warn = warn) +if(warn.print == TRUE & ! is.null(warn)){ +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) # to recover the warning messages, see $warn +} +on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) +return(output) +} + + +######## fun_post_plot() #### set graph param after plotting (axes redesign for instance) + + + + + +fun_post_plot <- function( +x.side = 0, +x.log.scale = FALSE, +x.categ = NULL, +x.categ.pos = NULL, +x.lab = "", +x.axis.size = 1.5, +x.label.size = 1.5, +x.dist.legend = 0.5, +x.nb.inter.tick = 1, +y.side = 0, +y.log.scale = FALSE, +y.categ = NULL, +y.categ.pos = NULL, +y.lab = "", +y.axis.size = 1.5, +y.label.size = 1.5, +y.dist.legend = 0.5, +y.nb.inter.tick = 1, +text.angle = 90, +tick.length = 0.5, +sec.tick.length = 0.3, +bg.color = NULL, +grid.lwd = NULL, +grid.col = "white", +corner.text = "", +corner.text.size = 1, +just.label.add = FALSE, +par.reset = FALSE, +custom.par = NULL +){ +# AIM +# redesign axis. If x.side = 0, y.side = 0, the function just adds text at topright of the graph and reset par() for next graphics and provides outputs (see below) +# provide also positions for legend or additional text on the graph +# use fun_prior_plot() before this function for initial inactivation of the axis drawings +# ARGUMENTS +# x.side: axis at the bottom (1) or top (3) of the region figure. Write 0 for no change +# x.log.scale: Log scale for the x-axis? Either TRUE or FALSE +# x.categ: character vector representing the classes (levels()) to specify when the x-axis is qualititative(stripchart, boxplot) +# x.categ.pos: position of the classes names (numeric vector of identical length than x.categ). If left NULL, this will be 1:length(levels()) +# x.lab: label of the x-axis. If x.side == 0 and x.lab != "", then x.lab is printed +# x.axis.size: positive numeric. Increase or decrease the size of the x axis numbers. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2. Also control the size of displayed categories +# x.label.size: positive numeric. Increase or decrease the size of the x axis legend text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 +# x.dist.legend: increase the number to move x-axis legends away in inches (first number of mgp argument of par() but in inches) +# x.nb.inter.tick: number of secondary ticks between main ticks on x-axis (only if not log scale). 0 means no secondary ticks +# y.side: axis at the left (2) or right (4) of the region figure. Write 0 for no change +# y.log.scale: Log scale for the y-axis? Either TRUE or FALSE +# y.categ: classes (levels()) to specify when the y-axis is qualititative(stripchart, boxplot) +# y.categ.pos: position of the classes names (numeric vector of identical length than y.categ). If left NULL, this will be 1:length(levels()) +# y.lab: label of the y-axis. If y.side == 0 and y.lab != "", then y.lab is printed +# y.axis.size: positive numeric. Increase or decrease the size of the y axis numbers. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2. Also control the size of displayed categories +# y.label.size: positive numeric. Increase or decrease the size of the y axis legend text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 +# y.dist.legend: increase the number to move y-axis legends away in inches (first number of mgp argument of par() but in inches) +# y.nb.inter.tick: number of secondary ticks between main ticks on y-axis (only if not log scale). 0 means non secondary ticks +# text.angle: angle of the text when axis is qualitative +# tick.length: length of the main ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc., 0 for no ticks) +# sec.tick.length: length of the secondary ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc., 0 for no ticks) +# bg.color: background color of the plot region. NULL for no color. BEWARE: cover/hide an existing plot ! +# grid.lwd: if non NULL, activate the grid line (specify the line width) +# grid.col: grid line color (only if grid.lwd non NULL) +# corner.text: text to add at the top right corner of the window +# corner.text.size: positive numeric. Increase or decrease the size of the text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 +# par.reset: to reset all the graphics parameters. BEWARE: TRUE can generate display problems, mainly in graphic devices with multiple figure regions +# just.label.add: just add axis labels (legend)? Either TRUE or FALSE. If TRUE, at least (x.side == 0 & x.lab != "") or (y.side == 0 & y.lab != "") must be set to display the corresponding x.lab or y.lab +# custom.par: list that provides the parameters that reset all the graphics parameters. BEWARE: if NULL and par.reset == TRUE, the default par() parameters are used +# RETURN +# a list containing: +# $x.mid.left.dev.region: middle of the left margin of the device region, in coordinates of the x-axis +# $x.left.dev.region: left side of the left margin (including the potential margin of the device region), in coordinates of the x-axis +# $x.mid.right.dev.region: middle of the right margin of the device region, in coordinates of the x-axis +# $x.right.dev.region: right side of the right margin (including the potential margin of the device region), in coordinates of the x-axis +# $x.mid.left.fig.region: middle of the left margin of the figure region, in coordinates of the x-axis +# $x.left.fig.region: left side of the left margin, in coordinates of the x-axis +# $x.mid.right.fig.region: middle of the right margin of the figure region, in coordinates of the x-axis +# $x.right.fig.region: right side of the right margin, in coordinates of the x-axis +# $x.left.plot.region: left side of the plot region, in coordinates of the x-axis +# $x.right.plot.region: right side of the plot region, in coordinates of the x-axis +# $x.mid.plot.region: middle of the plot region, in coordinates of the x-axis +# $y.mid.bottom.dev.region: middle of the bottom margin of the device region, in coordinates of the y-axis +# $y.bottom.dev.region: bottom side of the bottom margin (including the potential margin of the device region), in coordinates of the y-axis +# $y.mid.top.dev.region: middle of the top margin of the device region, in coordinates of the y-axis +# $y.top.dev.region: top side of the top margin (including the potential margin of the device region), in coordinates of the y-axis +# $y.mid.bottom.fig.region: middle of the bottom margin of the figure region, in coordinates of the y-axis +# $y.bottom.fig.region: bottom of the bottom margin of the figure region, in coordinates of the y-axis +# $y.mid.top.fig.region: middle of the top margin of the figure region, in coordinates of the y-axis +# $y.top.fig.region: top of the top margin of the figure region, in coordinates of the y-axis +# $y.top.plot.region: top of the plot region, in coordinates of the y-axis +# $y.bottom.plot.region: bottom of the plot region, in coordinates of the y-axis +# $y.mid.plot.region: middle of the plot region, in coordinates of the y-axis +# $text: warning text +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_open() to reinitialize graph parameters if par.reset = TRUE and custom.par = NULL +# EXAMPLES +# Example of log axis with log y-axis and unmodified x-axis: +# prior.par <- fun_prior_plot(param.reinitial = TRUE, xlog.scale = FALSE, ylog.scale = TRUE, remove.label = TRUE, remove.x.axis = FALSE, remove.y.axis = TRUE, down.space = 1, left.space = 1, up.space = 1, right.space = 1, orient = 1, dist.legend = 0.5, tick.length = 0.5, box.type = "n", amplif.label = 1, amplif.axis = 1, display.extend = FALSE, return.par = TRUE) ; plot(1:100, log = "y") ; fun_post_plot(y.side = 2, y.log.scale = prior.par$ylog, x.lab = "Values", y.lab = "TEST", y.axis.size = 1.25, y.label.size = 1.5, y.dist.legend = 0.7, just.label.add = ! prior.par$ann) +# Example of log axis with redrawn x-axis and y-axis: +# prior.par <- fun_prior_plot(param.reinitial = TRUE) ; plot(1:100) ; fun_post_plot(x.side = 1, x.lab = "Values", y.side = 2, y.lab = "TEST", y.axis.size = 1, y.label.size = 2, y.dist.legend = 0.6) +# Example of title easily added to a plot: +# plot(1:100) ; para <- fun_post_plot(corner.text = "TITLE ADDED") # try also: par(xpd = TRUE) ; text(x = para$x.mid.left.fig.region, y = para$y.mid.top.fig.region, labels = "TITLE ADDED", cex = 0.5) +# example with margins in the device region: +# windows(5,5) ; fun_prior_plot(box.type = "o") ; par(mai=c(0.5,0.5,0.5,0.5), omi = c(0.25,0.25,1,0.25), xaxs = "i", yaxs = "i") ; plot(0:10) ; a <- fun_post_plot(x.side = 0, y.side = 0) ; x <- c(a$x.mid.left.dev.region, a$x.left.dev.region, a$x.mid.right.dev.region, a$x.right.dev.region, a$x.mid.left.fig.region, a$x.left.fig.region, a$x.mid.right.fig.region, a$x.right.fig.region, a$x.right.plot.region, a$x.left.plot.region, a$x.mid.plot.region) ; y <- c(a$y.mid.bottom.dev.region, a$y.bottom.dev.region, a$y.mid.top.dev.region, a$y.top.dev.region, a$y.mid.bottom.fig.region, a$y.bottom.fig.region, a$y.mid.top.fig.region, a$y.top.fig.region, a$y.top.plot.region, a$y.bottom.plot.region, a$y.mid.plot.region) ; par(xpd = NA) ; points(x = rep(5, length(y)), y = y, pch = 16, col = "red") ; text(x = rep(5, length(y)), y = y, c("y.mid.bottom.dev.region", "y.bottom.dev.region", "y.mid.top.dev.region", "y.top.dev.region", "y.mid.bottom.fig.region", "y.bottom.fig.region", "y.mid.top.fig.region", "y.top.fig.region", "y.top.plot.region", "y.bottom.plot.region", "y.mid.plot.region"), cex = 0.65, col = grey(0.25)) ; points(y = rep(5, length(x)), x = x, pch = 16, col = "blue") ; text(y = rep(5, length(x)), x = x, c("x.mid.left.dev.region", "x.left.dev.region", "x.mid.right.dev.region", "x.right.dev.region", "x.mid.left.fig.region", "x.left.fig.region", "x.mid.right.fig.region", "x.right.fig.region", "x.right.plot.region", "x.left.plot.region", "x.mid.plot.region"), cex = 0.65, srt = 90, col = grey(0.25)) +# DEBUGGING +# x.side = 0 ; x.log.scale = FALSE ; x.categ = NULL ; x.categ.pos = NULL ; x.lab = "" ; x.axis.size = 1.5 ; x.label.size = 1.5 ; x.dist.legend = 1 ; x.nb.inter.tick = 1 ; y.side = 0 ; y.log.scale = FALSE ; y.categ = NULL ; y.categ.pos = NULL ; y.lab = "" ; y.axis.size = 1.5 ; y.label.size = 1.5 ; y.dist.legend = 0.7 ; y.nb.inter.tick = 1 ; text.angle = 90 ; tick.length = 0.5 ; sec.tick.length = 0.3 ; bg.color = NULL ; grid.lwd = NULL ; grid.col = "white" ; corner.text = "" ; corner.text.size = 1 ; just.label.add = FALSE ; par.reset = FALSE ; custom.par = NULL # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_open", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_open() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = x.side, options = c(0, 1, 3), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.log.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(x.categ)){ +tempo <- fun_check(data = x.categ, class = "character", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +if( ! is.null(x.categ.pos)){ +tempo <- fun_check(data = x.categ.pos, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = x.lab, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.axis.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.label.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.nb.inter.tick, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.side, options = c(0, 2, 4), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.log.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(y.categ)){ +tempo <- fun_check(data = y.categ, class = "character", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +if( ! is.null(y.categ.pos)){ +tempo <- fun_check(data = y.categ.pos, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = y.lab, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.axis.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.label.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.nb.inter.tick, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = text.angle, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = sec.tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +if( ! is.null(bg.color)){ +tempo <- fun_check(data = bg.color, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if( ! (bg.color %in% colors() | grepl(pattern = "^#", bg.color))){ # check color +tempo.cat <- paste0("ERROR IN ", function.name, ": bg.color ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # OR A COLOR NAME GIVEN BY colors()") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +if( ! is.null(grid.lwd)){ +tempo <- fun_check(data = grid.lwd, class = "vector", mode = "numeric", neg.values = FALSE, fun.name = function.name) ; eval(ee) +} +if( ! is.null(grid.col)){ +tempo <- fun_check(data = grid.col, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if( ! (grid.col %in% colors() | grepl(pattern = "^#", grid.col))){ # check color +tempo.cat <- paste0("ERROR IN ", function.name, ": grid.col ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # OR A COLOR NAME GIVEN BY colors()") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = corner.text, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = corner.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = just.label.add, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = par.reset, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(custom.par)){ +tempo <- fun_check(data = custom.par, typeof = "list", length = 1, fun.name = function.name) ; eval(ee) +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +text <- NULL +par(tcl = -par()$mgp[2] * tick.length) +if(x.log.scale == TRUE){ +grid.coord.x <- c(10^par("usr")[1], 10^par("usr")[2]) +}else{ +grid.coord.x <- c(par("usr")[1], par("usr")[2]) +} +if(y.log.scale == TRUE){ +grid.coord.y <- c(10^par("usr")[3], 10^par("usr")[4]) +}else{ +grid.coord.y <- c(par("usr")[3], par("usr")[4]) +} +if( ! is.null(bg.color)){ +rect(grid.coord.x[1], grid.coord.y[1], grid.coord.x[2], grid.coord.y[2], col = bg.color, border = NA) +} +if( ! is.null(grid.lwd)){ +grid(nx = NA, ny = NULL, col = grid.col, lty = 1, lwd = grid.lwd) +} +if(x.log.scale == TRUE){ +x.mid.left.dev.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) +x.left.dev.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1]) # in x coordinates +x.mid.right.dev.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) +x.right.dev.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2])) # in x coordinates +x.mid.left.fig.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) +x.left.fig.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1]) # in x coordinates +x.mid.right.fig.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) +x.right.fig.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2])) # in x coordinates +x.left.plot.region <- 10^par("usr")[1] # in x coordinates, left of the plot region (according to x scale) +x.right.plot.region <- 10^par("usr")[2] # in x coordinates, right of the plot region (according to x scale) +x.mid.plot.region <- 10^((par("usr")[2] + par("usr")[1]) / 2) # in x coordinates, right of the plot region (according to x scale) +}else{ +x.mid.left.dev.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) +x.left.dev.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1]) # in x coordinates +x.mid.right.dev.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) +x.right.dev.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2])) # in x coordinates +x.mid.left.fig.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) +x.left.fig.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1]) # in x coordinates +x.mid.right.fig.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) +x.right.fig.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2])) # in x coordinates +x.left.plot.region <- par("usr")[1] # in x coordinates, left of the plot region (according to x scale) +x.right.plot.region <- par("usr")[2] # in x coordinates, right of the plot region (according to x scale) +x.mid.plot.region <- (par("usr")[2] + par("usr")[1]) / 2 # in x coordinates, right of the plot region (according to x scale) +} +if(y.log.scale == TRUE){ +y.mid.bottom.dev.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (par("omd")[3] / 2)) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space +y.bottom.dev.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * par("omd")[3]) # in y coordinates +y.mid.top.dev.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space +y.top.dev.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4])) # in y coordinates +y.mid.bottom.fig.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] / 2) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space +y.bottom.fig.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3]) # in y coordinates +y.mid.top.fig.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space +y.top.fig.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4])) # in y coordinates +y.top.plot.region <- 10^par("usr")[4] # in y coordinates, top of the plot region (according to y scale) +y.bottom.plot.region <- 10^par("usr")[3] # in y coordinates, bottom of the plot region (according to y scale) +y.mid.plot.region <- (par("usr")[3] + par("usr")[4]) / 2 # in x coordinates, right of the plot region (according to x scale) +}else{ +y.mid.bottom.dev.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (par("omd")[3] / 2)) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space +y.bottom.dev.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * par("omd")[3]) # in y coordinates +y.mid.top.dev.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space +y.top.dev.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4])) # in y coordinates +y.mid.bottom.fig.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] / 2) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space +y.bottom.fig.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3]) # in y coordinates +y.mid.top.fig.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space +y.top.fig.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4])) # in y coordinates +y.top.plot.region <- par("usr")[4] # in y coordinates, top of the plot region (according to y scale) +y.bottom.plot.region <- par("usr")[3] # in y coordinates, bottom of the plot region (according to y scale) +y.mid.plot.region <- ((par("usr")[3] + par("usr")[4]) / 2) # in x coordinates, right of the plot region (according to x scale) +} +if(any(sapply(FUN = all.equal, c(1, 3), x.side) == TRUE)){ +par(xpd=FALSE, xaxt="s") +if(is.null(x.categ) & x.log.scale == TRUE){ +if(any(par()$xaxp[1:2] == 0L)){ # any(sapply(FUN = all.equal, par()$xaxp[1:2], 0) == TRUE) not used because we strictly need zero as a result. Beware: write "== TRUE", because the result is otherwise character and a warning message appears using any() +if(par()$xaxp[1] == 0L){ # isTRUE(all.equal(par()$xaxp[1], 0)) not used because we strictly need zero as a result +par(xaxp = c(10^-30, par()$xaxp[2:3])) # because log10(par()$xaxp[1] == 0) == -Inf +} +if(par()$xaxp[2] == 0L){ # isTRUE(all.equal(par()$xaxp[1], 0)) not used because we strictly need zero as a result +par(xaxp = c(par()$xaxp[1], 10^-30, par()$xaxp[3])) # because log10(par()$xaxp[2] == 0) == -Inf +} +} +axis(side = x.side, at = c(10^par()$usr[1], 10^par()$usr[2]), labels=rep("", 2), lwd=1, lwd.ticks = 0) # draw the axis line +mtext(side = x.side, text = x.lab, line = x.dist.legend / 0.2, las = 0, cex = x.label.size) +par(tcl = -par()$mgp[2] * sec.tick.length) # length of the secondary ticks are reduced +suppressWarnings(rug(10^outer(c((log10(par("xaxp")[1]) -1):log10(par("xaxp")[2])), log10(1:10), "+"), ticksize = NA, side = x.side)) # ticksize = NA to allow the use of par()$tcl value +par(tcl = -par()$mgp[2] * tick.length) # back to main ticks +axis(side = x.side, at = c(1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10), labels = expression(10^-15, 10^-14, 10^-13, 10^-12, 10^-11, 10^-10, 10^-9, 10^-8, 10^-7, 10^-6, 10^-5, 10^-4, 10^-3, 10^-2, 10^-1, 10^0, 10^1, 10^2, 10^3, 10^4, 10^5, 10^6, 10^7, 10^8, 10^9, 10^10), lwd = 0, lwd.ticks = 1, cex.axis = x.axis.size) +x.text <- 10^par("usr")[2] +}else if(is.null(x.categ) & x.log.scale == FALSE){ +axis(side=x.side, at=c(par()$usr[1], par()$usr[2]), labels=rep("", 2), lwd=1, lwd.ticks=0) # draw the axis line +axis(side=x.side, at=round(seq(par()$xaxp[1], par()$xaxp[2], length.out=par()$xaxp[3]+1), 2), cex.axis = x.axis.size) # axis(side=x.side, at=round(seq(par()$xaxp[1], par()$xaxp[2], length.out=par()$xaxp[3]+1), 2), labels = format(round(seq(par()$xaxp[1], par()$xaxp[2], length.out=par()$xaxp[3]+1), 2), big.mark=','), cex.axis = x.axis.size) # to get the 1000 comma separator +mtext(side = x.side, text = x.lab, line = x.dist.legend / 0.2, las = 0, cex = x.label.size) +if(x.nb.inter.tick > 0){ +inter.tick.unit <- (par("xaxp")[2] - par("xaxp")[1]) / par("xaxp")[3] +par(tcl = -par()$mgp[2] * sec.tick.length) # length of the ticks are reduced +suppressWarnings(rug(seq(par("xaxp")[1] - 10 * inter.tick.unit, par("xaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + x.nb.inter.tick)), ticksize = NA, x.side)) # ticksize = NA to allow the use of par()$tcl value +par(tcl = -par()$mgp[2] * tick.length) # back to main ticks +} +x.text <- par("usr")[2] +}else if(( ! is.null(x.categ)) & x.log.scale == FALSE){ +if(is.null(x.categ.pos)){ +x.categ.pos <- 1:length(x.categ) +}else if(length(x.categ.pos) != length(x.categ)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x.categ.pos MUST BE THE SAME LENGTH AS x.categ") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +par(xpd = TRUE) +if(isTRUE(all.equal(x.side, 1))){ #isTRUE(all.equal(x.side, 1)) is similar to x.side == 1L but deals with float +segments(x0 = x.left.plot.region, x1 = x.right.plot.region, y0 = y.bottom.plot.region, y1 = y.bottom.plot.region) # draw the line of the axis +text(x = x.categ.pos, y = y.mid.bottom.fig.region, labels = x.categ, srt = text.angle, cex = x.axis.size) +}else if(isTRUE(all.equal(x.side, 3))){ #isTRUE(all.equal(x.side, 1)) is similar to x.side == 3L but deals with float +segments(x0 = x.left.plot.region, x1 = x.right.plot.region, y0 = y.top.plot.region, y1 = y.top.plot.region) # draw the line of the axis +text(x = x.categ.pos, y = y.mid.top.fig.region, labels = x.categ, srt = text.angle, cex = x.axis.size) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": ARGUMENT x.side CAN ONLY BE 1 OR 3") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +par(xpd = FALSE) +x.text <- par("usr")[2] +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": PROBLEM WITH THE x.side (", x.side ,") OR x.log.scale (", x.log.scale,") ARGUMENTS") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else{ +x.text <- par("usr")[2] +} +if(any(sapply(FUN = all.equal, c(2, 4), y.side) == TRUE)){ +par(xpd=FALSE, yaxt="s") +if(is.null(y.categ) & y.log.scale == TRUE){ +if(any(par()$yaxp[1:2] == 0L)){ # any(sapply(FUN = all.equal, par()$yaxp[1:2], 0) == TRUE) not used because we strictly need zero as a result. Beware: write "== TRUE", because the result is otherwise character and a warning message appears using any() +if(par()$yaxp[1] == 0L){ # strict zero needed +par(yaxp = c(10^-30, par()$yaxp[2:3])) # because log10(par()$yaxp[1] == 0) == -Inf +} +if(par()$yaxp[2] == 0L){ # strict zero needed +par(yaxp = c(par()$yaxp[1], 10^-30, par()$yaxp[3])) # because log10(par()$yaxp[2] == 0) == -Inf +} +} +axis(side=y.side, at=c(10^par()$usr[3], 10^par()$usr[4]), labels=rep("", 2), lwd=1, lwd.ticks=0) # draw the axis line +par(tcl = -par()$mgp[2] * sec.tick.length) # length of the ticks are reduced +suppressWarnings(rug(10^outer(c((log10(par("yaxp")[1])-1):log10(par("yaxp")[2])), log10(1:10), "+"), ticksize = NA, side = y.side)) # ticksize = NA to allow the use of par()$tcl value +par(tcl = -par()$mgp[2] * tick.length) # back to main tick length +axis(side = y.side, at = c(1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10), labels = expression(10^-15, 10^-14, 10^-13, 10^-12, 10^-11, 10^-10, 10^-9, 10^-8, 10^-7, 10^-6, 10^-5, 10^-4, 10^-3, 10^-2, 10^-1, 10^0, 10^1, 10^2, 10^3, 10^4, 10^5, 10^6, 10^7, 10^8, 10^9, 10^10), lwd = 0, lwd.ticks = 1, cex.axis = y.axis.size) +y.text <- 10^(par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) +mtext(side = y.side, text = y.lab, line = y.dist.legend / 0.2, las = 0, cex = y.label.size) +}else if(is.null(y.categ) & y.log.scale == FALSE){ +axis(side=y.side, at=c(par()$usr[3], par()$usr[4]), labels=rep("", 2), lwd=1, lwd.ticks=0) # draw the axis line +axis(side=y.side, at=round(seq(par()$yaxp[1], par()$yaxp[2], length.out=par()$yaxp[3]+1), 2), cex.axis = y.axis.size) +mtext(side = y.side, text = y.lab, line = y.dist.legend / 0.2, las = 0, cex = y.label.size) +if(y.nb.inter.tick > 0){ +inter.tick.unit <- (par("yaxp")[2] - par("yaxp")[1]) / par("yaxp")[3] +par(tcl = -par()$mgp[2] * sec.tick.length) # length of the ticks are reduced +suppressWarnings(rug(seq(par("yaxp")[1] - 10 * inter.tick.unit, par("yaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + y.nb.inter.tick)), ticksize = NA, side=y.side)) # ticksize = NA to allow the use of par()$tcl value +par(tcl = -par()$mgp[2] * tick.length) # back to main tick length +} +y.text <- (par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) +}else if(( ! is.null(y.categ)) & y.log.scale == FALSE){ +if(is.null(y.categ.pos)){ +y.categ.pos <- 1:length(y.categ) +}else if(length(y.categ.pos) != length(y.categ)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y.categ.pos MUST BE THE SAME LENGTH AS y.categ") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +axis(side = y.side, at = y.categ.pos, labels = rep("", length(y.categ)), lwd=0, lwd.ticks=1) # draw the line of the axis +par(xpd = TRUE) +if(isTRUE(all.equal(y.side, 2))){ #isTRUE(all.equal(y.side, 2)) is similar to y.side == 2L but deals with float +text(x = x.mid.left.fig.region, y = y.categ.pos, labels = y.categ, srt = text.angle, cex = y.axis.size) +}else if(isTRUE(all.equal(y.side, 4))){ # idem +text(x = x.mid.right.fig.region, y = y.categ.pos, labels = y.categ, srt = text.angle, cex = y.axis.size) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": ARGUMENT y.side CAN ONLY BE 2 OR 4") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +par(xpd = FALSE) +y.text <- (par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": PROBLEM WITH THE y.side (", y.side ,") OR y.log.scale (", y.log.scale,") ARGUMENTS") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else{ +y.text <- (par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) +} +par(xpd=NA) +text(x = x.mid.right.fig.region, y = y.text, corner.text, adj=c(1, 1.1), cex = corner.text.size) # text at the topright corner. Replace x.right.fig.region by x.text if text at the right edge of the plot region +if(just.label.add == TRUE & isTRUE(all.equal(x.side, 0)) & x.lab != ""){ +text(x = x.mid.plot.region, y = y.mid.bottom.fig.region, x.lab, adj=c(0.5, 0.5), cex = x.label.size) # x label +} +if(just.label.add == TRUE & isTRUE(all.equal(y.side, 0)) & y.lab != ""){ +text(x = y.mid.plot.region, y = x.mid.left.fig.region, y.lab, adj=c(0.5, 0.5), cex = y.label.size) # x label +} +par(xpd=FALSE) +if(par.reset == TRUE){ +tempo.par <- fun_open(pdf = FALSE, return.output = TRUE) +invisible(dev.off()) # close the new window +if( ! is.null(custom.par)){ +if( ! names(custom.par) %in% names(tempo.par$ini.par)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": custom.par ARGUMENT SHOULD HAVE THE NAMES OF THE COMPARTMENT LIST COMING FROM THE par() LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +par(custom.par) +text <- c(text, "\nGRAPH PARAMETERS SET TO VALUES DEFINED BY custom.par ARGUMENT\n") +}else{ +par(tempo.par$ini.par) +text <- c(text, "\nGRAPH PARAMETERS RESET TO par() DEFAULT VALUES\n") +} +} +output <- list(x.mid.left.dev.region = x.mid.left.dev.region, x.left.dev.region = x.left.dev.region, x.mid.right.dev.region = x.mid.right.dev.region, x.right.dev.region = x.right.dev.region, x.mid.left.fig.region = x.mid.left.fig.region, x.left.fig.region = x.left.fig.region, x.mid.right.fig.region = x.mid.right.fig.region, x.right.fig.region = x.right.fig.region, x.left.plot.region = x.left.plot.region, x.right.plot.region = x.right.plot.region, x.mid.plot.region = x.mid.plot.region, y.mid.bottom.dev.region = y.mid.bottom.dev.region, y.bottom.dev.region = y.bottom.dev.region, y.mid.top.dev.region = y.mid.top.dev.region, y.top.dev.region = y.top.dev.region, y.mid.bottom.fig.region = y.mid.bottom.fig.region, y.bottom.fig.region = y.bottom.fig.region, y.mid.top.fig.region = y.mid.top.fig.region, y.top.fig.region = y.top.fig.region, y.top.plot.region = y.top.plot.region, y.bottom.plot.region = y.bottom.plot.region, y.mid.plot.region = y.mid.plot.region, text = text) +return(output) +} + + +######## fun_close() #### close specific graphic windows + + +fun_close <- function(kind = "pdf", return.text = FALSE){ +# AIM +# close only specific graphic windows (devices) +# ARGUMENTS: +# kind: vector, among c("windows", "quartz", "x11", "X11", "pdf", "bmp", "png", "tiff"), indicating the kind of graphic windows (devices) to close. BEWARE: either "windows", "quartz", "x11" or "X11" means that all the X11 GUI graphics devices will be closed, whatever the OS used +# return.text: print text regarding the kind parameter and the devices that were finally closed? +# RETURN +# text regarding the kind parameter and the devices that were finally closed +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# windows() ; windows() ; pdf() ; dev.list() ; fun_close(kind = c("pdf", "x11"), return.text = TRUE) ; dev.list() +# DEBUGGING +# kind = c("windows", "pdf") ; return.text = FALSE # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = kind, options = c("windows", "quartz", "x11", "X11", "pdf", "bmp", "png", "tiff"), fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return.text, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +text <- paste0("THE REQUIRED KIND OF GRAPHIC DEVICES TO CLOSE ARE ", paste(kind, collapse = " ")) +if(Sys.info()["sysname"] == "Windows"){ # Note that .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows +if(any(kind %in% c("windows", "quartz", "x11", "X11"))){ +tempo <- kind %in% c("windows", "quartz", "x11", "X11") +kind[tempo] <- "windows" # term are replaced by what is displayed when using a <- dev.list() ; names(a) +} +}else if(Sys.info()["sysname"] == "Linux"){ +if(any(kind %in% c("windows", "quartz", "x11", "X11"))){ +tempo.device <- suppressWarnings(try(X11(), silent = TRUE))[] # open a X11 window to try to recover the X11 system used +if( ! is.null(tempo.device)){ +text <- paste0(text, "\nCANNOT CLOSE GUI GRAPHIC DEVICES AS REQUIRED BECAUSE THIS LINUX SYSTEM DOES NOT HAVE IT") +}else{ +tempo <- kind %in% c("windows", "quartz", "x11", "X11") +kind[tempo] <- names(dev.list()[length(dev.list())]) # term are replaced by what is displayed when using a <- dev.list() ; names(a) +invisible(dev.off()) # close the X11 opened by tempo +} +} +}else{ # for macOS +if(any(kind %in% c("windows", "quartz", "x11", "X11"))){ +tempo <- kind %in% c("windows", "quartz", "x11", "X11") +kind[tempo] <- "quartz" # term are replaced by what is displayed when using a <- dev.list() ; names(a) +} +} +kind <- unique(kind) +if(length(dev.list()) != 0){ +for(i in length(names(dev.list())):1){ +if(names(dev.list())[i] %in% kind){ +text <- paste0(text, "\n", names(dev.list())[i], " DEVICE NUMBER ", dev.list()[i], " HAS BEEN CLOSED") +invisible(dev.off(dev.list()[i])) +} +} +} +if(return.text == TRUE){ +return(text) +} +} + + +################ Standard graphics + + +######## fun_empty_graph() #### text to display for empty graphs + + + + + +fun_empty_graph <- function( +text = NULL, +text.size = 1, +title = NULL, +title.size = 1.5 +){ +# AIM +# display an empty plot with a text in the middle of the window (for instance to specify that no plot can be drawn) +# ARGUMENTS +# text: character string of the message to display +# text.size: numeric value of the text size +# title: character string of the graph title +# title.size: numeric value of the title size (in points) +# RETURN +# an empty plot +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# simple example +# fun_empty_graph(text = "NO GRAPH") +# white page +# fun_empty_graph() # white page +# all the arguments +# fun_empty_graph(text = "NO GRAPH", text.size = 2, title = "GRAPH1", title.size = 1) +# DEBUGGING +# text = "NO GRAPH" ; title = "GRAPH1" ; text.size = 1 +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +if( ! is.null(text)){ +tempo <- fun_check(data = text, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(title)){ +tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = title.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +ini.par <- par(no.readonly = TRUE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened +par(ann=FALSE, xaxt="n", yaxt="n", mar = rep(1, 4), bty = "n", xpd = NA) +plot(1, 1, type = "n") # no display with type = "n" +x.left.dev.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1]) +y.top.dev.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4])) +if( ! is.null(text)){ +text(x = 1, y = 1, labels = text, cex = text.size) +} +if( ! is.null(title)){ +text(x = x.left.dev.region, y = y.top.dev.region, labels = title, adj=c(0, 1), cex = title.size) +} +par(ini.par) +} + + +################ gg graphics + + +######## fun_gg_palette() #### ggplot2 default color palette + + + + + +fun_gg_palette <- function(n, kind = "std"){ +# AIM +# provide colors used by ggplot2 +# the interest is to use another single color that is not the red one used by default +# for ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html +# ARGUMENTS +# n: number of groups on the graph +# kind: either "std" for standard gg colors, "dark" for darkened gg colors, or "light" for pastel gg colors +# RETURN +# the vector of hexadecimal colors +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# output of the function +# fun_gg_palette(n = 2) +# the ggplot2 palette when asking for 7 different colors +# plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7)) +# selection of the 5th color of the ggplot2 palette made of 7 different colors +# plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7)[5]) +# the ggplot2 palette made of 7 darkened colors +# plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7, kind = "dark")) +# the ggplot2 palette made of 7 lighten colors +# plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7, kind = "light")) +# DEBUGGING +# n = 0 +# kind = "std" +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = n, class = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & isTRUE(all.equal(n, 0))){ # isTRUE(all.equal(n, 0))) is similar to n == 0 but deals with float +tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT MUST BE A NON ZERO INTEGER. HERE IT IS: ", paste(n, collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +tempo <- fun_check(data = kind, options = c("std", "dark", "light"), length = 1, fun.name = function.name) ; eval(ee) +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +hues = seq(15, 375, length = n + 1) +hcl(h = hues, l = if(kind == "std"){65}else if(kind == "dark"){35}else if(kind == "light"){85}, c = 100)[1:n] +} + + +######## fun_gg_just() #### ggplot2 justification of the axis labeling, depending on angle + + + + + +fun_gg_just <- function(angle, pos, kind = "axis"){ +# AIM +# provide correct justification for text labeling, depending on the chosen angle +# WARNINGS +# justification behave differently on plot, depending whether it is used for annotayed text or for axis labelling. Indeed the latter has labelling constrained +# Of note, a bug in ggplot2: vjust sometimes does not work, i.e., the same justification result is obtained whatever the value used. This is the case with angle = 90, pos = "top", kind = "axis". While everything is fine with angle = 90, pos = "bottom", kind = "axis". At least, everything seems fine for kind = "axis" and pos = c("left", "bottom") +# ARGUMENTS +# angle: integer value of the text angle, using the same rules as in ggplot2. Positive values for counterclockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Negative values for clockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. +# pos: where text is? Either "top", "right", "bottom" or "left" of the elements to justify from +# kind: kind of text? Either "axis" or "text". In the first case, the pos argument refers to the axis position, and in the second to annotated text (using ggplot2::annotate() or ggplot2::geom_text()) +# RETURN +# a list containing: +# $angle: the submitted angle (value potentially reduced to fit the [-360 ; 360] interval, e.g., 460 -> 100, without impact on the final angle displayed) +# $pos: the selected position (argument pos) +# $kind: the selected kind of text (argument kind) +# $hjust: the horizontal justification +# $vjust: the vertical justification +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_gg_just(angle = 45, pos = "bottom") +# fun_gg_just(angle = (360*2 + 45), pos = "left") +# output <- fun_gg_just(angle = 45, pos = "bottom") ; obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; ggplot2::ggplot() + ggplot2::geom_bar(data = obs1, mapping = ggplot2::aes(x = group, y = time), stat = "identity") + ggplot2::theme(axis.text.x = ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)) +# output <- fun_gg_just(angle = -45, pos = "left") ; obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; ggplot2::ggplot() + ggplot2::geom_bar(data = obs1, mapping = ggplot2::aes(x = group, y = time), stat = "identity") + ggplot2::theme(axis.text.y = ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)) + ggplot2::coord_flip() +# output1 <- fun_gg_just(angle = 90, pos = "bottom") ; output2 <- fun_gg_just(angle = -45, pos = "left") ; obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; ggplot2::ggplot() + ggplot2::geom_bar(data = obs1, mapping = ggplot2::aes(x = group, y = time), stat = "identity") + ggplot2::theme(axis.text.x = ggplot2::element_text(angle = output1$angle, hjust = output1$hjust, vjust = output1$vjust), axis.text.y = ggplot2::element_text(angle = output2$angle, hjust = output2$hjust, vjust = output2$vjust)) +# output <- fun_gg_just(angle = -45, pos = "left") ; obs1 <- data.frame(time = 1, km = 1, bird = "pigeon", stringsAsFactors = FALSE) ; ggplot2::ggplot(data = obs1, mapping = ggplot2::aes(x = time, y = km)) + ggplot2::geom_point() + ggplot2::geom_text(mapping = ggplot2::aes(label = bird), angle = output$angle, hjust = output$hjust, vjust = output$vjust) +# obs1 <- data.frame(time = 1:10, km = 1:10, bird = c(NA, NA, NA, "pigeon", NA, "cat", NA, NA, NA, NA), stringsAsFactors = FALSE) ; fun_open(width = 4, height = 4) ; for(i0 in c("text", "axis")){for(i1 in c("top", "right", "bottom", "left")){for(i2 in c(0, 45, 90, 135, 180, 225, 270, 315, 360)){output <- fun_gg_just(angle = i2, pos = i1, kind = i0) ; title <- paste0("kind: ", i0, " | pos: ", i1, " | angle = ", i2, " | hjust: ", output$hjust, " | vjust: ", output$vjust) ; if(i0 == "text"){print(ggplot2::ggplot(data = obs1, mapping = ggplot2::aes(x = time, y = km)) + ggplot2::geom_point(color = fun_gg_palette(1), alpha = 0.5) + ggplot2::ggtitle(title) + ggplot2::geom_text(mapping = ggplot2::aes(label = bird), angle = output$angle, hjust = output$hjust, vjust = output$vjust) + ggplot2::theme(title = ggplot2::element_text(size = 5)))}else{print(ggplot2::ggplot(data = obs1, mapping = ggplot2::aes(x = time, y = km)) + ggplot2::geom_point(color = fun_gg_palette(1), alpha = 0.5) + ggplot2::ggtitle(title) + ggplot2::geom_text(mapping = ggplot2::aes(label = bird)) + ggplot2::scale_x_continuous(position = ifelse(i1 == "top", "top", "bottom")) + ggplot2::scale_y_continuous(position = ifelse(i1 == "right", "right", "left")) + ggplot2::theme(title = ggplot2::element_text(size = 5), axis.text.x = if(i1 %in% c("top", "bottom")){ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)}, axis.text.y = if(i1 %in% c("right", "left")){ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)}))}}}} ; dev.off() +# DEBUGGING +# angle = 45 ; pos = "left" ; kind = "axis" +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument primary checking +# arg with no default values +mandat.args <- c( +"angle", +"pos" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# using fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = angle, class = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = pos, options = c("left", "top", "right", "bottom"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = kind, options = c("axis", "text"), length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end using fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <- c( +"angle", +"pos", +"kind" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# end second round of checking and data preparation +# main code +# to get angle between -360 and 360 +while(angle > 360){ +angle <- angle - 360 +} +while(angle < -360){ +angle <- angle + 360 +} +# end to get angle between -360 and 360 +# justifications +if(pos %in% c("bottom", "top")){ +# code below is for if(pos == "bottom"){ +if(any(sapply(FUN = all.equal, c(-360, -180, 0, 180, 360), angle) == TRUE)){ # equivalent of angle == -360 | angle == -180 | angle == 0 | angle == 180 | angle == 360 but deals with floats +hjust <- 0.5 +if(kind == "text"){ +if(any(sapply(FUN = all.equal, c(-360, 0, 360), angle) == TRUE)){ +vjust <- 1 +}else if(any(sapply(FUN = all.equal, c(-180, 180), angle) == TRUE)){ +vjust <- 0 +} +}else{ +vjust <- 0.5 +} +}else if(any(sapply(FUN = all.equal, c(-270, 90), angle) == TRUE)){ +hjust <- 1 +vjust <- 0.5 +}else if(any(sapply(FUN = all.equal, c(-90, 270), angle) == TRUE)){ +hjust <- 0 +vjust <- 0.5 +}else if((angle > -360 & angle < -270) | (angle > 0 & angle < 90)){ +hjust <- 1 +vjust <- 1 +}else if((angle > -270 & angle < -180) | (angle > 90 & angle < 180)){ +hjust <- 1 +vjust <- 0 +}else if((angle > -180 & angle < -90) | (angle > 180 & angle < 270)){ +hjust <- 0 +vjust <- 0 +if(kind == "text" & pos == "top"){ +hjust <- 1 +} +}else if((angle > -90 & angle < 0) | (angle > 270 & angle < 360)){ +hjust <- 0 +vjust <- 1 +} +if(pos == "top"){ +if( ! ((angle > -180 & angle < -90) | (angle > 180 & angle < 270))){ +hjust <- 1 - hjust +} +vjust <- 1 - vjust +} +}else if(pos %in% c("left", "right")){ +# code below is for if(pos == "left"){ +if(any(sapply(FUN = all.equal, c(-270, -90, 90, 270), angle) == TRUE)){ # equivalent of angle == -270 | angle == -90 | angle == 90 | angle == 270 but deals with floats +hjust <- 0.5 +if(kind == "text"){ +if(any(sapply(FUN = all.equal, c(-90, 90), angle) == TRUE)){ +vjust <- 0 +}else if(any(sapply(FUN = all.equal, c(-270, 270), angle) == TRUE)){ +vjust <- 1 +} +}else{ +vjust <- 0.5 +} +}else if(any(sapply(FUN = all.equal, c(-360, 0, 360), angle) == TRUE)){ +hjust <- 1 +vjust <- 0.5 +}else if(any(sapply(FUN = all.equal, c(-180, 180), angle) == TRUE)){ +hjust <- 0 +vjust <- 0.5 +}else if((angle > -360 & angle < -270) | (angle > 0 & angle < 90)){ +hjust <- 1 +vjust <- 0 +}else if((angle > -270 & angle < -180) | (angle > 90 & angle < 180)){ +hjust <- 0 +vjust <- 0 +}else if((angle > -180 & angle < -90) | (angle > 180 & angle < 270)){ +hjust <- 0 +vjust <- 1 +}else if((angle > -90 & angle < 0) | (angle > 270 & angle < 360)){ +hjust <- 1 +vjust <- 1 +} +if(pos == "right"){ +hjust <- 1 - hjust +if( ! (((angle > -270 & angle < -180) | (angle > 90 & angle < 180)) | ((angle > -180 & angle < -90) | (angle > 180 & angle < 270)))){ +vjust <- 1 - vjust +} +} +} +# end justifications +output <- list(angle = angle, pos = pos, kind = kind, hjust = hjust, vjust = vjust) +return(output) +} + + +######## fun_gg_get_legend() #### get the legend of ggplot objects + + + + + +fun_gg_get_legend <- function(ggplot_built, fun.name = NULL, lib.path = NULL){ +# AIM +# get legend of ggplot objects +# # from https://stackoverflow.com/questions/12539348/ggplot-separate-legend-and-plot +# ARGUMENTS +# ggplot_built: a ggplot build object +# fun.name: single character string indicating the name of the function using fun_gg_get_legend() for warning and error messages. Ignored if NULL +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL +# RETURN +# a list of class c("gtable", "gTree", "grob", "gDesc"), providing legend information of ggplot_built objet, or NULL if the ggplot_built object has no legend +# REQUIRED PACKAGES +# ggplot2 +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_pack() +# EXAMPLES +# Simple example +# obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; p <- ggplot2::ggplot() + ggplot2::geom_point(data = obs1, mapping = ggplot2::aes(x = group, y = time, fill = group)) ; fun_gg_get_legend(ggplot_built = ggplot2::ggplot_build(p)) +# Error message because no legend in the ggplot +# obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; p <- ggplot2::ggplot() + ggplot2::geom_point(data = obs1, mapping = ggplot2::aes(x = group, y = time)) ; fun_gg_get_legend(ggplot_built = ggplot2::ggplot_build(p)) +# DEBUGGING +# obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; p <- ggplot2::ggplot() + ggplot2::geom_point(data = obs1, mapping = ggplot2::aes(x = group, y = time)) ; ggplot_built = ggplot2::ggplot_build(p) ; fun.name = NULL ; lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +req.function <- c( +"fun_check", +"fun_pack" +) +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED ", i1, "() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end required function checking +# argument primary checking +# arg with no default values +mandat.args <- c( +"ggplot_built" +) +tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# using fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = ggplot_built, class = "ggplot_built", mode = "list", fun.name = function.name) ; eval(ee) +if( ! is.null(fun.name)){ +tempo <- fun_check(data = fun.name, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +} +if( ! is.null(arg.check)){ +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +} +# end using fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking +# management of NA +if(any(is.na(ggplot_built)) | any(is.na(fun.name)) | any(is.na(lib.path))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": NO ARGUMENT CAN HAVE NA VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA +# management of NULL +if(is.null(ggplot_built)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nggplot_built ARGUMENT CANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL +if( ! is.null(lib.path)){ +if( ! all(dir.exists(lib.path))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end second round of checking +# package checking +fun_pack(req.package = c("ggplot2"), lib.path = lib.path) +# end package checking +# main code +win.nb <- dev.cur() +pdf(file = NULL) +tmp <- ggplot2::ggplot_gtable(ggplot_built) +# BEWARE with ggplot_gtable : open a blanck device https://stackoverflow.com/questions/17012518/why-does-this-r-ggplot2-code-bring-up-a-blank-display-device +invisible(dev.off()) +if(win.nb > 1){ # to go back to the previous active device, if == 1 means no opened device +dev.set(win.nb) +} +leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box") +if(length(leg) == 0L){ +legend <- NULL +}else{ +legend <- tmp$grobs[[leg]] +} +return(legend) +} + + +######## fun_gg_point_rast() #### ggplot2 raster scatterplot layer + + + + + +fun_gg_point_rast <- function( +data = NULL, +mapping = NULL, +stat = "identity", +position = "identity", +..., +na.rm = FALSE, +show.legend = NA, +inherit.aes = TRUE, +raster.width = NULL, +raster.height = NULL, +raster.dpi = 300, +inactivate = TRUE, +lib.path = NULL +){ +# AIM +# equivalent to ggplot2::geom_point() but in raster mode +# use it like ggplot2::geom_point() with the main raster.dpi additional argument +# WARNINGS +# can be long to generate the plot +# use a square plot region. Otherwise, the dots will have ellipsoid shape +# solve the transparency problems with some GUI +# this function is derived from the geom_point_rast() function, created by Viktor Petukhov , and present in the ggrastr package (https://rdrr.io/github/VPetukhov/ggrastr/src/R/geom-point-rast.R, MIT License, Copyright (c) 2017 Viktor Petukhov). Has been placed here to minimize package dependencies +# ARGUMENTS +# classical arguments of geom_point(), shown here https://rdrr.io/github/VPetukhov/ggrastr/man/geom_point_rast.html +# raster.width : width of the result image (in inches). Default: deterined by the current device parameters +# raster.height: height of the result image (in inches). Default: deterined by the current device parameters +# raster.dpi: resolution of the result image +# inactivate: logical. Inactivate the fun.name argument of the fun_check() function? If TRUE, the name of the fun_check() function in error messages coming from this function. Use TRUE if fun_gg_point_rast() is used like this: eval(parse(text = "fun_gg_point_rast")) +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL +# RETURN +# a raster scatter plot +# REQUIRED PACKAGES +# ggplot2 +# grid (included in the R installation packages but not automatically loaded) +# Cairo +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_pack() +# EXAMPLES +# Two pdf in the current directory +# set.seed(1) ; data1 = data.frame(x = rnorm(100000), y = rnorm(10000), stringsAsFactors = TRUE) ; fun_open(pdf.name = "Raster") ; ggplot2::ggplot() + fun_gg_point_rast(data = data1, mapping = ggplot2::aes(x = x, y = y)) ; fun_open(pdf.name = "Vectorial") ; ggplot2::ggplot() + ggplot2::geom_point(data = data1, mapping = ggplot2::aes(x = x, y = y)) ; dev.off() ; dev.off() +# DEBUGGING +# +# function name +if(all(inactivate == FALSE)){ # inactivate has to be used here but will be fully checked below +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +}else if(all(inactivate == TRUE)){ +function.name <- NULL +}else{ +tempo.cat <- paste0("ERROR IN fun_gg_point_rast(): CODE INCONSISTENCY 1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_pack", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +if( ! is.null(data)){ +tempo <- fun_check(data = data, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +if( ! is.null(mapping)){ +tempo <- fun_check(data = mapping, class = "uneval", typeof = "list", fun.name = function.name) ; eval(ee) # aes() is tested +} +# stat and position not tested because too complicate +tempo <- fun_check(data = na.rm, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = show.legend, class = "vector", mode = "logical", length = 1, na.contain = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = inherit.aes, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(raster.width)){ +tempo <- fun_check(data = raster.width, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +} +if( ! is.null(raster.height)){ +tempo <- fun_check(data = raster.height, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = raster.dpi, class = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = inactivate, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# package checking +fun_pack(req.package = c("ggplot2"), lib.path = lib.path) +fun_pack(req.package = c("grid"), lib.path = lib.path) +fun_pack(req.package = c("Cairo"), lib.path = lib.path) +# end package checking +# additional functions +DrawGeomPointRast <- function(data, panel_params, coord, na.rm = FALSE, raster.width = NULL, raster.height= NULL, raster.dpi = raster.dpi){ +if (is.null(raster.width)){ +raster.width <- par('fin')[1] +} +if (is.null(raster.height)){ + raster.height <- par('fin')[2] +} +prev_dev_id <- dev.cur() +p <- ggplot2::GeomPoint$draw_panel(data, panel_params, coord) +dev_id <- Cairo::Cairo(type='raster', width = raster.width*raster.dpi, height = raster.height*raster.dpi, dpi = raster.dpi, units = 'px', bg = "transparent")[1] +grid::pushViewport(grid::viewport(width = 1, height = 1)) +grid::grid.points(x = p$x, y = p$y, pch = p$pch, size = p$size, +name = p$name, gp = p$gp, vp = p$vp, draw = T) +grid::popViewport() +cap <- grid::grid.cap() +invisible(dev.off(dev_id)) +invisible(dev.set(prev_dev_id)) +grid::rasterGrob(cap, x = 0, y = 0, width = 1, height = 1, default.units = "native", just = c("left","bottom")) +} +# end additional functions +# main code +GeomPointRast <- ggplot2::ggproto("GeomPointRast", ggplot2::GeomPoint, draw_panel = DrawGeomPointRast) +ggplot2::layer( +data = data, +mapping = mapping, +stat = stat, +geom = GeomPointRast, +position = position, +show.legend = show.legend, +inherit.aes = inherit.aes, +params = list( +na.rm = na.rm, +raster.width = raster.width, +raster.height = raster.height, +raster.dpi = raster.dpi, +... +) +) +# end main code +} + + +######## fun_gg_boxplot() #### ggplot2 boxplot + background dots if required + + + + +######## fun_gg_scatter() #### ggplot2 scatterplot + lines (up to 6 overlays totally) + + + + +######## fun_gg_heatmap() #### ggplot2 heatmap + overlaid mask if required + + +#test plot.margin = margin(up.space.mds, right.space.mds, down.space.mds, left.space.mds, "inches") to set the dim of the region plot ? +# if matrix is full of zero (or same value I guess), heatmap is complicate. Test it and error message + +fun_gg_heatmap <- function( +data1, +legend.name1 = "", +low.color1 = "blue", +mid.color1 = "white", +high.color1 = "red", +limit1 = NULL, +midpoint1 = NULL, +data2 = NULL, +color2 = "black", +alpha2 = 0.5, +invert2 = FALSE, +text.size = 12, +title = "", +title.text.size = 12, +show.scale = TRUE, +rotate = FALSE, +return = FALSE, +plot = TRUE, +add = NULL, +warn.print = FALSE, +lib.path = NULL +){ +# AIM +# ggplot2 heatmap with the possibility to overlay a mask +# see also: +# draw : http://www.sthda.com/english/wiki/ggplot2-quick-correlation-matrix-heatmap-r-software-and-data-visualization +# same range scale : https://stackoverflow.com/questions/44655723/r-ggplot2-heatmap-fixed-scale-color-between-graphs +# for ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html +# ARGUMENTS +# data1: numeric matrix or data frame resulting from the conversion of the numeric matrix by reshape2::melt() +# legend.name1: character string of the data1 heatmap scale legend +# low.color1: character string of the color (i.e., "blue" or "#0000FF") of the lowest scale value +# mid.color1: same as low.color1 but for the middle scale value. If NULL, the middle color is the default color between low.color1 and high.color1. BEWARE: argument midpoint1 is not ignored, even if mid.color1 is NULL, meaning that the default mid color can still be controled +# high.color1: same as low.color1 but for the highest scale value +# limit1: 2 numeric values defining the lowest and higest color scale values. If NULL, take the range of data1 values +# midpoint1: single numeric value defining the value corresponding to the mid.color1 argument. A warning message is returned if midpoint1 does not correspond to the mean of limit1 values, because the color scale is not linear anymore. If NULL, takes the mean of limit1 values. Mean of data1, instead of mean of limit1, can be used here if required +# data2: binary mask matrix (made of 0 and 1) of same dimension as data1 or a data frame resulting from the conversion of the binary mask matrix by reshape2::melt(). Value 1 of data2 will correspond to color2 argument (value 0 will be NA color), and the opposite if invert2 argument is TRUE (inverted mask) +# color2: color of the 1 values of the binary mask matrix. The 0 values will be color NA +# alpha2: numeric value (from 0 to 1) of the mask transparency +# invert2: logical. Invert the mask (1 -> 0 and 0 -> 1)? +# text.size: numeric value of the size of the texts in scale +# title: character string of the graph title +# title.text.size: numeric value of the title size (in points) +# show.scale: logical. Show color scale? +# rotate: logical. Rotate the heatmap 90° clockwise? +# return: logical. Return the graph parameters? +# plot: logical. Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting +# add: character string allowing to add more ggplot2 features (dots, lines, themes, etc.). BEWARE: (1) must start with "+" just after the simple or double opening quote (no space, end of line, carriage return, etc., allowed), (2) must finish with ")" just before the simple or double closing quote (no space, end of line, carriage return, etc., allowed) and (3) each function must be preceded by "ggplot2::" (for instance: "ggplot2::coord_flip()). If the character string contains the "ggplot2::theme" string, then internal ggplot2 theme() and theme_classic() functions will be inactivated to be reused by add. BEWARE: handle this argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions +# warn.print: logical. Print warnings at the end of the execution? No print if no warning messages +# lib.path: absolute path of the required packages, if not in the default folders +# RETURN +# a heatmap if plot argument is TRUE +# a list of the graph info if return argument is TRUE: +# $data: a list of the graphic info +# $axes: a list of the axes info +# $scale: the scale info (lowest, mid and highest values) +# $warn: the warning messages. Use cat() for proper display. NULL if no warning +# REQUIRED PACKAGES +# ggplot2 +# reshape2 +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_pack() +# fun_round() +# EXAMPLES +# fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), title = "GRAPH 1") +# fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), return = TRUE) +# fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), legend.name1 = "VALUE", title = "GRAPH 1", text.size = 5, data2 = matrix(rep(c(1,0,0,0), 4), ncol = 4), invert2 = FALSE, return = TRUE) +# diagonal matrix +# fun_gg_heatmap(data1 = matrix(c(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1), ncol = 4)) +# fun_gg_heatmap(data1 = reshape2::melt(matrix(c(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1), ncol = 4))) +# error message +# fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), data2 = matrix(rep(c(1,0,0,0), 5), ncol = 5)) +# fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), data2 = reshape2::melt(matrix(rep(c(1,0,0,0), 4), ncol = 4))) +# fun_gg_heatmap(data1 = reshape2::melt(matrix(1:16, ncol = 4)), data2 = reshape2::melt(matrix(rep(c(1,0,0,0), 4), ncol = 4))) +# DEBUGGING +# data1 = matrix(1:16, ncol = 4) ; legend.name1 = "" ; low.color1 = "blue" ; mid.color1 = "white" ; high.color1 = "red" ; limit1 = NULL ; midpoint1 = NULL ; data2 = matrix(rep(c(1,0,0,0), 4), ncol = 4) ; color2 = "black" ; alpha2 = 0.5 ; invert2 = FALSE ; text.size = 12 ; title = "" ; title.text.size = 12 ; show.scale = TRUE ; rotate = FALSE ; return = FALSE ; plot = TRUE ; add = NULL ; warn.print = TRUE ; lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_pack", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_round", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_round() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# no reserved words required for this function +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +if(all(is.matrix(data1))){ +tempo <- fun_check(data = data1, class = "matrix", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) +}else if(all(is.data.frame(data1))){ +tempo <- fun_check(data = data1, class = "data.frame", length = 3, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +# structure of reshape2::melt() data frame +tempo <- fun_check(data = data1[, 1], data.name = "COLUMN 1 OF data1 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = data1[, 2], data.name = "COLUMN 2 OF data1 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = data1[, 3], data.name = "COLUMN 3 OF data1 (reshape2::melt() DATA FRAME)", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A NUMERIC MATRIX OR A DATA FRAME OUTPUT OF THE reshape::melt() FUNCTION") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = legend.name1, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = low.color1, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (all(low.color1 %in% colors() | grepl(pattern = "^#", low.color1)))){ # check that all strings of low.color1 start by # +tempo.cat <- paste0("ERROR IN ", function.name, ": low.color1 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(mid.color1)){ +tempo <- fun_check(data = mid.color1, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (all(mid.color1 %in% colors() | grepl(pattern = "^#", mid.color1)))){ # check that all strings of mid.color1 start by # +tempo.cat <- paste0("ERROR IN ", function.name, ": mid.color1 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = high.color1, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (all(high.color1 %in% colors() | grepl(pattern = "^#", high.color1)))){ # check that all strings of high.color1 start by # +tempo.cat <- paste0("ERROR IN ", function.name, ": high.color1 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(limit1)){ +tempo <- fun_check(data = limit1, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & any(limit1 %in% c(Inf, -Inf))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": limit1 ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +if( ! is.null(midpoint1)){ +tempo <- fun_check(data = midpoint1, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +} +if( ! is.null(data2)){ +if(all(is.matrix(data2))){ +tempo <- fun_check(data = data2, class = "matrix", mode = "numeric", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! all(unique(data2) %in% c(0,1))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": MATRIX IN data2 MUST BE MADE OF 0 AND 1 ONLY (MASK MATRIX)") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & all(is.matrix(data1)) & ! identical(dim(data1), dim(data2))){ # matrix and matrix +tempo.cat <- paste0("ERROR IN ", function.name, ": MATRIX DIMENSION IN data2 MUST BE IDENTICAL AS MATRIX DIMENSION IN data1. HERE IT IS RESPECTIVELY:\n", paste(dim(data2), collapse = " "), "\n", paste(dim(data1), collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & all(is.data.frame(data1)) & nrow(data1) != prod(dim(data2))){ # reshape2 and matrix +tempo.cat <- paste0("ERROR IN ", function.name, ": DATA FRAME IN data2 MUST HAVE ROW NUMBER EQUAL TO PRODUCT OF DIMENSIONS OF data1 MATRIX. HERE IT IS RESPECTIVELY:\n", paste(nrow(data1), collapse = " "), "\n", paste(prod(dim(data2)), collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else if(all(is.data.frame(data2))){ +tempo <- fun_check(data = data2, class = "data.frame", length = 3, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +# structure of reshape2::melt() data frame +tempo <- fun_check(data = data2[, 1], data.name = "COLUMN 1 OF data2 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = data2[, 2], data.name = "COLUMN 2 OF data2 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = data2[, 3], data.name = "COLUMN 3 OF data2 (reshape2::melt() DATA FRAME)", mode = "numeric", fun.name = function.name) ; eval(ee) +} +if(tempo$problem == FALSE & ! all(unique(data2[, 3]) %in% c(0,1))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THIRD COLUMN OF DATA FRAME IN data2 MUST BE MADE OF 0 AND 1 ONLY (MASK DATA FRAME)") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & all(is.data.frame(data1)) & ! identical(dim(data1), dim(data2))){ # data frame and data frame +tempo.cat <- paste0("ERROR IN ", function.name, ": DATA FRAME DIMENSION IN data2 MUST BE IDENTICAL TO DATA FRAME DIMENSION IN data1. HERE IT IS RESPECTIVELY:\n", paste(dim(data2), collapse = " "), "\n", paste(dim(data1), collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & all(is.matrix(data1)) & nrow(data2) != prod(dim(data1))){ # reshape2 and matrix +tempo.cat <- paste0("ERROR IN ", function.name, ": DATA FRAME IN data2 MUST HAVE ROW NUMBER EQUAL TO PRODUCT OF DIMENSION OF data1 MATRIX. HERE IT IS RESPECTIVELY:\n", paste(nrow(data2), collapse = " "), "\n", paste(prod(dim(data1)), collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A NUMERIC MATRIX OR A DATA FRAME OUTPUT OF THE reshape::melt() FUNCTION") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = color2, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (all(color2 %in% colors() | grepl(pattern = "^#", color2)))){ # check that all strings of color2 start by # +tempo.cat <- paste0("ERROR IN ", function.name, ": color2 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = alpha2, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = invert2, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = title, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = show.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = plot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(add)){ +tempo <- fun_check(data = add, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! grepl(pattern = "^\\+", add)){ # check that the add string start by + +tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & ! grepl(pattern = "ggplot2::", add)){ # +tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST CONTAIN \"ggplot2::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & ! grepl(pattern = ")$", add)){ # check that the add string finished by ) +tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# package checking +fun_pack(req.package = c("reshape2", "ggplot2"), lib.path = lib.path) +# end package checking +# main code +ini.warning.length <- options()$warning.length +options(warning.length = 8170) +warn <- NULL +warn.count <- 0 +if(all(is.matrix(data1))){ +data1 <- reshape2::melt(data1) # transform a matrix into a data frame with 2 coordinates columns and the third intensity column +} +if(rotate == TRUE){ +data1[, 1] <- rev(data1[, 1]) +} +if(is.null(limit1)){ +if(any(data1[, 3] %in% c(Inf, -Inf))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE data1 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE THIRD COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +limit1 <- range(data1[, 3], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE limit1 ARGUMENT IS NULL -> RANGE OF data1 ARGUMENT HAS BEEN TAKEN: ", paste(fun_round(limit1), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +if(suppressWarnings(any(limit1 %in% c(Inf, -Inf)))){ +tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED LIMIT CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(is.null(midpoint1)){ +midpoint1 <- mean(limit1, na.rm = TRUE) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE midpoint1 ARGUMENT IS NULL -> MEAN OF limit1 ARGUMENT HAS BEEN TAKEN: ", paste(fun_round(midpoint1), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else if(fun_round(midpoint1, 9) != fun_round(mean(limit1), 9)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE midpoint1 ARGUMENT (", fun_round(mean(midpoint1), 9), ") DOES NOT CORRESPOND TO THE MEAN OF THE limit1 ARGUMENT (", fun_round(mean(limit1), 9), "). COLOR SCALE IS NOT LINEAR") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data2)){ +if(all(is.matrix(data2))){ +data2 <- reshape2::melt(data2) # transform a matrix into a data frame with 2 coordinates columns and the third intensity column +} +if(rotate == TRUE){ +data2[, 1] <- rev(data2[, 1]) +} +data2[, 3] <- factor(data2[, 3]) # to converte continuous scale into discrete scale +} +tempo.gg.name <- "gg.indiv.plot." +tempo.gg.count <- 0 # to facilitate debugging +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggplot()) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_raster(data = data1, mapping = ggplot2::aes_string(x = names(data1)[ifelse(rotate == FALSE, 2, 1)], y = names(data1)[ifelse(rotate == FALSE, 1, 2)], fill = names(data1)[3]), show.legend = show.scale)) # show.legend option do not remove the legend, only the aesthetic of the legend (dot, line, etc.) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_gradient2(low = low.color1, high = high.color1, mid = mid.color1, midpoint = midpoint1, limit = limit1, breaks = c(limit1[1], midpoint1, limit1[2]), labels = fun_round(c(limit1[1], midpoint1, limit1[2])), name = legend.name1)) +if( ! is.null(data2)){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_raster(data = data2, mapping = ggplot2::aes_string(x = names(data2)[ifelse(rotate == FALSE, 2, 1)], y = names(data2)[ifelse(rotate == FALSE, 1, 2)], alpha = names(data2)[3]), fill = color2, show.legend = FALSE)) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", values = if(invert2 == FALSE){c(0, alpha2)}else{c(alpha2, 0)}, guide = FALSE)) +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_raster(data = data2, mapping = ggplot2::aes_string(x = names(data2)[ifelse(rotate == FALSE, 2, 1)], y = names(data2)[ifelse(rotate == FALSE, 1, 2)], group = names(data2)[3]), fill = data2[, 3], alpha = alpha2, show.legend = FALSE)) # BEWARE: this does not work if NA present, because geom_raster() has a tendency to complete empty spaces, and thus, behave differently than geom_tile(). See https://github.com/tidyverse/ggplot2/issues/3025 +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_fixed()) # x = y +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_y_reverse()) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) +add.check <- TRUE +if( ! is.null(add)){ # if add is NULL, then = 0 +if(grepl(pattern = "ggplot2::theme", add) == TRUE){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT -> INTERNAL GGPLOT2 THEME FUNCTIONS theme() AND theme_classic() HAVE BEEN INACTIVATED, TO BE USED BY THE USER") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +add.check <- FALSE +} +} +if(add.check == TRUE){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_classic(base_size = text.size)) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme( +text = ggplot2::element_text(size = text.size), +plot.title = ggplot2::element_text(size = title.text.size), # stronger than text +line = ggplot2::element_blank(), +axis.title = ggplot2::element_blank(), +axis.text = ggplot2::element_blank(), +axis.ticks = ggplot2::element_blank(), +panel.background = ggplot2::element_blank() +)) +} +if(plot == TRUE){ +# suppressWarnings( +print(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add})))) +# ) +}else{ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") PLOT NOT SHOWN AS REQUESTED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if(warn.print == TRUE & ! is.null(warn)){ +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) +} +on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) +if(return == TRUE){ +output <- ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + ")))) +output <- output$data +names(output)[1] <- "heatmap" +if( ! is.null(data2)){ +names(output)[2] <- "mask" +} +return(list(data = output, axes = output$layout$panel_params[[1]], scale = c(limit1[1], midpoint1, limit1[2]), warn = warn)) +} +} + + +######## fun_gg_empty_graph() #### text to display for empty graphs + + + + + +fun_gg_empty_graph <- function( +text = NULL, +text.size = 12, +title = NULL, +title.size = 8, +lib.path = NULL +){ +# AIM +# display an empty ggplot2 plot with a text in the middle of the window (for instance to specify that no plot can be drawn) +# ARGUMENTS +# text: character string of the message to display +# text.size: numeric value of the text size (in points) +# title: character string of the graph title +# title.size: numeric value of the title size (in points) +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL +# RETURN +# an empty plot +# REQUIRED PACKAGES +# ggplot2 +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_pack() +# EXAMPLES +### simple example +# fun_gg_empty_graph(text = "NO GRAPH") +### white page +# fun_gg_empty_graph() +### all the arguments +# fun_gg_empty_graph(text = "NO GRAPH", text.size = 8, title = "GRAPH1", title.size = 10, lib.path = NULL) +# DEBUGGING +# text = "NO GRAPH" ; text.size = 12 ; title = "GRAPH1" ; title.size = 8 ; lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_pack", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +if( ! is.null(text)){ +tempo <- fun_check(data = text, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(title)){ +tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = title.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# package checking +fun_pack(req.package = c("ggplot2"), lib.path = lib.path) +# end package checking +# main code +tempo.gg.name <- "gg.indiv.plot." +tempo.gg.count <- 0 +# no need loop part +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggplot()) +if( ! is.null(text)){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text(data = data.frame(x = 1, y = 1, stringsAsFactors = TRUE), ggplot2::aes(x = x, y = y, label = text), size = text.size)) +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_void()) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( +plot.title = ggplot2::element_text(size = title.size) # stronger than text +)) +suppressWarnings(print(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))) +} + + +################ Graphic extraction + + +######## fun_trim() #### display values from a quantitative variable and trim according to defined cut-offs + +# Add name of the variable in the graph +# not max and min for boxplot but 1.5IQR +fun_trim <- function( +data, +displayed.nb = NULL, +single.value.display = FALSE, +trim.method = "", +trim.cutoffs = c(0.05, +0.975), +interval.scale.disp = TRUE, +down.space = 0.75, +left.space = 0.75, +up.space = 0.3, +right.space = 0.25, +orient = 1, +dist.legend = 0.37, +box.type = "l", +amplif.label = 1.25, +amplif.axis = 1.25, +std.x.range = TRUE, +std.y.range = TRUE, +cex.pt = 0.2, +col.box = hsv(0.55, +0.8, +0.8), +x.nb.inter.tick = 4, +y.nb.inter.tick = 0, +tick.length = 1, +sec.tick.length = 0.75, +corner.text = "", +amplif.legend = 1, +corner.text.size = 0.75, +trim.return = FALSE +){ +# AIM +# trim and display values from a numeric vector or matrix +# plot 4 graphs: stripchart of values, stripchart of rank of values, histogram and normal QQPlot +# different kinds of intervals are displayed on the top of graphes to facilitate the analysis of the variable and a trimming setting +# the trimming interval chosen is displayed on top of graphs +# both trimmed and not trimmed values are returned in a list +# ARGUMENTS +# data: values to plot (either a numeric vector or a numeric matrix) +# displayed.nb: number of values displayed. If NULL, all the values are displayed. Otherwise, if the number of values is over displayed.nb, then displayed.nb values are displayed after random selection +# single.value.display: provide the 4 graphs if data is made of a single (potentially repeated value)? If FALSE, an empty graph is displayed if data is made of a single (potentially repeated value). And the return list is made of NULL compartments +# trim.method: Write "" if not required. write "mean.sd" if mean +/- sd has to be displayed as a trimming interval (only recommanded for normal distribution). Write "quantile" to display a trimming interval based on quantile cut-offs. No other possibility allowed. See trim.cutoffs below +# trim.cutoffs: 2 values cutoff for the trimming interval displayed, each value between 0 and 1. Not used if trim.method == "".The couple of values c(lower, upper) represents the lower and upper boundaries of the trimming interval (in proportion), which represent the interval of distribution kept (between 0 and 1). Example: trim.cutoffs = c(0.05, 0.975). What is strictly kept for the display is ]lower , upper[, boundaries excluded. Using the "mean.sd" method, 0.025 and 0.975 represent 95% CI which is mean +/- 1.96 * sd +# interval.scale.disp: display sd and quantiles intervals on top of graphs ? +# down.space: lower vertical margin (in inches, mai argument of par()) +# left.space: left horizontal margin (in inches, mai argument of par()) +# up.space: upper vertical margin between plot region and grapical window (in inches, mai argument of par()) +# right.space: right horizontal margin (in inches, mai argument of par()) +# orient: scale number orientation (las argument of par()). 0, always parallel to the axis; 1, always horizontal; 2, always perpendicular to the axis; 3, always vertical +# dist.legend: numeric value that moves axis legends away in inches (first number of mgp argument of par() but in inches thus / 0.2) +# box.type: bty argument of par(). Either "o", "l", "7", "c", "u", "]", the resulting box resembles the corresponding upper case letter. A value of "n" suppresses the box +# amplif.label: increase or decrease the size of the text in legends +# amplif.axis: increase or decrease the size of the scale numbers in axis +# std.x.range: standard range on the x-axis? TRUE (no range extend) or FALSE (4% range extend). Controls xaxs argument of par() (TRUE is xaxs = "i", FALSE is xaxs = "r") +# std.y.range: standard range on the y-axis? TRUE (no range extend) or FALSE (4% range extend). Controls yaxs argument of par() (TRUE is yaxs = "i", FALSE is yaxs = "r") +# cex.pt: size of points in stripcharts (in inches, thus cex.pt will be thereafter / 0.2) +# col.box: color of boxplot +# x.nb.inter.tick: number of secondary ticks between main ticks on x-axis (only if not log scale). Zero means non secondary ticks +# y.nb.inter.tick: number of secondary ticks between main ticks on y-axis (only if not log scale). Zero means non secondary ticks +# tick.length: length of the ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc. 0 means no tick +# sec.tick.length: length of the secondary ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc., 0 for no ticks) +# corner.text: text to add at the top right corner of the window +# amplif.legend: increase or decrease the size of the text of legend +# corner.text.size: positive numeric. Increase or decrease the size of the text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 +# trim.return: return the trimmed and non trimmed values? NULL returned for trimmed and non trimmed values if trim.method == "" +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# RETURN +# a list containing: +# $trim.method: correspond to trim.method above +# $trim.cutoffs: correspond to trim.cutoffs above +# $real.trim.cutoffs: the two boundary values (in the unit of the numeric vector or numeric matrix analyzed). NULL +# $trimmed.values: the values outside of the trimming interval as defined in trim.cutoffs above +# $kept.values: the values inside the trimming interval as defined in trim.cutoffs above +# EXAMPLES +# fun_trim(data = c(1:100, 1:10), displayed.nb = NULL, single.value.display = FALSE, trim.method = "mean.sd", trim.cutoffs = c(0.05, 0.975), interval.scale.disp = TRUE, down.space = 0.75, left.space = 0.75, up.space = 0.3, right.space = 0.25, orient = 1, dist.legend = 0.37, box.type = "l", amplif.label = 1.25, amplif.axis = 1.25, std.x.range = TRUE, std.y.range = TRUE, cex.pt = 0.2, col.box = hsv(0.55, 0.8, 0.8), x.nb.inter.tick = 4, y.nb.inter.tick = 0, tick.length = 0.5, sec.tick.length = 0.3, corner.text = "", amplif.legend = 1, corner.text.size = 0.75, trim.return = TRUE) +# DEBUGGING +# data = c(1:100, 1:10) ; displayed.nb = NULL ; single.value.display = FALSE ; trim.method = "quantile" ; trim.cutoffs = c(0.05, 0.975) ; interval.scale.disp = TRUE ; down.space = 1 ; left.space = 1 ; up.space = 0.5 ; right.space = 0.25 ; orient = 1 ; dist.legend = 0.5 ; box.type = "l" ; amplif.label = 1 ; amplif.axis = 1 ; std.x.range = TRUE ; std.y.range = TRUE ; cex.pt = 0.1 ; col.box = hsv(0.55, 0.8, 0.8) ; x.nb.inter.tick = 4 ; y.nb.inter.tick = 0 ; tick.length = 0.5 ; sec.tick.length = 0.3 ; corner.text = "" ; amplif.legend = 1 ; corner.text.size = 0.75 ; trim.return = TRUE # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +# argument checking without fun_check() +if( ! (all(class(data) == "numeric") | all(class(data) == "integer") | (all(class(data) %in% c("matrix", "array")) & base::mode(data) == "numeric"))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A NUMERIC VECTOR OR NUMERIC MATRIX") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end argument checking without fun_check() +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +if( ! is.null(displayed.nb)){ +tempo <- fun_check(data = displayed.nb, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if(displayed.nb < 2){ +tempo.cat <- paste0("ERROR IN ", function.name, ": displayed.nb ARGUMENT MUST BE A SINGLE INTEGER VALUE GREATER THAN 1 AND NOT: ", paste(displayed.nb, collapse = " ")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = single.value.display, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = trim.method, options = c("", "mean.sd", "quantile"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = trim.cutoffs, class = "vector", mode = "numeric", length = 2, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = interval.scale.disp, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = down.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = left.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = up.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = right.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = orient, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.type, options = c("o", "l", "7", "c", "u", "]", "n"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = amplif.label, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = amplif.axis, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = std.x.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = std.y.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = cex.pt, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = col.box, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.nb.inter.tick, class = "integer", length = 1, neg.values = FALSE, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.nb.inter.tick, class = "integer", length = 1, neg.values = FALSE, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = sec.tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = corner.text, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = amplif.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = corner.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = trim.return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +if(all(is.na(data) | ! is.finite(data))){ +tempo.cat <- paste0("ERROR IN fun_trim FUNCTION\ndata ARGUMENT CONTAINS ONLY NA OR Inf") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end argument checking +# main code +if(all(class(data)%in% c("matrix", "array"))){ +data <- as.vector(data) +} +na.nb <- NULL +if(any(is.na(data))){ +na.nb <- sum(c(is.na(data))) +data <- data[ ! is.na(data)] +} +color.cut <- hsv(0.75, 1, 1) # color of interval selected +col.mean <- hsv(0.25, 1, 0.8) # color of interval using mean+/-sd +col.quantile <- "orange" # color of interval using quantiles +quantiles.selection <- c(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.975, 0.99) # quantiles used in axis to help for choosing trimming cutoffs +if(single.value.display == FALSE & length(unique(data)) == 1L){ +par(bty = "n", xaxt = "n", yaxt = "n", xpd = TRUE) +plot(1, pch = 16, col = "white", xlab = "", ylab = "") +text(x = 1, y = 1, paste0("No graphic displayed\nBecause data made of a single different value (", formatC(as.double(table(data))), ")"), cex = 2) +output <- list(trim.method = NULL, trim.cutoffs = NULL, real.trim.cutoffs = NULL, trimmed.values = NULL, kept.values = NULL) +}else{ +output <- list(trim.method = trim.method, trim.cutoffs = trim.cutoffs, real.trim.cutoffs = NULL, trimmed.values = NULL, kept.values = NULL) +fun.rug <- function(sec.tick.length.f = sec.tick.length, x.nb.inter.tick.f = x.nb.inter.tick, y.nb.inter.tick.f = y.nb.inter.tick){ +if(x.nb.inter.tick.f > 0){ +inter.tick.unit <- (par("xaxp")[2] - par("xaxp")[1]) / par("xaxp")[3] +par.ini <- par()[c("xpd", "tcl")] +par(xpd = FALSE) +par(tcl = -par()$mgp[2] * sec.tick.length.f) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) +suppressWarnings(rug(seq(par("xaxp")[1] - 10 * inter.tick.unit, par("xaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + x.nb.inter.tick.f)), ticksize = NA, side = 1)) # ticksize = NA to allow the use of par()$tcl value +par(par.ini) +rm(par.ini) +} +if(y.nb.inter.tick.f > 0){ +inter.tick.unit <- (par("yaxp")[2] - par("yaxp")[1]) / par("yaxp")[3] +par.ini <- par()[c("xpd", "tcl")] +par(xpd = FALSE) +par(tcl = -par()$mgp[2] * sec.tick.length.f) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) +suppressWarnings(rug(seq(par("yaxp")[1] - 10 * inter.tick.unit, par("yaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + y.nb.inter.tick.f)), ticksize = NA, side = 2)) # ticksize = NA to allow the use of par()$tcl value +par(par.ini) +rm(par.ini) +} +} +fun.add.cut <- function(data.f, trim.method.f = trim.method, trim.cutoffs.f = trim.cutoffs, color.cut.f = color.cut, return.f = FALSE){ +# DEBUGGING +# data.f = data ; trim.method.f = "mean.sd"; trim.cutoffs.f = trim.cutoffs ; color.cut.f = color.cut ; return.f = TRUE +real.trim.cutoffs.f <- NULL +if(trim.method.f != ""){ +data.f <- sort(data.f) +par.ini <- par()$xpd +par(xpd = FALSE) +if(trim.method.f == "mean.sd"){ +real.trim.cutoffs.f <- qnorm(trim.cutoffs.f, mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)) +abline(v = qnorm(trim.cutoffs.f, mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)), col = color.cut.f) +segments(qnorm(trim.cutoffs.f[1], mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)), par()$usr[4] * 0.75, qnorm(trim.cutoffs.f[2], mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)), par()$usr[4] * 0.75, col = color.cut.f) +} +if(trim.method.f == "quantile"){ +real.trim.cutoffs.f <- quantile(data.f, probs = trim.cutoffs.f, type = 7, na.rm = TRUE) +abline(v = quantile(data.f, probs = trim.cutoffs.f, type = 7, na.rm = TRUE), col = color.cut.f) +segments(quantile(data.f, probs = trim.cutoffs.f[1], type = 7, na.rm = TRUE), par()$usr[4] * 0.75, quantile(data.f, probs = trim.cutoffs.f[2], type = 7, na.rm = TRUE), par()$usr[4] * 0.75, col = color.cut.f) +} +par(par.ini) +if(return.f == TRUE){ +trimmed.values.f <- data.f[data.f <= real.trim.cutoffs.f[1] | data.f >= real.trim.cutoffs.f[2]] +kept.values.f <- data.f[data.f > real.trim.cutoffs.f[1] & data.f < real.trim.cutoffs.f[2]] +} +}else{ +real.trim.cutoffs.f <- NULL +trimmed.values.f <- NULL +kept.values.f <- NULL +} +if(return.f == TRUE){ +output <- list(trim.method = trim.method.f, trim.cutoffs = trim.cutoffs.f, real.trim.cutoffs = real.trim.cutoffs.f, trimmed.values = trimmed.values.f, kept.values = kept.values.f) +return(output) +} +} +fun.interval.scale.display <- function(data.f, col.quantile.f = col.quantile, quantiles.selection.f = quantiles.selection, col.mean.f = col.mean){ # intervals on top of graphs +par.ini <- par()[c("mgp", "xpd")] +par(mgp = c(0.25, 0.25, 0), xpd = NA) +axis(side = 3, at = c(par()$usr[1], par()$usr[2]), labels = rep("", 2), col = col.quantile.f, lwd.ticks = 0) +par(xpd = FALSE) +axis(side = 3, at = quantile(as.vector(data.f), probs = quantiles.selection.f, type = 7, na.rm = TRUE), labels = quantiles.selection.f, col.axis = col.quantile.f, col = col.quantile.f) +par(mgp = c(1.75, 1.75, 1.5), xpd = NA) +axis(side = 3, at = c(par()$usr[1], par()$usr[2]), labels = rep("", 2), col = col.mean.f, lwd.ticks = 0) +par(xpd = FALSE) +axis(side = 3, at = m + s * qnorm(quantiles.selection.f), labels = formatC(round(qnorm(quantiles.selection.f), 2)), col.axis = col.mean.f, col = col.mean.f, lwd.ticks = 1) +par(par.ini) +} +zone<-matrix(1:4, ncol=2) +layout(zone) +par(omi = c(0, 0, 1.5, 0), mai = c(down.space, left.space, up.space, right.space), las = orient, mgp = c(dist.legend / 0.2, 0.5, 0), xpd = FALSE, bty= box.type, cex.lab = amplif.label, cex.axis = amplif.axis, xaxs = ifelse(std.x.range, "i", "r"), yaxs = ifelse(std.y.range, "i", "r")) +par(tcl = -par()$mgp[2] * tick.length) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) +if(is.null(displayed.nb)){ +sampled.data <- as.vector(data) +if(corner.text == ""){ +corner.text <- paste0("ALL VALUES OF THE DATASET DISPLAYED") +}else{ +corner.text <- paste0(corner.text, "\nALL VALUES OF THE DATASET DISPLAYED") +} +}else{ +if(length(as.vector(data)) > displayed.nb){ +sampled.data <- sample(as.vector(data), displayed.nb, replace = FALSE) +if(corner.text == ""){ +corner.text <- paste0("WARNING: ONLY ", displayed.nb, " VALUES ARE DISPLAYED AMONG THE ", length(as.vector(data)), " VALUES OF THE DATASET ANALYZED") +}else{ +corner.text <- paste0(corner.text, "\nWARNING: ONLY ", displayed.nb, " VALUES ARE DISPLAYED AMONG THE ", length(as.vector(data)), " VALUES OF THE DATASET ANALYZED") +} +}else{ +sampled.data <- as.vector(data) +if(corner.text == ""){ +corner.text <- paste0("WARNING: THE DISPLAYED NUMBER OF VALUES PARAMETER ", deparse(substitute(displayed.nb)), " HAS BEEN SET TO ", displayed.nb, " WHICH IS ABOVE THE NUMBER OF VALUES OF THE DATASET ANALYZED -> ALL VALUES DISPLAYED") +}else{ +corner.text <- paste0(corner.text, "\nWARNING: THE DISPLAYED NUMBER OF VALUES PARAMETER ", deparse(substitute(displayed.nb)), " HAS BEEN SET TO ", displayed.nb, " WHICH IS ABOVE THE NUMBER OF VALUES OF THE DATASET ANALYZED -> ALL VALUES DISPLAYED") +} +} +} +if( ! is.null(na.nb)){ +if(corner.text == ""){ +corner.text <- paste0("WARNING: NUMBER OF NA REMOVED IS ", na.nb) +}else{ +corner.text <- paste0("WARNING: NUMBER OF NA REMOVED IS ", na.nb) +} +} +stripchart(sampled.data, method="jitter", jitter=0.4, vertical=FALSE, ylim=c(0.5, 1.5), group.names = "", xlab = "Value", ylab="", pch=1, cex = cex.pt / 0.2) +fun.rug(y.nb.inter.tick.f = 0) +boxplot(as.vector(data), horizontal=TRUE, add=TRUE, boxwex = 0.4, staplecol = col.box, whiskcol = col.box, medcol = col.box, boxcol = col.box, range = 0, whisklty = 1) +m <- mean(as.vector(data), na.rm = TRUE) +s <- sd(as.vector(data), na.rm = TRUE) +segments(m, 0.8, m, 1, lwd=2, col="red") # mean +segments(m -1.96 * s, 0.9, m + 1.96 * s, 0.9, lwd=1, col="red") # mean +graph.xlim <- par()$usr[1:2] # for hist() and qqnorm() below +if(interval.scale.disp == TRUE){ +fun.interval.scale.display(data.f = data) +if(corner.text == ""){ +corner.text <- paste0("MULTIPLYING FACTOR DISPLAYED (MEAN +/- SD) ON SCALES: ", paste(formatC(round(qnorm(quantiles.selection), 2))[-(1:(length(quantiles.selection) - 1) / 2)], collapse = ", "), "\nQUANTILES DISPLAYED ON SCALES: ", paste(quantiles.selection, collapse = ", ")) +}else{ +corner.text <- paste0(corner.text, "\nMULTIPLYING FACTOR DISPLAYED (MEAN +/- SD) ON SCALES: ", paste(formatC(round(qnorm(quantiles.selection), 2))[-(1:(length(quantiles.selection) - 1) / 2)], collapse = ", "), "\nQUANTILES DISPLAYED ON SCALES: ", paste(quantiles.selection, collapse = ", ")) +} +} +output.tempo <- fun.add.cut(data.f = data, return.f = TRUE) # to recover real.trim.cutoffs +if(trim.return == TRUE){ +output <- output.tempo +} +par(xpd = NA) +if(trim.method != ""){ +if(corner.text == ""){ +corner.text <- paste0("SELECTED CUT-OFFS (PROPORTION): ", paste(trim.cutoffs, collapse = ", "), "\nSELECTED CUT-OFFS: ", paste(output.tempo$real.trim.cutoffs, collapse = ", ")) +}else{ +corner.text <- paste0(corner.text, "\nSELECTED CUT-OFFS (PROPORTION): ", paste(trim.cutoffs, collapse = ", "), "\nSELECTED CUT-OFFS: ", paste(output.tempo$real.trim.cutoffs, collapse = ", ")) +} +if(interval.scale.disp == TRUE){ +legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- 1.96sd", paste0("Trimming interval: ", paste0(trim.cutoffs, collapse = " , ")), "Mean +/- sd multiplying factor", "Quantile"), yjust = 0, lty=1, col=c(col.box, "red", color.cut, col.mean, col.quantile), bty="n", cex = amplif.legend) +}else{ +legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- 1.96sd", paste0("Trimming interval: ", paste0(trim.cutoffs, collapse = " , "))), yjust = 0, lty=1, col=c(col.box, "red", color.cut), bty="n", cex = amplif.legend, y.intersp=1.25) +} +}else{ +if(interval.scale.disp == TRUE){ +legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- sd", "Mean +/- sd multiplying factor", "Quantile"), yjust = 0, lty=1, col=c(col.box, "red", col.mean, col.quantile), bty="n", cex = amplif.legend) +}else{ +legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- sd"), yjust = 0, lty=1, col=c(col.box, "red"), bty="n", cex = amplif.legend, y.intersp=1.25) +} +} +par(xpd = FALSE, xaxs = ifelse(std.x.range, "i", "r"), yaxs = ifelse(std.y.range, "i", "r")) +hist(as.vector(data), main = "", xlim = graph.xlim, xlab = "Value", ylab="Density", col = grey(0.25)) # removed: breaks = seq(min(as.vector(data), na.rm = TRUE), max(as.vector(data), na.rm = TRUE), length.out = length(as.vector(data)) / 10) +abline(h = par()$usr[3]) +fun.rug() +if(interval.scale.disp == TRUE){ +fun.interval.scale.display(data.f = data) +} +fun.add.cut(data.f = data) +par(xaxs = ifelse(std.x.range, "i", "r")) +stripchart(rank(sampled.data), method="stack", vertical=FALSE, ylim=c(0.99, 1.3), group.names = "", xlab = "Rank of values", ylab="", pch=1, cex = cex.pt / 0.2) +fun.rug(y.nb.inter.tick.f = 0) +x.text <- par("usr")[2] + (par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1]) * (1 - par("plt")[2]) / 2 +y.text <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par()$omd[4] / 2) * ((par("plt")[4] - par("plt")[3])))) * (1 - par("omd")[4])) # BEWARE. Here in "(par()$omd[4] / 2", division by two because there are 2 graphs staked on the y axis, and not one +par(xpd=NA) +text(x = x.text, y = y.text, paste0(corner.text), adj=c(1, 1.1), cex = corner.text.size) # text at the topright corner +par(xpd=FALSE) +par(xaxs = ifelse(std.x.range, "i", "r"), yaxs = ifelse(std.y.range, "i", "r")) +qqnorm(as.vector(sampled.data), main = "", datax = TRUE, ylab = "Value", pch = 1, col = "red", cex = cex.pt / 0.2) +fun.rug() +if(diff(quantile(as.vector(data), probs = c(0.25, 0.75), na.rm = TRUE)) != 0){ # otherwise, error generated +qqline(as.vector(data), datax = TRUE) +} +if(interval.scale.disp == TRUE){ +fun.interval.scale.display(data.f = data) +} +fun.add.cut(data.f = data) +} +if(trim.return == TRUE){ +return(output) +} +} + + +######## fun_segmentation() #### segment a dot cloud on a scatterplot and define the dots from another cloud outside the segmentation + + +fun_segmentation <- function( +data1, +x1, +y1, +x.range.split = NULL, +x.step.factor = 10, +y.range.split = NULL, +y.step.factor = 10, +error = 0, +data2 = NULL, +x2, +y2, +data2.pb.dot = "unknown", +xy.cross.kind = "&", +plot = FALSE, +graph.in.file = FALSE, +raster = TRUE, +warn.print = FALSE, +lib.path = NULL +){ +# AIM +# if data1 is a data frame corresponding to the data set of a scatterplot (with a x column for x-axis values and a y column for the y-axis column), then fun_segmentation() delimits a frame around the dots cloud using a sliding window set by x.range.split and x.step.factor to frame the top and bottom part of the cloud, and set by y.range.split and y.step.factor to frame the left and right part of the cloud +# if a second data frame is provided, corresponding to the data set of a scatterplot (with a x column for x-axis values and a y column for the y-axis column), then fun_segmentation() defines the dots of this data frame, outside of the frame of the first data frame +# WARNINGS +# if dots from data2 look significant on the graph (outside the frame) but are not (not black on the last figure), this is probably because the frame is flat on the zero coordinate (no volume inside the frame at this position). Thus, no way to conclude that data2 dots here are significant. These dots are refered to as "unknown". The pb.dot argument deals with such dots +# dots that are sometimes inside and outside the frame, depending on the sliding window, are treated differently: they are removed. Such dots are neither classified as "signif", "non signif" or "unknown", but as "inconsistent" +# unknown dots are treated as finally significant, not significant, or unknown (data2.pb.dot argument) for each x-axis and y-axis separately. Then, the union or intersection of significant dots is performed (argument xy.cross.kind). See the example section +# ARGUMENTS +# data1: a data frame containing a column of x-axis values and a column of y-axis values +# x1: character string of the data1 column name for x-axis (first column of data1 by default) +# y1: character string of the data1 column name for y-axis (second column of data1 by default) +# x.range.split: positive non null numeric value giving the number of interval on the x value range. if x.range is the range of the dots on the x-axis, then abs(diff(x.range) / x.range.split) gives the window size. Window size decreases when range.split increases. In unit of x-axis. Write NULL if not required. At least one of the x.range.split and y.range.split must be non NULL +# x.step.factor: positive non null numeric value giving the shift step of the window. If x.step.factor = 1, no overlap during the sliding (when the window slides from position n to position n+1, no overlap between the two positions). If x.step.factor = 2, 50% of overlap (when the window slides from position n to position n+1, the window on position n+1 overlap 50% of the window when it was on position n) +# y.range.split: same as x.range.split for the y-axis. At least one of the x.range.split and y.range.split must be non NULL +# y.step.factor: same as x.step.factor for the y-axis +# error: proportion (from 0 to 1) of false positives (i.e., proportion of dots from data1 outside of the frame). 0.05 means 5% of the dots from data1 outside of the frame +# data2: a data frame containing a column of x-axis values and a column of y-axis values, for which outside dots of the data1 cloud has to be determined. Write NULL if not required +# x2: character string of the data1 column name for x-axis (first column of data1 by default) +# y2: character string of the data1 column name for y-axis (second column of data1 by default) +# data2.pb.dot: unknown dots are explain in the warning section above. If "signif", then the unknown dots are finally considered as significant (outside the frame). If "not.signif", then the unknown dots are finally considered as non significant (inside the frame). If "unknown", no conclusion are drawn from these dots. See the examples below +# xy.cross.kind: if data2 is non null and if both x.range.split and y.range.split are non null, which dots are finally significants? Write "&" for intersection of outside dots on x and on y. Write "|" for union of outside dots on x and on y. See the examples below +# plot: logical. Print graphs that check the frame? +# graph.in.file: logical. Graphs sent into a graphic device already opened? If FALSE, GUI are opened for each graph. If TRUE, no GUI are opended. The graphs are displayed on the current active graphic device. Ignored if plot is FALSE +# raster: logical. Dots in raster mode? If FALSE, dots from each geom_point from geom argument are in vectorial mode (bigger pdf and long to display if millions of dots). If TRUE, dots from each geom_point from geom argument are in matricial mode (smaller pdf and easy display if millions of dots, but long to generate the layer). If TRUE, the region plot will be square to avoid a bug in fun_gg_point_rast(). If TRUE, solve the transparency problem with some GUI. Not considered if plot is FALSE +# warn.print: logical. Print warnings at the end of the execution? No print if no warning messages +# lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL. Ignored if plot is FALSE +# RETURN +# several graphs if plot is TRUE +# a list containing: +# $data1.removed.row.nb: which rows have been removed due to NA; NaN, -Inf or Inf detection in x1 or y1 columns (NULL if no row removed) +# $data1.removed.rows: removed rows (NULL if no row removed) +# $data2.removed.row.nb: which rows have been removed due to NA; NaN, -Inf or Inf detection in x2 or y2 columns (NULL if no row removed) +# $data2.removed.rows: removed rows (NULL if no row removed) +# $hframe: x and y coordinates of the bottom and top frames for frame plotting (frame1 for the left step and frame2 for the right step) +# $vframe: x and y coordinates of the left and right frames for frame plotting (frame1 for the down step and frame2 for the top step) +# $data1.signif.dot: the significant dots of data1 (i.e., dots outside the frame). A good segmentation should not have any data1.signif.dot +# $data1.non.signif.dot: the non significant dots of data1 (i.e., dots inside the frame) +# $data1.inconsistent.dot: see the warning section above +# $data2.signif.dot: the significant dots of data2 if non NULL (i.e., dots outside the frame) +# $data2.non.signif.dot: the non significant dots of data2 (i.e., dots inside the frame) +# $data2.unknown.dot: the problematic dots of data2 (i.e., data2 dots outside of the range of data1, or data2 dots in a sliding window without data1 dots). Is systematically NULL except if argument data2.pb.dot = "unknown" and some data2 dots are in such situation. Modifying the segmentation x.range.split, x.step.factor, y.range.split, y.step.factor arguments can solve this problem +# $data2.inconsistent.dot: see the warning section above +# $axes: the x-axis and y-axis info +# $warn: the warning messages. Use cat() for proper display. NULL if no warning +# REQUIRED PACKAGES +# ggplot2 if plot is TRUE +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# if plot is TRUE: +# fun_pack() +# fun_open() +# fun_gg_palette() +# fun_gg_scatter() +# fun_gg_empty_graph() +# fun_close() +# EXAMPLES +# example explaining the unknown and inconsistent dots, and the cross + +# set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data1[5:7, 2] <- NA ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; data2[11:13, 1] <- Inf ; set.seed(NULL) ; fun_segmentation(data1 = data1, x1 = names(data1)[1], y1 = names(data1)[2], x.range.split = 20, x.step.factor = 10, y.range.split = 23, y.step.factor = 10, error = 0, data2 = data2, x2 = names(data2)[1], y2 = names(data2)[2], data2.pb.dot = "not.signif", xy.cross.kind = "|", plot = TRUE, graph.in.file = FALSE, raster = FALSE, lib.path = NULL) +# set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; fun_segmentation(data1 = data1, x1 = names(data1)[1], y1 = names(data1)[2], x.range.split = NULL, x.step.factor = 10, y.range.split = 23, y.step.factor = 10, error = 0, data2 = data2, x2 = names(data2)[1], y2 = names(data2)[2], data2.pb.dot = "unknown", xy.cross.kind = "|", plot = TRUE, graph.in.file = FALSE, raster = FALSE, lib.path = NULL) +# set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; fun_segmentation(data1 = data1, x1 = names(data1)[1], y1 = names(data1)[2], x.range.split = 20, x.step.factor = 10, y.range.split = NULL, y.step.factor = 10, error = 0, data2 = data2, x2 = names(data2)[1], y2 = names(data2)[2], data2.pb.dot = "unknown", xy.cross.kind = "&", plot = TRUE, graph.in.file = FALSE, raster = FALSE, lib.path = NULL) +# DEBUGGING +# set.seed(1) ; data1 = data.frame(x = rnorm(50), y = rnorm(50), stringsAsFactors = TRUE) ; data1[5:7, 2] <- NA ; x1 = names(data1)[1] ; y1 = names(data1)[2] ; x.range.split = 5 ; x.step.factor = 10 ; y.range.split = 5 ; y.step.factor = 10 ; error = 0 ; data2 = data.frame(x = rnorm(50, 0, 2), y = rnorm(50, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; x2 = names(data2)[1] ; y2 = names(data2)[2] ; data2.pb.dot = "unknown" ; xy.cross.kind = "|" ; plot = TRUE ; graph.in.file = FALSE ; raster = FALSE ; warn.print = TRUE ; lib.path = NULL +# set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; x1 = names(data1)[1] ; y1 = names(data1)[2] ; x.range.split = 20 ; x.step.factor = 10 ; y.range.split = 23 ; y.step.factor = 10 ; error = 0 ; x2 = names(data2)[1] ; y2 = names(data2)[2] ; data2.pb.dot = "not.signif" ; xy.cross.kind = "|" ; plot = TRUE ; graph.in.file = FALSE ; raster = FALSE ; warn.print = TRUE ; lib.path = NULL +# set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; x1 = names(data1)[1] ; y1 = names(data1)[2] ; x.range.split = 20 ; x.step.factor = 10 ; y.range.split = NULL ; y.step.factor = 10 ; error = 0 ; x2 = names(data2)[1] ; y2 = names(data2)[2] ; data2.pb.dot = "unknown" ; xy.cross.kind = "&" ; plot = TRUE ; graph.in.file = FALSE ; raster = FALSE ; warn.print = TRUE ; lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +ini.warning.length <- options()$warning.length +warn <- NULL +warn.count <- 0 +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data1, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & length(data1) < 2){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A DATA FRAME OF AT LEAST 2 COLUMNS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = x1, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (x1 %in% names(data1))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x1 ARGUMENT MUST BE A COLUMN NAME OF data1") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & x1 %in% names(data1)){ +tempo <- fun_check(data = data1[, x1], data.name = "x1 COLUMN OF data1", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = y1, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (y1 %in% names(data1))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y1 ARGUMENT MUST BE A COLUMN NAME OF data1") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & y1 %in% names(data1)){ +tempo <- fun_check(data = data1[, y1], data.name = "y1 COLUMN OF data1", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +if(is.null(x.range.split) & is.null(y.range.split)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": AT LEAST ONE OF THE x.range.split AND y.range.split ARGUMENTS MUST BE NON NULL") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(x.range.split)){ +tempo <- fun_check(data = x.range.split, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & x.range.split < 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x.range.split ARGUMENT CANNOT BE LOWER THAN 1") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +if( ! is.null(y.range.split)){ +tempo <- fun_check(data = y.range.split, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & y.range.split < 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y.range.split ARGUMENT CANNOT BE LOWER THAN 1") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = x.step.factor, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & x.step.factor < 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x.step.factor ARGUMENT CANNOT BE LOWER THAN 1") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = y.step.factor, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & y.step.factor < 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y.step.factor ARGUMENT CANNOT BE LOWER THAN 1") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = error, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(data2)){ +if(is.null(x2) | is.null(y2)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x2 AND y2 ARGUMENTS CANNOT BE NULL IF data2 ARGUMENT IS NON NULL") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = data2, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & length(data2) < 2){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data2 ARGUMENT MUST BE A DATA FRAME OF AT LEAST 2 COLUMNS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(x2)){ +tempo <- fun_check(data = x2, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (x2 %in% names(data2))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x2 ARGUMENT MUST BE A COLUMN NAME OF data2") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & x2 %in% names(data2)){ +tempo <- fun_check(data = data2[, x2], data.name = "x2 COLUMN OF data2", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +} +if( ! is.null(y2)){ +tempo <- fun_check(data = y2, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & ! (y2 %in% names(data2))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y2 ARGUMENT MUST BE A COLUMN NAME OF data2") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & y2 %in% names(data2)){ +tempo <- fun_check(data = data2[, y2], data.name = "y2 COLUMN OF data2", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) +} +} +} +if( ! is.null(data2)){ +tempo <- fun_check(data = data2.pb.dot, options = c("signif", "not.signif", "unknown"), length = 1, fun.name = function.name) ; eval(ee) +} +if( ! (is.null(x.range.split)) & ! (is.null(y.range.split))){ +tempo <- fun_check(data = xy.cross.kind, options = c("&", "|"), length = 1, fun.name = function.name) ; eval(ee) +} +tempo <- fun_check(data = plot, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & plot == TRUE){ +tempo <- fun_check(data = raster, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = graph.in.file, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & graph.in.file == TRUE & is.null(dev.list())){ +tempo.cat <- paste0("ERROR IN ", function.name, ": \ngraph.in.file PARAMETER SET TO TRUE BUT NO ACTIVE GRAPHIC DEVICE DETECTED") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo$problem == FALSE & graph.in.file == TRUE & ! is.null(dev.list())){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHS PRINTED IN THE CURRENT DEVICE (TYPE ", toupper(names(dev.cur())), ")") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# other required function checking +if(plot == TRUE){ +if(length(utils::find("fun_pack", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_open", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_open() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_gg_palette", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_gg_palette() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_gg_empty_graph", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_gg_empty_graph() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_gg_scatter", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_gg_scatter() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_close", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_close() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end other required function checking +# package checking +if(plot == TRUE){ +fun_pack(req.package = c("ggplot2"), lib.path = lib.path) +} +# end package checking +# main code +# na and Inf detection and removal (done now to be sure of the correct length of categ) +data1.removed.row.nb <- NULL +data1.removed.rows <- NULL +data2.removed.row.nb <- NULL +data2.removed.rows <- NULL +if(any(is.na(data1[, c(x1, y1)])) | any(is.infinite(data1[, x1])) | any(is.infinite(data1[, y1]))){ +tempo.na <- unlist(lapply(lapply(c(data1[c(x1, y1)]), FUN = is.na), FUN = which)) +tempo.inf <- unlist(lapply(lapply(c(data1[c(x1, y1)]), FUN = is.infinite), FUN = which)) +data1.removed.row.nb <- sort(unique(c(tempo.na, tempo.inf))) +if(length(data1.removed.row.nb) > 0){ +data1.removed.rows <- data1[data1.removed.row.nb, ] +} +if(length(data1.removed.row.nb) == nrow(data1)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": AT LEAST ONE NA, NaN, -Inf OR Inf DETECTED IN EACH ROW OF data1. FUNCTION CANNOT BE USED ON EMPTY DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(data1.removed.row.nb) > 0){ +data1 <- data1[-data1.removed.row.nb, ] +} +if(nrow(data1) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x1, y1), collapse = " "), " OF data1 AND CORRESPONDING ROWS REMOVED (SEE $data1.removed.row.nb AND $data1.removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NO NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x1, y1), collapse = " "), " OF data1. NO ROW REMOVED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data2)){ +if(any(is.na(data2[, c(x2, y2)])) | any(is.infinite(data2[, x2])) | any(is.infinite(data2[, y2]))){ +tempo.na <- unlist(lapply(lapply(c(data2[c(x2, y2)]), FUN = is.na), FUN = which)) +tempo.inf <- unlist(lapply(lapply(c(data2[c(x2, y2)]), FUN = is.infinite), FUN = which)) +data2.removed.row.nb <- sort(unique(c(tempo.na, tempo.inf))) +if(length(data2.removed.row.nb) > 0){ +data2.removed.rows <- data2[data2.removed.row.nb, ] +} +if(length(data2.removed.row.nb) == nrow(data2)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": AT LEAST ONE NA, NaN, -Inf OR Inf DETECTED IN EACH ROW OF data2. FUNCTION CANNOT BE USED ON EMPTY DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(data2.removed.row.nb) > 0){ +data2 <- data2[-data2.removed.row.nb, ] +} +if(nrow(data2) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x2, y2), collapse = " "), " OF data2 AND CORRESPONDING ROWS REMOVED (SEE $data2.removed.row.nb AND $data2.removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NO NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x2, y2), collapse = " "), " OF data2. NO ROW REMOVED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end na and Inf detection and removal (done now to be sure of the correct length of categ) +# row annotation (dot number) +# data1 <- data1[ ! duplicated(data1[, c(x1, y1)]), ] # do not remove the dots that have same x and y values, because they will have different dot number -> not the same position on the matrices (so true for symmetric matrices) +data1 <- cbind(data1, DOT_NB = 1:nrow(data1), stringsAsFactors = TRUE) +if( ! is.null(data2)){ +# data2 <- data2[ ! duplicated(data2[, c(x2, y2)]), ] # do not remove the dots that have same x and y values, because they will have different dot number -> not the same position on the matrices (so true for symmetric matrices) +data2 <- cbind(data2, DOT_NB = 1:nrow(data2), stringsAsFactors = TRUE) +} +# end row annotation (dot number) + + + + +# Method using x unit interval +# may be create vector of each column to increase speed +x.data1.l <- NULL # x coord of the y upper and lower limits defined on the data1 cloud for left step line +x.data1.r <- NULL # x coord of the y upper and lower limits defined on the data1 cloud for right step line +y.data1.down.limit.l <- NULL # lower limit of the data1 cloud for left step line +y.data1.top.limit.l <- NULL # upper limit of the data1 cloud for left step line +y.data1.down.limit.r <- NULL # lower limit of the data1 cloud for right step line +y.data1.top.limit.r <- NULL # upper limit of the data1 cloud for left step line +if(any(data1[, x1] %in% c(Inf, -Inf))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE data1 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE x1 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +x.range <- range(data1[, x1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +if(suppressWarnings(any(x.range %in% c(Inf, -Inf)))){ +tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED x.range CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data1[, y1] %in% c(Inf, -Inf))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE data1 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE y1 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +y.range <- range(data1[, y1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +if(suppressWarnings(any(x.range %in% c(Inf, -Inf)))){ +tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED y.range CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +x.range.plot <- range(data1[, x1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +y.range.plot <- range(data1[, y1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +if( ! is.null(data2)){ +if(any(data2[, x2] %in% c(Inf, -Inf))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE data2 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE x2 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +x.range.plot <- range(data1[, x1], data2[, x2], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +if(any(data2[, y2] %in% c(Inf, -Inf))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE data2 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE y2 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +y.range.plot <- range(data1[, y1], data2[, y2], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +} +if(suppressWarnings(any(x.range.plot %in% c(Inf, -Inf)))){ +tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED x.range.plot CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 (AND data2?) ARGUMENTS ARE NA OR Inf ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(suppressWarnings(any(y.range.plot %in% c(Inf, -Inf)))){ +tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED y.range.plot CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 (AND data2?) ARGUMENTS ARE NA OR Inf ONLY") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(x.range.split)){ +# data.frame ordering to slide the window from small to big values + sliding window definition +data1 <- data1[order(data1[, x1], na.last = TRUE), ] +if( ! is.null(data2)){ +data2 <- data2[order(data2[, x2], na.last = TRUE), ] +} +x.win.size <- abs(diff(x.range) / x.range.split) # in unit of x-axis +step <- x.win.size / x.step.factor +# end data.frame ordering to slide the window from small to big values + sliding window definition +# x-axis sliding and y-axis limits of the data1 cloud -> y significant data2 +loop.nb <- ceiling((diff(x.range) - x.win.size) / step) # x.win.size + n * step covers the x range if x.win.size + n * step >= diff(x.range), thus if n >= (diff(x.range) - x.win.size) / step +y.outside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are upper or lower than the frame +y.inside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are not upper or lower than the frame +y.data1.median <- median(data1[, y1], na.rm = TRUE) # will be used for sliding windows without data1 in it +if( ! is.null(data2)){ +y.outside.data2.dot.nb <- integer() # vector that will contain the selected 1D coordinates (i.e., dots) of data2 that are upper or lower than the data1 frame +y.inside.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are not upper or lower than the data1 frame +y.unknown.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are problematic: data2 dots outside of the range of data1, or data2 dots in a sliding window without data1 dots +# recover data2 dots outside the range of data1 +if(any(data2[, x2] < x.range[1])){ +y.unknown.data2.dot.nb <- c(y.unknown.data2.dot.nb, data2$DOT_NB[data2[, x2] < x.range[1]]) +#tempo.warn & indicate the interval +} +if(any(data2[, x2] > x.range[2])){ +y.unknown.data2.dot.nb <- c(y.unknown.data2.dot.nb, data2$DOT_NB[data2[, x2] > x.range[2]]) +#tempo.warn & indicate the interval +} +# end recover data2 dots outside the range of data1 +} +# loop.ini.time <- as.numeric(Sys.time()) +for(i1 in 0:(loop.nb + 1)){ +min.pos <- x.range[1] + step * i1 # lower position of the sliding window in data1 +max.pos <- min.pos + x.win.size # upper position of the sliding window in data1 +x.data1.l <- c(x.data1.l, min.pos, min.pos + step) # min.pos + step to make the steps +x.data1.r <- c(x.data1.r, max.pos, max.pos + step) # max.pos + step to make the steps +x.data1.dot.here <- data1[, x1] >= min.pos & data1[, x1] < max.pos # is there data1 dot present in the sliding window, considering the x axis? +if( ! is.null(data2)){ +x.data2.dot.here <- data2[, x2] >= min.pos & data2[, x2] < max.pos # is there data2 dot present in the sliding window, considering the x axis? +} +# recover the data1 dots outside the frame +if(any(x.data1.dot.here == TRUE)){ +tempo.y.data1.top.limit <- quantile(data1[x.data1.dot.here, y1], probs = 1 - error, na.rm = TRUE) +tempo.y.data1.down.limit <- quantile(data1[x.data1.dot.here, y1], probs = 0 + error, na.rm = TRUE) +y.data1.top.limit.l <- c(y.data1.top.limit.l, tempo.y.data1.top.limit, tempo.y.data1.top.limit) +y.data1.down.limit.l <- c(y.data1.down.limit.l, tempo.y.data1.down.limit, tempo.y.data1.down.limit) +y.data1.top.limit.r <- c(y.data1.top.limit.r, tempo.y.data1.top.limit, tempo.y.data1.top.limit) +y.data1.down.limit.r <- c(y.data1.down.limit.r, tempo.y.data1.down.limit, tempo.y.data1.down.limit) +y.data1.dot.signif <- ( ! ((data1[, y1] <= tempo.y.data1.top.limit) & (data1[, y1] >= tempo.y.data1.down.limit))) & x.data1.dot.here # is there data1 dot present in the sliding window, above or below the data1 limits, considering the y axis? +y.data1.dot.not.signif <- x.data1.dot.here & ! y.data1.dot.signif +y.outside.data1.dot.nb <- c(y.outside.data1.dot.nb, data1$DOT_NB[y.data1.dot.signif]) # recover the row number of data1 +y.outside.data1.dot.nb <- unique(y.outside.data1.dot.nb) +y.inside.data1.dot.nb <- c(y.inside.data1.dot.nb, data1$DOT_NB[y.data1.dot.not.signif]) +y.inside.data1.dot.nb <- unique(y.inside.data1.dot.nb) +}else{ +y.data1.top.limit.l <- c(y.data1.top.limit.l, y.data1.median, y.data1.median) +y.data1.down.limit.l <- c(y.data1.down.limit.l, y.data1.median, y.data1.median) +y.data1.top.limit.r <- c(y.data1.top.limit.r, y.data1.median, y.data1.median) +y.data1.down.limit.r <- c(y.data1.down.limit.r, y.data1.median, y.data1.median) +} +# end recover the data1 dots outside the frame +# recover the data2 dots outside the frame +if( ! is.null(data2)){ +if(any(x.data1.dot.here == TRUE) & any(x.data2.dot.here == TRUE)){ +y.data2.dot.signif <- ( ! ((data2[, y2] <= tempo.y.data1.top.limit) & (data2[, y2] >= tempo.y.data1.down.limit))) & x.data2.dot.here # is there data2 dot present in the sliding window, above or below the data1 limits, considering the y axis? +y.data2.dot.not.signif <- x.data2.dot.here & ! y.data2.dot.signif +y.outside.data2.dot.nb <- c(y.outside.data2.dot.nb, data2$DOT_NB[y.data2.dot.signif]) +y.outside.data2.dot.nb <- unique(y.outside.data2.dot.nb) +y.inside.data2.dot.nb <- c(y.inside.data2.dot.nb, data2$DOT_NB[y.data2.dot.not.signif]) +y.inside.data2.dot.nb <- unique(y.inside.data2.dot.nb) +}else if(any(x.data1.dot.here == FALSE) & any(x.data2.dot.here == TRUE)){ # problem: data2 dots in the the window but no data1 dots to generates the quantiles +y.unknown.data2.dot.nb <- c(y.unknown.data2.dot.nb, data2$DOT_NB[x.data2.dot.here]) +y.unknown.data2.dot.nb <- unique(y.unknown.data2.dot.nb) +#tempo.warn & indicate the interval + + + + +# tempo.warn <- paste0("FROM FUNCTION ", function.name, ": THE [", round(min.pos, 3), " ; ", round(max.pos, 3), "] INTERVAL DOES NOT CONTAIN data1 X VALUES BUT CONTAINS data2 X VALUES WHICH CANNOT BE EVALUATED.\nTHE CONCERNED data2 ROW NUMBERS ARE:\n", paste(which(x.data1.dot.here == FALSE & x.data2.dot.here == TRUE), collapse = "\n")) +# warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end recover the data2 dots outside the frame +# if(any(i1 == seq(1, loop.nb, 500))){ +# loop.fin.time <- as.numeric(Sys.time()) # time of process end +# cat(paste0("COMPUTATION TIME OF LOOP ", i1, " / ", loop.nb, ": ", as.character(lubridate::seconds_to_period(round(loop.fin.time - loop.ini.time))), "\n")) +# } +} +if(max.pos < x.range[2]){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE SLIDING WINDOW HAS NOT REACHED THE MAX VALUE OF data1 ON THE X-AXIS: ", max.pos, " VERSUS ", x.range[2]) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +y.incon.data1.dot.nb.final <- unique(c(y.outside.data1.dot.nb[y.outside.data1.dot.nb %in% y.inside.data1.dot.nb], y.inside.data1.dot.nb[y.inside.data1.dot.nb %in% y.outside.data1.dot.nb])) # inconsistent dots: if a row number of y.inside.data1.dot.nb is present in y.outside.data1.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list +y.outside.data1.dot.nb.final <- y.outside.data1.dot.nb[ ! (y.outside.data1.dot.nb %in% y.incon.data1.dot.nb.final)] # inconsistent dots removed from the outside list +y.inside.data1.dot.nb.final <- y.inside.data1.dot.nb[ ! (y.inside.data1.dot.nb %in% y.incon.data1.dot.nb.final)] # inconsistent dots removed from the inside list +if( ! is.null(data2)){ +# if some unknown dots are also inside, and/or outside, they are put in the inside and/or outside. Ok, because then the intersection between inside and outside is treated -> inconsistent dots +tempo.unknown.out <- y.unknown.data2.dot.nb[y.unknown.data2.dot.nb %in% y.outside.data2.dot.nb] +y.outside.data2.dot.nb <- unique(c(y.outside.data2.dot.nb, tempo.unknown.out)) # if a row number of y.unknown.data2.dot.nb is present in y.outside.data2.dot.nb, it is put into outside +tempo.unknown.in <- y.unknown.data2.dot.nb[y.unknown.data2.dot.nb %in% y.inside.data2.dot.nb] +y.inside.data2.dot.nb <- unique(c(y.inside.data2.dot.nb, tempo.unknown.in)) # if a row number of y.unknown.data2.dot.nb is present in y.inside.data2.dot.nb, it is put into inside +y.unknown.data2.dot.nb.final <- y.unknown.data2.dot.nb[ ! (y.unknown.data2.dot.nb %in% c(y.outside.data2.dot.nb, y.inside.data2.dot.nb))] # then dots also in inside and outside are remove from unknown +y.incon.data2.dot.nb.final <- unique(c(y.outside.data2.dot.nb[y.outside.data2.dot.nb %in% y.inside.data2.dot.nb], y.inside.data2.dot.nb[y.inside.data2.dot.nb %in% y.outside.data2.dot.nb])) # inconsistent dots: if a row number of y.inside.data2.dot.nb is present in y.outside.data2.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list +y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb[ ! (y.outside.data2.dot.nb %in% y.incon.data2.dot.nb.final)] # inconsistent dots removed from the outside list +y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb[ ! (y.inside.data2.dot.nb %in% y.incon.data2.dot.nb.final)] # inconsistent dots removed from the inside list +} +# end x-axis sliding and y-axis limits of the data1 cloud -> y significant data2 +} +# end Method using x unit interval + + + + +# Method using y unit interval +y.data1.d <- NULL # y coord of the x upper and lower limits defined on the data1 cloud for down step line +y.data1.t <- NULL # y coord of the x upper and lower limits defined on the data1 cloud for top step line +x.data1.left.limit.d <- NULL # left limit of the data1 cloud for down step line +x.data1.right.limit.d <- NULL # right limit of the data1 cloud for down step line +x.data1.left.limit.t <- NULL # left limit of the data1 cloud for top step line +x.data1.right.limit.t <- NULL # right limit of the data1 cloud for top step line +if( ! is.null(y.range.split)){ +# data.frame ordering to slide the window from small to big values + sliding window definition +data1 <- data1[order(data1[, y1], na.last = TRUE), ] +if( ! is.null(data2)){ +data2 <- data2[order(data2[, y2], na.last = TRUE), ] +} +y.win.size <- abs(diff(y.range) / y.range.split) # in unit of y-axis +step <- y.win.size / y.step.factor +# end data.frame ordering to slide the window from small to big values + sliding window definition +# y-axis sliding and x-axis limits of the data1 cloud -> x significant data2 +loop.nb <- ceiling((diff(y.range) - y.win.size) / step) # y.win.size + n * step covers the y range if y.win.size + n * step >= diff(y.range), thus if n >= (diff(y.range) - y.win.size) / step +x.outside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are upper or lower than the frame +x.inside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are not upper or lower than the frame +x.data1.median <- median(data1[, x1], na.rm = TRUE) # will be used for sliding window without data1 in it +if( ! is.null(data2)){ +x.outside.data2.dot.nb <- integer() # vector that will contain the selected 1D coordinates (i.e., dots) of data2 that are upper or lower than the data1 frame +x.inside.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are not upper or lower than the data1 frame +x.unknown.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are problematic: data2 dots outside of the range of data1, or data2 dots in a sliding window without data1 dots +# recover data2 dots outside the range of data1 +if(any(data2[, y2] < y.range[1])){ +x.unknown.data2.dot.nb <- c(x.unknown.data2.dot.nb, data2$DOT_NB[data2[, y2] < y.range[1]]) +} +if(any(data2[, y2] > y.range[2])){ +x.unknown.data2.dot.nb <- c(x.unknown.data2.dot.nb, data2$DOT_NB[data2[, y2] > y.range[2]]) +} +# end recover data2 dots outside the range of data1 +} +# loop.ini.time <- as.numeric(Sys.time()) +for(i1 in 0:(loop.nb + 1)){ +min.pos <- y.range[1] + step * i1 # lower position of the sliding window in data1 +max.pos <- min.pos + y.win.size # upper position of the sliding window in data1 +y.data1.d <- c(y.data1.d, min.pos, min.pos + step) # min.pos + step to make the steps +y.data1.t <- c(y.data1.t, max.pos, max.pos + step) # max.pos + step to make the steps +y.data1.dot.here <- data1[, y1] >= min.pos & data1[, y1] < max.pos # is there data1 dot present in the sliding window, considering the y axis? +if( ! is.null(data2)){ +y.data2.dot.here <- data2[, y2] >= min.pos & data2[, y2] < max.pos # is there data2 dot present in the sliding window, considering the y axis? +} +# recover the data1 dots outside the frame +if(any(y.data1.dot.here == TRUE)){ +tempo.x.data1.right.limit <- quantile(data1[y.data1.dot.here, x1], probs = 1 - error, na.rm = TRUE) +tempo.x.data1.left.limit <- quantile(data1[y.data1.dot.here, x1], probs = 0 + error, na.rm = TRUE) +x.data1.right.limit.d <- c(x.data1.right.limit.d, tempo.x.data1.right.limit, tempo.x.data1.right.limit) +x.data1.left.limit.d <- c(x.data1.left.limit.d, tempo.x.data1.left.limit, tempo.x.data1.left.limit) +x.data1.right.limit.t <- c(x.data1.right.limit.t, tempo.x.data1.right.limit, tempo.x.data1.right.limit) +x.data1.left.limit.t <- c(x.data1.left.limit.t, tempo.x.data1.left.limit, tempo.x.data1.left.limit) +x.data1.dot.signif <- ( ! ((data1[, x1] <= tempo.x.data1.right.limit) & (data1[, x1] >= tempo.x.data1.left.limit))) & y.data1.dot.here # is there data2 dot present in the sliding window, above or below the data1 limits, considering the x axis? +x.data1.dot.not.signif <- y.data1.dot.here & ! x.data1.dot.signif +x.outside.data1.dot.nb <- c(x.outside.data1.dot.nb, data1$DOT_NB[x.data1.dot.signif]) # recover the row number of data1 +x.outside.data1.dot.nb <- unique(x.outside.data1.dot.nb) +x.inside.data1.dot.nb <- c(x.inside.data1.dot.nb, data1$DOT_NB[x.data1.dot.not.signif]) +x.inside.data1.dot.nb <- unique(x.inside.data1.dot.nb) +}else{ +x.data1.right.limit.d <- c(x.data1.right.limit.d, x.data1.median, x.data1.median) +x.data1.left.limit.d <- c(x.data1.left.limit.d, x.data1.median, x.data1.median) +x.data1.right.limit.t <- c(x.data1.right.limit.t, x.data1.median, x.data1.median) +x.data1.left.limit.t <- c(x.data1.left.limit.t, x.data1.median, x.data1.median) +} +# end recover the data1 dots outside the frame +# recover the data2 dots outside the frame +if( ! is.null(data2)){ +if(any(y.data1.dot.here == TRUE) & any(y.data2.dot.here == TRUE)){ +x.data2.dot.signif <- ( ! ((data2[, x2] <= tempo.x.data1.right.limit) & (data2[, x2] >= tempo.x.data1.left.limit))) & y.data2.dot.here # is there data2 dot present in the sliding window, above or below the data1 limits, considering the x axis? +x.data2.dot.not.signif <- y.data2.dot.here & ! x.data2.dot.signif +x.outside.data2.dot.nb <- c(x.outside.data2.dot.nb, data2$DOT_NB[x.data2.dot.signif]) +x.outside.data2.dot.nb <- unique(x.outside.data2.dot.nb) +x.inside.data2.dot.nb <- c(x.inside.data2.dot.nb, data2$DOT_NB[x.data2.dot.not.signif]) +x.inside.data2.dot.nb <- unique(x.inside.data2.dot.nb) +}else if(any(y.data1.dot.here == FALSE) & any(y.data2.dot.here == TRUE)){ # recover the data2 dots outside the range of the data1 cloud +x.unknown.data2.dot.nb <- c(x.unknown.data2.dot.nb, data2$DOT_NB[y.data2.dot.here]) +x.unknown.data2.dot.nb <- unique(x.unknown.data2.dot.nb) + + + +# tempo.warn <- paste0("FROM FUNCTION ", function.name, ": THE [", round(min.pos, 3), " ; ", round(max.pos, 3), "] INTERVAL DOES NOT CONTAIN data1 Y VALUES BUT CONTAINS data2 Y VALUES WHICH CANNOT BE EVALUATED.\nTHE CONCERNED data2 ROW NUMBERS ARE:\n", paste(which(y.data1.dot.here == FALSE & y.data2.dot.here == TRUE), collapse = "\n")) +# warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end recover the data2 dots outside the frame +# if(any(i1 == seq(1, loop.nb, 500))){ +# loop.fin.time <- as.numeric(Sys.time()) # time of process end +# cat(paste0("COMPUTATION TIME OF LOOP ", i1, " / ", loop.nb, ": ", as.character(lubridate::seconds_to_period(round(loop.fin.time - loop.ini.time))), "\n")) +# } +} +if(max.pos < y.range[2]){ +tempo.cat <- paste0("ERROR IN ", function.name, ": THE SLIDING WINDOW HAS NOT REACHED THE MAX VALUE OF data1 ON THE Y-AXIS: ", max.pos, " VERSUS ", y.range[2]) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +x.incon.data1.dot.nb.final <- unique(c(x.outside.data1.dot.nb[x.outside.data1.dot.nb %in% x.inside.data1.dot.nb], x.inside.data1.dot.nb[x.inside.data1.dot.nb %in% x.outside.data1.dot.nb])) # inconsistent dots: if a row number of x.inside.data1.dot.nb is present in x.outside.data1.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list +x.outside.data1.dot.nb.final <- x.outside.data1.dot.nb[ ! (x.outside.data1.dot.nb %in% x.incon.data1.dot.nb.final)] # inconsistent dots removed from the outside list +x.inside.data1.dot.nb.final <- x.inside.data1.dot.nb[ ! (x.inside.data1.dot.nb %in% x.incon.data1.dot.nb.final)] # inconsistent dots removed from the inside list +if( ! is.null(data2)){ +# if some unknown dots are also inside, and/or outside, they are put in the inside and/or outside. Ok, because then the intersection between inside and outside is treated -> inconsistent dots +tempo.unknown.out <- x.unknown.data2.dot.nb[x.unknown.data2.dot.nb %in% x.outside.data2.dot.nb] +x.outside.data2.dot.nb <- unique(c(x.outside.data2.dot.nb, tempo.unknown.out)) # if a row number of x.unknown.data2.dot.nb is present in x.outside.data2.dot.nb, it is put into outside +tempo.unknown.in <- x.unknown.data2.dot.nb[x.unknown.data2.dot.nb %in% x.inside.data2.dot.nb] +x.inside.data2.dot.nb <- unique(c(x.inside.data2.dot.nb, tempo.unknown.in)) # if a row number of x.unknown.data2.dot.nb is present in x.inside.data2.dot.nb, it is put into inside +x.unknown.data2.dot.nb.final <- x.unknown.data2.dot.nb[ ! (x.unknown.data2.dot.nb %in% c(x.outside.data2.dot.nb, x.inside.data2.dot.nb))] # then dots also in inside and outside are remove from unknown +x.incon.data2.dot.nb.final <- unique(c(x.outside.data2.dot.nb[x.outside.data2.dot.nb %in% x.inside.data2.dot.nb], x.inside.data2.dot.nb[x.inside.data2.dot.nb %in% x.outside.data2.dot.nb])) # inconsistent dots: if a row number of x.inside.data2.dot.nb is present in x.outside.data2.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list +x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb[ ! (x.outside.data2.dot.nb %in% x.incon.data2.dot.nb.final)] # inconsistent dots removed from the outside list +x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb[ ! (x.inside.data2.dot.nb %in% x.incon.data2.dot.nb.final)] # inconsistent dots removed from the inside list +} +# end y-axis sliding and x-axis limits of the data1 cloud -> x significant data2 +} +# end Method using y unit interval + + + +# recovering the frame coordinates +hframe = rbind( +data.frame( +x = if(is.null(x.data1.l)){NULL}else{x.data1.l}, +y = if(is.null(x.data1.l)){NULL}else{y.data1.down.limit.l}, +kind = if(is.null(x.data1.l)){NULL}else{"down.frame1"}, +stringsAsFactors = TRUE +), +data.frame( +x = if(is.null(x.data1.r)){NULL}else{x.data1.r}, +y = if(is.null(x.data1.r)){NULL}else{y.data1.down.limit.r}, +kind = if(is.null(x.data1.r)){NULL}else{"down.frame2"}, +stringsAsFactors = TRUE +), +data.frame( +x = if(is.null(x.data1.l)){NULL}else{x.data1.l}, +y = if(is.null(x.data1.l)){NULL}else{y.data1.top.limit.l}, +kind = if(is.null(x.data1.l)){NULL}else{"top.frame1"}, +stringsAsFactors = TRUE +), +data.frame( +x = if(is.null(x.data1.r)){NULL}else{x.data1.r}, +y = if(is.null(x.data1.r)){NULL}else{y.data1.top.limit.r}, +kind = if(is.null(x.data1.r)){NULL}else{"top.frame2"}, +stringsAsFactors = TRUE +), +stringsAsFactors = TRUE +) +vframe = rbind( +data.frame( +x = if(is.null(y.data1.d)){NULL}else{x.data1.left.limit.d}, +y = if(is.null(y.data1.d)){NULL}else{y.data1.d}, +kind = if(is.null(y.data1.d)){NULL}else{"left.frame1"}, +stringsAsFactors = TRUE +), +data.frame( +x = if(is.null(y.data1.t)){NULL}else{x.data1.left.limit.t}, +y = if(is.null(y.data1.t)){NULL}else{y.data1.t}, +kind = if(is.null(y.data1.t)){NULL}else{"left.frame2"}, +stringsAsFactors = TRUE +), +data.frame( +x = if(is.null(y.data1.d)){NULL}else{x.data1.right.limit.d}, +y = if(is.null(y.data1.d)){NULL}else{y.data1.d}, +kind = if(is.null(y.data1.d)){NULL}else{"right.frame1"}, +stringsAsFactors = TRUE +), +data.frame( +x = if(is.null(y.data1.t)){NULL}else{x.data1.right.limit.t}, +y = if(is.null(y.data1.t)){NULL}else{y.data1.t}, +kind = if(is.null(y.data1.t)){NULL}else{"right.frame2"}, +stringsAsFactors = TRUE +), +stringsAsFactors = TRUE +) +# end recovering the frame coordinates +# recovering the dot coordinates +data1.signif.dot <- NULL +data1.non.signif.dot <- NULL +data1.incon.dot <- NULL +data2.signif.dot <- NULL +data2.non.signif.dot <- NULL +data2.unknown.dot <- NULL +data2.incon.dot <- NULL +if(( ! is.null(x.range.split)) & ( ! is.null(y.range.split))){ +# inconsistent dots recovery +if(length(unique(c(x.incon.data1.dot.nb.final, y.incon.data1.dot.nb.final))) > 0){ +data1.incon.dot <- data1[data1$DOT_NB %in% unique(c(x.incon.data1.dot.nb.final, y.incon.data1.dot.nb.final)), ] # if a dot in inconsistent in x or y -> classified as inconsistent (so unique() used) +# removal of the inconsistent dot in the other classifications +x.inside.data1.dot.nb.final <- x.inside.data1.dot.nb.final[ ! x.inside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] +y.inside.data1.dot.nb.final <- y.inside.data1.dot.nb.final[ ! y.inside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] +x.outside.data1.dot.nb.final <- x.outside.data1.dot.nb.final[ ! x.outside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] +y.outside.data1.dot.nb.final <- y.outside.data1.dot.nb.final[ ! y.outside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] +x.unknown.data1.dot.nb.final <- x.unknown.data1.dot.nb.final[ ! x.unknown.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] +y.unknown.data1.dot.nb.final <- y.unknown.data1.dot.nb.final[ ! y.unknown.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] +# end removal of the inconsistent dot in the other classifications +} +if( ! is.null(data2)){ +if(length(unique(c(x.incon.data2.dot.nb.final, y.incon.data2.dot.nb.final))) > 0){ +data2.incon.dot <- data2[data2$DOT_NB %in% unique(c(x.incon.data2.dot.nb.final, y.incon.data2.dot.nb.final)), ] +# removal of the inconsistent dot in the other classifications +x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb.final[ ! x.inside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] +y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb.final[ ! y.inside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] +x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb.final[ ! x.outside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] +y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb.final[ ! y.outside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] +x.unknown.data2.dot.nb.final <- x.unknown.data2.dot.nb.final[ ! x.unknown.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] +y.unknown.data2.dot.nb.final <- y.unknown.data2.dot.nb.final[ ! y.unknown.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] +# end removal of the inconsistent dot in the other classifications +} +} +# end inconsistent dots recovery +# unknown dots recovery +if( ! is.null(data2)){ +if(data2.pb.dot == "signif"){ +x.outside.data2.dot.nb.final <- unique(c(x.outside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) +x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb.final[ ! x.inside.data2.dot.nb.final %in% x.unknown.data2.dot.nb.final] # remove x.unknown.data2.dot.nb.final from x.inside.data2.dot.nb.final +y.outside.data2.dot.nb.final <- unique(c(y.outside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) +y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb.final[ ! y.inside.data2.dot.nb.final %in% y.unknown.data2.dot.nb.final] # remove y.unknown.data2.dot.nb.final from y.inside.data2.dot.nb.final +x.unknown.data2.dot.nb.final <- NULL +y.unknown.data2.dot.nb.final <- NULL +data2.unknown.dot <- NULL +}else if(data2.pb.dot == "not.signif"){ +x.inside.data2.dot.nb.final <- unique(c(x.inside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) +x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb.final[ ! x.outside.data2.dot.nb.final %in% x.unknown.data2.dot.nb.final] # remove x.unknown.data2.dot.nb.final from x.outside.data2.dot.nb.final +y.inside.data2.dot.nb.final <- unique(c(y.inside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) +y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb.final[ ! y.outside.data2.dot.nb.final %in% y.unknown.data2.dot.nb.final] # remove y.unknown.data2.dot.nb.final from y.outside.data2.dot.nb.final +x.unknown.data2.dot.nb.final <- NULL +y.unknown.data2.dot.nb.final <- NULL +data2.unknown.dot <- NULL +}else if(data2.pb.dot == "unknown"){ +if(length(unique(c(x.unknown.data2.dot.nb.final, y.unknown.data2.dot.nb.final))) > 0){ +data2.unknown.dot <- data2[data2$DOT_NB %in% unique(c(x.unknown.data2.dot.nb.final, y.unknown.data2.dot.nb.final)), ] # if a dot in unknown in x or y -> classified as unknown (so unique() used) +x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb.final[ ! x.outside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove x.unknown.data2.dot.nb.final from x.outside.data2.dot.nb.final +x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb.final[ ! x.inside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove x.unknown.data2.dot.nb.final from x.inside.data2.dot.nb.final +y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb.final[ ! y.outside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove y.unknown.data2.dot.nb.final from y.outside.data2.dot.nb.final +y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb.final[ ! y.inside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove y.unknown.data2.dot.nb.final from y.inside.data2.dot.nb.final +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 3") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end unknown dots recovery +# sign and non sign dot recovery +if(xy.cross.kind == "|"){ # here the problem is to deal with significant dots depending on x and y. Thus I start with that, recover dots finally non significant in outside and put them in inside (when &), and remove from inside the dots in outside +if(length(unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final))) > 0){ +tempo.outside <- unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final)) # union so unique() used +tempo.inside <- unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)) +tempo.inside <- tempo.inside[ ! tempo.inside %in% tempo.outside] +data1.signif.dot <- data1[data1$DOT_NB %in% tempo.outside, ] +data1.non.signif.dot <- data1[data1$DOT_NB %in% tempo.inside, ] +}else{ +data1.non.signif.dot <- data1[unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it +} +}else if(xy.cross.kind == "&"){ +if(sum(x.outside.data1.dot.nb.final %in% y.outside.data1.dot.nb.final) > 0){ # that is intersection +tempo.outside <- unique(x.outside.data1.dot.nb.final[x.outside.data1.dot.nb.final %in% y.outside.data1.dot.nb.final]) # intersection +tempo.outside.removed <- unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final))[ ! unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final)) %in% tempo.outside] +tempo.inside <- unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)) +data1.signif.dot <- data1[data1$DOT_NB %in% tempo.outside, ] +data1.non.signif.dot <- data1[data1$DOT_NB %in% tempo.inside, ] +}else{ +data1.non.signif.dot <- data1[unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 4") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(data2)){ +if(xy.cross.kind == "|"){ # here the problem is to deal with significant dots depending on x and y. Thus I start with that, recover dots finally non significant in outside and put them in inside (when &), and remove from inside the dots in outside +if(length(unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final))) > 0){ +tempo.outside <- unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final)) # union so unique() used +tempo.inside <- unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)) +tempo.inside <- tempo.inside[ ! tempo.inside %in% tempo.outside] +data2.signif.dot <- data2[data2$DOT_NB %in% tempo.outside, ] +data2.non.signif.dot <- data2[data2$DOT_NB %in% tempo.inside, ] +}else{ +data2.non.signif.dot <- data2[unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it +} +}else if(xy.cross.kind == "&"){ +if(sum(x.outside.data2.dot.nb.final %in% y.outside.data2.dot.nb.final) > 0){ # that is intersection +tempo.outside <- unique(x.outside.data2.dot.nb.final[x.outside.data2.dot.nb.final %in% y.outside.data2.dot.nb.final]) # intersection +tempo.outside.removed <- unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final))[ ! unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final)) %in% tempo.outside] +tempo.inside <- unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)) +data2.signif.dot <- data2[data2$DOT_NB %in% tempo.outside, ] +data2.non.signif.dot <- data2[data2$DOT_NB %in% tempo.inside, ] +}else{ +data2.non.signif.dot <- data2[unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 5") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end sign and non sign dot recovery +}else if(( ! is.null(x.range.split)) & is.null(y.range.split)){ +# inconsistent dots recovery +if(length(y.incon.data1.dot.nb.final) > 0){ +data1.incon.dot <- data1[data1$DOT_NB %in% y.incon.data1.dot.nb.final, ] +} +if( ! is.null(data2)){ +if(length(y.incon.data2.dot.nb.final) > 0){ +data2.incon.dot <- data2[data2$DOT_NB %in% y.incon.data2.dot.nb.final, ] +} +}# end inconsistent dots recovery +# unknown dots recovery +if( ! is.null(data2)){ +if(data2.pb.dot == "signif"){ +y.outside.data2.dot.nb.final <- unique(c(y.outside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) +}else if(data2.pb.dot == "not.signif"){ +y.inside.data2.dot.nb.final <- unique(c(y.inside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) +}else if(data2.pb.dot == "unknown"){ +if(length(y.unknown.data2.dot.nb.final) > 0){ +data2.unknown.dot <- data2[data2$DOT_NB %in% y.unknown.data2.dot.nb.final, ] +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 6") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end unknown dots recovery +# sign and non sign dot recovery +if(length(y.outside.data1.dot.nb.final) > 0){ +data1.signif.dot <- data1[data1$DOT_NB %in% y.outside.data1.dot.nb.final, ] +} +if(length(y.inside.data1.dot.nb.final) > 0){ +data1.non.signif.dot <- data1[data1$DOT_NB %in% y.inside.data1.dot.nb.final, ] +} +if( ! is.null(data2)){ +if(length(y.outside.data2.dot.nb.final) > 0){ +data2.signif.dot <- data2[data2$DOT_NB %in% y.outside.data2.dot.nb.final, ] +} +if(length(y.inside.data2.dot.nb.final) > 0){ +data2.non.signif.dot <- data2[data2$DOT_NB %in% y.inside.data2.dot.nb.final, ] +} +} +# end sign and non sign dot recovery +}else if(is.null(x.range.split) & ( ! is.null(y.range.split))){ +# inconsistent dots recovery +if(length(x.incon.data1.dot.nb.final) > 0){ +data1.incon.dot <- data1[data1$DOT_NB %in% x.incon.data1.dot.nb.final, ] +} +if( ! is.null(data2)){ +if(length(x.incon.data2.dot.nb.final) > 0){ +data2.incon.dot <- data2[data2$DOT_NB %in% x.incon.data2.dot.nb.final, ] +} +}# end inconsistent dots recovery +# unknown dots recovery +if( ! is.null(data2)){ +if(data2.pb.dot == "signif"){ +x.outside.data2.dot.nb.final <- unique(c(x.outside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) +}else if(data2.pb.dot == "not.signif"){ +x.inside.data2.dot.nb.final <- unique(c(x.inside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) +}else if(data2.pb.dot == "unknown"){ +if(length(x.unknown.data2.dot.nb.final) > 0){ +data2.unknown.dot <- data2[data2$DOT_NB %in% x.unknown.data2.dot.nb.final, ] +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 7") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end unknown dots recovery +# sign and non sign dot recovery +if(length(x.outside.data1.dot.nb.final) > 0){ +data1.signif.dot <- data1[data1$DOT_NB %in% x.outside.data1.dot.nb.final, ] +} +if(length(x.inside.data1.dot.nb.final) > 0){ +data1.non.signif.dot <- data1[data1$DOT_NB %in% x.inside.data1.dot.nb.final, ] +} +if( ! is.null(data2)){ +if(length(x.outside.data2.dot.nb.final) > 0){ +data2.signif.dot <- data2[data2$DOT_NB %in% x.outside.data2.dot.nb.final, ] +} +if(length(x.inside.data2.dot.nb.final) > 0){ +data2.non.signif.dot <- data2[data2$DOT_NB %in% x.inside.data2.dot.nb.final, ] +} +} +# end sign and non sign dot recovery +} +# end recovering the dot coordinates +# verif +if(any(data1.signif.dot$DOT_NB %in% data1.non.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", FUNCTION.NAME, ": CODE INCONSISTENCY 8") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data1.non.signif.dot$DOT_NB %in% data1.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", FUNCTION.NAME, ": CODE INCONSISTENCY 9") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data1.signif.dot$DOT_NB %in% data1.incon.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 10") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data1.incon.dot$DOT_NB %in% data1.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 11") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data1.non.signif.dot$DOT_NB %in% data1.incon.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 12") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data1.incon.dot$DOT_NB %in% data1.non.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 13") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(data2)){ +if(any(data2.signif.dot$DOT_NB %in% data2.non.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 14") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.non.signif.dot$DOT_NB %in% data2.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 15") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.signif.dot$DOT_NB %in% data2.unknown.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 16") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.unknown.dot$DOT_NB %in% data2.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 17") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.signif.dot$DOT_NB %in% data2.incon.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 18") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.incon.dot$DOT_NB %in% data2.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 19") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.non.signif.dot$DOT_NB %in% data2.unknown.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 20") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.unknown.dot$DOT_NB %in% data2.non.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 21") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.non.signif.dot$DOT_NB %in% data2.incon.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 22") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.incon.dot$DOT_NB %in% data2.non.signif.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 23") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.unknown.dot$DOT_NB %in% data2.incon.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 24") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(data2.incon.dot$DOT_NB %in% data2.unknown.dot$DOT_NB)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 25") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end verif +# plot +# recovering the axes data whatever plot or not +if(is.null(data2)){ +axes <- fun_gg_scatter(data1 = list(data1), x = list(x1), y = list(y1), categ = list(NULL), color = list(fun_gg_palette(2)[2]), geom = list("geom_point"), alpha = list(0.5), x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, plot = FALSE, return = TRUE)$axes +}else{ +axes <- fun_gg_scatter(data1 = list(data1, data2), x = list(x1, x2), y = list(y1, y2), categ = list(NULL, NULL), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1]), geom = list("geom_point", "geom_point"), alpha = list(0.5, 0.5), x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, plot = FALSE, return = TRUE)$axes +} +# end recovering the axes data whatever plot or not +if(plot == TRUE){ +# add a categ for plot legend +tempo.df.name <- c("data1", "data1.signif.dot", "data1.incon.dot", "data2", "data2.signif.dot", "data2.unknown.dot", "data2.incon.dot") +tempo.class.name <- c("data1", "data1", "data1", "data2", "data2", "data2", "data2") +for(i2 in 1:length(tempo.df.name)){ +if( ! is.null(get(tempo.df.name[i2], env = sys.nframe(), inherit = FALSE))){ +assign(tempo.df.name[i2], data.frame(get(tempo.df.name[i2], env = sys.nframe(), inherit = FALSE), kind = tempo.class.name[i2]), +stringsAsFactors = TRUE) +} +} +# end add a categ for plot legend +if(( ! is.null(x.range.split)) & ( ! is.null(y.range.split))){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, vframe), x = list(x1, "x", "x"), y = list(y1, "y", "y"), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5), title = "DATA1", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data1.signif.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, vframe, data1.signif.dot), x = list(x1, "x", "x", x1), y = list(y1, "y", "y", y1), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME" , "VERT FRAME", "SIGNIF DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), "black"), geom = list("geom_point", "geom_path", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA1 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA1 SIGNIFICANT DOTS") +} +if( ! is.null(data1.incon.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, vframe, data1.incon.dot), x = list(x1, "x", "x", x1), y = list(y1, "y", "y", y1), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME" , "VERT FRAME", "INCONSISTENT DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), fun_gg_palette(7)[6]), geom = list("geom_point", "geom_path", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA1 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1\nINCONSISTENT DOTS", text.size = 8, title = "DATA1 + DATA1 INCONSISTENT DOTS") +} +if( ! is.null(data2)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, hframe , vframe), x = list(x1, x2, "x", "x"), y = list(y1, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data2.signif.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.signif.dot, hframe , vframe), x = list(x1, x2, x2, "x", "x"), y = list(y1, y2, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "SIGNIF DOTS", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], "black", rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS") +} +if( ! is.null(data2.incon.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.incon.dot, hframe , vframe), x = list(x1, x2, x2, "x", "x"), y = list(y1, y2, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "INCONSISTENT DOTS", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[6], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nINCONSISTENT DOTS", text.size = 8, title = "DATA2 + DATA2 INCONSISTENT DOTS") +} +if( ! is.null(data2.unknown.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.unknown.dot, hframe , vframe), x = list(x1, x2, x2, "x", "x"), y = list(y1, y2, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "UNKNOWN DOTS", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[5], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 UNKNOWN DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) + +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nUNKNOWN DOTS", text.size = 12, title = "DATA2 + DATA2 UNKNOWN DOTS") +} +} +}else if(( ! is.null(x.range.split)) & is.null(y.range.split)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe), x = list(x1, "x"), y = list(y1, "y"), categ = list("kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_path"), alpha = list(0.5, 0.5), title = "DATA1", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data1.signif.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, data1.signif.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME", "SIGNIF DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), "black"), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA1 SIGNIFICANT DOTS") +} +if( ! is.null(data1.incon.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, data1.incon.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME", "INCONSISTENT DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), fun_gg_palette(7)[6]), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1\nINCONSISTENT DOTS", text.size = 8, title = "DATA1 + DATA1 INCONSISTENT DOTS") +} +if( ! is.null(data2)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, hframe), x = list(x1, x2, "x"), y = list(y1, y2, "y"), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA2", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data2.signif.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.signif.dot, hframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "SIGNIF DOTS", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], "black", rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS") +} +if( ! is.null(data2.incon.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.incon.dot, hframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "INCONSISTENT DOTS", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[6], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nINCONSISTENT DOTS", text.size = 8, title = "DATA2 + DATA2 INCONSISTENT DOTS") +} +if( ! is.null(data2.unknown.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.unknown.dot, hframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "UNKNOWN DOTS", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[5], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 UNKNOWN DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nUNKNOWN DOTS", text.size = 8, title = "DATA2 + DATA2 UNKNOWN DOTS") +} +} +}else if(is.null(x.range.split) & ( ! is.null(y.range.split))){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, vframe), x = list(x1, "x"), y = list(y1, "y"), categ = list("kind", "kind"), legend.name = list("DATASET", "VERT FRAME"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_path"), alpha = list(0.5, 0.5), title = "DATA1", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data1.signif.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, vframe, data1.signif.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "VERT FRAME", "SIGNIF DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), "black"), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA1 SIGNIFICANT DOTS") +} +if( ! is.null(data1.incon.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, vframe, data1.incon.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "VERT FRAME", "INCONSISTENT DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), fun_gg_palette(7)[6]), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1\nINCONSISTENT DOTS", text.size = 8, title = "DATA1 + DATA1 INCONSISTENT DOTS") +} +if( ! is.null(data2)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, vframe), x = list(x1, x2, "x"), y = list(y1, y2, "y"), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA2", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(data2.signif.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.signif.dot, vframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "SIGNIF DOTS", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], "black", rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS") +} +if( ! is.null(data2.incon.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.incon.dot, vframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "INCONSISTENT DOTS", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[6], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nINCONSISTENT DOTS", text.size = 8, title = "DATA2 + DATA2 INCONSISTENT DOTS") +} +if( ! is.null(data2.unknown.dot)){ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.unknown.dot, vframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "UNKNOWN DOTS", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[5], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 UNKNOWN DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) +if( ! is.null(tempo.graph$warn)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(graph.in.file == FALSE){ +fun_open(pdf = FALSE) +} +fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nUNKNOWN DOTS", text.size = 8, title = "DATA2 + DATA2 UNKNOWN DOTS") +} +} +} +} +# end plot +if(warn.print == TRUE & ! is.null(warn)){ +options(warning.length = 8170) +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) +} +on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) +tempo.list <- list(data1.removed.row.nb = data1.removed.row.nb, data1.removed.rows = data1.removed.rows, data2.removed.row.nb = data2.removed.row.nb, data2.removed.rows = data2.removed.rows, hframe = hframe, vframe = vframe, data1.signif.dot = data1.signif.dot, data1.non.signif.dot = data1.non.signif.dot, data1.inconsistent.dot = data1.incon.dot, data2.signif.dot = data2.signif.dot, data2.non.signif.dot = data2.non.signif.dot, data2.unknown.dot = data2.unknown.dot, data2.inconsistent.dot = data2.incon.dot, axes = axes, warn = warn) +return(tempo.list) +} + + +################ Import + + +######## fun_pack() #### check if R packages are present and import into the working environment + + +fun_pack <- function( +req.package, +load = FALSE, +lib.path = NULL +){ +# AIM +# check if the specified R packages are present in the computer and import them into the working environment +# ARGUMENTS +# req.package: character vector of package names to import +# load: logical. Load the package into the environement (using library())? Interesting if packages are not in default folders or for checking the functions names of packages using search() +# lib.path: optional character vector specifying the absolute pathways of the directories containing some of the listed packages in the req.package argument, if not in the default directories. Ignored if NULL +# RETURN +# nothing +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_pack(req.package = "nopackage") +# fun_pack(req.package = "ggplot2") +# fun_pack(req.package = "ggplot2", lib.path = "blablabla") +# DEBUGGING +# req.package = "ggplot2" ; lib.path = "C:/Program Files/R/R-3.5.1/library" +# req.package = "serpentine" ; lib.path = "C:/users/gael/appdata/roaming/python/python36/site-packages" +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = req.package, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = load, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +if(is.null(lib.path)){ +lib.path <- .libPaths() # .libPaths(new = lib.path) # or .libPaths(new = c(.libPaths(), lib.path)) +}else{ +.libPaths(new = sub(x = lib.path, pattern = "/$|\\\\$", replacement = "")) # .libPaths(new = ) add path to default path. BEWARE: .libPaths() does not support / at the end of a submitted path. Thus check and replace last / or \\ in path +lib.path <- .libPaths() +} +tempo <- NULL +for(i1 in 1:length(req.package)){ +if( ! req.package[i1] %in% rownames(utils::installed.packages(lib.loc = lib.path))){ +tempo <- c(tempo, req.package[i1]) +} +} +if( ! is.null(tempo)){ +tempo.cat <- paste0( +"ERROR IN ", +function.name, +": PACKAGE", +ifelse(length(tempo) == 1L, paste0("\n\n", tempo, "\n\n"), paste0("S\n", paste(tempo, collapse = "\n"), "\n")), +"MUST BE INSTALLED IN", +ifelse(length(lib.path) == 1L, "", " ONE OF THESE FOLDERS"), +":\n", +paste(lib.path, collapse = "\n") +) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(load == TRUE){ +for(i2 in 1:length(req.package)){ +suppressMessages(suppressWarnings(suppressPackageStartupMessages(library(req.package[i2], lib.loc = lib.path, quietly = TRUE, character.only = TRUE)))) +} +} +} + + +######## fun_python_pack() #### check if python packages are present + + +fun_python_pack <- function( +req.package, +python.exec.path = NULL, +lib.path = NULL, +R.lib.path = NULL +){ +# AIM +# check if the specified python packages are present in the computer (no import) +# WARNINGS +# for python 3.7. Previous versions return an error "Error in sys$stdout$flush() : attempt to apply non-function" +# ARGUMENTS +# req.package: character vector of package names to import +# python.exec.path: optional character vector specifying the absolute pathways of the executable python file to use (associated to the packages to use). If NULL, the reticulate::import_from_path() function used in fun_python_pack() seeks for an available version of python.exe, and then uses python_config(python_version, required_module, python_versions). But might not be the correct one for the lib.path parameter specified. Thus, it is recommanded to do not leave NULL, notably when using computing clusters +# lib.path: optional character vector specifying the absolute pathways of the directories containing some of the listed packages in the req.package argument, if not in the default directories +# R.lib.path: absolute path of the reticulate packages, if not in the default folders +# RETURN +# nothing +# REQUIRED PACKAGES +# reticulate +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# fun_pack() +# EXAMPLES +# example of error message +# fun_python_pack(req.package = "nopackage") +# example without error message (require the installation of the python serpentine package from https://github.com/koszullab/serpentine +# fun_python_pack(req.package = "serpentine", python.exec.path = "C:/ProgramData/Anaconda3/python.exe", lib.path = "c:/programdata/anaconda3/lib/site-packages/") +# another example of error message +# fun_python_pack(req.package = "serpentine", lib.path = "blablabla") +# DEBUGGING +# req.package = "serpentine" ; python.exec.path = "C:/ProgramData/Anaconda3/python.exe" ; lib.path = "c:/programdata/anaconda3/lib/site-packages/" ; R.lib.path = NULL +# req.package = "bad" ; lib.path = NULL ; R.lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(utils::find("fun_pack", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = req.package, class = "character", fun.name = function.name) ; eval(ee) +if( ! is.null(python.exec.path)){ +tempo <- fun_check(data = python.exec.path, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(file.exists(python.exec.path))){ # separation to avoid the problem of tempo$problem == FALSE and python.exec.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": FILE PATH INDICATED IN THE python.exec.path ARGUMENT DOES NOT EXISTS:\n", paste(python.exec.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if( ! is.null(R.lib.path)){ +tempo <- fun_check(data = R.lib.path, class = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(R.lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and R.lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE R.lib.path ARGUMENT DOES NOT EXISTS:\n", paste(R.lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# package checking +fun_pack(req.package = "reticulate", lib.path = R.lib.path) +# end package checking +# main code +if(is.null(python.exec.path)){ +python.exec.path <- reticulate::py_run_string(" +import sys ; +path_lib = sys.path +") # python string +python.exec.path <- python.exec.path$path_lib +} +if(is.null(lib.path)){ +lib.path <- reticulate::py_run_string(" +import sys ; +path_lib = sys.path +") # python string +lib.path <- lib.path$path_lib +} +reticulate::use_python(Sys.which(python.exec.path), required = TRUE) # required to avoid the use of erratic python exec by reticulate::import_from_path() +for(i1 in 1:length(req.package)){ +tempo.try <- vector("list", length = length(lib.path)) +for(i2 in 1:length(lib.path)){ +tempo.try[[i2]] <- suppressWarnings(try(reticulate::import_from_path(req.package[i1], path = lib.path[i2]), silent = TRUE)) +tempo.try[[i2]] <- suppressWarnings(try(reticulate::import_from_path(req.package[i1], path = lib.path[i2]), silent = TRUE)) # done twice to avoid the error message about flushing present the first time but not the second time. see https://stackoverflow.com/questions/57357001/reticulate-1-13-error-in-sysstdoutflush-attempt-to-apply-non-function +} +if(all(sapply(tempo.try, FUN = grepl, pattern = "[Ee]rror"))){ +print(tempo.try) +tempo.cat <- paste0("ERROR IN ", function.name, ": PACKAGE ", req.package[i1], " MUST BE INSTALLED IN THE MENTIONNED DIRECTORY:\n", paste(lib.path, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} # else{ +# suppressMessages(suppressWarnings(suppressPackageStartupMessages(assign(req.package[i1], reticulate::import(req.package[i1]))))) # not required because try() already evaluates +# } +} +} + + +################ Print / Exporting results (text & tables) + + +######## fun_report() #### print string or data object into output file + + +# Problem with 1D tables : names over the table not printed. In addition, see how the 2D tables are printed. + +fun_report <- function( +data, +output = "results.txt", +path = "C:/Users/Gael/Desktop/", +overwrite = FALSE, +rownames.kept = FALSE, +vector.cat = FALSE, +noquote = TRUE, +sep = 2 +){ +# AIM +# log file function: print a character string or a data object into a same output file +# ARGUMENTS +# data: object to print in the output file. If NULL, nothing is done, with no warning +# output: name of the output file +# path: location of the output file +# overwrite: (logical) if output file already exists, defines if the printing is appended (default FALSE) or if the output file content is erased before printing (TRUE) +# rownames.kept: (logical) defines whether row names have to be removed or not in small tables (less than length.rows rows) +# vector.cat (logical). If TRUE print a vector of length > 1 using cat() instead of capture.output(). Otherwise (default FALSE) the opposite +# noquote: (logical). If TRUE no quote are present for the characters +# sep: number of separating lines after printed data (must be integer) +# RETURN +# nothing +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_report() +# fun_report(data = 1:3, output = "results.txt", path = "C:/Users/Gael/Desktop", overwrite = TRUE, rownames.kept = FALSE, vector.cat = FALSE, noquote = FALSE, sep = 2) +# DEBUGGING +# data = 1:3 ; output = "results.txt" ; path = "C:/Users/Gael/Desktop" ; overwrite = TRUE ; rownames.kept = FALSE ; vector.cat = FALSE ; noquote = FALSE ; sep = 2 # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# argument checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = output, class = "character", length = 1, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & output == ""){ +tempo.cat <- paste0("ERROR IN ", function.name, ": output ARGUMENT AS \"\" DOES NOT CORRESPOND TO A VALID FILE NAME") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo <- fun_check(data = path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": path ARGUMENT DOES NOT CORRESPOND TO EXISTING DIRECTORY\n", paste(path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = overwrite, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = rownames.kept, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = vector.cat, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = noquote, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = sep, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# the 4 next lines are inactivated but kept because at a time, I might have a problem with data (solved with data = NULL). These 4 lines are just to know how to detect a missing argument. Important here because if data is not provided, print the code of the data function +# arg.user.list <- as.list(match.call(expand.dots = FALSE))[-1] # recover all the arguments provided by the function user (excluding the argument with defaults values not provided by the user. Thus, it is really the list indicated by the user) +# default.arg.list <- formals(fun = sys.function(sys.parent())) # list of all the arguments of the function with their default values (not the values of the user !). It seems that ls() as first line of the function provide the names of the arguments (empty, called, etc., or not) +# arg.without.default.value <- sapply(default.arg.list, is.symbol) & sapply(sapply(default.arg.list, as.character), identical, "") # logical to detect argument without default values (these are typeof "symbol" and class "name" and empty character +# if( ! all(names(default.arg.list)[arg.without.default.value] %in% names(arg.user.list))){ # test that the arguments with no null values are provided by the user +# tempo.cat <- paste0("ERROR IN ", function.name, ": VALUE REQUIRED FOR THESE ARGUMENTS WITH NO DEFAULTS VALUES: ", paste(names(default.arg.list)[arg.without.default.value][ ! names(default.arg.list)[arg.without.default.value] %in% names(arg.user.list)], collapse = " ")) +# stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +# } +# end argument checking +# main code +if( ! is.null(data)){ +if(all(class(data) == "data.frame") | all(class(data) == "table") | all(class(data) %in% c("matrix", "array"))){ # before R4.0.0, it was all(class(data) %in% c("matrix", "data.frame", "table")) +if(rownames.kept == FALSE & all(class(data) == "data.frame") & nrow(data) != 0 & nrow(data) <= 4){ # for data frames with nrows <= 4 +rownames.output.tables <- "" +length.rows <- nrow(data) +for(i in 1:length.rows){ # replace the rownames of the first 4 rows by increasing number of spaces (because identical row names not allowed in data frames). This method cannot be extended to more rows as the printed data frame is shifted on the right because of "big empty rownames" +rownames.output.tables <- c(rownames.output.tables, paste0(rownames.output.tables[i]," ", collapse="")) +} +row.names(data) <- rownames.output.tables[1:length.rows] +}else if(rownames.kept == FALSE & (all(class(data) == "table") | all(class(data) %in% c("matrix", "array")))){ # before R4.0.0, it was & all(class(data) %in% c("matrix", "table")) +rownames(data) <- rep("", nrow(data)) # identical row names allowed in matrices and tables +} +if(noquote == TRUE){ +utils::capture.output(noquote(data), file=paste0(path, "/", output), append = ! overwrite) +}else{ +utils::capture.output(data, file=paste0(path, "/", output), append = ! overwrite) +} +}else if(is.vector(data) & all(class(data) != "list") & (length(data) == 1L | vector.cat == TRUE)){ +if(noquote == TRUE){ +cat(noquote(data), file= paste0(path, "/", output), append = ! overwrite) +}else{ +cat(data, file= paste0(path, "/", output), append = ! overwrite) +} +}else if(all(base::mode(data) == "character")){ # characters (array, list, factor or vector with vector.cat = FALSE) +if(noquote == TRUE){ +utils::capture.output(noquote(data), file=paste0(path, "/", output), append = ! overwrite) +}else{ +utils::capture.output(data, file=paste0(path, "/", output), append = ! overwrite) +} +}else{ # other object (S4 for instance, which do not like noquote() +utils::capture.output(data, file=paste0(path, "/", output), append = ! overwrite) +} +sep.final <- paste0(rep("\n", sep), collapse = "") +write(sep.final, file= paste0(path, "/", output), append = TRUE) # add a sep +} +} + + +######## fun_get_message() #### return error/warning/other messages of an expression (that can be exported) + + +fun_get_message <- function( +data, +kind = "error", +header = TRUE, +print.no = FALSE, +text = NULL, +env = NULL +){ +# AIM +# evaluate an instruction written between "" and return the first of the error, or warning or standard (non error non warning) messages if ever exist +# using argument print.no = FALSE, return NULL if no message, which is convenient in some cases +# WARNINGS +# Only the first message is returned +# Always use the env argument when fun_get_message() is used inside functions +# The function does not prevent printing if print() is used inside the instruction tested. To prevent that, use tempo <- capture.output(error <- fun_get_message(data = "fun_check(data = 'a', class = mean, neg.values = FALSE, print = TRUE)")). The return of fun_get_message() is assigned into error and the printed messages are captured by capture.output() and assigned into tempo. See the examples +# ARGUMENTS +# data: character string to evaluate +# kind: character string. Either "error" to get error messages, or "warning" to get warning messages, or "message" to get non error and non warning messages +# header: logical. Add a header in the returned message? +# print.no: logical. Print a message saying that no message reported? +# text: character string added to the output message (even if no message exists and print.no is TRUE). Inactivated if header is FALSE +# env: the name of an existing environment. NULL if not required +# RETURN +# the message or NULL if no message and print.no is FALSE +# REQUIRED PACKAGES +# none +# REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION +# fun_check() +# EXAMPLES +# fun_get_message(data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)", kind = "error", print.no = TRUE, text = "IN A") +# fun_get_message(data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)", kind = "warning", print.no = TRUE, text = "IN A") +# fun_get_message(data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)", kind = "message", print.no = TRUE, text = "IN A") +# fun_get_message(data = "wilcox.test()", kind = "error", print.no = TRUE, text = "IN A") +# fun_get_message(data = "sum(1)", kind = "error", print.no = TRUE, text = "IN A") +# fun_get_message(data = "message('ahah')", kind = "error", print.no = TRUE, text = "IN A") +# fun_get_message(data = "message('ahah')", kind = "message", print.no = TRUE, text = "IN A") +# fun_get_message(data = "ggplot2::ggplot(data = data.frame(X = 1:10, stringsAsFactors = TRUE), mapping = ggplot2::aes(x = X)) + ggplot2::geom_histogram()", kind = "message", print.no = TRUE, text = "IN FUNCTION 1") +# set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun_get_message(data = 'fun_gg_boxplot(data = obs1, y = "Time", categ = "Group1")', kind = "message", print.no = TRUE, text = "IN FUNCTION 1") +# DEBUGGING +# data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)" ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging +# data = "sum(1)" ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging +# set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; data = 'fun_gg_boxplot(data1 = obs1, y = "Time", categ = "Group1")' ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging +# data = "message('ahah')" ; kind = "error" ; header = TRUE ; print.no = TRUE ; text = "IN A" ; env = NULL +# data = 'ggplot2::ggplot(data = data.frame(X = "a", stringsAsFactors = TRUE), mapping = ggplot2::aes(x = X)) + ggplot2::geom_histogram()' ; kind = "message" ; header = TRUE ; print.no = FALSE ; text = NULL # for function debugging +# data = 'ggplot2::ggplot(data = data.frame(X = "a", stringsAsFactors = TRUE), mapping = ggplot2::aes(x = X)) + ggplot2::geom_histogram()' ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL # for function debugging +# data = "emmeans::emmeans(object = emm.rg, specs = contrast.var)" ; kind = "message" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +# end function name +# required function checking +if(length(utils::find("fun_check", mode = "function")) == 0L){ +tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# no need to use reserved words to avoid bugs, because it is local, and exists("tempo.warning", inherit = FALSE), never use the scope +# argument checking +# argument checking with fun_check() +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data, class = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = kind, options = c("error", "warning", "message"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = print.no, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = header, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(text)){ +tempo <- fun_check(data = text, class = "character", length = 1, fun.name = function.name) ; eval(ee) +} +if( ! is.null(env)){ +tempo <- fun_check(data = env, class = "environment", fun.name = function.name) ; eval(ee) # +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# end argument checking with fun_check() +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument checking +# main code +pdf(file = NULL) # send plots into a NULL file, no pdf file created +window.nb <- dev.cur() +invisible(dev.set(window.nb)) +# last warning cannot be used because suppressWarnings() does not modify last.warning present in the base evironment (created at first warning in a new R session), or warnings() # to reset the warning history : unlockBinding("last.warning", baseenv()) ; assign("last.warning", NULL, envir = baseenv()) +output <- NULL +tempo.error <- try(suppressMessages(suppressWarnings(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env}))), silent = TRUE) # get error message, not warning or messages +if(any(class(tempo.error) %in% c("gg", "ggplot"))){ +tempo.error <- try(suppressMessages(suppressWarnings(ggplot2::ggplot_build(tempo.error))), silent = TRUE)[1] +} +if(exists("tempo.error", inherit = FALSE) == TRUE){ # inherit = FALSE avoid the portee lexical and thus the declared word +if( ! all(class(tempo.error) == "try-error")){ # deal with NULL and S4 objects. Old code: ! (all(class(tempo.error) == "try-error") & any(grepl(x = tempo.error, pattern = "^Error|^error|^ERROR"))) but problem with S4 objects. Old code : if((length(tempo.error) > 0 & ! any(grepl(x = tempo.error, pattern = "^Error|^error|^ERROR"))) | (length(tempo.error) == 0) ){ but problem when tempo.error is a list but added this did not work: | ! all(class(tempo.error) == "character") +tempo.error <- NULL +} +}else{ +tempo.error <- NULL +} +if(kind == "error" & ! is.null(tempo.error)){ # +if(header == TRUE){ +tempo.error[1] <- gsub(x = tempo.error[1], pattern = "^Error i|^error i|^ERROR I", replacement = "I") +output <- paste0("ERROR MESSAGE REPORTED", ifelse(is.null(text), "", " "), text, ":\n", tempo.error[1]) # +}else{ +output <- tempo.error[1] # +} +}else if(kind == "error" & is.null(tempo.error) & print.no == TRUE){ +output <- paste0("NO ERROR MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) +}else if(kind != "error" & ( ! is.null(tempo.error)) & print.no == TRUE){ +output <- paste0("NO ", ifelse(kind == "warning", "WARNING", "STANDARD (NON ERROR AND NON WARNING)"), " MESSAGE BECAUSE OF ERROR MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) +}else if(is.null(tempo.error)){ +fun.warning.capture <- function(expr){ +# from demo(error.catching) typed in the R console, coming from ?tryCatch +# see also http://mazamascience.com/WorkingWithData/?p=912 +# return a character string or NULL +# expr <- wilcox.test.default(c(1, 1, 3), c(1, 2, 4), paired = TRUE) +W <- NULL +w.handler <- function(w){ # warning handler +W <<- w # send to the above env, i.e., the inside of the fun.warning.capture function +invokeRestart("muffleWarning") # here w.handler() muffles all the warnings. See http://romainfrancois.blog.free.fr/index.php?post/2009/05/20/Disable-specific-warnings to muffle specific warnings and print others +} +output <- list( +value = suppressMessages(withCallingHandlers(tryCatch(expr, error = function(e){e}), warning = w.handler)), # BEWARE: w.handler is a function written without (), like in other functions with FUN argument +warning = W # processed by w.handler() +) +return(if(is.null(output$warning)){NULL}else{as.character(output$warning)}) +} +tempo.warn <- fun.warning.capture(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env})) +# warn.options.ini <- options()$warn ; options(warn = 1) ; tempo.warn <- utils::capture.output({tempo <- suppressMessages(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env}))}, type = "message") ; options(warn = warn.options.ini) # this recover warnings not messages and not errors but does not work in all enviroments +tempo.message <- utils::capture.output({ +tempo <- suppressMessages(suppressWarnings(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env}))) +if(any(class(tempo) %in% c("gg", "ggplot"))){ +tempo <- ggplot2::ggplot_build(tempo) +}else{ +tempo <- suppressWarnings(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env})) +} +}, type = "message") # recover messages not warnings and not errors +if(kind == "warning" & ! is.null(tempo.warn)){ +if(length(tempo.warn) > 0){ # to avoid character(0) +if( ! any(sapply(tempo.warn, FUN = "grepl", pattern = "() FUNCTION:$"))){ +tempo.warn <- paste(unique(tempo.warn), collapse = "\n") # if FALSE, means that the tested data is a special function. If TRUE, means that the data is a standard function. In that case, the output of capture.output() is two strings per warning messages: if several warning messages -> identical first string, which is removed in next messages by unique() +}else{ +tempo.warn <- paste(tempo.warn, collapse = "\n") +} +if(header == TRUE){ +if(any(grepl(x = tempo.warn[[1]], pattern = "^simpleWarning i"))){ +tempo.warn[[1]] <- gsub(x = tempo.warn[[1]], pattern = "^Warning i", replacement = "I") +} +if(any(grepl(x = tempo.warn[[1]], pattern = "^Warning i"))){ +tempo.warn[[1]] <- gsub(x = tempo.warn[[1]], pattern = "^Warning i", replacement = "I") +} +output <- paste0("WARNING MESSAGE REPORTED", ifelse(is.null(text), "", " "), text, ":\n", tempo.warn) # +}else{ +output <- tempo.warn # +} +}else{ +if(print.no == TRUE){ +output <- paste0("NO WARNING MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) +} # no need else{} here because output is already NULL at first +} +}else if(kind == "warning" & is.null(tempo.warn) & print.no == TRUE){ +output <- paste0("NO WARNING MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) +}else if(kind == "message" & exists("tempo.message", inherit = FALSE) == TRUE){ # inherit = FALSE avoid the portee lexical and thus the declared word +if(length(tempo.message) > 0){ # if something is returned by capture.ouptput() (only in this env) with a length more than 1 +if(header == TRUE){ +output <- paste0("STANDARD (NON ERROR AND NON WARNING) MESSAGE REPORTED", ifelse(is.null(text), "", " "), text, ":\n", tempo.message) # +}else{ +output <- tempo.message # +} +}else{ +if(print.no == TRUE){ +output <- paste0("NO STANDARD (NON ERROR AND NON WARNING) MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) +} # no need else{} here because output is already NULL at first +} +}else if(kind == "message" & exists("tempo.message", inherit = FALSE) == FALSE & print.no == TRUE){ +output <- paste0("NO STANDARD (NON ERROR AND NON WARNING) MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) +} # no need else{} here because output is already NULL at first +} # no need else{} here because output is already NULL at first +invisible(dev.off(window.nb)) # end send plots into a NULL file +return(output) # do not use cat() because the idea is to reuse the message +} + + + + + +# Error: class order not good when a class is removed due to NA +# Error: line 136 in check 20201126 with add argument +# Solve this: sometimes error messages can be more than the max display (8170). Thus, check every paste0("ERROR IN ", function.name, and trunck the message if to big. In addition, add at the begining of the warning message that it is too long and see the $warn output for complete message. Add also this into fun_scatter +# add dot.shape ? See with available aesthetic layers +# rasterise: https://cran.r-project.org/web/packages/ggrastr/vignettes/Raster_geoms.html +# add horizontal argument and deal any conflict with vertical argument. Start with horizontal = NULL as default. If ! is.null() -> convert vertical if required + +fun_gg_boxplot <- function( +data1, +y, +categ, +categ.class.order = NULL, +categ.color = NULL, +box.legend.name = NULL, +box.fill = FALSE, +box.width = 0.5, +box.space = 0.1, +box.line.size = 0.75, +box.notch = FALSE, +box.alpha = 1, +box.mean = TRUE, +box.whisker.kind = "std", +box.whisker.width = 0, +dot.color = grey(0.25), +dot.categ = NULL, +dot.categ.class.order = NULL, +dot.legend.name = NULL, +dot.tidy = FALSE, +dot.tidy.bin.nb = 50, +dot.jitter = 0.5, +dot.seed = 2, +dot.size = 3, +dot.alpha = 0.5, +dot.border.size = 0.5, +dot.border.color = NULL, +x.lab = NULL, +x.angle = 0, +y.lab = NULL, +y.lim = NULL, +y.log = "no", +y.tick.nb = NULL, +y.second.tick.nb = 1, +y.include.zero = FALSE, +y.top.extra.margin = 0.05, +y.bottom.extra.margin = 0.05, +stat.pos = "top", +stat.mean = FALSE, +stat.size = 4, +stat.dist = 5, +stat.angle = 0, +vertical = TRUE, +text.size = 12, +title = "", +title.text.size = 8, +legend.show = TRUE, +legend.width = 0.5, +article = TRUE, +grid = FALSE, +add = NULL, +return = FALSE, +return.ggplot = FALSE, +return.gtable = TRUE, +plot = TRUE, +warn.print = FALSE, +lib.path = NULL +){ +# AIM +# Plot ggplot2 boxplots + dots + means +# For ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html +# WARNINGS +# Rows containing NA in data1[, c(y, categ)] will be removed before processing, with a warning (see below) +# Hinges are not computed like in the classical boxplot() function of R. See https://ggplot2.tidyverse.org/reference/geom_boxplot.html +# To have a single box, please create a factor column with a single class and specify the name of this column in the categ argument. For a single set of grouped boxes, create a factor column with a single class and specify this column in categ argument as first element (i.e., as categ1, knowing that categ2 must also be specified in this situation). See categ argument below +# The dot.alpha argument can alter the display of the color boxes when using pdf output +# Size arguments (box.line.size, dot.size, dot.border.size, stat.size, text.size and title.text.size) are in mm. See Hadley comment in https://stackoverflow.com/questions/17311917/ggplot2-the-unit-of-size. See also http://sape.inf.usi.ch/quick-reference/ggplot2/size). Unit object are not accepted, but conversion can be used (e.g., grid::convertUnit(grid::unit(0.2, "inches"), "mm", valueOnly = TRUE)) +# Display seems to be done twice on Windows devices (like a blink). However, no double plots on pdf devices. Thus, the blink remains mysterious +# To remove boxes and have only dots, use box.alpha = 0 +# ARGUMENTS +# data1: data frame containing one column of quantitative values (see the y argument below) and one or two columns of categories (see the categ argument below). Duplicated column names are not allowed +# y: character string of the data1 column name for y-axis (column containing numeric values). Numeric values will be split according to the classes of the column names indicated in the categ argument to generate the boxes and will also be used to plot the dots +# categ: vector of character strings of the data1 column name for categories (column of characters or factors). Must be either one or two column names. If a single column name (further referred to as categ1), then one box per class of categ1. If two column names (further referred to as categ1 and categ2), then one box per class of categ2, which form a group of boxes in each class of categ1. WARNING: no empty classes allowed. To have a single box, create a factor column with a single class and specify the name of this column in the categ argument (here, no categ2 in categ argument). For a single set of grouped boxes, create a factor column with a single class and specify this column in categ argument as first element (i.e., as categ1), in addition to the already used category (as categ2 in this situation) +# categ.class.order: list indicating the order of the classes of categ1 and categ2 represented on the boxplot (the first compartment for categ1 and and the second for categ2). If categ.class.order == NULL, classes are represented according to the alphabetical order. Some compartments can be NULL and others not. See the categ argument for categ1 and categ2 description +# categ.color: vector of color character string for box frames (see the categ argument for categ1 and categ2 description) +# If categ.color == NULL, default colors of ggplot2, whatever categ1 and categ2 +# If categ.color is non-null and only categ1 in categ argument, categ.color can be either: +# (1) a single color string. All the boxes will have this color, whatever the number of classes of categ1 +# (2) a vector of string colors, one for each class of categ1. Each color will be associated according to categ.class.order of categ1 +# (3) a vector or factor of string colors, like if it was one of the column of data1 data frame. WARNING: a single color per class of categ1 and a single class of categ1 per color must be respected +# Color functions, like grey(), hsv(), etc., are also accepted +# Positive integers are also accepted instead of character strings, as long as above rules about length are respected. Integers will be processed by fun_gg_palette() using the maximal integer value among all the integers in categ.color (see fun_gg_palette()) +# If categ.color is non-null and categ1 and categ2 are specified, all the rules described above will apply to categ2 instead of categ1 (colors will be determined for boxes inside a group of boxes) +# box.legend.name: character string of the legend title. If box.legend.name is NULL, then box.legend.name <- categ1 if only categ1 is present, and box.legend.name <- categ2 if categ1 and categ2 are present in the categ argument. Write "" if no legend required. See the categ argument for categ1 and categ2 description +# box.fill: logical. Fill the box? If TRUE, the categ.color argument will be used to generate filled boxplots (the box frames being black) as well as filled outlier dots (the dot border being controlled by the dot.border.color argument). If all the dots are plotted (argument dot.color other than NULL), they will be over the boxes. If FALSE, the categ.color argument will be used to color the box frames and the outlier dot borders. If all the dots are plotted, they will be beneath the boxes +# box.width: single numeric value (from 0 to 1) of width of either boxes or group of boxes +# When categ argument has a single categ1 element (i.e., separate boxes. See the categ argument for categ1 and categ2 description), then each class of categ1 is represented by a single box. In that case, box.width argument defines each box width, from 0 (no box width) to 1 (max box width), but also the space between boxes (the code uses 1 - box.width for the box spaces). Of note, xmin and xmax of the fun_gg_boxplot() output report the box boundaries (around x-axis unit 1, 2, 3, etc., for each box) +# When categ argument has a two categ1 and categ2 elements (i.e., grouped boxes), box.width argument defines the width allocated for each set of grouped boxes, from 0 (no group width) to 1 (max group width), but also the space between grouped boxes (the code uses 1 - box.width for the spaces). Of note, xmin and xmax of the fun_gg_boxplot() output report the box boundaries (around x-axis unit 1, 2, 3, etc., for each set of grouped box) +# box.space: single numeric value (from 0 to 1) indicating the box separation inside grouped boxes, when categ argument has a two categ1 and categ2 elements. 0 means no space and 1 means boxes shrunk to a vertical line. Ignored if categ argument has a single categ1 element +# box.line.size: single numeric value of line width of boxes and whiskers in mm +# box.notch: logical. Notched boxplot? It TRUE, display notched boxplot, notches corresponding approximately to the 95% confidence interval of the median (the notch interval is exactly 1.58 x Inter Quartile Range (IQR) / sqrt(n), with n the number of values that made the box). If notch intervals between two boxes do not overlap, it can be interpreted as significant median differences +# box.alpha: single numeric value (from 0 to 1) of box transparency (full transparent to full opaque, respectively). To remove boxplots, use box.alpha = 0 +# box.mean: logical. Add mean value? If TRUE, a diamond-shaped dot, with the horizontal diagonal corresponding to the mean value, is displayed over each boxplot +# box.whisker.kind: range of the whiskers. Either "no" (no whiskers), or "std" (length of each whisker equal to 1.5 x Inter Quartile Range (IQR)), or "max" (length of the whiskers up or down to the most distant dot) +# box.whisker.width: single numeric value (from 0 to 1) of the whisker width, with 0 meaning no whiskers and 1 meaning a width equal to the box width +# dot.color: vector of color character string ruling the dot colors and the dot display. See the example section below for easier understanding of the rules described here +# If NULL, no dots plotted +# If "same", the dots will have the same colors as the respective boxplots +# Otherwise, as in the rule (1), (2) or (3) described in the categ.color argument, except that in the possibility (3), the rule "a single color per class of categ and a single class of categ per color", does not have to be respected (for instance, each dot can have a different color). Colors will also depend on the dot.categ argument. If dot.categ is NULL, then colors will be applied to each class of the last column name specified in categ. If dot.categ is non-NULL, colors will be applied to each class of the column name specified in dot.categ. See examples +# dot.categ: optional single character string of a column name (further referred to as categ3) of the data1 argument. This column of data1 will be used to generate a legend for dots, in addition to the legend for boxes. See the dot.color argument for details about the way the legend is built using the two dot.categ and dot.color arguments. If NULL, no legend created and the colors of dots will depend on dot.color and categ arguments (as explained in the dot.color argument) +# dot.categ.class.order: optional vector of character strings indicating the order of the classes of categ3 (see the dot.categ argument). If dot.categ is non-NULL and dot.categ.class.order is NULL, classes are displayed in the legend according to the alphabetical order. Ignored if dot.categ is NULL +# dot.legend.name: optional character string of the legend title for categ3 (see the dot.categ argument). If dot.legend.name == NULL, dot.categ value is used (name of the column in data1). Write "" if no legend required. Ignored if dot.categ is NULL +# dot.tidy: logical. Nice dot spreading? If TRUE, use the geom_dotplot() function for a nice representation. WARNING: change the true quantitative coordinates of dots (i.e., y-axis values for vertical display) because of binning. Thus, the gain in aestheticism is associated with a loss in precision that can be very important. If FALSE, dots are randomly spread on the qualitative axis, using the dot.jitter argument (see below) keeping the true quantitative coordinates +# dot.tidy.bin.nb: positive integer indicating the number of bins (i.e., nb of separations) of the y.lim range. Each dot will then be put in one of the bin, with a diameter of the width of the bin. In other words, increase the number of bins to have smaller dots. Not considered if dot.tidy is FALSE +# dot.jitter: numeric value (from 0 to 1) of random dot horizontal dispersion (for vertical display), with 0 meaning no dispersion and 1 meaning dispersion in the corresponding box width interval. Not considered if dot.tidy is TRUE +# dot.seed: integer value that set the random seed. Using the same number will generate the same dot jittering. Write NULL to have different jittering each time the same instruction is run. Ignored if dot.tidy is TRUE +# dot.size: numeric value of dot diameter in mm. Not considered if dot.tidy is TRUE +# dot.alpha: numeric value (from 0 to 1) of dot transparency (full transparent to full opaque, respectively) +# dot.border.size: numeric value of border dot width in mm. Write zero for no dot border. If dot.tidy is TRUE, value 0 remove the border and other values leave the border without size control (geom_doplot() feature) +# dot.border.color: single character color string defining the color of the dot border (same color for all the dots, whatever their categories). If dot.border.color == NULL, the border color will be the same as the dot color. A single integer is also accepted instead of a character string, that will be processed by fun_gg_palette() +# x.lab: a character string or expression for x-axis legend. If NULL, character string of categ1 (see the categ argument for categ1 and categ2 description) +# x.angle: integer value of the text angle for the x-axis numbers, using the same rules as in ggplot2. Positive values for counterclockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Negative values for clockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. +# y.lab: a character string or expression for y-axis legend. If NULL, character string of the y argument +# y.lim: 2 numeric values indicating the range of the y-axis. Order matters (for inverted axis). If NULL, the range of the x column name of data1 will be used. +# y.log: either "no", "log2" (values in the y argument column of the data1 data frame will be log2 transformed and y-axis will be log2 scaled) or "log10" (values in the y argument column of the data1 data frame will be log10 transformed and y-axis will be log10 scaled). WARNING: not possible to have horizontal boxes with a log axis, due to a bug in ggplot2 (see https://github.com/tidyverse/ggplot2/issues/881) +# y.tick.nb: approximate number of desired values labeling the y-axis (i.e., main ticks, see the n argument of the the cute::fun_scale() function). If NULL and if y.log is "no", then the number of labeling values is set by ggplot2. If NULL and if y.log is "log2" or "log10", then the number of labeling values corresponds to all the exposant integers in the y.lim range (e.g., 10^1, 10^2 and 10^3, meaning 3 main ticks for y.lim = c(9, 1200)). WARNING: if non-NULL and if y.log is "log2" or "log10", labeling can be difficult to read (e.g., ..., 10^2, 10^2.5, 10^3, ...) +# y.second.tick.nb: number of desired secondary ticks between main ticks. Ignored if y.log is other than "no" (log scale plotted). Use argument return = TRUE and see $plot$y.second.tick.values to have the values associated to secondary ticks. IF NULL, no secondary ticks +# y.include.zero: logical. Does y.lim range include 0? Ignored if y.log is "log2" or "log10" +# y.top.extra.margin: single proportion (between 0 and 1) indicating if extra margins must be added to y.lim. If different from 0, add the range of the axis multiplied by y.top.extra.margin (e.g., abs(y.lim[2] - y.lim[1]) * y.top.extra.margin) to the top of y-axis +# y.bottom.extra.margin: idem as y.top.extra.margin but to the bottom of y-axis +# stat.pos: add the median number above the corresponding box. Either NULL (no number shown), "top" (at the top of the plot region) or "above" (above each box) +# stat.mean: logical. Display mean numbers instead of median numbers? Ignored if stat.pos is NULL +# stat.size: numeric value of the stat font size in mm. Ignored if stat.pos is NULL +# stat.dist: numeric value of the stat distance in percentage of the y-axis range (stat.dist = 5 means move the number displayed at 5% of the y-axis range). Ignored if stat.pos is NULL or "top" +# stat.angle: integer value of the angle of stat, using the same rules as in ggplot2. Positive values for counterclockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Negative values for clockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. +# vertical: logical. Vertical boxes? WARNING: will be automatically set to TRUE if y.log argument is other than "no". Indeed, not possible to have horizontal boxes with a log axis, due to a bug in ggplot2 (see https://github.com/tidyverse/ggplot2/issues/881) +# text.size: numeric value of the font size of the (1) axis numbers, (2) axis labels and (3) texts in the graphic legend (in mm) +# title: character string of the graph title +# title.text.size: numeric value of the title font size in mm +# legend.show: logical. Show legend? Not considered if categ argument is NULL, because this already generate no legend, excepted if legend.width argument is non-NULL. In that specific case (categ is NULL, legend.show is TRUE and legend.width is non-NULL), an empty legend space is created. This can be useful when desiring graphs of exactly the same width, whatever they have legends or not +# legend.width: single proportion (between 0 and 1) indicating the relative width of the legend sector (on the right of the plot) relative to the width of the plot. Value 1 means that the window device width is split in 2, half for the plot and half for the legend. Value 0 means no room for the legend, which will overlay the plot region. Write NULL to inactivate the legend sector. In such case, ggplot2 will manage the room required for the legend display, meaning that the width of the plotting region can vary between graphs, depending on the text in the legend +# article: logical. If TRUE, use an article theme (article like). If FALSE, use a classic related ggplot theme. Use the add argument (e.g., add = "+ggplot2::theme_classic()" for the exact classic ggplot theme +# grid: logical. Draw lines in the background to better read the box values? Not considered if article == FALSE (grid systematically present) +# add: character string allowing to add more ggplot2 features (dots, lines, themes, facet, etc.). Ignored if NULL +# WARNING: (1) the string must start with "+", (2) the string must finish with ")" and (3) each function must be preceded by "ggplot2::". Example: "+ ggplot2::coord_flip() + ggplot2::theme_bw()" +# If the character string contains the "ggplot2::theme" string, then the article argument of fun_gg_boxplot() (see above) is ignored with a warning. In addition, some arguments can be overwritten, like x.angle (check all the arguments) +# Handle the add argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions +# WARNING: the call of objects inside the quotes of add can lead to an error if the name of these objects are some of the fun_gg_boxplot() arguments. Indeed, the function will use the internal argument instead of the global environment object. Example article <- "a" in the working environment and add = '+ ggplot2::ggtitle(article)'. The risk here is to have TRUE as title. To solve this, use add = '+ ggplot2::ggtitle(get("article", envir = .GlobalEnv))' +# return: logical. Return the graph parameters? +# return.ggplot: logical. Return the ggplot object in the output list? Ignored if return argument is FALSE. WARNING: always assign the fun_gg_boxplot() function (e.g., a <- fun_gg_boxplot()) if return.ggplot argument is TRUE, otherwise, double plotting is performed. See $ggplot in the RETURN section below for more details +# return.gtable: logical. Return the ggplot object as gtable of grobs in the output list? Ignored if plot argument is FALSE. Indeed, the graph must be plotted to get the grobs dispositions. See $gtable in the RETURN section below for more details +# plot: logical. Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting +# warn.print: logical. Print warnings at the end of the execution? ? If FALSE, warning messages are never printed, but can still be recovered in the returned list. Some of the warning messages (those delivered by the internal ggplot2 functions) are not apparent when using the argument plot = FALSE +# lib.path: character string indicating the absolute path of the required packages (see below). if NULL, the function will use the R library default folders +# RETURN +# A boxplot if plot argument is TRUE +# A list of the graph info if return argument is TRUE: +# $data: the initial data with graphic information added +# $stat: the graphic statistics (mostly equivalent to ggplot_build()$data[[2]]) +# $removed.row.nb: which rows have been removed due to NA/Inf detection in y and categ columns (NULL if no row removed) +# $removed.rows: removed rows (NULL if no row removed) +# $plot: the graphic box and dot coordinates +# $dots: dot coordinates +# $main.box: coordinates of boxes +# $median: median coordinates +# $sup.whisker: coordinates of top whiskers (y for base and y.end for extremities) +# $inf.whisker: coordinates of bottom whiskers (y for base and y.end for extremities) +# $sup.whisker.edge: coordinates of top whisker edges (x and xend) +# $inf.whisker.edge: coordinates of bottom whisker edges(x and xend) +# $mean: diamond mean coordinates (only if box.mean argument is TRUE) +# $stat.pos: coordinates of stat numbers (only if stat.pos argument is not NULL) +# y.second.tick.positions: coordinates of secondary ticks (only if y.second.tick.nb argument is non-NULL or if y.log argument is different from "no") +# y.second.tick.values: values of secondary ticks. NULL except if y.second.tick.nb argument is non-NULL or if y.log argument is different from "no") +# $panel: the variable names used for the panels (NULL if no panels). WARNING: NA can be present according to ggplot2 upgrade to v3.3.0 +# $axes: the x-axis and y-axis info +# $warn: the warning messages. Use cat() for proper display. NULL if no warning. WARNING: warning messages delivered by the internal ggplot2 functions are not apparent when using the argument plot = FALSE +# $ggplot: ggplot object that can be used for reprint (use print(...$ggplot) or update (use ...$ggplot + ggplot2::...). NULL if return.ggplot argument is FALSE. Of note, a non-NULL $ggplot in the output list is sometimes annoying as the manipulation of this list prints the plot +# $gtable: gtable object that can be used for reprint (use gridExtra::grid.arrange(...$ggplot) or with additionnal grobs (see the grob decomposition in the examples). NULL if return.ggplot argument is FALSE. Contrary to $ggplot, a non-NULL $gtable in the output list is not annoying as the manipulation of this list does not print the plot +# REQUIRED PACKAGES +# ggplot2 +# gridExtra +# lemon (in case of use in the add argument) +# scales +# REQUIRED FUNCTIONS FROM THE cute PACKAGE +# fun_check() +# fun_comp_1d() +# fun_comp_2d() +# fun_gg_just() +# fun_gg_palette() +# fun_inter_ticks() +# fun_name_change() +# fun_pack() +# fun_round() +# fun_scale() +# EXAMPLE +# set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(20, 100, 10), rnorm(20, 200, 50), rnorm(20, 500, 60), rnorm(20, 100, 50)), Categ1 = rep(c("CAT", "DOG"), times = 40), Categ2 = rep(c("A", "B", "C", "D"), each = 20), Color1 = rep(c("coral", "lightblue"), times = 40), Color2 = rep(c("#9F2108", "#306100", "#007479", "#8500C0"), each = 20), stringsAsFactors = TRUE) ; set.seed(NULL) ; fun_gg_boxplot(data1 = obs1, y = "Time", categ = "Categ1") +# see http +# DEBUGGING +# set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Categ1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; set.seed(NULL) ; obs1$Time[1:10] <- NA ; data1 = obs1 ; y = "Time" ; categ = c("Categ1") ; categ.class.order = NULL ; categ.color = NULL ; box.legend.name = NULL ; box.fill = FALSE ; box.width = 0.5 ; box.space = 0.1 ; box.line.size = 0.75 ; box.notch = FALSE ; box.alpha = 1 ; box.mean = TRUE ; box.whisker.kind = "std" ; box.whisker.width = 0 ; dot.color = grey(0.25) ; dot.categ = NULL ; dot.categ.class.order = NULL ; dot.legend.name = NULL ; dot.tidy = FALSE ; dot.tidy.bin.nb = 50 ; dot.jitter = 0.5 ; dot.seed = 2 ; dot.size = 3 ; dot.alpha = 0.5 ; dot.border.size = 0.5 ; dot.border.color = NULL ; x.lab = NULL ; x.angle = 0 ; y.lab = NULL ; y.lim = NULL ; y.log = "no" ; y.tick.nb = NULL ; y.second.tick.nb = 1 ; y.include.zero = FALSE ; y.top.extra.margin = 0.05 ; y.bottom.extra.margin = 0.05 ; stat.pos = "top" ; stat.mean = FALSE ; stat.size = 4 ; stat.dist = 5 ; stat.angle = 0 ; vertical = TRUE ; text.size = 12 ; title = "" ; title.text.size = 8 ; legend.show = TRUE ; legend.width = 0.5 ; article = TRUE ; grid = FALSE ; add = NULL ; return = FALSE ; return.ggplot = FALSE ; return.gtable = TRUE ; plot = TRUE ; warn.print = FALSE ; lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") +arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments +arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +req.function <- c( +"fun_comp_2d", +"fun_gg_just", +"fun_gg_palette", +"fun_name_change", +"fun_pack", +"fun_check", +"fun_round", +"fun_scale", +"fun_inter_ticks" +) +tempo <- NULL +for(i1 in req.function){ +if(length(find(i1, mode = "function")) == 0L){ +tempo <- c(tempo, i1) +} +} +if( ! is.null(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# reserved words to avoid bugs (names of dataframe columns used in this function) +reserved.words <- c("categ.check", "categ.color", "dot.color", "dot.categ", "dot.max", "dot.min", "group", "PANEL", "group.check", "MEAN", "tempo.categ1", "tempo.categ2", "text.max.pos", "text.min.pos", "x", "x.y", "y", "y.check", "y_from.dot.max", "ymax", "tidy_group", "binwidth") +# end reserved words to avoid bugs (used in this function) +# arg with no default values +mandat.args <- c( +"data1", +"y", +"categ" +) +tempo <- eval(parse(text = paste0("missing(", paste0(mandat.args, collapse = ") | missing("), ")"))) +if(any(tempo)){ # normally no NA for missing() output +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", "HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo <- fun_check(data = data1, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = categ, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if( ! is.null(categ.class.order)){ +tempo <- fun_check(data = categ.class.order, class = "list", fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = categ.class.order, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(box.legend.name)){ +tempo <- fun_check(data = box.legend.name, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = box.legend.name, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(categ.color)){ +tempo1 <- fun_check(data = categ.color, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = categ.color, class = "factor", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.check.color <- fun_check(data = categ.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, neg.values = FALSE, fun.name = function.name)$problem +if(tempo.check.color == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(any(categ.color == 0L, na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = categ.color, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = box.fill, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.space, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.line.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.notch, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.alpha, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.mean, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.whisker.kind, options = c("no", "std", "max"), length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = box.whisker.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(dot.color)){ +tempo1 <- fun_check(data = dot.color, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = dot.color, class = "factor", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.check.color <- fun_check(data = dot.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, neg.values = FALSE, fun.name = function.name)$problem +if(tempo.check.color == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(any(dot.color == 0L, na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = dot.color, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(dot.categ)){ +tempo <- fun_check(data = dot.categ, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = dot.categ, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(dot.categ.class.order)){ +tempo <- fun_check(data = dot.categ.class.order, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = dot.categ.class.order, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(dot.legend.name)){ +tempo <- fun_check(data = dot.legend.name, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = dot.legend.name, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = dot.tidy, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = dot.tidy.bin.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if(dot.tidy.bin.nb == 0L){ # length and NA checked above +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.tidy.bin.nb ARGUMENT MUST BE A NON-NULL AND POSITVE INTEGER VALUE") # integer possible because dealt above +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +tempo <- fun_check(data = dot.jitter, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(dot.seed)){ +tempo <- fun_check(data = dot.seed, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = TRUE, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = dot.seed, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = dot.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = dot.alpha, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = dot.border.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +if( ! is.null(dot.border.color)){ +tempo1 <- fun_check(data = dot.border.color, class = "vector", mode = "character", length = 1, fun.name = function.name) +tempo2 <- fun_check(data = dot.border.color, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.border.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR STRING STARTING BY #, OR (2) A COLOR NAME GIVEN BY colors(), OR (3) AN INTEGER VALUE") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +}else if(tempo1$problem == FALSE & tempo2$problem == TRUE){ +if( ! all(dot.border.color %in% colors() | grepl(pattern = "^#", dot.border.color), na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.border.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR STRING STARTING BY #, OR (2) A COLOR NAME GIVEN BY colors(), OR (3) AN INTEGER VALUE") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = dot.border.color, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(x.lab)){ +tempo1 <- fun_check(data = x.lab, class = "expression", length = 1, fun.name = function.name) +tempo2 <- fun_check(data = x.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lab ARGUMENT MUST BE A SINGLE CHARACTER STRING OR EXPRESSION") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = x.lab, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = x.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) +if( ! is.null(y.lab)){ +tempo1 <- fun_check(data = y.lab, class = "expression", length = 1, fun.name = function.name) +tempo2 <- fun_check(data = y.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lab ARGUMENT MUST BE A SINGLE CHARACTER STRING OR EXPRESSION") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.lab, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(y.lim)){ +tempo <- fun_check(data = y.lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if(any(is.infinite(y.lim))){ # normally no NA for is.infinite() output +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.lim, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = y.log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(y.tick.nb)){ +tempo <- fun_check(data = y.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if(y.tick.nb < 0){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.tick.nb ARGUMENT MUST BE A NON NULL POSITIVE INTEGER") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.tick.nb, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(y.second.tick.nb)){ +tempo <- fun_check(data = y.second.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if(y.second.tick.nb <= 0){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.second.tick.nb ARGUMENT MUST BE A NON NULL POSITIVE INTEGER") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.second.tick.nb, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = y.include.zero, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.top.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.bottom.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(stat.pos)){ +tempo <- fun_check(data = stat.pos, options = c("top", "above"), length = 1, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = stat.pos, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = stat.mean, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = stat.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = stat.dist, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = stat.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = vertical, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = legend.show, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(legend.width)){ +tempo <- fun_check(data = legend.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = legend.width, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = article, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = grid, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(add)){ +tempo <- fun_check(data = add, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = add, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return.ggplot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return.gtable, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = plot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path), na.rm = TRUE)){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, "\nDIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = lib.path, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if(any(arg.check) == TRUE){ # normally no NA +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking +# second round of checking and data preparation +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"data1", +"y", +"categ", +"box.fill", +"box.width", +"box.space", +"box.line.size", +"box.notch", +"box.alpha", +"box.mean", +"box.whisker.kind", +"box.whisker.width", +# "dot.color", # inactivated because can be null +"dot.tidy", +"dot.tidy.bin.nb", +"dot.jitter", +# "dot.seed", # inactivated because can be null +"dot.size", +"dot.alpha", +"dot.border.size", +"x.angle", +"y.log", +# "y.second.tick.nb", # inactivated because can be null +"y.include.zero", +"y.top.extra.margin", +"y.bottom.extra.margin", +# "stat.pos", # inactivated because can be null +"stat.mean", +"stat.size", +"stat.dist", +"stat.angle", +"vertical", +"text.size", +"title", +"title.text.size", +"legend.show", +# "legend.width", # inactivated because can be null +"article", +"grid", +"return", +"return.ggplot", +"return.gtable", +"plot", +"warn.print" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){# normally no NA with is.null() +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# code that protects set.seed() in the global environment +# see also Protocol 100-rev0 Parallelization in R.docx +if(exists(".Random.seed", envir = .GlobalEnv)){ # if .Random.seed does not exists, it means that no random operation has been performed yet in any R environment +tempo.random.seed <- .Random.seed +on.exit(assign(".Random.seed", tempo.random.seed, env = .GlobalEnv)) +}else{ +on.exit(set.seed(NULL)) # inactivate seeding -> return to complete randomness +} +set.seed(dot.seed) +# end code that protects set.seed() in the global environment +# warning initiation +ini.warning.length <- options()$warning.length +options(warning.length = 8170) +warn <- NULL +warn.count <- 0 +# end warning initiation +# other checkings +if(any(duplicated(names(data1)), na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nDUPLICATED COLUMN NAMES OF data1 ARGUMENT NOT ALLOWED:\n", paste(names(data1)[duplicated(names(data1))], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! (y %in% names(data1))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny ARGUMENT MUST BE A COLUMN NAME OF data1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +tempo <- fun_check(data = data1[, y], data.name = "y COLUMN OF data1", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) +if(tempo$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny ARGUMENT MUST BE NUMERIC COLUMN IN data1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(length(categ) > 2){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg ARGUMENT CANNOT HAVE MORE THAN 2 COLUMN NAMES OF data1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if( ! all(categ %in% names(data1))){ # all() without na.rm -> ok because categ cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg ARGUMENT MUST BE COLUMN NAMES OF data1. HERE IT IS:\n", paste(categ, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(length(dot.categ) > 1){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT CANNOT HAVE MORE THAN 1 COLUMN NAMES OF data1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if( ! all(dot.categ %in% names(data1))){ # all() without na.rm -> ok because dot.categ cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT MUST BE COLUMN NAMES OF data1. HERE IT IS:\n", paste(dot.categ, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# reserved word checking +if(any(names(data1) %in% reserved.words, na.rm = TRUE)){ +if(any(duplicated(names(data1)), na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nDUPLICATED COLUMN NAMES OF data1 ARGUMENT NOT ALLOWED:\n", paste(names(data1)[duplicated(names(data1))], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(dot.categ)){ +if(dot.categ %in% categ){ +reserved.words <- c(reserved.words, paste0(dot.categ, "_DOT")) # paste0(dot.categ, "_DOT") is added to the reserved words because in such situation, a new column will be added to data1 that is named paste0(dot.categ, "_DOT") +} +} +tempo.output <- fun_name_change(names(data1), reserved.words) +for(i2 in 1:length(tempo.output$ini)){ # a loop to be sure to take the good ones +names(data1)[names(data1) == tempo.output$ini[i2]] <- tempo.output$post[i2] +if(any(y == tempo.output$ini[i2])){ # any() without na.rm -> ok because y cannot be NA (tested above) +y[y == tempo.output$ini[i2]] <- tempo.output$post[i2] +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN y ARGUMENT (COLUMN NAMES OF data1 ARGUMENT),\n", tempo.output$ini[i2], " HAS BEEN REPLACED BY ", tempo.output$post[i2], "\nBECAUSE RISK OF BUG AS SOME NAMES IN y ARGUMENT ARE RESERVED WORD USED BY THE ", function.name, " FUNCTION") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# WARNING: names of y argument potentially replaced +if(any(categ == tempo.output$ini[i2])){ # any() without na.rm -> ok because categ cannot be NA (tested above) +categ[categ == tempo.output$ini[i2]] <- tempo.output$post[i2] +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN categ ARGUMENT (COLUMN NAMES OF data1 ARGUMENT),\n", tempo.output$ini[i2], " HAS BEEN REPLACED BY ", tempo.output$post[i2], "\nBECAUSE RISK OF BUG AS SOME NAMES IN categ ARGUMENT ARE RESERVED WORD USED BY THE ", function.name, " FUNCTION") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# WARNING: names of categ argument potentially replaced +if( ! is.null(dot.categ)){ +if(any(dot.categ == tempo.output$ini[i2])){ # any() without na.rm -> ok because dot.categ cannot be NA (tested above) +dot.categ[dot.categ == tempo.output$ini[i2]] <- tempo.output$post[i2] +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN dot.categ ARGUMENT (COLUMN NAMES OF data1 ARGUMENT),\n", tempo.output$ini[i2], " HAS BEEN REPLACED BY ", tempo.output$post[i2], "\nBECAUSE RISK OF BUG AS SOME NAMES IN dot.categ ARGUMENT ARE RESERVED WORD USED BY THE ", function.name, " FUNCTION") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# WARNING: names of dot.categ argument potentially replaced +} +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") REGARDING COLUMN NAMES REPLACEMENT, THE NAMES\n", paste(tempo.output$ini, collapse = " "), "\nHAVE BEEN REPLACED BY\n", paste(tempo.output$post, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +if( ! (is.null(add) | is.null(tempo.output$ini))){ +if(grepl(x = add, pattern = paste(tempo.output$ini, collapse = "|"))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF COLUMN NAMES OF data1 IN THE add ARGUMENT STRING, THAT CORRESPOND TO RESERVED STRINGS FOR ", function.name, "\nCOLUMN NAMES HAVE TO BE CHANGED\nTHE PROBLEMATIC COLUMN NAMES ARE SOME OF THESE NAMES:\n", paste(tempo.output$ini, collapse = " "), "\nIN THE DATA FRAME OF data1 AND IN THE STRING OF add ARGUMENT, TRY TO REPLACE NAMES BY:\n", paste(tempo.output$post, collapse = " "), "\n\nFOR INFORMATION, THE RESERVED WORDS ARE:\n", paste(reserved.words, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +} +if( ! (is.null(add))){ +if(any(sapply(X = arg.names, FUN = grepl, x = add), na.rm = TRUE)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NAMES OF ", function.name, " ARGUMENTS DETECTED IN THE add STRING:\n", paste(arg.names[sapply(X = arg.names, FUN = grepl, x = add)], collapse = "\n"), "\nRISK OF WRONG OBJECT USAGE INSIDE ", function.name) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end reserved word checking +# verif of add +if( ! is.null(add)){ +if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by + +tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ # +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by ) +tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end verif of add +# management of add containing facet +facet.categ <- NULL +if( ! is.null(add)){ +facet.check <- TRUE +tempo <- unlist(strsplit(x = add, split = "\\s*\\+\\s*(ggplot2|lemon)\\s*::\\s*")) # +tempo <- sub(x = tempo, pattern = "^facet_wrap", replacement = "ggplot2::facet_wrap") +tempo <- sub(x = tempo, pattern = "^facet_grid", replacement = "ggplot2::facet_grid") +tempo <- sub(x = tempo, pattern = "^facet_rep", replacement = "lemon::facet_rep") +if(any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap"), na.rm = TRUE)){ +tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")]))) +facet.categ <- names(tempo1$params$facets) +tempo.text <- "facet_wrap OR facet_rep_wrap" +facet.check <- FALSE +}else if(grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")){ +tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")]))) +facet.categ <- c(names(tempo1$params$rows), names(tempo1$params$cols)) +tempo.text <- "facet_grid OR facet_rep_grid" +facet.check <- FALSE +} +if(facet.check == FALSE & ! all(facet.categ %in% names(data1))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL # all() without na.rm -> ok because facet.categ cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end management of add containing facet +# conversion of categ columns in data1 into factors +for(i1 in 1:length(categ)){ +tempo1 <- fun_check(data = data1[, categ[i1]], data.name = paste0("categ NUMBER ", i1, " OF data1"), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = data1[, categ[i1]], data.name = paste0("categ NUMBER ", i1, " OF data1"), class = "factor", na.contain = TRUE, fun.name = function.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\n", paste0("categ NUMBER ", i1, " OF data1"), " MUST BE A FACTOR OR CHARACTER VECTOR") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(tempo1$problem == FALSE){ # character vector +if(box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN categ NUMBER ", i1, " IN data1, THE CHARACTER COLUMN HAS BEEN CONVERTED TO FACTOR, WITH LEVELS ACCORDING TO THE ALPHABETICAL ORDER") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +data1[, categ[i1]] <- factor(data1[, categ[i1]]) # if already a factor, change nothing, if characters, levels according to alphabetical order +} +# OK: all the categ columns of data1 are factors from here +# end conversion of categ columns in data1 into factors + + + +# management of log scale and Inf removal +if(any(( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf # normally no NA with is.finite0() and is.na() +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") PRESENCE OF -Inf OR Inf VALUES IN THE ", y, " COLUMN OF THE data1 ARGUMENT AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +data1.ini <- data1 # strictly identical to data1 except that in data1 y is log converted if and only if y.log != "no" +if(y.log != "no"){ +tempo1 <- ! is.finite(data1[, y]) # where are initial NA and Inf +data1[, y] <- suppressWarnings(get(y.log)(data1[, y]))# no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +if(any( ! (tempo1 | is.finite(data1[, y])))){ # normally no NA with is.finite +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") LOG CONVERSION INTRODUCED -Inf OR Inf OR NaN VALUES IN THE ", y, " COLUMN OF THE data1 ARGUMENT AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# Inf removal +if(any(( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf # normally no NA with is.finite +removed.row.nb <- which(( ! is.finite(data1[, y])) & ( ! is.na(data1[, y]))) +removed.rows <- data1.ini[removed.row.nb, ] # here data1.ini used to have the y = O rows that will be removed because of Inf creation after log transformation +data1 <- data1[-removed.row.nb, ] # +data1.ini <- data1.ini[-removed.row.nb, ] # +}else{ +removed.row.nb <- NULL +removed.rows <- data.frame(stringsAsFactors = FALSE) +} +# From here, data1 and data.ini have no more Inf +# end Inf removal +if(y.log != "no" & ! is.null(y.lim)){ +if(any(y.lim <= 0)){ # any() without na.rm -> ok because y.lim cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(any( ! is.finite(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}))){ # normally no NA with is.finite +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT RETURNS INF/NA WITH THE y.log ARGUMENT SET TO ", y.log, "\nAS SCALE COMPUTATION IS ", ifelse(y.log == "log10", "log10", "log2"), ":\n", paste(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(y.log != "no" & y.include.zero == TRUE){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") y.log ARGUMENT SET TO ", y.log, " AND y.include.zero ARGUMENT SET TO TRUE -> y.include.zero ARGUMENT RESET TO FALSE BECAUSE 0 VALUE CANNOT BE REPRESENTED IN LOG SCALE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +y.include.zero <- FALSE +} +if(y.log != "no" & vertical == FALSE){ +vertical <- TRUE +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") BECAUSE OF A BUG IN ggplot2, CANNOT FLIP BOXES HORIZONTALLY WITH A Y.LOG SCALE -> vertical ARGUMENT RESET TO TRUE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# end management of log scale and Inf removal +# na detection and removal (done now to be sure of the correct length of categ) +column.check <- unique(c(y, categ, if( ! is.null(dot.color) & ! is.null(dot.categ)){dot.categ}, if( ! is.null(facet.categ)){facet.categ})) # dot.categ because can be a 3rd column of data1, categ.color and dot.color will be tested later +if(any(is.na(data1[, column.check]))){ # data1 used here instead of data1.ini in case of new NaN created by log conversion (neg values) # normally no NA with is.na +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NA DETECTED IN COLUMNS OF data1 AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +for(i2 in 1:length(column.check)){ +if(any(is.na(data1[, column.check[i2]]))){ # normally no NA with is.na +tempo.warn <- paste0("NA REMOVAL DUE TO COLUMN ", column.check[i2], " OF data1") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n", tempo.warn))) +} +} +tempo <- unique(unlist(lapply(lapply(c(data1[column.check]), FUN = is.na), FUN = which))) +removed.row.nb <- c(removed.row.nb, tempo) # removed.row.nb created to remove Inf +removed.rows <- rbind(removed.rows, data1.ini[tempo, ], stringsAsFactors = FALSE) # here data1.ini used to have the non NA rows that will be removed because of NAN creation after log transformation (neg values for instance) +column.check <- column.check[ ! column.check == y] # remove y to keep quali columns +if(length(tempo) != 0){ +data1 <- data1[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former +data1.ini <- data1.ini[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers than the former +for(i3 in 1:length(column.check)){ +if(any( ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]]), na.rm = TRUE)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN COLUMN ", column.check[i3], " OF data1, THE FOLLOWING CLASSES HAVE DISAPPEARED AFTER NA/Inf REMOVAL (IF COLUMN USED IN THE PLOT, THIS CLASS WILL NOT BE DISPLAYED):\n", paste(unique(removed.rows[, column.check[i3]])[ ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]])], collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +} +count.categ <- 0 +for(i2 in 1:length(column.check)){ +if(column.check[i2] %in% categ){ +count.categ <- count.categ + 1 +} +if(column.check[i2] == categ[count.categ]){ +categ.class.order[count.categ] <- list(levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])]) # remove the absent color in the character vector +data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(categ.class.order[[count.categ]])) +} +if( ! is.null(dot.color) & ! is.null(dot.categ)){ # reminder : dot.categ cannot be a column name of categ anymore (because in that case dot.categ name is changed into "..._DOT" +if(column.check[i2] == dot.categ){ +dot.categ.class.order <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])] # remove the absent color in the character vector +data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(dot.categ.class.order)) +} +} +if(column.check[i2] %in% facet.categ){ # works if facet.categ == NULL this method should keep the order of levels when removing some levels +tempo.levels <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(as.character(data1[, column.check[i2]]))] +data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = tempo.levels) +} +} +} +# end na detection and removal (done now to be sure of the correct length of categ) +# From here, data1 and data.ini have no more NA or NaN in y, categ, dot.categ (if dot.color != NULL) and facet.categ + + + +if( ! is.null(categ.class.order)){ +if(length(categ.class.order) != length(categ)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.class.order ARGUMENT MUST BE A LIST OF LENGTH EQUAL TO LENGTH OF categ\nHERE IT IS LENGTH: ", length(categ.class.order), " VERSUS ", length(categ)) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +for(i3 in 1:length(categ.class.order)){ +if(is.null(categ.class.order[[i3]])){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE categ.class.order COMPARTMENT ", i3, " IS NULL. ALPHABETICAL ORDER WILL BE APPLIED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +data1[, categ[i3]] <- factor(as.character(data1[, categ[i3]])) # if already a factor, change nothing, if characters, levels according to alphabetical order +categ.class.order[[i3]] <- levels(data1[, categ[i3]]) # character vector that will be used later +}else{ +tempo <- fun_check(data = categ.class.order[[i3]], data.name = paste0("COMPARTMENT ", i3 , " OF categ.class.order ARGUMENT"), class = "vector", mode = "character", length = length(levels(data1[, categ[i3]])), fun.name = function.name) # length(data1[, categ[i1]) -> if data1[, categ[i1] was initially character vector, then conversion as factor after the NA removal, thus class number ok. If data1[, categ[i1] was initially factor, no modification after the NA removal, thus class number ok +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(any(duplicated(categ.class.order[[i3]]), na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i3, " OF categ.class.order ARGUMENT CANNOT HAVE DUPLICATED CLASSES: ", paste(categ.class.order[[i3]], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if( ! (all(categ.class.order[[i3]] %in% unique(data1[, categ[i3]]), na.rm = TRUE) & all(unique(data1[, categ[i3]]) %in% categ.class.order[[i3]], na.rm = TRUE))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i3, " OF categ.class.order ARGUMENT MUST BE CLASSES OF ELEMENT ", i3, " OF categ ARGUMENT\nHERE IT IS:\n", paste(categ.class.order[[i3]], collapse = " "), "\nFOR COMPARTMENT ", i3, " OF categ.class.order AND IT IS:\n", paste(unique(data1[, categ[i3]]), collapse = " "), "\nFOR COLUMN ", categ[i3], " OF data1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +data1[, categ[i3]] <- factor(data1[, categ[i3]], levels = categ.class.order[[i3]]) # reorder the factor + +} +names(categ.class.order)[i3] <- categ[i3] +} +} +}else{ +categ.class.order <- vector("list", length = length(categ)) +tempo.categ.class.order <- NULL +for(i2 in 1:length(categ.class.order)){ +categ.class.order[[i2]] <- levels(data1[, categ[i2]]) +names(categ.class.order)[i2] <- categ[i2] +tempo.categ.class.order <- c(tempo.categ.class.order, ifelse(i2 != 1, "\n", ""), categ.class.order[[i2]]) +} +if(box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE categ.class.order SETTING IS NULL. ALPHABETICAL ORDER WILL BE APPLIED FOR BOX ORDERING:\n", paste(tempo.categ.class.order, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# categ.class.order not NULL anymore (list) +if(is.null(box.legend.name) & box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE box.legend.name SETTING IS NULL. NAMES OF categ WILL BE USED: ", paste(categ, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +box.legend.name <- categ[length(categ)] # if only categ1, then legend name of categ1, if length(categ) == 2L, then legend name of categ2 +} +# box.legend.name not NULL anymore (character string) +# management of categ.color +if( ! is.null(categ.color)){ +# check the nature of color +# integer colors into gg_palette +tempo.check.color <- fun_check(data = categ.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name)$problem +if(tempo.check.color == FALSE){ +# convert integers into colors +categ.color <- fun_gg_palette(max(categ.color, na.rm = TRUE))[categ.color] +} +# end integer colors into gg_palette +if( ! (all(categ.color %in% colors() | grepl(pattern = "^#", categ.color)))){ # check that all strings of low.color start by #, # all() without na.rm -> ok because categ.color cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors(): ", paste(unique(categ.color), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(is.na(categ.color)) & box.alpha != 0){ # normally no NA with is.na +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") categ.color ARGUMENT CONTAINS NA") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# end check the nature of color +# check the length of color +categ.len <- length(categ) # if only categ1, then colors for classes of categ1, if length(categ) == 2L, then colors for classes of categ2 +if(length(data1[, categ[categ.len]]) == length(levels(data1[, categ[categ.len]])) & length(categ.color) == length(data1[, categ[categ.len]])){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE NUMBER OF CLASSES OF THE COLUMN ", categ[categ.len], " THE NUMBER OF ROWS OF THIS COLUMN AND THE NUMBER OF COLORS OF THE categ.color ARGUMENT ARE ALL EQUAL. BOX COLORS WILL BE ATTRIBUTED ACCORDING THE LEVELS OF ", categ[categ.len], ", NOT ACCORDING TO THE ROWS OF ", categ[categ.len]) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if(length(categ.color) == length(levels(data1[, categ[categ.len]]))){ # here length(categ.color) is equal to the different number of categ +# data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor +data1 <- data.frame(data1, categ.color = data1[, categ[categ.len]], stringsAsFactors = TRUE) # no need stringsAsFactors here for stat.nolog as factors remain factors +data1$categ.color <- factor(data1$categ.color, labels = categ.color) # replace the characters of data1[, categ[categ.len]] put in the categ.color column by the categ.color (can be write like this because categ.color is length of levels of data1[, categ[categ.len]]) +if(box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN ", categ[categ.len], " OF categ ARGUMENT, THE FOLLOWING COLORS:\n", paste(categ.color, collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else if(length(categ.color) == length(data1[, categ[categ.len]])){# here length(categ.color) is equal to nrow(data1) -> Modif to have length(categ.color) equal to the different number of categ (length(categ.color) == length(levels(data1[, categ[categ.len]]))) +data1 <- data.frame(data1, categ.color = categ.color, stringsAsFactors = TRUE) +tempo.check <- unique(data1[ , c(categ[categ.len], "categ.color")]) +if( ! (nrow(tempo.check) == length(unique(categ.color)) & nrow(tempo.check) == length(unique(data1[ , categ[categ.len]])))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT HAS THE LENGTH OF data1 ROW NUMBER\nBUT IS INCORRECTLY ASSOCIATED TO EACH CLASS OF categ ", categ[categ.len], ":\n", paste(unique(mapply(FUN = "paste", data1[ ,categ[categ.len]], data1[ ,"categ.color"])), collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +# data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor +categ.color <- unique(data1$categ.color[order(data1[, categ[categ.len]])]) # Modif to have length(categ.color) equal to the different number of categ (length(categ.color) == length(levels(data1[, categ[categ.len]]))) +if(box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") categ.color ARGUMENT HAS THE LENGTH OF data1 ROW NUMBER\nCOLORS HAVE BEEN RESPECTIVELY ASSOCIATED TO EACH CLASS OF categ ", categ[categ.len], " AS:\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " "), "\n", paste(categ.color, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +}else if(length(categ.color) == 1L){ +# data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor +data1 <- data.frame(data1, categ.color = categ.color, stringsAsFactors = TRUE) +categ.color <- rep(categ.color, length(levels(data1[, categ[categ.len]]))) +if(box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") categ.color ARGUMENT HAS LENGTH 1, MEANING THAT ALL THE DIFFERENT CLASSES OF ", categ[categ.len], "\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " "), "\nWILL HAVE THE SAME COLOR\n", paste(categ.color, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE (1) LENGTH 1, OR (2) THE LENGTH OF data1 NROWS AFTER NA/Inf REMOVAL, OR (3) THE LENGTH OF THE CLASSES IN THE categ ", categ[categ.len], " COLUMN. HERE IT IS COLOR LENGTH ", length(categ.color), " VERSUS CATEG LENGTH ", length(data1[, categ[categ.len]]), " AND CATEG CLASS LENGTH ", length(unique(data1[, categ[categ.len]])), "\nPRESENCE OF NA/Inf COULD BE THE PROBLEM") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else{ +categ.len <- length(categ) # if only categ1, then colors for classes of categ1, if length(categ) == 2L, then colors for classes of categ2 +# data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor +categ.color <- fun_gg_palette(length(levels(data1[, categ[categ.len]]))) +data1 <- data.frame(data1, categ.color = data1[, categ[categ.len]], stringsAsFactors = TRUE) +data1$categ.color <- factor(data1$categ.color, labels = categ.color) # replace the characters of data1[, categ[categ.len]] put in the categ.color column by the categ.color (can be write like this because categ.color is length of levels of data1[, categ[categ.len]]) +if(box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL categ.color ARGUMENT -> COLORS RESPECTIVELY ATTRIBUTED TO EACH CLASS OF ", categ[categ.len], " IN data1:\n", paste(categ.color, collapse = " "), "\n", paste(levels(data1[, categ[categ.len]]), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# categ.color not NULL anymore +categ.color <- as.character(categ.color) +# categ.color is a character string representing the diff classes +data1$categ.color <- factor(data1$categ.color, levels = unique(categ.color)) # ok because if categ.color is a character string, the order make class 1, class 2, etc. unique() because no duplicates allowed +# data1$categ.color is a factor with order of levels -> categ.color +# end management of categ.color +# management of dot.color +if( ! is.null(dot.color)){ +# optional legend of dot colors +if( ! is.null(dot.categ)){ +ini.dot.categ <- dot.categ +if( ! dot.categ %in% names(data1)){ # no need to use all() because length(dot.categ) = 1 +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT MUST BE A COLUMN NAME OF data1. HERE IT IS:\n", dot.categ) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(dot.categ %in% categ){ # no need to use all() because length(dot.categ) = 1. Do not use dot.categ %in% categ[length(categ)] -> error +# management of dot legend if dot.categ %in% categ (because legends with the same name are joined in ggplot2) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE COLUMN NAME OF data1 INDICATED IN THE dot.categ ARGUMENT (", dot.categ, ") HAS BEEN REPLACED BY ", paste0(dot.categ, "_DOT"), " TO AVOID MERGED LEGEND BY GGPLOT2") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +data1 <- data.frame(data1, dot.categ = data1[, dot.categ], stringsAsFactors = TRUE) # dot.categ is not a column name of data1 (checked above with reserved words) +dot.categ <- paste0(dot.categ, "_DOT") +names(data1)[names(data1) == "dot.categ"] <- dot.categ # paste0(dot.categ, "_DOT") is not a column name of data1 (checked above with reserved words) +# tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT CANNOT BE A COLUMN NAME OF data1 ALREADY SPECIFIED IN THE categ ARGUMENT:\n", dot.categ, "\nINDEED, dot.categ ARGUMENT IS MADE TO HAVE MULTIPLE DOT COLORS NOT RELATED TO THE BOXPLOT CATEGORIES") +# stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +tempo1 <- fun_check(data = data1[, dot.categ], data.name = paste0(dot.categ, " COLUMN OF data1"), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = data1[, dot.categ], data.name = paste0(dot.categ, " COLUMN OF data1"), class = "factor", na.contain = TRUE, fun.name = function.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ COLUMN MUST BE A FACTOR OR CHARACTER VECTOR") # +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +data1[, dot.categ] <- factor(data1[, dot.categ]) # if already a factor, change nothing, if characters, levels according to alphabetical order +# dot.categ column of data1 is factor from here +if( ! is.null(dot.categ.class.order)){ +if(any(duplicated(dot.categ.class.order), na.rm = TRUE)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ.class.order ARGUMENT CANNOT HAVE DUPLICATED CLASSES: ", paste(dot.categ.class.order, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if( ! (all(dot.categ.class.order %in% levels(data1[, dot.categ])) & all(levels(data1[, dot.categ]) %in% dot.categ.class.order, na.rm = TRUE))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ.class.order ARGUMENT MUST BE CLASSES OF dot.categ ARGUMENT\nHERE IT IS:\n", paste(dot.categ.class.order, collapse = " "), "\nFOR dot.categ.class.order AND IT IS:\n", paste(levels(data1[, dot.categ]), collapse = " "), "\nFOR dot.categ COLUMN (", ini.dot.categ, ") OF data1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +data1[, dot.categ] <- factor(data1[, dot.categ], levels = dot.categ.class.order) # reorder the factor +} +}else{ +if(all(dot.color == "same") & length(dot.color)== 1L){ # all() without na.rm -> ok because dot.color cannot be NA (tested above) +dot.categ.class.order <- unlist(categ.class.order[length(categ)]) +data1[, dot.categ] <- factor(data1[, dot.categ], levels = dot.categ.class.order) # reorder the factor +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE dot.categ.class.order SETTING IS NULL AND dot.color IS \"same\". ORDER OF categ.class.order WILL BE APPLIED FOR LEGEND DISPLAY: ", paste(dot.categ.class.order, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +dot.categ.class.order <- sort(levels(data1[, dot.categ])) +data1[, dot.categ] <- factor(data1[, dot.categ], levels = dot.categ.class.order) # reorder the factor +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE dot.categ.class.order SETTING IS NULL. ALPHABETICAL ORDER WILL BE APPLIED FOR LEGEND DISPLAY: ", paste(dot.categ.class.order, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# dot.categ.class.order not NULL anymore (character string) if dot.categ is not NULL +if(all(dot.color == "same") & length(dot.color)== 1L){ # all() without na.rm -> ok because dot.color cannot be NA (tested above) +if( ! identical(ini.dot.categ, categ[length(categ)])){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nWHEN dot.color ARGUMENT IS \"same\", THE COLUMN NAME IN dot.categ ARGUMENT MUST BE IDENTICAL TO THE LAST COLUMN NAME IN categ ARGUMENT. HERE IT IS:\ndot.categ: ", paste(ini.dot.categ, collapse = " "), "\ncateg: ", paste(categ, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if( ! fun_comp_1d(unlist(categ.class.order[length(categ)]), dot.categ.class.order)$identical.content){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nWHEN dot.color ARGUMENT IS \"same\",\nLAST COMPARTMENT OF categ.class.order ARGUMENT AND dot.categ.class.order ARGUMENT CANNOT BE DIFFERENT:\nLAST COMPARTMENT OF categ.class.order: ", paste(unlist(categ.class.order[length(categ)]), collapse = " "), "\ndot.categ.class.order: ", paste(dot.categ.class.order, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +for(i3 in 1:length(categ)){ +if(identical(categ[i3], ini.dot.categ) & ! identical(unlist(categ.class.order[i3]), dot.categ.class.order) & identical(sort(unlist(categ.class.order[i3])), sort(dot.categ.class.order))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE dot.categ ARGUMENT SETTING IS PRESENT IN THE categ ARGUMENT SETTING, BUT ORDER OF THE CLASSES IS NOT THE SAME:\ncateg.class.order: ", paste(unlist(categ.class.order[i3]), collapse = " "), "\ndot.categ.class.order: ", paste(dot.categ.class.order, collapse = " "), "\nNOTE THAT ORDER OF categ.class.order IS THE ONE USED FOR THE AXIS REPRESENTATION") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +if(is.null(dot.legend.name)){ +dot.legend.name <- if(ini.dot.categ %in% categ[length(categ)]){dot.categ}else{ini.dot.categ} # +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE dot.legend.name SETTING IS NULL -> ", dot.legend.name, " WILL BE USED AS LEGEND TITLE OF DOTS") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# dot.legend.name not NULL anymore (character string) +}else{ +if( ! is.null(dot.categ.class.order)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE dot.categ.class.order ARGUMENT IS NOT NULL, BUT IS THE dot.categ ARGUMENT\n-> dot.categ.class.order NOT CONSIDERED AS NO LEGEND WILL BE DRAWN") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# But dot.categ.class.order will be converted to NULL below (not now) +} +# end optional legend of dot colors +# check the nature of color +# integer colors into gg_palette +tempo.check.color <- fun_check(data = dot.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name)$problem +if(tempo.check.color == FALSE){ +# convert integers into colors +dot.color <- fun_gg_palette(max(dot.color, na.rm = TRUE))[dot.color] +} +# end integer colors into gg_palette +if(all(dot.color == "same") & length(dot.color)== 1L){# all() without na.rm -> ok because dot.color cannot be NA (tested above) +dot.color <- categ.color # same color of the dots as the corresponding box color +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") dot.color ARGUMENT HAS BEEN SET TO \"same\"\nTHUS, DOTS WILL HAVE THE SAME COLORS AS THE CORRESPONDING BOXPLOT") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else if( ! (all(dot.color %in% colors() | grepl(pattern = "^#", dot.color)))){ # check that all strings of low.color start by #, # all() without na.rm -> ok because dot.color cannot be NA (tested above) +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR VECTOR STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) INTEGERS, OR THE STRING \"same\"\nHERE IT IS: ", paste(unique(dot.color), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(any(is.na(dot.color))){ # normally no NA with is.finite +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") dot.color ARGUMENT CONTAINS NA") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# end check the nature of color +# check the length of color +if( ! is.null(dot.categ)){ +# optional legend of dot colors +if(length(data1[, dot.categ]) == length(levels(data1[, dot.categ])) & length(dot.color) == length(data1[, dot.categ])){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE NUMBER OF CLASSES OF THE COLUMN ", dot.categ, " THE NUMBER OF ROWS OF THIS COLUMN AND THE NUMBER OF COLORS OF THE dot.color ARGUMENT ARE ALL EQUAL. DOT COLORS WILL BE ATTRIBUTED ACCORDING THE LEVELS OF ", dot.categ, ", NOT ACCORDING TO THE ROWS OF ", dot.categ) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if(length(dot.color) > 1 & ! (length(dot.color) == length(unique(data1[, dot.categ])) | length(dot.color) == length(data1[, dot.categ]))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nWHEN LENGTH OF THE dot.color ARGUMENT IS MORE THAN 1, IT MUST BE EQUAL TO THE NUMBER OF 1) ROWS OR 2) LEVELS OF dot.categ COLUMN (", dot.categ, "):\ndot.color: ", paste(dot.color, collapse = " "), "\ndot.categ LEVELS: ", paste(levels(data1[, dot.categ]), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else if(length(dot.color) > 1 & length(dot.color) == length(unique(data1[, dot.categ]))){ +data1 <- data.frame(data1, dot.color = data1[, dot.categ], stringsAsFactors = TRUE) +data1$dot.color <- factor(data1$dot.color, labels = dot.color) # do not use labels = unique(dot.color). Otherwise, we can have green1 green2 when dot.color is c("green", "green") +}else if(length(dot.color) > 1 & length(dot.color) == length(data1[, dot.categ])){ +data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) +}else if(length(dot.color)== 1L){ # to deal with single color. Warning: & length(dot.categ.class.order) > 1 removed because otherwise, the data1 is not with dot.color column when length(dot.categ.class.order) == 1 +data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) +} +dot.color <- as.character(unique(data1$dot.color[order(data1[, dot.categ])])) # reorder the dot.color character vector +if(length(dot.color)== 1L & length(dot.categ.class.order) > 1){ # to deal with single color +dot.color <- rep(dot.color, length(dot.categ.class.order)) +} +tempo.check <- unique(data1[ , c(dot.categ, "dot.color")]) +if(length(unique(data1[ , "dot.color"])) > 1 & ( ! (nrow(tempo.check) == length(unique(data1[ , "dot.color"])) & nrow(tempo.check) == length(unique(data1[ , dot.categ]))))){ # length(unique(data1[ , "dot.color"])) > 1 because if only one color, can be attributed to each class of dot.categ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT IS INCORRECTLY ASSOCIATED TO EACH CLASS OF dot.categ (", dot.categ, ") COLUMN:\n", paste(unique(mapply(FUN = "paste", data1[ , dot.categ], data1[ ,"dot.color"])), collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN dot.categ ARGUMENT (", ini.dot.categ, "), THE FOLLOWING COLORS OF DOTS:\n", paste(dot.color, collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(data1[, dot.categ]), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# dot.color is a character string representing the diff classes of dot.categ +# data1$dot.color is a factor with order of levels -> dot.categ +# end optional legend of dot colors +}else{ +categ.len <- length(categ) # if only categ1, then colors for classes of categ1, if length(categ) == 2L, then colors for classes of categ2 +if(length(dot.color) == length(levels(data1[, categ[categ.len]]))){ # here length(dot.color) is equal to the different number of categ +# data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor +data1 <- data.frame(data1, dot.color = data1[, categ[categ.len]], stringsAsFactors = TRUE) +data1$dot.color <- factor(data1$dot.color, labels = dot.color) +if(box.alpha != 0){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN ", categ[categ.len], " OF categ ARGUMENT, THE FOLLOWING COLORS:\n", paste(dot.color, collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else if(length(dot.color) == length(data1[, categ[categ.len]])){# here length(dot.color) is equal to nrow(data1) -> Modif to have length(dot.color) equal to the different number of categ (length(dot.color) == length(levels(data1[, categ[categ.len]]))) +data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) +}else if(length(dot.color)== 1L & ! all(dot.color == "same")){ # all() without na.rm -> ok because dot.color cannot be NA (tested above) +# data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor +data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) +dot.color <- rep(dot.color, length(levels(data1[, categ[categ.len]]))) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") dot.color ARGUMENT HAS LENGTH 1, MEANING THAT ALL THE DIFFERENT CLASSES OF ", categ[categ.len], "\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " "), "\nWILL HAVE THE SAME COLOR\n", paste(dot.color, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT MUST BE (1) LENGTH 1, OR (2) THE LENGTH OF data1 NROWS AFTER NA/Inf REMOVAL, OR (3) THE LENGTH OF THE CLASSES IN THE categ ", categ[categ.len], " COLUMN. HERE IT IS COLOR LENGTH ", length(dot.color), " VERSUS CATEG LENGTH ", length(data1[, categ[categ.len]]), " AND CATEG CLASS LENGTH ", length(unique(data1[, categ[categ.len]])), "\nPRESENCE OF NA/Inf COULD BE THE PROBLEM") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end check the length of color +dot.color <- as.character(dot.color) +# dot.color is a character string representing the diff classes +data1$dot.color <- factor(data1$dot.color, levels = unique(dot.color)) # ok because if dot.color is a character string, the order make class 1, class 2, etc. If dot.color is a column of data1, then levels will be created, without incidence, except if dot.categ specified (see below). unique() because no duplicates allowed +# data1$dot.color is a factor with order of levels -> dot.color +} +# end optional legend of dot colors +}else if(is.null(dot.color) & ! (is.null(dot.categ) & is.null(dot.categ.class.order) & is.null(dot.legend.name))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") dot.categ OR dot.categ.class.order OR dot.legend.name ARGUMENT HAS BEEN SPECIFIED BUT dot.color ARGUMENT IS NULL (NO DOT PLOTTED)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# dot.color either NULL (no dot plotted) or character string (potentially representing the diff classes of dot.categ) +# data1$dot.color is either NA or a factor (with order of levels -> depending on dot.categ or categ[length(categ)], or other +if(is.null(dot.categ)){ +dot.categ.class.order <- NULL # because not used anyway +} +# dot.categ.class.order either NULL if dot.categ is NULL (no legend displayed) or character string (potentially representing the diff classes of dot.categ) +# end management of dot.color +if(is.null(dot.color) & box.fill == FALSE & dot.alpha <= 0.025){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE FOLLOWING ARGUMENTS WERE SET AS:\ndot.color = NULL (NOT ALL DOTS BUT ONLY POTENTIAL OUTLIER DOTS DISPLAYED)\nbox.fill = FALSE (NO FILLING COLOR FOR BOTH BOXES AND POTENTIAL OUTLIER DOTS)\ndot.alpha = ", fun_round(dot.alpha, 4), "\n-> POTENTIAL OUTLIER DOTS MIGHT NOT BE VISIBLE BECAUSE ALMOST TRANSPARENT") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if(is.null(dot.color) & box.fill == FALSE & dot.border.size == 0){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE FOLLOWING ARGUMENTS WERE SET AS:\ndot.color = NULL (NOT ALL DOTS BUT ONLY POTENTIAL OUTLIER DOTS DISPLAYED)\nbox.fill = FALSE (NO FILLING COLOR FOR BOTH BOXES AND POTENTIAL OUTLIER DOTS)\ndot.border.size = 0 (NO BORDER FOR POTENTIAL OUTLIER DOTS)\n-> THESE SETTINGS ARE NOT ALLOWED BECAUSE THE POTENTIAL OUTLIER DOTS WILL NOT BE VISIBLE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# integer dot.border.color into gg_palette +if( ! is.null(dot.border.color)){ +tempo <- fun_check(data = dot.border.color, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) +if(tempo$problem == FALSE){ # convert integers into colors +dot.border.color <- fun_gg_palette(max(dot.border.color, na.rm = TRUE))[dot.border.color] +} +} +# end integer dot.border.color into gg_palette +# na detection and removal (done now to be sure of the correct length of categ) +column.check <- c("categ.color", if( ! is.null(dot.color)){"dot.color"}) # +if(any(is.na(data1[, column.check]))){ # data1 used here instead of data1.ini in case of new NaN created by log conversion (neg values) # normally no NA with is.na +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NA DETECTED IN COLUMNS ", paste(column.check, collapse = " "), " OF data1 AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +for(i2 in 1:length(column.check)){ +if(any(is.na(data1[, column.check[i2]]))){ # normally no NA with is.na +tempo.warn <- paste0("NA REMOVAL DUE TO COLUMN ", column.check[i2], " OF data1") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n", tempo.warn))) +} +} +tempo <- unique(unlist(lapply(lapply(c(data1[column.check]), FUN = is.na), FUN = which))) +removed.row.nb <- c(removed.row.nb, tempo) +removed.rows <- rbind(removed.rows, data1[tempo, ], stringsAsFactors = FALSE) # here data1 used because categorical columns tested +if(length(tempo) != 0){ +data1 <- data1[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former +data1.ini <- data1.ini[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former +for(i3 in 1:length(column.check)){ +if(any( ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]]), na.rm = TRUE)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN COLUMN ", column.check[i3], " OF data1, THE FOLLOWING CLASSES HAVE DISAPPEARED AFTER NA/Inf REMOVAL (IF COLUMN USED IN THE PLOT, THIS CLASS WILL NOT BE DISPLAYED):\n", paste(unique(removed.rows[, column.check[i3]])[ ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]])], collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +} +for(i2 in 1:length(column.check)){ +if(column.check[i2] == "categ.color"){ +categ.color <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])] # remove the absent color in the character vector +if(length(categ.color)== 1L & length(unlist(categ.class.order[length(categ)])) > 1){ # to deal with single color +categ.color <- rep(categ.color, length(unlist(categ.class.order[length(categ)]))) +} +data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(categ.color)) +} +if(column.check[i2] == "dot.color"){ +dot.color <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])] # remove the absent color in the character vector +if(length(dot.color)== 1L & length(dot.categ.class.order) > 1){ # to deal with single color. If dot.categ.class.order == NULL (which is systematically the case if dot.categ == NULL), no rep(dot.color, length(dot.categ.class.order) +dot.color <- rep(dot.color, length(dot.categ.class.order)) +} +data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(dot.color)) +} +} +} +# end na detection and removal (done now to be sure of the correct length of categ) +# From here, data1 and data.ini have no more NA or NaN +# end other checkings +# reserved word checking +#already done above +# end reserved word checking +# end second round of checking and data preparation + + +# package checking +fun_pack(req.package = c( +"ggplot2", +"gridExtra", +"lemon", +"scales" +), lib.path = lib.path) +# end package checking + + + + + +# main code +# y coordinates recovery (create ini.box.coord, dot.coord and modify data1) +if(length(categ)== 1L){ +# width commputations +box.width2 <- box.width +box.space <- 0 # to inactivate the shrink that add space between grouped boxes, because no grouped boxes here +# end width commputations +# data1 check categ order for dots coordinates recovery +data1 <- data.frame(data1, categ.check = data1[, categ[1]], stringsAsFactors = TRUE) +data1$categ.check <- as.integer(data1$categ.check) # to check that data1[, categ[1]] and dot.coord$group are similar, during merging +# end data1 check categ order for dots coordinates recovery +# per box dots coordinates recovery +tempo.gg.name <- "gg.indiv.plot." +tempo.gg.count <- 0 +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[1]), stroke = dot.border.size, size = dot.size, alpha = dot.alpha, shape = 21)) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = if(is.null(categ.color)){rep(NA, length(unique(data1[, categ[1]])))}else if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[1]])))}else{categ.color})) # categ.color used for dot colors because at that stage, we do not care about colors +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, fill = categ[1]), coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf})) # fill because this is what is used with geom_box # to easily have the equivalent of the grouped boxes +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[1]])))}else{categ.color})) +# end per box dots coordinates recovery +}else if(length(categ) == 2L){ +# width commputations +box.width2 <- box.width / length(unique(data1[, categ[length(categ)]])) # real width of each box in x-axis unit, among the set of grouped box. Not relevant if no grouped boxes length(categ)== 1L +# end width commputations +# data1 check categ order for dots coordinates recovery +tempo.factor <- paste0(data1[order(data1[, categ[2]], data1[, categ[1]]), categ[2]], "_", data1[order(data1[, categ[2]], data1[, categ[1]]), categ[1]]) +data1 <- data.frame(data1[order(data1[, categ[2]], data1[, categ[1]]), ], categ.check = factor(tempo.factor, levels = unique(tempo.factor)), stringsAsFactors = TRUE) +data1$categ.check <- as.integer(data1$categ.check) +# end data1 check categ order for dots coordinates recovery +# per box dots coordinates recovery +tempo.gg.name <- "gg.indiv.plot." +tempo.gg.count <- 0 +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[2]), stroke = dot.border.size, size = dot.size, alpha = dot.alpha, shape = 21)) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = if(is.null(categ.color)){rep(NA, length(unique(data1[, categ[2]])))}else if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[2]])))}else{categ.color})) # categ.color used for dot colors because at that stage, we do not care about colors +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, fill = categ[2]), coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf})) # fill because this is what is used with geom_box # to easily have the equivalent of the grouped boxes +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[2]])))}else{categ.color})) +# end per box dots coordinates recovery +}else{ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if( ! is.null(stat.pos)){ +stat.just <- fun_gg_just( +angle = stat.angle, +pos = ifelse( +vertical == TRUE, +ifelse(stat.pos == "top", "bottom", "top"), # "bottom" because we want justification for text that are below the ref point which is the top of the graph. The opposite for "above" +ifelse(stat.pos == "top", "left", "right") # "left" because we want justification for text that are on the left of the ref point which is the right border of the graph. The opposite for "above" +), +kind = "text" +) +} +# has in fact no interest because ggplot2 does not create room for geom_text() +tempo.data.max <- data1[which.max(data1[, y]), ] +tempo.data.max <- data.frame(tempo.data.max, label = formatC(tempo.data.max[, y], digit = 2, drop0trailing = TRUE, format = "f"), stringsAsFactors = TRUE) +# end has in fact no interest because ggplot2 does not create room for geom_text() +tempo.graph.info.ini <- ggplot2::ggplot_build(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if( ! is.null(stat.pos)){' + ggplot2::geom_text(data = tempo.data.max, mapping = ggplot2::aes_string(x = 1, y = y, label = "label"), size = stat.size, color = "black", angle = stat.angle, hjust = stat.just$hjust, vjust = stat.just$vjust)'})))) # added here to have room for annotation +dot.coord <- tempo.graph.info.ini$data[[1]] +dot.coord$x <- as.numeric(dot.coord$x) # because weird class +dot.coord$PANEL <- as.numeric(dot.coord$PANEL) # because numbers as levels. But may be a problem is facet are reordered ? +tempo.mean <- aggregate(x = dot.coord$y, by = list(dot.coord$group, dot.coord$PANEL), FUN = mean, na.rm = TRUE) +names(tempo.mean)[names(tempo.mean) == "x"] <- "MEAN" +names(tempo.mean)[names(tempo.mean) == "Group.1"] <- "BOX" +names(tempo.mean)[names(tempo.mean) == "Group.2"] <- "PANEL" +dot.coord <- data.frame( +dot.coord[order(dot.coord$group, dot.coord$y), ], # dot.coord$PANEL deals below +y.check = as.double(data1[order(data1$categ.check, data1[, y]), y]), +categ.check = data1[order(data1$categ.check, data1[, y]), "categ.check"], +dot.color = if(is.null(dot.color)){NA}else{data1[order(data1$categ.check, data1[, y]), "dot.color"]}, +data1[order(data1$categ.check, data1[, y]), ][categ], # avoid the renaming below +stringsAsFactors = TRUE +) # y.check to be sure that the order is the same between the y of data1 and the y of dot.coord +# names(dot.coord)[names(dot.coord) == "tempo.categ1"] <- categ[1] +if( ! is.null(dot.categ)){ +dot.coord <- data.frame(dot.coord, data1[order(data1$categ.check, data1[, y]), ][dot.categ], stringsAsFactors = TRUE) # avoid the renaming +} +if( ! is.null(facet.categ)){ +dot.coord <- data.frame(dot.coord, data1[order(data1$categ.check, data1[, y]), ][facet.categ], stringsAsFactors = TRUE) # for facet panels +tempo.test <- NULL +for(i2 in 1:length(facet.categ)){ +tempo.test <- paste0(tempo.test, ".", formatC(as.numeric(dot.coord[, facet.categ[i2]]), width = nchar(max(as.numeric(dot.coord[, facet.categ[i2]]), na.rm = TRUE)), flag = "0")) # convert factor into numeric with leading zero for proper ranking # merge the formatC() to create a new factor. The convertion to integer should recreate the correct group number. Here as.numeric is used and not as.integer in case of numeric in facet.categ (because comes from add and not checked by fun_check, contrary to categ) +} +tempo.test <- as.integer(factor(tempo.test)) +if( ! identical(as.integer(dot.coord$PANEL), tempo.test)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nas.integer(dot.coord$PANEL) AND tempo.test MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(dot.tidy == TRUE){ +if( ! is.null(dot.categ)){ +dot.coord <- data.frame(dot.coord, tidy_group = data1[order(data1$categ.check, data1[, y]), ][, dot.categ], stringsAsFactors = TRUE) # avoid the renaming +# tidy_group_coord is to be able to fuse table when creating the table for dot coordinates +if(dot.categ %in% categ){ +dot.coord <- data.frame(dot.coord, tidy_group_coord = dot.coord$group, stringsAsFactors = TRUE) +}else{ +dot.coord <- data.frame(dot.coord, tidy_group_coord = as.integer(factor(paste0( +formatC(as.integer(dot.coord[, categ[1]]), width = nchar(max(as.integer(dot.coord[, categ[1]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking +".", +if(length(categ) == 2L){formatC(as.integer(dot.coord[, categ[2]]), width = nchar(max(as.integer(dot.coord[, categ[2]]), na.rm = TRUE)), flag = "0")}, # convert factor into numeric with leading zero for proper ranking +if(length(categ) == 2L){"."}, +formatC(as.integer(dot.coord[, dot.categ]), width = nchar(max(as.integer(dot.coord[, dot.categ]), na.rm = TRUE)), flag = "0") # convert factor into numeric with leading zero for proper ranking +)), stringsAsFactors = TRUE) # merge the 2 or 3 formatC() to create a new factor. The convertion to integer should recreate the correct group number +) # for tidy dot plots +} +}else{ +dot.coord <- data.frame(dot.coord, tidy_group = if(length(categ)== 1L){ +dot.coord[, categ]}else{as.integer(factor(paste0( +formatC(as.integer(dot.coord[, categ[1]]), width = nchar(max(as.integer(dot.coord[, categ[1]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking +".", +formatC(as.integer(dot.coord[, categ[2]]), width = nchar(max(as.integer(dot.coord[, categ[2]]), na.rm = TRUE)), flag = "0")# convert factor into numeric with leading zero for proper ranking +)), stringsAsFactors = TRUE) # merge the 2 formatC() to create a new factor. The convertion to integer should recreate the correct group number +}) # for tidy dot plots +# tidy_group_coord is to be able to fuse table when creating the table for dot coordinates +dot.coord <- data.frame(dot.coord, tidy_group_coord = dot.coord$group, stringsAsFactors = TRUE) +} +} +if( ! (identical(dot.coord$y, dot.coord$y.check) & identical(dot.coord$group, dot.coord$categ.check))){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\n(dot.coord$y AND dot.coord$y.check) AS WELL AS (dot.coord$group AND dot.coord$categ.check) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +if( ! identical(tempo.mean[order(tempo.mean$BOX, tempo.mean$PANEL), ]$BOX, unique(dot.coord[order(dot.coord$group, dot.coord$PANEL), c("group", "PANEL")])$group)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\n(tempo.mean$BOX, tempo.mean$PANEL) AND (dot.coord$group, dot.coord$PANEL) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +tempo <- unique(dot.coord[order(dot.coord$group, dot.coord$PANEL), c(categ, if( ! is.null(dot.color) & ! is.null(dot.categ)){if(dot.categ != ini.dot.categ){dot.categ}}, if( ! is.null(facet.categ)){facet.categ}), drop = FALSE]) +# names(tempo) <- paste0(names(tempo), ".mean") +tempo.mean <- data.frame(tempo.mean[order(tempo.mean$BOX, tempo.mean$PANEL), ], tempo, stringsAsFactors = TRUE) +} +} +# at that stage, categ color and dot color are correctly attributed in data1, box.coord and dot.coord +# end y dot coordinates recovery (create ini.box.coord, dot.coord and modify data1) +# ylim range +if(is.null(y.lim)){ +y.lim <- tempo.graph.info.ini$layout$panel_params[[1]]$y.range # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +if(any(( ! is.finite(y.lim)) | is.na(y.lim)) | length(y.lim) != 2){ # kept but normally no more Inf in data1 # normally no NA with is.finite, etc. +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\ntempo.graph.info.ini$layout$panel_params[[1]]$y.range[1] CONTAINS NA OR Inf OR HAS LENGTH 1") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else if(y.log != "no"){ +y.lim <- get(y.log)(y.lim) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +} +if(y.log != "no"){ +# normally this control is not necessary anymore +if(any( ! is.finite(y.lim))){ # normally no NA with is.finite +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " "), "\nPLEASE, CHECK DATA VALUES (PRESENCE OF ZERO OR INF VALUES)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +if(suppressWarnings(all(y.lim %in% c(Inf, -Inf)))){ # all() without na.rm -> ok because y.lim cannot be NA (tested above) +# normally this control is not necessary anymore +tempo.cat <- paste0("ERROR IN ", function.name, " y.lim CONTAINS Inf VALUES, MAYBE BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY OR BECAUSE OF LOG SCALE REQUIREMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +if(suppressWarnings(any(is.na(y.lim)))){ # normally no NA with is.na +# normally this control is not necessary anymore +tempo.cat <- paste0("ERROR IN ", function.name, " y.lim CONTAINS NA OR NaN VALUES, MAYBE BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY OR BECAUSE OF LOG SCALE REQUIREMENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +y.lim.order <- order(y.lim) # to deal with inverse axis +y.lim <- sort(y.lim) +y.lim[1] <- y.lim[1] - abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.bottom.extra.margin, y.top.extra.margin) # diff(y.lim.order) > 0 medians not inversed axis +y.lim[2] <- y.lim[2] + abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.top.extra.margin, y.bottom.extra.margin) # diff(y.lim.order) > 0 medians not inversed axis +if(y.include.zero == TRUE){ # no need to check y.log != "no" because done before +y.lim <- range(c(y.lim, 0), na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +} +y.lim <- y.lim[y.lim.order] +if(any(is.na(y.lim))){ # normally no NA with is.na +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 2") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end ylim range + + + + + + +# drawing +# constant part +tempo.gg.name <- "gg.indiv.plot." +tempo.gg.count <- 0 +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add is directly put here to deal with additional variable of data, like when using facet_grid. No problem if add is a theme, will be dealt below +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::xlab(if(is.null(x.lab)){categ[1]}else{x.lab})) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ylab(if(is.null(y.lab)){y}else{y.lab})) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) +# text angle management +axis.just <- fun_gg_just(angle = x.angle, pos = ifelse(vertical == TRUE, "bottom", "left"), kind = "axis") +# end text angle management +add.check <- TRUE +if( ! is.null(add)){ # if add is NULL, then = 0 +if(grepl(pattern = "ggplot2\\s*::\\s*theme", add) == TRUE){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT\n-> INTERNAL GGPLOT2 THEME FUNCTIONS theme() AND theme_classic() HAVE BEEN INACTIVATED, TO BE USED BY THE USER\n-> article ARGUMENT WILL BE IGNORED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +add.check <- FALSE +} +} +if(add.check == TRUE & article == TRUE){ +# WARNING: not possible to add theme()several times. NO message but the last one overwrites the others +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_classic(base_size = text.size)) +if(grid == TRUE){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( +text = ggplot2::element_text(size = text.size), +plot.title = ggplot2::element_text(size = title.text.size), # stronger than text +line = ggplot2::element_line(size = 0.5), +legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend +axis.line.y.left = ggplot2::element_line(colour = "black"), # draw lines for the y axis +axis.line.x.bottom = ggplot2::element_line(colour = "black"), # draw lines for the x axis +panel.grid.major.x = if(vertical == TRUE){NULL}else{ggplot2::element_line(colour = "grey85", size = 0.75)}, +panel.grid.major.y = if(vertical == TRUE){ggplot2::element_line(colour = "grey85", size = 0.75)}else{NULL}, +panel.grid.minor.y = if(vertical == TRUE){ggplot2::element_line(colour = "grey90", size = 0.25)}else{NULL}, +axis.text.x = if(vertical == TRUE){ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}else{NULL}, +axis.text.y = if(vertical == TRUE){NULL}else{ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}, +strip.background = ggplot2::element_rect(fill = NA, colour = NA) # for facet background +)) +}else{ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( +text = ggplot2::element_text(size = text.size), +plot.title = ggplot2::element_text(size = title.text.size), # stronger than text +line = ggplot2::element_line(size = 0.5), +legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend +axis.line.y.left = ggplot2::element_line(colour = "black"), +axis.line.x.bottom = ggplot2::element_line(colour = "black"), +axis.text.x = if(vertical == TRUE){ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}else{NULL}, +axis.text.y = if(vertical == TRUE){NULL}else{ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}, +strip.background = ggplot2::element_rect(fill = NA, colour = NA) +)) +} +}else if(add.check == TRUE & article == FALSE){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( +text = ggplot2::element_text(size = text.size), +plot.title = ggplot2::element_text(size = title.text.size), # stronger than text +line = ggplot2::element_line(size = 0.5), +legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend +panel.background = ggplot2::element_rect(fill = "grey95"), +axis.line.y.left = ggplot2::element_line(colour = "black"), +axis.line.x.bottom = ggplot2::element_line(colour = "black"), +panel.grid.major.x = ggplot2::element_line(colour = "grey85", size = 0.75), +panel.grid.major.y = ggplot2::element_line(colour = "grey85", size = 0.75), +panel.grid.minor.x = ggplot2::element_blank(), +panel.grid.minor.y = ggplot2::element_line(colour = "grey90", size = 0.25), +strip.background = ggplot2::element_rect(fill = NA, colour = NA), +axis.text.x = if(vertical == TRUE){ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}else{NULL}, +axis.text.y = if(vertical == TRUE){NULL}else{ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)} +)) +} +# Contrary to fun_gg_bar(), cannot plot the boxplot right now, because I need the dots plotted first +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, group = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), color = NA, width = box.width, fill = NA)) # this is to set the graph (i.e., a blanck boxplot to be able to use x coordinates to plot dots before boxes) +# end constant part + + + + +# graphic info recovery (including means) +tempo.graph.info <- ggplot2::ggplot_build(eval(parse(text = paste0(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), ' + ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, fill = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), width = box.width, notch = box.notch, coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf}) + ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[length(categ)]])))}else{categ.color})')))) # will be recovered later again, when ylim will be considered +tempo.yx.ratio <- (tempo.graph.info$layout$panel_params[[1]]$y.range[2] - tempo.graph.info$layout$panel_params[[1]]$y.range[1]) / (tempo.graph.info$layout$panel_params[[1]]$x.range[2] - tempo.graph.info$layout$panel_params[[1]]$x.range[1]) +box.coord <- tempo.graph.info$data[[2]] # to have the summary statistics of the plot. Contrary to ini.box.plot, now integrates ylim Here because can be required for stat.pos when just box are plotted +box.coord$x <- as.numeric(box.coord$x) # because x is of special class that block comparison of values using identical +box.coord$PANEL <- as.numeric(box.coord$PANEL) # because numbers as levels. But may be a problem is facet are reordered ? +box.coord <- box.coord[order(box.coord$group, box.coord$PANEL), ] +if( ! (identical(tempo.mean$BOX, box.coord$group) & identical(tempo.mean$PANEL, box.coord$PANEL))){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nidentical(tempo.mean$BOX, box.coord$group) & identical(tempo.mean$PANEL, box.coord$PANEL) DO NOT HAVE THE SAME VALUE ORDER") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +# tempo <- c(categ, if( ! is.null(dot.color) & ! is.null(dot.categ)){if(dot.categ != ini.dot.categ){dot.categ}}, if( ! is.null(facet.categ)){facet.categ}) +if(any(names(tempo.mean) %in% names(box.coord), na.rm = TRUE)){ +names(tempo.mean)[names(tempo.mean) %in% names(box.coord)] <- paste0(names(tempo.mean)[names(tempo.mean) %in% names(box.coord)], ".mean") +} +box.coord <- data.frame(box.coord, tempo.mean, stringsAsFactors = TRUE) +} +# end graphic info recovery (including means) + + + +# stat output (will also serve for boxplot and mean display) +# x not added now (to do not have them in stat.nolog) +stat <- data.frame( +MIN = box.coord$ymin_final, +QUART1 = box.coord$lower, +MEDIAN = box.coord$middle, +MEAN = box.coord$MEAN, +QUART3 = box.coord$upper, +MAX = box.coord$ymax_final, +WHISK_INF = box.coord$ymin, +BOX_INF = box.coord$lower, +NOTCH_INF = box.coord$notchlower, +NOTCH_SUP = box.coord$notchupper, +BOX_SUP = box.coord$upper, +WHISK_SUP = box.coord$ymax, +OUTLIERS = box.coord["outliers"], +tempo.mean[colnames(tempo.mean) != "MEAN"], +COLOR = box.coord$fill, +stringsAsFactors = TRUE +) # box.coord["outliers"] written like this because it is a list. X coordinates not put now because several features to set +names(stat)[names(stat) == "outliers"] <- "OUTLIERS" +stat.nolog <- stat # stat.nolog ini will serve for outputs +if(y.log != "no"){ +stat.nolog[c("MIN", "QUART1", "MEDIAN", "MEAN", "QUART3", "MAX", "WHISK_INF", "BOX_INF", "NOTCH_INF", "NOTCH_SUP", "BOX_SUP", "WHISK_SUP")] <- ifelse(y.log == "log2", 2, 10)^(stat.nolog[c("MIN", "QUART1", "MEDIAN", "MEAN", "QUART3", "MAX", "WHISK_INF", "BOX_INF", "NOTCH_INF", "NOTCH_SUP", "BOX_SUP", "WHISK_SUP")]) +stat.nolog$OUTLIERS <- lapply(stat.nolog$OUTLIERS, FUN = function(X){ifelse(y.log == "log2", 2, 10)^X}) +} +# end stat output (will also serve for boxplot and mean display) + + + + + + +# x coordinates management (for random plotting and for stat display) +# width commputations +width.ini <- c(box.coord$xmax - box.coord$xmin)[1] # all the box widths are equal here. Only the first one taken +width.correct <- width.ini * box.space / 2 +if( ! (identical(stat$BOX, box.coord$group) & identical(stat$PANEL, box.coord$PANEL))){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nidentical(stat$BOX, box.coord$group) & identical(stat$PANEL, box.coord$PANEL) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +stat <- data.frame( +stat, +X = box.coord$x, +X_BOX_INF = box.coord$xmin + width.correct, +X_BOX_SUP = box.coord$xmax - width.correct, +X_NOTCH_INF = box.coord$x - (box.coord$x - (box.coord$xmin + width.correct)) / 2, +X_NOTCH_SUP = box.coord$x + (box.coord$x - (box.coord$xmin + width.correct)) / 2, +X_WHISK_INF = box.coord$x - (box.coord$x - (box.coord$xmin + width.correct)) * box.whisker.width, +X_WHISK_SUP = box.coord$x + (box.coord$x - (box.coord$xmin + width.correct)) * box.whisker.width, +# tempo.mean[colnames(tempo.mean) != "MEAN"], # already added above +stringsAsFactors = TRUE +) +stat$COLOR <- factor(stat$COLOR, levels = unique(categ.color)) +if( ! all(stat$NOTCH_SUP < stat$BOX_SUP & stat$NOTCH_INF > stat$BOX_INF, na.rm = TRUE) & box.notch == TRUE){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") SOME NOTCHES ARE BEYOND BOX HINGES. TRY ARGUMENT box.notch = FALSE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +dot.jitter <- c((box.coord$xmax - width.correct) - (box.coord$xmin + width.correct))[1] * dot.jitter # real dot.jitter. (box.coord$xmin + width.correct) - (box.coord$xmax - width.correct))[1] is the width of the box. Is equivalent to (box.coord$x - (box.coord$xmin + width.correct))[1] * 2 +# end width commputations +if( ! is.null(dot.color)){ +# random dots +if(dot.tidy == FALSE){ +dot.coord.rd1 <- merge(dot.coord, box.coord[c("fill", "PANEL", "group", "x")], by = c("PANEL", "group"), sort = FALSE) # rd for random. Send the coord of the boxes into the coord data.frame of the dots (in the column x.y). WARNING: by = c("PANEL", "group") without fill column because PANEL & group columns are enough as only one value of x column per group number in box.coord. Thus, no need to consider fill column +if(nrow(dot.coord.rd1) != nrow(dot.coord)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.rd1 DATA FRAME. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +sampled.dot.jitter <- if(nrow(dot.coord.rd1)== 1L){runif(n = nrow(dot.coord.rd1), min = - dot.jitter / 2, max = dot.jitter / 2)}else{sample(x = runif(n = nrow(dot.coord.rd1), min = - dot.jitter / 2, max = dot.jitter / 2), size = nrow(dot.coord.rd1), replace = FALSE)} +dot.coord.rd2 <- data.frame(dot.coord.rd1, dot.x = dot.coord.rd1$x.y + sampled.dot.jitter, stringsAsFactors = TRUE) # set the dot.jitter thanks to runif and dot.jitter range. Then, send the coord of the boxes into the coord data.frame of the dots (in the column x.y) +if(length(categ)== 1L){ +tempo.data1 <- unique(data.frame(data1[categ[1]], group = as.integer(data1[, categ[1]]), stringsAsFactors = TRUE)) # categ[1] is factor +names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") +verif <- paste0(categ[1], ".check") +}else if(length(categ) == 2L){ +tempo.data1 <- unique( +data.frame( +data1[c(categ[1], categ[2])], +group = as.integer(factor(paste0( +formatC(as.integer(data1[, categ[2]]), width = nchar(max(as.integer(data1[, categ[2]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking +".", +formatC(as.integer(data1[, categ[1]]), width = nchar(max(as.integer(data1[, categ[1]]), na.rm = TRUE)), flag = "0")# convert factor into numeric with leading zero for proper ranking +)), stringsAsFactors = TRUE) # merge the 2 formatC() to create a new factor. The convertion to integer should recreate the correct group number +) +) # categ[2] first if categ[2] is used to make the categories in ggplot and categ[1] is used to make the x-axis +names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") +names(tempo.data1)[names(tempo.data1) == categ[2]] <- paste0(categ[2], ".check") +verif <- c(paste0(categ[1], ".check"), paste0(categ[2], ".check")) +}else{ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 3") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +dot.coord.rd3 <- merge(dot.coord.rd2, tempo.data1, by = intersect("group", "group"), sort = FALSE) # send the factors of data1 into coord. WARNING: I have replaced by = "group" by intersect("group", "group") because of an error due to wrong group group merging in dot.coord.rd3 +if(nrow(dot.coord.rd3) != nrow(dot.coord) | ( ! fun_comp_2d(dot.coord.rd3[categ], dot.coord.rd3[verif])$identical.content)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.rd3 DATA FRAME. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end random dots +} +# tidy dots +# coordinates are recovered during plotting (see dot.coord.tidy1 below) +# end tidy dots +} +# end x coordinates management (for random plotting and for stat display) + + + + + +# boxplot display before dot display if box.fill = TRUE +coord.names <- NULL +# creation of the data frame for (main box + legend) and data frame for means +if(box.notch == FALSE){ +for(i3 in 1:length(categ)){ +if(i3== 1L){ +tempo.polygon <- data.frame(GROUPX = c(t(stat[, rep(categ[i3], 5)])), stringsAsFactors = TRUE) +}else{ +tempo.polygon <- cbind(tempo.polygon, c(t(stat[, rep(categ[i3], 5)])), stringsAsFactors = TRUE) +} +} +names(tempo.polygon) <- categ +tempo.polygon <- data.frame(X = c(t(stat[, c("X_BOX_INF", "X_BOX_SUP", "X_BOX_SUP", "X_BOX_INF", "X_BOX_INF")])), Y = c(t(stat[, c("BOX_INF", "BOX_INF", "BOX_SUP", "BOX_SUP", "BOX_INF")])), COLOR = c(t(stat[, c("COLOR", "COLOR", "COLOR", "COLOR", "COLOR")])), BOX = as.character(c(t(stat[, c("BOX", "BOX", "BOX", "BOX", "BOX")]))), tempo.polygon, stringsAsFactors = TRUE) +if( ! is.null(facet.categ)){ +for(i4 in 1:length(facet.categ)){ +tempo.polygon <- data.frame(tempo.polygon, c(t(stat[, c(facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4])])), stringsAsFactors = TRUE) +names(tempo.polygon)[length(names(tempo.polygon))] <- facet.categ[i4] +} +} +}else{ +for(i3 in 1:length(categ)){ +if(i3== 1L){ +tempo.polygon <- data.frame(GROUPX = c(t(stat[, rep(categ[i3], 11)])), stringsAsFactors = TRUE) +}else{ +tempo.polygon <- cbind(tempo.polygon, c(t(stat[, rep(categ[i3], 11)])), stringsAsFactors = TRUE) +} +} +names(tempo.polygon) <- categ +tempo.polygon <- data.frame(X = c(t(stat[, c("X_BOX_INF", "X_BOX_SUP", "X_BOX_SUP", "X_NOTCH_SUP", "X_BOX_SUP", "X_BOX_SUP", "X_BOX_INF", "X_BOX_INF", "X_NOTCH_INF", "X_BOX_INF", "X_BOX_INF")])), Y = c(t(stat[, c("BOX_INF", "BOX_INF", "NOTCH_INF", "MEDIAN", "NOTCH_SUP", "BOX_SUP", "BOX_SUP", "NOTCH_SUP", "MEDIAN", "NOTCH_INF", "BOX_INF")])), COLOR = c(t(stat[, c("COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR")])), BOX = as.character(c(t(stat[, c("BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX")]))), tempo.polygon, stringsAsFactors = TRUE) +if( ! is.null(facet.categ)){ +for(i4 in 1:length(facet.categ)){ +tempo.polygon <- data.frame(tempo.polygon, c(t(stat[, c(facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4])])), stringsAsFactors = TRUE) +names(tempo.polygon)[length(names(tempo.polygon))] <- facet.categ[i4] +} +} +} +tempo.polygon$COLOR <- factor(tempo.polygon$COLOR, levels = unique(categ.color)) +if( ! is.null(categ.class.order)){ +for(i3 in 1:length(categ)){ +tempo.polygon[, categ[i3]] <- factor(tempo.polygon[, categ[i3]], levels = categ.class.order[[i3]]) +} +} +# modified name of dot.categ column (e.g., "Categ1_DOT") must be included for boxplot using ridy dots +if( ! is.null(dot.color) & ! is.null(dot.categ)){ +if(dot.categ != ini.dot.categ){ +tempo.polygon <- data.frame(tempo.polygon, GROUPX = tempo.polygon[, ini.dot.categ], stringsAsFactors = TRUE) +names(tempo.polygon)[names(tempo.polygon) == "GROUPX"] <- dot.categ + +} +} +tempo.diamon.mean <- data.frame(X = c(t(stat[, c("X", "X_NOTCH_INF", "X", "X_NOTCH_SUP", "X")])), Y = c(t(cbind(stat["MEAN"] - (stat[, "X"] - stat[, "X_NOTCH_INF"]) * tempo.yx.ratio, stat["MEAN"], stat["MEAN"] + (stat[, "X"] - stat[, "X_NOTCH_INF"]) * tempo.yx.ratio, stat["MEAN"], stat["MEAN"] - (stat[, "X"] - stat[, "X_NOTCH_INF"]) * tempo.yx.ratio, stringsAsFactors = TRUE))), COLOR = c(t(stat[, c("COLOR", "COLOR", "COLOR", "COLOR", "COLOR")])), GROUP = c(t(stat[, c("BOX", "BOX", "BOX", "BOX", "BOX")])), stringsAsFactors = TRUE) # stringsAsFactors = TRUE for cbind() because stat["MEAN"] is a data frame. Otherwise, stringsAsFactors is not an argument for cbind() on vectors +if( ! is.null(facet.categ)){ +for(i3 in 1:length(facet.categ)){ +tempo.diamon.mean <- data.frame(tempo.diamon.mean, c(t(stat[, c(facet.categ[i3], facet.categ[i3], facet.categ[i3], facet.categ[i3], facet.categ[i3])])), stringsAsFactors = TRUE) +names(tempo.diamon.mean)[length(names(tempo.diamon.mean))] <- facet.categ[i3] +} +} +tempo.diamon.mean$COLOR <- factor(tempo.diamon.mean$COLOR, levels = unique(categ.color)) +# end creation of the data frame for (main box + legend) and data frame for means +if(box.fill == TRUE){ +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[length(categ)], fill = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), width = box.width, size = box.line.size, notch = box.notch, coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf}, alpha = box.alpha, outlier.shape = if( ! is.null(dot.color)){NA}else{21}, outlier.color = if( ! is.null(dot.color)){NA}else{dot.border.color}, outlier.fill = if( ! is.null(dot.color)){NA}else{NULL}, outlier.size = if( ! is.null(dot.color)){NA}else{dot.size}, outlier.stroke = if( ! is.null(dot.color)){NA}else{dot.border.size}, outlier.alpha = if( ! is.null(dot.color)){NA}else{dot.alpha})) # the color, size, etc. of the outliers are dealt here. outlier.color = NA to do not plot outliers when dots are already plotted. Finally, boxplot redrawn (see below) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_polygon( +data = tempo.polygon, +mapping = ggplot2::aes_string(x = "X", y = "Y", group = "BOX", fill = categ[length(categ)], color = categ[length(categ)]), +size = box.line.size, +alpha = box.alpha # works only for fill, not for color +)) +coord.names <- c(coord.names, "main.box") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha)) # +coord.names <- c(coord.names, "sup.whisker") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_INF, yend = WHISK_INF, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha)) # +coord.names <- c(coord.names, "inf.whisker") +if(box.whisker.width > 0){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha, lineend = "round")) # +coord.names <- c(coord.names, "sup.whisker.edge") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_INF, yend = WHISK_INF, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha, lineend = "round")) # +coord.names <- c(coord.names, "inf.whisker.edge") +} +if(box.mean == TRUE){ +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = stat, mapping = ggplot2::aes_string(x = "X", y = "MEAN", group = categ[length(categ)]), shape = 23, stroke = box.line.size * 2, fill = stat$COLOR, size = box.mean.size, color = "black", alpha = box.alpha)) # group used in aesthetic to do not have it in the legend +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_polygon( +data = tempo.diamon.mean, +mapping = ggplot2::aes(x = X, y = Y, group = GROUP), +fill = tempo.diamon.mean[, "COLOR"], +color = hsv(0, 0, 0, alpha = box.alpha), # outline of the polygon in black but with alpha +size = box.line.size, +alpha = box.alpha +)) +coord.names <- c(coord.names, "mean") +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = if(box.notch == FALSE){X_BOX_INF}else{X_NOTCH_INF}, xend = if(box.notch == FALSE){X_BOX_SUP}else{X_NOTCH_SUP}, y = MEDIAN, yend = MEDIAN, group = categ[length(categ)]), color = "black", size = box.line.size * 2, alpha = box.alpha)) # +coord.names <- c(coord.names, "median") +} +# end boxplot display before dot display if box.fill = TRUE + + + + + + +# dot display +if( ! is.null(dot.color)){ +if(dot.tidy == FALSE){ +if(is.null(dot.categ)){ +if(dot.border.size == 0){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( +data = dot.coord.rd3, +mapping = ggplot2::aes_string(x = "dot.x", y = "y", group = categ[length(categ)]), +size = dot.size, +shape = 19, +color = dot.coord.rd3$dot.color, +alpha = dot.alpha +)) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic +}else{ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( +data = dot.coord.rd3, +mapping = ggplot2::aes_string(x = "dot.x", y = "y", group = categ[length(categ)]), +shape = 21, +stroke = dot.border.size, +color = if(is.null(dot.border.color)){dot.coord.rd3$dot.color}else{rep(dot.border.color, nrow(dot.coord.rd3))}, +size = dot.size, +fill = dot.coord.rd3$dot.color, +alpha = dot.alpha +)) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic +} +}else{ +if(dot.border.size == 0){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( +data = dot.coord.rd3, +mapping = ggplot2::aes_string(x = "dot.x", y = "y", alpha = dot.categ), +size = dot.size, +shape = 19, +color = dot.coord.rd3$dot.color +)) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic +}else{ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( +data = dot.coord.rd3, +mapping = ggplot2::aes_string(x = "dot.x", y = "y", alpha = dot.categ), +size = dot.size, +shape = 21, +stroke = dot.border.size, +color = if(is.null(dot.border.color)){dot.coord.rd3$dot.color}else{rep(dot.border.color, nrow(dot.coord.rd3))}, +fill = dot.coord.rd3$dot.color +)) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", name = dot.legend.name, values = rep(dot.alpha, length(dot.categ.class.order)), guide = ggplot2::guide_legend(override.aes = list(fill = dot.color, color = if(is.null(dot.border.color)){dot.color}else{dot.border.color}, stroke = dot.border.size, alpha = dot.alpha)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor +} +coord.names <- c(coord.names, "dots") +}else if(dot.tidy == TRUE){ +# here plot using group -> no scale +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_dotplot( +data = dot.coord, +mapping = ggplot2::aes_string(x = categ[1], y = "y", group = "group"), # not dot.categ here because the classes of dot.categ create new separations +position = ggplot2::position_dodge(width = box.width), +binpositions = "all", +binaxis = "y", +stackdir = "center", +alpha = dot.alpha, +fill = dot.coord$dot.color, +stroke = dot.border.size, +color = if(is.null(dot.border.color)){dot.coord$dot.color}else{rep(dot.border.color, nrow(dot.coord))}, +show.legend = FALSE, # WARNING: do not use show.legend = TRUE because it uses the arguments outside aes() as aesthetics (here color and fill). Thus I must find a way using ggplot2::scale_discrete_manual() +binwidth = (y.lim[2] - y.lim[1]) / dot.tidy.bin.nb +)) # geom_dotplot ggplot2 v3.3.0: I had to remove rev() in fill and color # very weird behavior of geom_dotplot ggplot2 v3.2.1, (1) because with aes group = (to avoid legend), the dot plotting is not good in term of coordinates, and (2) because data1 seems reorderer according to x = categ[1] before plotting. Thus, I have to use fill = dot.coord[rev(order(dot.coord[, categ[1]], decreasing = TRUE)), "dot.color"] to have the good corresponding colors # show.legend option do not remove the legend, only the aesthetic of the legend (dot, line, etc.) +coord.names <- c(coord.names, "dots") +if( ! is.null(dot.categ)){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_dotplot( +data = dot.coord, +mapping = ggplot2::aes_string(x = categ[1], y = "y", alpha = dot.categ), # not dot.categ here because the classes of dot.categ create new separations +position = ggplot2::position_dodge(width = box.width), +binpositions = "all", +binaxis = "y", +stackdir = "center", +fill = NA, +stroke = NA, +color = NA, +# WARNING: do not use show.legend = TRUE because it uses the arguments outside aes() as aesthetics (here color and fill). Thus I must find a way using ggplot2::scale_discrete_manual() +binwidth = (y.lim[2] - y.lim[1]) / dot.tidy.bin.nb +)) # geom_dotplot ggplot2 v3.3.0: I had to remove rev() in fill and color # very weird behavior of geom_dotplot ggplot2 v3.2.1, (1) because with aes group = (to avoid legend), the dot plotting is not good in term of coordinates, and (2) because data1 seems reorderer according to x = categ[1] before plotting. Thus, I have to use fill = dot.coord[rev(order(dot.coord[, categ[1]], decreasing = TRUE)), "dot.color"] to have the good corresponding colors # show.legend option do not remove the legend, only the aesthetic of the legend (dot, line, etc.) +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "linetype", name = dot.legend.name, values = rep(1, length(categ.color)))) # values = rep("black", length(categ.color)) are the values of color (which is the border color of dots), and this modify the border color on the plot. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor +coord.names <- c(coord.names, "bad_remove") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", name = dot.legend.name, values = rep(dot.alpha, length(dot.categ.class.order)), labels = dot.categ.class.order, guide = ggplot2::guide_legend(title = if(ini.dot.categ == categ[length(categ)]){dot.categ}else{ini.dot.categ}, override.aes = list(fill = levels(dot.coord$dot.color), color = if(is.null(dot.border.color)){levels(dot.coord$dot.color)}else{dot.border.color}, stroke = dot.border.size, alpha = dot.alpha)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor +} +# coordinates of tidy dots +tempo.coord <- ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))$data # to have the tidy dot coordinates +if(length(which(sapply(X = tempo.coord, FUN = function(X){any(names(X) == "binwidth", na.rm = TRUE)}))) != 1){ # detect the compartment of tempo.coord which is the binned data frame +# if(length(which(sapply(tempo.coord, FUN = nrow) == nrow(data1))) > if(is.null(dot.categ)){1}else{2}){ # this does not work if only one dot per class, thus replaced by above # if(is.null(dot.categ)){1}else{2} because 1 dotplot if dot.categ is NULL and 2 dotplots if not, with the second being a blank dotplot with wrong coordinates. Thus take the first in that situation +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nEITHER MORE THAN 1 OR NO COMPARTMENT HAVING A DATA FRAME WITH binwidth AS COLUMN NAME IN THE tempo.coord LIST (FOR TIDY DOT COORDINATES). CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +# dot.coord.tidy1 <- tempo.coord[[which(sapply(tempo.coord, FUN = nrow) == nrow(data1))[1]]] # this does not work if only one dot per class, thus replaced by above # the second being a blank dotplot with wrong coordinates. Thus take the first whatever situation +dot.coord.tidy1 <- tempo.coord[[which(sapply(X = tempo.coord, FUN = function(X){any(names(X) == "binwidth", na.rm = TRUE)}))]] # detect the compartment of tempo.coord which is the binned data frame +dot.coord.tidy1$x <- as.numeric(dot.coord.tidy1$x) # because weird class +dot.coord.tidy1$PANEL <- as.numeric(dot.coord.tidy1$PANEL) # because numbers as levels. But may be a problem is facet are reordered ? +} +# tempo.box.coord <- merge(box.coord, unique(dot.coord[, c("PANEL", "group", categ)]), by = c("PANEL", "group"), sort = FALSE) # not required anymore because box.coord already contains categ do not add dot.categ and tidy_group_coord here because the coordinates are for stats. Add the categ in box.coord. WARNING: by = c("PANEL", "group") without fill column because PANEL & group columns are enough as only one value of x column per group number in box.coord. Thus, no need to consider fill column +# below inactivated because not true when dealing with dot.categ different from categ +# if(nrow(tempo.box.coord) != nrow(box.coord)){ +# tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT tempo.box.coord DATA FRAME. CODE HAS TO BE MODIFIED") +# stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +# } +dot.coord.tidy2 <- merge(dot.coord.tidy1, box.coord[c("fill", "PANEL", "group", "x", categ)], by = c("PANEL", "group"), sort = FALSE) # send the coord of the boxes into the coord data.frame of the dots (in the column x.y).WARNING: by = c("PANEL", "group") without fill column because PANEL & group columns are enough as only one value of x column per group number in tempo.box.coord. Thus, no need to consider fill colum # DANGER: from here the fill.y and x.y (from tempo.box.coord) are not good in dot.coord.tidy2. It is ok because Categ1 Categ2 from tempo.box.coord are ok with the group column from dot.coord.tidy1. This is due to the fact that dot.coord.tidy resulting from geom_dotplot does not make the same groups as the other functions +if(nrow(dot.coord.tidy2) != nrow(dot.coord)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.tidy2 DATA FRAME. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# From here, check for dot.coord.tidy3 which wil be important for stat over the plot. WARNING: dot.categ has nothing to do here for stat coordinates. Thus, not in tempo.data1 +if(length(categ)== 1L){ +tempo.data1 <- unique(data.frame(data1[categ[1]], group = as.integer(data1[, categ[1]]), stringsAsFactors = TRUE)) # categ[1] is factor +names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") +verif <- paste0(categ[1], ".check") +}else if(length(categ) == 2L){ +tempo.data1 <- unique( +data.frame( +data1[c(categ[1], categ[2])], +group = as.integer(factor(paste0( +formatC(as.integer(data1[, categ[2]]), width = nchar(max(as.integer(data1[, categ[2]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking +".", +formatC(as.integer(data1[, categ[1]]), width = nchar(max(as.integer(data1[, categ[1]]), na.rm = TRUE)), flag = "0")# convert factor into numeric with leading zero for proper ranking +)), stringsAsFactors = TRUE) # merge the 2 formatC() to create a new factor. The convertion to integer should recreate the correct group number +) +) # categ[2] first if categ[2] is used to make the categories in ggplot and categ[1] is used to make the x-axis +names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") +names(tempo.data1)[names(tempo.data1) == categ[2]] <- paste0(categ[2], ".check") +verif <- c(paste0(categ[1], ".check"), paste0(categ[2], ".check")) +}else{ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 4") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +dot.coord.tidy3 <- merge(dot.coord.tidy2, tempo.data1, by = intersect("group", "group"), sort = FALSE) # send the factors of data1 into coord. WARNING: I have tested intersect("group", "group") instead of by = "group". May be come back to by = "group" in case of error. But I did this because of an error in dot.coord.rd3 above +if(nrow(dot.coord.tidy3) != nrow(dot.coord) | ( ! fun_comp_2d(dot.coord.tidy3[categ], dot.coord.tidy3[verif])$identical.content)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.tidy3 DATA FRAME. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end coordinates of tidy dots +} +} +# end dot display + + + +# boxplot display (if box.fill = FALSE, otherwise, already plotted above) +if(box.fill == TRUE){ +# overcome "work only for the filling of boxes, not for the frame. See https://github.com/tidyverse/ggplot2/issues/252" +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[length(categ)]])))}else{categ.color}, guide = ggplot2::guide_legend(order = 1))) #, guide = ggplot2::guide_legend(override.aes = list(fill = levels(tempo.polygon$COLOR), color = "black")))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = rep(hsv(0, 0, 0, alpha = box.alpha), length(unique(data1[, categ[length(categ)]]))), guide = ggplot2::guide_legend(order = 1))) # , guide = ggplot2::guide_legend(override.aes = list(color = "black", alpha = box.alpha)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor # outline of the polygon in black but with alpha +}else{ +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[length(categ)], fill = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), width = box.width, size = box.line.size, notch = box.notch, alpha = box.alpha, coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf}, outlier.shape = if( ! is.null(dot.color)){NA}else{21}, outlier.color = if( ! is.null(dot.color)){NA}else{if(dot.border.size == 0){NA}else{dot.border.color}}, outlier.fill = if( ! is.null(dot.color)){NA}else{NULL}, outlier.size = if( ! is.null(dot.color)){NA}else{dot.size}, outlier.stroke = if( ! is.null(dot.color)){NA}else{dot.border.size}, outlier.alpha = if( ! is.null(dot.color)){NA}else{dot.alpha})) # the color, size, etc. of the outliers are dealt here. outlier.color = NA to do not plot outliers when dots are already plotted +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_path( +data = tempo.polygon, +mapping = ggplot2::aes_string(x = "X", y = "Y", group = "BOX", color = categ[length(categ)]), +size = box.line.size, +alpha = box.alpha, +lineend = "round", +linejoin = "round" +)) +coord.names <- c(coord.names, "main.box") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = if(box.notch == FALSE){X_BOX_INF}else{X_NOTCH_INF}, xend = if(box.notch == FALSE){X_BOX_SUP}else{X_NOTCH_SUP}, y = MEDIAN, yend = MEDIAN, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size * 2, alpha = box.alpha)) # +coord.names <- c(coord.names, "median") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha)) # +coord.names <- c(coord.names, "sup.whisker") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_INF, yend = WHISK_INF, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha)) # +coord.names <- c(coord.names, "inf.whisker") +if(box.whisker.width > 0){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha, lineend = "round")) # +coord.names <- c(coord.names, "sup.whisker.edge") +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_INF, yend = WHISK_INF, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha, lineend = "round")) # +coord.names <- c(coord.names, "inf.whisker.edge") +} +if(box.mean == TRUE){ +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = stat, mapping = ggplot2::aes_string(x = "X", y = "MEAN", group = categ[length(categ)]), shape = 23, stroke = box.line.size * 2, color = stat$COLOR, size = box.mean.size, fill = NA, alpha = box.alpha)) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_path( +data = tempo.diamon.mean, +mapping = ggplot2::aes(x = X, y = Y, group = GROUP), +color = tempo.diamon.mean[, "COLOR"], +size = box.line.size, +alpha = box.alpha, +lineend = "round", +linejoin = "round" +)) +coord.names <- c(coord.names, "mean") +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = rep(NA, length(unique(data1[, categ[length(categ)]]))))) #, guide = ggplot2::guide_legend(override.aes = list(color = categ.color)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[length(categ)]])))}else{categ.color}, guide = ggplot2::guide_legend(override.aes = list(alpha = if(plot == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list()) == 0L & Sys.info()["sysname"] == "Windows"))){1}else{box.alpha})))) # , guide = ggplot2::guide_legend(override.aes = list(color = as.character(categ.color))))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor +if(plot == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list()) == 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 +# to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +if(box.alpha == 0){ # remove box legend because no boxes drawn +# add this after the scale_xxx_manual() for boxplots +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = "none", color = "none")) # inactivate the legend +} +# end boxplot display (if box.fill = FALSE, otherwise, already plotted above) + + + + +# stat display +# layer after dots but ok, behind dots on the plot +if( ! is.null(stat.pos)){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NUMBERS DISPLAYED ARE ", ifelse(stat.mean == FALSE, "MEDIANS", "MEANS")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +if(stat.pos == "top"){ +tempo.stat <- data.frame(stat, Y = y.lim[2], stringsAsFactors = TRUE) # I had to create a data frame for geom_tex() so that facet is taken into account, (ggplot2::annotate() does not deal with facet because no data and mapping arguments). Of note, facet.categ is in tempo.stat, via tempo.mean, via dot.coord +if(stat.mean == FALSE){tempo.stat$MEDIAN <- formatC(stat.nolog$MEDIAN, digit = 2, drop0trailing = TRUE, format = "f")}else{tempo.stat$MEAN <- formatC(stat.nolog$MEAN, digit = 2, drop0trailing = TRUE, format = "f")} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text( +data = tempo.stat, +mapping = ggplot2::aes_string(x = "X", y = "Y", label = ifelse(stat.mean == FALSE, "MEDIAN", "MEAN")), +size = stat.size, +color = "black", +angle = stat.angle, +hjust = stat.just$hjust, +vjust = stat.just$vjust +)) # stat$X used here because identical to stat.nolog but has the X. WARNING: no need of order() for labels because box.coord$x set the order. For justification, see https://stackoverflow.com/questions/7263849/what-do-hjust-and-vjust-do-when-making-a-plot-using-ggplot +coord.names <- c(coord.names, "stat.pos") +}else if(stat.pos == "above"){ +# stat coordinates +if( ! is.null(dot.color)){ # for text just above max dot +if(dot.tidy == FALSE){ +tempo.stat.ini <- dot.coord.rd3 +}else if(dot.tidy == TRUE){ +tempo.stat.ini <- dot.coord.tidy3 +tempo.stat.ini$x.y <- tempo.stat.ini$x.x # this is just to be able to use tempo.stat.ini$x.y for untidy or tidy dots (remember that dot.coord.tidy3$x.y is not good, see above) +} +stat.coord1 <- aggregate(x = tempo.stat.ini["y"], by = {x.env <- if(length(categ)== 1L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]])}else if(length(categ) == 2L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]], tempo.stat.ini[, categ[2]])} ; names(x.env) <- if(length(categ)== 1L){c("group", "PANEL", "x.y", categ[1])}else if(length(categ) == 2L){c("group", "PANEL", "x.y", categ[1], categ[2])} ; x.env}, FUN = min, na.rm = TRUE) +names(stat.coord1)[names(stat.coord1) == "y"] <- "dot.min" +stat.coord2 <- aggregate(x = tempo.stat.ini["y"], by = {x.env <- if(length(categ)== 1L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]])}else if(length(categ) == 2L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]], tempo.stat.ini[, categ[2]])} ; names(x.env) <- if(length(categ)== 1L){c("group", "PANEL", "x.y", categ[1])}else if(length(categ) == 2L){c("group", "PANEL", "x.y", categ[1], categ[2])} ; x.env}, FUN = max, na.rm = TRUE) +names(stat.coord2) <- paste0(names(stat.coord2), "_from.dot.max") +names(stat.coord2)[names(stat.coord2) == "y_from.dot.max"] <- "dot.max" +stat.coord3 <- cbind(box.coord[order(box.coord$group, box.coord$PANEL), ], stat.coord1[order(stat.coord1$group, stat.coord1$x.y), ], stat.coord2[order(stat.coord2$group, stat.coord2$x.y), ], stringsAsFactors = TRUE) # +if( ! all(identical(round(stat.coord3$x, 9), round(as.numeric(stat.coord3$x.y), 9)), na.rm = TRUE)){ # as.numeric() because stat.coord3$x is class "mapped_discrete" "numeric" +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nFUSION OF box.coord, stat.coord1 AND stat.coord2 ACCORDING TO box.coord$x, stat.coord1$x.y AND stat.coord2$x.y IS NOT CORRECT. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +# text.coord <- stat.coord3[, c("x", "group", "dot.min", "dot.max")] +# names(text.coord)[names(text.coord) == "dot.min"] <- "text.min.pos" +#names(text.coord)[names(text.coord) == "dot.max"] <- "text.max.pos" +box.coord <- box.coord[order(box.coord$x, box.coord$group, box.coord$PANEL), ] +# text.coord <- text.coord[order(text.coord$x), ] # to be sure to have the two objects in the same order for x. WARNING: cannot add identical(as.integer(text.coord$group), as.integer(box.coord$group)) because with error, the correspondence between x and group is not the same +stat.coord3 <- stat.coord3[order(stat.coord3$x, stat.coord3$group, stat.coord3$PANEL), ] # to be sure to have the two objects in the same order for x. WARNING: cannot add identical(as.integer(text.coord$group), as.integer(box.coord$group)) because with error, the correspondence between x and group is not the same +if( ! (identical(box.coord$x, stat.coord3$x) & identical(box.coord$group, stat.coord3$group) & identical(box.coord$PANEL, stat.coord3$PANEL))){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\ntext.coord AND box.coord DO NOT HAVE THE SAME x, group AND PANEL COLUMN CONTENT") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +}else{ +stat.coord3 <- box.coord +} +stat.coord3 <- data.frame( +stat.coord3, +Y = stat.coord3[, ifelse( +is.null(dot.color), +ifelse(diff(y.lim) > 0, "ymax", "ymin"), +ifelse(diff(y.lim) > 0, "ymax_final", "ymin_final") +)], +stringsAsFactors = TRUE +) # ymax is top whisker, ymax_final is top dot +# stat.coord3 <- data.frame(stat.coord3, Y = vector("numeric", length = nrow(stat.coord3)), stringsAsFactors = TRUE) +# check.Y <- as.logical(stat.coord3$Y) # convert everything in Y into FALSE (because Y is full of zero) +# end stat coordinates +# stat display +# performed twice: first for y values >=0, then y values < 0, because only a single value allowed for hjust anf vjust +if(stat.mean == FALSE){ +tempo.center.ref <- "middle" +}else{ +tempo.center.ref <- "MEAN" +} +# if(is.null(dot.color)){ +# tempo.low.ref <- "ymin" +# tempo.high.ref <- "ymax" +# }else{ +# tempo.low.ref <- "ymin_final" +# tempo.high.ref <- "ymax_final" +# } +# tempo.log.high <- if(diff(y.lim) > 0){stat.coord3[, tempo.center.ref] >= 0}else{stat.coord3[, tempo.center.ref] < 0} +# tempo.log.low <- if(diff(y.lim) > 0){stat.coord3[, tempo.center.ref] < 0}else{stat.coord3[, tempo.center.ref] >= 0} +# stat.coord3$Y[tempo.log.high] <- stat.coord3[tempo.log.high, tempo.high.ref] +# stat.coord3$Y[tempo.log.low] <- stat.coord3[tempo.log.low, tempo.low.ref] +# add distance +stat.coord3$Y <- stat.coord3$Y + diff(y.lim) * stat.dist / 100 +# end add distance +# correct median or mean text format +if(y.log != "no"){ +stat.coord3[, tempo.center.ref] <- ifelse(y.log == "log2", 2, 10)^(stat.coord3[, tempo.center.ref]) +} +stat.coord3[, tempo.center.ref] <- formatC(stat.coord3[, tempo.center.ref], digit = 2, drop0trailing = TRUE, format = "f") +# end correct median or mean text format +# if(any(tempo.log.high) == TRUE){ +# tempo.stat <- stat.coord3[tempo.log.high,] +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text( +data = stat.coord3, +mapping = ggplot2::aes_string(x = "x", y = "Y", label = tempo.center.ref), +size = stat.size, +color = "black", +angle = stat.angle, +hjust = stat.just$hjust, +vjust = stat.just$vjust +)) # WARNING: no need of order() for labels because box.coord$x set the order +coord.names <- c(coord.names, "stat.pos") +# } +# if(any(tempo.log.low) == TRUE){ +# tempo.stat <- stat.coord3[tempo.log.low,] +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text( +# data = tempo.stat, +# mapping = ggplot2::aes_string(x = "x", y = "Y", label = tempo.center.ref), +# size = stat.size, +# color = "black", +# hjust = ifelse(vertical == TRUE, 0.5, 0.5 + stat.dist), +# vjust = ifelse(vertical == TRUE, 0.5 + stat.dist, 0.5) +# )) # WARNING: no need of order() for labels because box.coord$x set the order +# coord.names <- c(coord.names, "stat.pos.negative") +# } +# end stat display +}else{ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 5") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +} +# end stat display +# legend management +if(legend.show == FALSE){ # must be here because must be before bef.final.plot <- +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = "none", color = "none", alpha = "none")) # inactivate the initial legend +} +# end legend management + + + +# y scale management (cannot be before dot plot management) +# the rescaling aspect is complicated and not intuitive. See: +# explaination: https://github.com/tidyverse/ggplot2/issues/3948 +# the oob argument of scale_y_continuous() https://ggplot2.tidyverse.org/reference/scale_continuous.html +# see also https://github.com/rstudio/cheatsheets/blob/master/data-visualization-2.1.pdf +# secondary ticks +bef.final.plot <- ggplot2::ggplot_build(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), ' + if(vertical == TRUE){ggplot2::scale_y_continuous(expand = c(0, 0), limits = sort(y.lim), oob = scales::rescale_none)}else{ggplot2::coord_flip(ylim = y.lim)}')))) # here I do not need the x-axis and y-axis orientation, I just need the number of main ticks and the legend. I DI NOT UNDERSTAND THE COMMENT HERE BECAUSE WE NEED COORD_FLiP +tempo.coord <- bef.final.plot$layout$panel_params[[1]] +# y.second.tick.positions: coordinates of secondary ticks (only if y.second.tick.nb argument is non NULL or if y.log argument is different from "no") +if(y.log != "no"){ # integer main ticks for log2 and log10 +tempo.scale <- (as.integer(min(y.lim, na.rm = TRUE)) - 1):(as.integer(max(y.lim, na.rm = TRUE)) + 1) +}else{ +tempo <- if(is.null(attributes(tempo.coord$y$breaks))){tempo.coord$y$breaks}else{unlist(attributes(tempo.coord$y$breaks))} +if(all(is.na(tempo))){# all() without na.rm -> ok because is.na() cannot be NA +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nONLY NA IN tempo.coord$y$breaks") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +} +tempo.scale <- fun_scale(lim = y.lim, n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) # in ggplot 3.3.0, tempo.coord$y.major_source replaced by tempo.coord$y$breaks. If fact: n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) replaced by n = ifelse(is.null(y.tick.nb), 4, y.tick.nb)) +} +y.second.tick.values <- NULL +y.second.tick.pos <- NULL +if(y.log != "no"){ +tempo <- fun_inter_ticks(lim = y.lim, log = y.log) +y.second.tick.values <- tempo$values +y.second.tick.pos <- tempo$coordinates +# if(vertical == TRUE){ # do not remove in case the bug is fixed +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", y = y.second.tick.pos, yend = y.second.tick.pos, x = tempo.coord$x.range[1], xend = tempo.coord$x.range[1] + diff(tempo.coord$x.range) / 80)) +# }else{ # not working because of the ggplot2 bug +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", x = y.second.tick.pos, xend = y.second.tick.pos, y = tempo.coord$y.range[1], yend = tempo.coord$y.range[1] + diff(tempo.coord$y.range) / 80)) +# } +coord.names <- c(coord.names, "y.second.tick.positions") +}else if(( ! is.null(y.second.tick.nb)) & y.log == "no"){ +# if(y.second.tick.nb > 0){ #inactivated because already checked before +if(length(tempo.scale) < 2){ +tempo.cat1 <- c("y.tick.nb", "y.second.tick.nb") +tempo.cat2 <- sapply(list(y.tick.nb, y.second.tick.nb), FUN = paste0, collapse = " ") +tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") +tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE NUMBER OF GENERATED TICKS FOR THE Y-AXIS IS NOT CORRECT: ", length(tempo.scale), "\nUSING THESE ARGUMENT SETTINGS (NO DISPLAY MEANS NULL VALUE):\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n"), "\nPLEASE, TEST OTHER VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +tempo <- fun_inter_ticks(lim = y.lim, log = y.log, breaks = tempo.scale, n = y.second.tick.nb) +} +y.second.tick.values <- tempo$values +y.second.tick.pos <- tempo$coordinates +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( +geom = "segment", +y = y.second.tick.pos, +yend = y.second.tick.pos, +x = if(vertical == TRUE){tempo.coord$x.range[1]}else{tempo.coord$y.range[1]}, +xend = if(vertical == TRUE){tempo.coord$x.range[1] + diff(tempo.coord$x.range) / 80}else{tempo.coord$y.range[1] + diff(tempo.coord$y.range) / 80} +)) +coord.names <- c(coord.names, "y.second.tick.positions") +} +# end y.second.tick.positions +# for the ggplot2 bug with y.log, this does not work: eval(parse(text = ifelse(vertical == FALSE & y.log == "log10", "ggplot2::scale_x_continuous", "ggplot2::scale_y_continuous"))) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_y_continuous( +breaks = tempo.scale, +minor_breaks = y.second.tick.pos, +labels = if(y.log == "log10"){scales::trans_format("identity", scales::math_format(10^.x))}else if(y.log == "log2"){scales::trans_format("identity", scales::math_format(2^.x))}else if(y.log == "no"){ggplot2::waiver()}else{tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 6") ; stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)}, # == in stop() to be able to add several messages between == +expand = c(0, 0), # remove space after after axis limits +limits = sort(y.lim), # NA indicate that limits must correspond to data limits but ylim() already used +oob = scales::rescale_none, +trans = ifelse(diff(y.lim) < 0, "reverse", "identity") # equivalent to ggplot2::scale_y_reverse() but create the problem of y-axis label disappearance with y.lim decreasing. Thus, do not use. Use ylim() below and after this +)) +if(vertical == TRUE){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_cartesian(ylim = y.lim)) # problem of ggplot2::ylim() is that it redraws new breaks # coord_cartesian(ylim = y.lim)) not used because bug -> y-axis label disappearance with y.lim decreasing I DO NOT UNDERSTAND THIS MESSAGE WHILE I USE COORD_CARTESIAN # clip = "off" to have secondary ticks outside plot region does not work +}else{ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_flip(ylim = y.lim)) # clip = "off" to have secondary ticks outside plot region does not work # create the problem of y-axis label disappearance with y.lim decreasing. IDEM ABOVE + +} +# end y scale management (cannot be before dot plot management) + + +# legend management +if( ! is.null(legend.width)){ +legend.final <- fun_gg_get_legend(ggplot_built = bef.final.plot, fun.name = function.name, lib.path = lib.path) # get legend +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = "none", color = "none", alpha = "none")) # inactivate the initial legend +if(is.null(legend.final) & plot == TRUE){ # even if any(unlist(legend.disp)) is TRUE +legend.final <- ggplot2::ggplot()+ggplot2::theme_void() # empty graph instead of legend +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") LEGEND REQUESTED (NON NULL categ ARGUMENT OR legend.show ARGUMENT SET TO TRUE)\nBUT IT SEEMS THAT THE PLOT HAS NO LEGEND -> EMPTY LEGEND SPACE CREATED BECAUSE OF THE NON NULL legend.width ARGUMENT\n") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end legend management + + +# drawing +fin.plot <- suppressMessages(suppressWarnings(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))) +grob.save <- NULL +if(plot == TRUE){ +# following lines inactivated because of problem in warn.recov and message.recov +# assign("env_fun_get_message", new.env()) +# assign("tempo.gg.name", tempo.gg.name, envir = env_fun_get_message) +# assign("tempo.gg.count", tempo.gg.count, envir = env_fun_get_message) +# assign("add", add, envir = env_fun_get_message) +# two next line: for the moment, I cannot prevent the warning printing +# warn.recov <- fun_get_message(paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add}), kind = "warning", header = FALSE, print.no = FALSE, env = env_fun_get_message) # for recovering warnings printed by ggplot() functions +# message.recov <- fun_get_message('print(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add}))))', kind = "message", header = FALSE, print.no = FALSE, env = env_fun_get_message) # for recovering messages printed by ggplot() functions +# if( ! (return == TRUE & return.ggplot == TRUE)){ # because return() plots when return.ggplot is TRUE # finally not used -> see return.ggplot description +if(is.null(legend.width)){ +grob.save <- suppressMessages(suppressWarnings(gridExtra::grid.arrange(fin.plot))) +}else{ +grob.save <-suppressMessages(suppressWarnings(gridExtra::grid.arrange(fin.plot, legend.final, ncol=2, widths=c(1, legend.width)))) +} +# } +# suppressMessages(suppressWarnings(print(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add})))))) +}else{ +# following lines inactivated because of problem in warn.recov and message.recov +# message.recov <- NULL +# warn.recov <- NULL +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") PLOT NOT SHOWN AS REQUESTED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# end drawing + + + +# output +# following lines inactivated because of problem in warn.recov and message.recov +# if( ! (is.null(warn) & is.null(warn.recov) & is.null(message.recov))){ +# warn <- paste0(warn, "\n\n", if(length(warn.recov) > 0 | length(message.recov) > 0){paste0(paste0("MESSAGES FROM ggplot2 FUNCTIONS: ", ifelse( ! is.null(warn.recov), unique(message.recov), ""), ifelse( ! is.null(message.recov), unique(message.recov), ""), collapse = "\n\n"), "\n\n")}) +# }else if( ! (is.null(warn) & is.null(warn.recov)) & is.null(message.recov)){ +# warn <- paste0(warn, "\n\n", if(length(warn.recov) > 0){paste0(paste0("MESSAGES FROM ggplot2 FUNCTIONS: ", unique(warn.recov), collapse = "\n\n"), "\n\n")}) +# }else if( ! (is.null(warn) & is.null(message.recov)) & is.null(warn.recov)){ +# warn <- paste0(warn, "\n\n", if(length(message.recov) > 0){paste0(paste0("MESSAGES FROM ggplot2 FUNCTIONS: ", unique(message.recov), collapse = "\n\n"), "\n\n")}) +# } +if(warn.print == TRUE & ! is.null(warn)){ +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) +} +on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) +if(return == TRUE){ +tempo.output <- ggplot2::ggplot_build(fin.plot) +tempo.output$data <- tempo.output$data[-1] # remove the first data because corresponds to the initial empty boxplot +if(length(tempo.output$data) != length(coord.names)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nlength(tempo.output$data) AND length(coord.names) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +names(tempo.output$data) <- coord.names +tempo.output$data <- tempo.output$data[coord.names != "bad_remove"] +} +tempo <- tempo.output$layout$panel_params[[1]] +output <- list( +data = data1.ini, +stat = stat.nolog, +removed.row.nb = removed.row.nb, +removed.rows = removed.rows, +plot = c(tempo.output$data, y.second.tick.values = list(y.second.tick.values)), +panel = facet.categ, +axes = list( +x.range = tempo$x.range, +x.labels = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{tempo$x$scale$get_labels()}, # is.null(attributes(tempo$x$breaks)) test if it is number (TRUE) or character (FALSE) +x.positions = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{unlist(attributes(tempo$x$breaks))}, +y.range = tempo$y.range, +y.labels = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{tempo$y$scale$get_labels()}, +y.positions = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{unlist(attributes(tempo$y$breaks))} +), +warn = paste0("\n", warn, "\n\n"), +ggplot = if(return.ggplot == TRUE){fin.plot}else{NULL}, # fin.plot plots the graph if return == TRUE +gtable = if(return.gtable == TRUE){grob.save}else{NULL} +) +return(output) # this plots the graph if return.ggplot is TRUE and if no assignment +} +# end output +# end main code +} + + + + + + +# add density +# rasterise all kind: https://cran.r-project.org/web/packages/ggrastr/vignettes/Raster_geoms.html + + +fun_gg_scatter <- function( +data1, +x, +y, +categ = NULL, +categ.class.order = NULL, +color = NULL, +geom = "geom_point", +geom.step.dir = "hv", +geom.stick.base = NULL, +alpha = 0.5, +dot.size = 2, +dot.shape = 21, +dot.border.size = 0.5, +dot.border.color = NULL, +line.size = 0.5, +line.type = "solid", +x.lim = NULL, +x.lab = NULL, +x.log = "no", +x.tick.nb = NULL, +x.second.tick.nb = NULL, +x.include.zero = FALSE, +x.left.extra.margin = 0.05, +x.right.extra.margin = 0.05, +x.text.angle = 0, +y.lim = NULL, +y.lab = NULL, +y.log = "no", +y.tick.nb = NULL, +y.second.tick.nb = NULL, +y.include.zero = FALSE, +y.top.extra.margin = 0.05, +y.bottom.extra.margin = 0.05, +y.text.angle = 0, +raster = FALSE, +raster.ratio = 1, +raster.threshold = NULL, +text.size = 12, +title = "", +title.text.size = 12, +legend.show = TRUE, +legend.width = 0.5, +legend.name = NULL, +article = TRUE, +grid = FALSE, +add = NULL, +return = FALSE, +return.ggplot = FALSE, +return.gtable = TRUE, +plot = TRUE, +warn.print = FALSE, +lib.path = NULL +){ +# AIM +# Plot ggplot2 scatterplot with the possibility to overlay dots from up to 3 different data frames (-> three different legends) and lines from up to 3 different data frames (-> three different legends) -> up to 6 overlays totally +# For ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html +# WARNINGS +# Rows containing NA in data1[, c(x, y, categ)] will be removed before processing, with a warning (see below) +# Size arguments (dot.size, dot.border.size, line.size, text.size and title.text.size) are in mm. See Hadley comment in https://stackoverflow.com/questions/17311917/ggplot2-the-unit-of-size. See also http://sape.inf.usi.ch/quick-reference/ggplot2/size). Unit object are not accepted, but conversion can be used (e.g., grid::convertUnit(grid::unit(0.2, "inches"), "mm", valueOnly = TRUE)) +# ARGUMENTS +# data1: a dataframe compatible with ggplot2, or a list of data frames. Order matters for the order of the legend and for the layer staking (starting from below to top) +# x: single character string of the data1 column name for x-axis coordinates. If data1 is a list, then x must be a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Write NULL for each "geom_hline" in geom argument +# y: single character string of the data1 column name for y-axis coordinates. If data1 is a list, then y must be a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Write NULL for each "geom_vline" in geom argument +# categ: either NULL or a single character string or a list of single character strings, indicating the data1 column names to use for categories which creates legend display +# If categ == NULL, no categories -> no legend displayed +# If data1 is a data frame, categ must be a single character string of the data1 column name for categories +# If data1 is a list, then categ must be a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Some of the list compartments can be NULL (no legend display for these compartments), and other not +# categ.class.order: either (1) NULL or (2) a vector of character strings or (3) a list of these vectors, setting the order of the classes of categ in the legend display +# If categ.class.order is NULL, classes are represented according to the alphabetical order +# If data1 is a data frame, categ.class.order must be a vector of character strings specifying the different classes in the categ column name of data1 +# If data1 is a list, then categ.class.order must be a list of vector of character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Some of the list compartments can be NULL (alphabetical order for these compartments), and other not +# color: either (1) NULL, or (2) a vector of character strings or integers, or (3) a list of vectors of character strings or integers +# If color is NULL, default colors of ggplot2 +# If data1 is a data frame, color argument can be either: +# (1) a single color string. All the dots of the corresponding data1 will have this color, whatever the categ value (NULL or not) +# (2) if categ is non-null, a vector of string colors, one for each class of categ. Each color will be associated according to the categ.class.order argument if specified, or to the alphabetical order of categ classes otherwise +# (3) if categ is non-null, a vector or factor of string colors, like if it was one of the column of data1 data frame. WARNING: a single color per class of categ and a single class of categ per color must be respected +# Positive integers are also accepted instead of character strings, as long as above rules about length are respected. Integers will be processed by fun_gg_palette() using the max integer value among all the integers in color (see fun_gg_palette()) +# If data1 is a list, then color argument must be either: +# (1) a list of character strings or integers, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. +# (2) a single character string or a single integer +# With a list (first possibility), the rules described for when data1 is a data frame apply to each compartment of the list. Some of the compartments can be NULL. In that case, a different grey color will be used for each NULL compartment. With a single value (second possibility), the same color will be used for all the dots and lines, whatever the data1 list +# geom: single character string of the kind of plot, or a list of single character strings +# Either: +# "geom_point" (scatterplot) +# "geom_line" (coordinates plotted then line connection, from the lowest to highest x coordinates first and from the lowest to highest y coordinates thenafter) +# "geom_path" (coordinates plotted then line connection respecting the row order in data1) +# "geom_step" coordinates plotted then line connection respecting the row order in data1 but drawn in steps). See the geom.step.dir argument +# "geom_hline" (horizontal line, no x value provided) +# "geom_vline" (vertical line, no y value provided) +# "geom_stick" (dots as vertical bars) +# If data1 is a list, then geom must be either: +# (1) a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. +# (2) a single character string. In that case the same kind of plot will apply for the different compartments of the data1 list +# WARNING concerning "geom_hline" or "geom_vline": +# (1) x or y argument must be NULL, respectively +# (2) x.lim or y.lim argument must NOT be NULL, respectively, if only these kind of lines are drawn (if other geom present, then x.lim = NULL and y.lim = NULL will generate x.lim and y.lim defined by these other geom, which is not possible with "geom_hline" or "geom_vline" alone) +# (3) the function will draw n lines for n values in the x argument column name of the data1 data frame. If several colors required, the categ argument must be specified and the corresponding categ column name must exist in the data1 data frame with a different class name for each row +# geom.step.dir: single character string indicating the direction when using "geom_step" of the geom argument, or a list of single character strings +# Either: +# "vh" (vertical then horizontal) +# "hv" (horizontal then vertical) +# "mid" (step half-way between adjacent x-values) +# See https://ggplot2.tidyverse.org/reference/geom_path.html +# If data1 is a list, then geom.step.dir must be either: +# (1) a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. The value in compartments related to other geom values than "geom_step" will be ignored +# (2) a single character string, which will be used for all the "geom_step" values of the geom argument, whatever the data1 list +# geom.stick.base: either (1) NULL or (2) a single numeric value or (3) a list of single numeric values, setting the base of the sticks when using "geom_stick" of the geom argument +# If geom.stick.base is NULL, the bottom limit of the y-axis is taken as the base +# If data1 is a list, then geom.stick.base must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the values in compartments related to other geom values than "geom_stick" will be ignored. With a single value (latter possibility), the same base will be used for all the sticks, whatever the data1 list +# Warning: the y-axis limits are not modified by the value of geom.stick.base, meaning that this value can be outside of the range of y.lim. Add the value of geom.stick.base also in the y.lim argument if required +# Warning: if geom.stick.base is NULL, the bottom limit of the y-axis is taken as the base. Thus, be careful with inverted y-axis +# alpha: single numeric value (from 0 to 1) of transparency. If data1 is a list, then alpha must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. In that case the same transparency will apply for the different compartments of the data1 list +# dot.size: single numeric value of dot shape radius? in mm. If data1 is a list, then dot.size must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the value in compartments related to lines will be ignored. With a single value (latter possibility), the same dot.size will be used for all the dots, whatever the data1 list +# dot.shape: value indicating the shape of the dots (see https://ggplot2.tidyverse.org/articles/ggplot2-specs.html) If data1 is a list, then dot.shape must be either (1) a list of single shape values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single shape value. With a list (former possibility), the value in compartments related to lines will be ignored. With a single value (latter possibility), the same dot.shape will be used for all the dots, whatever the data1 list +# dot.border.size: single numeric value of border dot width in mm. Write zero for no dot border. If data1 is a list, then dot.border.size must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the value in compartments related to lines will be ignored. With a single value (latter possibility), the same dot.border.size will be used for all the dots, whatever the data1 list +# dot.border.color: single character color string defining the color of the dot border (same border color for all the dots, whatever their categories). If dot.border.color == NULL, the border color will be the same as the dot color. A single integer is also accepted instead of a character string, that will be processed by fun_gg_palette() +# line.size: single numeric value of line width in mm. If data1 is a list, then line.size must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the value in compartments related to dots will be ignored. With a single value (latter possibility), the same line.size will be used for all the lines, whatever the data1 list +# line.type: value indicating the kind of lines (see https://ggplot2.tidyverse.org/articles/ggplot2-specs.html) If data1 is a list, then line.type must be either (1) a list of single line kind values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single line kind value. With a list (former possibility), the value in compartments related to dots will be ignored. With a single value (latter possibility), the same line.type will be used for all the lines, whatever the data1 list +# x.lim: 2 numeric values setting the x-axis range. Order of the 2 values matters (for inverted axis). If NULL, the range of the x column name of data1 will be used +# x.lab: a character string or expression for x-axis label. If NULL, will use the first value of x (x column name of the first data frame in data1). Warning message if the elements in x are different between data frames in data1 +# x.log: either "no", "log2" (values in the x column name of the data1 data frame will be log2 transformed and x-axis will be log2 scaled) or "log10" (values in the x column name of the data1 data frame will be log10 transformed and x-axis will be log10 scaled) +# x.tick.nb: approximate number of desired values labeling the x-axis (i.e., main ticks, see the n argument of the the cute::fun_scale() function). If NULL and if x.log is "no", then the number of labeling values is set by ggplot2. If NULL and if x.log is "log2" or "log10", then the number of labeling values corresponds to all the exposant integers in the x.lim range (e.g., 10^1, 10^2 and 10^3, meaning 3 main ticks for x.lim = c(9, 1200)). WARNING: if non-NULL and if x.log is "log2" or "log10", labeling can be difficult to read (e.g., ..., 10^2, 10^2.5, 10^3, ...) +# x.second.tick.nb: number of desired secondary ticks between main ticks. Ignored if x.log is other than "no" (log scale plotted). Use argument return = TRUE and see $plot$x.second.tick.values to have the values associated to secondary ticks. IF NULL, no secondary ticks +# x.include.zero: logical. Does x.lim range include 0? Ignored if x.log is "log2" or "log10" +# x.left.extra.margin: single proportion (between 0 and 1) indicating if extra margins must be added to x.lim. If different from 0, add the range of the axis multiplied by x.left.extra.margin (e.g., abs(x.lim[2] - x.lim[1]) * x.left.extra.margin) to the left of x-axis +# x.right.extra.margin: idem as x.left.extra.margin but to the right of x-axis +# x.text.angle: integer value of the text angle for the x-axis labeling values, using the same rules as in ggplot2. Use positive value for clockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Use negative values for counterclockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. +# y.lim: 2 numeric values setting the y-axis range. Order of the 2 values matters (for inverted axis). If NULL, the range of the y column name of data1 will be used +# y.lab: a character string or expression for y-axis label. If NULL, will use the first value of y (y column name of the first data frame in data1). Warning message if the elements in y are different between data frames in data1 +# y.log: either "no", "log2" (values in the y column name of the data1 data frame will be log2 transformed and y-axis will be log2 scaled) or "log10" (values in the y column name of the data1 data frame will be log10 transformed and y-axis will be log10 scaled) +# y.tick.nb: approximate number of desired values labeling the y-axis (i.e., main ticks, see the n argument of the the cute::fun_scale() function). If NULL and if y.log is "no", then the number of labeling values is set by ggplot2. If NULL and if y.log is "log2" or "log10", then the number of labeling values corresponds to all the exposant integers in the y.lim range (e.g., 10^1, 10^2 and 10^3, meaning 3 main ticks for y.lim = c(9, 1200)). WARNING: if non-NULL and if y.log is "log2" or "log10", labeling can be difficult to read (e.g., ..., 10^2, 10^2.5, 10^3, ...) +# y.second.tick.nb: number of desired secondary ticks between main ticks. Ignored if y.log is other than "no" (log scale plotted). Use argument return = TRUE and see $plot$y.second.tick.values to have the values associated to secondary ticks. IF NULL, no secondary ticks +# y.include.zero: logical. Does y.lim range include 0? Ignored if y.log is "log2" or "log10" +# y.top.extra.margin: single proportion (between 0 and 1) indicating if extra margins must be added to y.lim. If different from 0, add the range of the axis multiplied by y.top.extra.margin (e.g., abs(y.lim[2] - y.lim[1]) * y.top.extra.margin) to the top of y-axis +# y.bottom.extra.margin: idem as y.top.extra.margin but to the bottom of y-axis +# y.text.angle: integer value of the text angle for the y-axis labeling values, using the same rules as in ggplot2. Use positive value for clockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Use negative values for counterclockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. +# raster: logical. Dots in raster mode? If FALSE, dots from each "geom_point" from geom argument are plotted in vectorial mode (bigger pdf and long to display if lots of dots). If TRUE, dots from each "geom_point" from geom argument are plotted in matricial mode (smaller pdf and easy display if lots of dots, but it takes time to generate the layer). If TRUE, the raster.ratio argument is used to avoid an ellipsoid representation of the dots. If TRUE, solve the transparency problem with some GUI. Overriden by the non-NULL raster.threshold argument +# raster.ratio: single numeric value indicating the height / width ratio of the graphic device used (for instance provided by the $dim compartment in the output of the fun_open() function). The default value is 1 because by default R opens a square graphic device. But this argument has to be set when using other device dimensions. Ignored if raster == FALSE +# raster.threshold: positive integer value indicating the limit of the dot number above which "geom_point" layers from the geom argument switch from vectorial mode to matricial mode (see the raster argument). If any layer is matricial, then the raster.ratio argument is used to avoid an ellipsoid representation of the dots. If non-NULL, it overrides the raster argument +# text.size: numeric value of the font size of the (1) axis numbers and axis legends and (2) texts in the graphic legend (in mm) +# title: character string of the graph title +# title.text.size: numeric value of the title font size in mm +# legend.show: logical. Show legend? Not considered if categ argument is NULL, because this already generate no legend, excepted if legend.width argument is non-NULL. In that specific case (categ is NULL, legend.show is TRUE and legend.width is non-NULL), an empty legend space is created. This can be useful when desiring graphs of exactly the same width, whatever they have legends or not +# legend.width: single proportion (between 0 and 1) indicating the relative width of the legend sector (on the right of the plot) relative to the width of the plot. Value 1 means that the window device width is split in 2, half for the plot and half for the legend. Value 0 means no room for the legend, which will overlay the plot region. Write NULL to inactivate the legend sector. In such case, ggplot2 will manage the room required for the legend display, meaning that the width of the plotting region can vary between graphs, depending on the text in the legend +# legend.name: character string of the legend title. If legend.name is NULL and categ argument is not NULL, then legend.name <- categ. If data1 is a list, then legend.name must be a list of character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Some of the list compartments can be NULL, and other not +# article: logical. If TRUE, use an article theme (article like). If FALSE, use a classic related ggplot theme. Use the add argument (e.g., add = "+ggplot2::theme_classic()" for the exact classic ggplot theme +# grid: logical. Draw lines in the background to better read the box values? Not considered if article == FALSE (grid systematically present) +# add: character string allowing to add more ggplot2 features (dots, lines, themes, facet, etc.). Ignored if NULL +# WARNING: (1) the string must start with "+", (2) the string must finish with ")" and (3) each function must be preceded by "ggplot2::". Example: "+ ggplot2::coord_flip() + ggplot2::theme_bw()" +# If the character string contains the "ggplot2::theme" string, then the article argument of fun_gg_scatter() (see above) is ignored with a warning. In addition, some arguments can be overwritten, like x.angle (check all the arguments) +# Handle the add argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions +# WARNING: the call of objects inside the quotes of add can lead to an error if the name of these objects are some of the fun_gg_scatter() arguments. Indeed, the function will use the internal argument instead of the global environment object. Example article <- "a" in the working environment and add = '+ ggplot2::ggtitle(article)'. The risk here is to have TRUE as title. To solve this, use add = '+ ggplot2::ggtitle(get("article", envir = .GlobalEnv))' +# return: logical. Return the graph parameters? +# return.ggplot: logical. Return the ggplot object in the output list? Ignored if return argument is FALSE. WARNING: always assign the fun_gg_scatter() function (e.g., a <- fun_gg_scatter()) if return.ggplot argument is TRUE, otherwise, double plotting is performed. See $ggplot in the RETURN section below for more details +# return.gtable: logical. Return the ggplot object as gtable of grobs in the output list? Ignored if plot argument is FALSE. Indeed, the graph must be plotted to get the grobs dispositions. See $gtable in the RETURN section below for more details +# plot: logical. Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting +# warn.print: logical. Print warnings at the end of the execution? ? If FALSE, warning messages are never printed, but can still be recovered in the returned list. Some of the warning messages (those delivered by the internal ggplot2 functions) are not apparent when using the argument plot = FALSE +# lib.path: character string indicating the absolute path of the required packages (see below). if NULL, the function will use the R library default folders +# RETURN +# a scatter plot if plot argument is TRUE +# a list of the graph info if return argument is TRUE: +# $data: the initial data with graphic information added. WARNING: if the x.log or y.log argument is not "no", x or y argument column of the data1 data frame are log2 or log10 converted in $data, respectively. Use 2^values or 10^$values to recover the initial values +# $removed.row.nb: a list of the removed rows numbers in data frames (because of NA). NULL if no row removed +# $removed.rows: a list of the removed rows in data frames (because of NA). NULL if no row removed +# $plot: the graphic box and dot coordinates +# $dots: dot coordinates +# y.second.tick.positions: coordinates of secondary ticks (only if y.second.tick.nb argument is non-null or if y.log argument is different from "no") +# y.second.tick.values: values of secondary ticks. NULL except if y.second.tick.nb argument is non-null or if y.log argument is different from "no") +# $panel: the variable names used for the panels (NULL if no panels). WARNING: NA can be present according to ggplot2 upgrade to v3.3.0 +# $axes: the x-axis and y-axis info +# $warn: the warning messages. Use cat() for proper display. NULL if no warning. WARNING: warning messages delivered by the internal ggplot2 functions are not apparent when using the argument plot = FALSE +# $ggplot: ggplot object that can be used for reprint (use print($ggplot) or update (use $ggplot + ggplot2::...). NULL if return.ggplot argument is FALSE. Of note, a non-null $ggplot in the output list is sometimes annoying as the manipulation of this list prints the plot +# $gtable: gtable object that can be used for reprint (use gridExtra::grid.arrange(...$ggplot) or with additionnal grobs (see the grob decomposition in the examples). NULL if return.ggplot argument is FALSE. Contrary to $ggplot, a non-NULL $gtable in the output list is not annoying as the manipulation of this list does not print the plot +# REQUIRED PACKAGES +# ggplot2 +# gridExtra +# lemon (in case of use in the add argument) +# scales +# if raster plots are drawn (see the raster and raster.threshold arguments): +# Cairo +# grid +# REQUIRED FUNCTIONS FROM THE cute PACKAGE +# fun_gg_empty_graph() +# fun_gg_palette() +# fun_gg_point_rast() +# fun_pack() +# fun_check() +# fun_round() +# fun_scale() +# fun_inter_ticks() +# EXAMPLES +# set.seed(1) ; obs1 <- data.frame(Km = c(2, 1, 6, 5, 4, 7), Time = c(2, 1, 6, 5, 4, 7)^2, Car = c("TUUT", "TUUT", "TUUT", "WIIM", "WIIM", "WIIM"), Color1 = rep(c("coral", "lightblue"), each = 3), stringsAsFactors = TRUE) ; fun_gg_scatter(data1 = obs1, x = "Km", y = "Time") +# DEBUGGING +# set.seed(1) ; obs1 <- data.frame(km = rnorm(1000, 10, 3), time = rnorm(1000, 10, 3), group1 = rep(c("A1", "A2"), 500), stringsAsFactors = TRUE) ; obs2 <-data.frame(km = rnorm(1000, 15, 3), time = rnorm(1000, 15, 3), group2 = rep(c("G1", "G2"), 500), stringsAsFactors = TRUE) ; set.seed(NULL) ; obs1$km[2:3] <- NA ; data1 = list(L1 = obs1, L2 = obs2) ; x = list(L1 = "km", L2 = "km") ; y = list(L1 = "time", L2 = "time") ; categ = list(L1 = "group1", L2 = "group2") ; categ = NULL ; categ.class.order = NULL ; color = NULL ; geom = "geom_point" ; geom.step.dir = "hv" ; geom.stick.base = NULL ; alpha = 0.5 ; dot.size = 2 ; dot.shape = 21 ; dot.border.size = 0.5 ; dot.border.color = NULL ; line.size = 0.5 ; line.type = "solid" ; x.lim = NULL ; x.lab = NULL ; x.log = "no" ; x.tick.nb = NULL ; x.second.tick.nb = NULL ; x.include.zero = FALSE ; x.left.extra.margin = 0.05 ; x.right.extra.margin = 0.05 ; x.text.angle = 0 ; y.lim = NULL ; y.lab = NULL ; y.log = "no" ; y.tick.nb = NULL ; y.second.tick.nb = NULL ; y.include.zero = FALSE ; y.top.extra.margin = 0.05 ; y.bottom.extra.margin = 0.05 ; y.text.angle = 0 ; raster = FALSE ; raster.ratio = 1 ; raster.threshold = NULL ; text.size = 12 ; title = "" ; title.text.size = 12 ; legend.show = TRUE ; legend.width = 0.5 ; legend.name = NULL ; article = TRUE ; grid = FALSE ; add = NULL ; return = FALSE ; return.ggplot = FALSE ; return.gtable = TRUE ; plot = TRUE ; warn.print = FALSE ; lib.path = NULL +# function name +function.name <- paste0(as.list(match.call(expand.dots=FALSE))[[1]], "()") +arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments +arg.user.setting <- as.list(match.call(expand.dots=FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) +# end function name +# required function checking +req.function <- c( +"fun_check", +"fun_gg_just", +"fun_gg_empty_graph", +"fun_gg_palette", +"fun_gg_point_rast", +"fun_round", +"fun_pack", +"fun_scale", +"fun_inter_ticks" +) +tempo <- NULL +for(i1 in req.function){ +if(length(find(i1, mode = "function"))== 0L){ +tempo <- c(tempo, i1) +} +} +if( ! is.null(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end required function checking +# reserved words to avoid bugs (used in this function) +reserved.words <- c("fake_x", "fake_y", "fake_categ") +# end reserved words to avoid bugs (used in this function) +# arg with no default values +mandat.args <- c( +"data1", +"x", +"y" +) +tempo <- eval(parse(text = paste0("missing(", paste0(mandat.args, collapse = ") | missing("), ")"))) +if(any(tempo)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", "HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end arg with no default values +# argument primary checking +arg.check <- NULL # +text.check <- NULL # +checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools +ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) +tempo1 <- fun_check(data = data1, class = "data.frame", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = data1, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A DATA FRAME OR A LIST OF DATA FRAMES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(x)){ +tempo1 <- fun_check(data = x, class = "vector", mode = "character", na.contain = TRUE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = x, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = x, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(y)){ +tempo1 <- fun_check(data = y, class = "vector", mode = "character", na.contain = TRUE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = y, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(categ)){ +tempo1 <- fun_check(data = categ, class = "vector", mode = "character", length = 1, fun.name = function.name) +tempo2 <- fun_check(data = categ, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": categ ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = categ, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(categ.class.order)){ +if(is.null(categ)){ +tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT IS NOT NULL, BUT categ IS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo1 <- fun_check(data = categ.class.order, class = "vector", mode = "character", fun.name = function.name) +tempo2 <- fun_check(data = categ.class.order, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT MUST BE A VECTOR OF CHARACTER STRINGS OR A LIST OF VECTOR OF CHARACTER STRINGS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = categ.class.order, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(legend.name)){ +tempo1 <- fun_check(data = legend.name, class = "vector", mode = "character", na.contain = TRUE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = legend.name, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": legend.name ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = legend.name, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(color)){ +tempo1 <- fun_check(data = color, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = color, class = "factor", na.contain = TRUE, fun.name = function.name) +tempo3 <- fun_check(data = color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name) +tempo4 <- fun_check(data = color, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo4$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE & tempo3$problem == TRUE & tempo4$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": color ARGUMENT MUST BE A VECTOR (OF CHARACTER STRINGS OR INTEGERS) OR A FACTOR OR A LIST OF THESE POSSIBILITIES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = color, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo1 <- fun_check(data = geom, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = geom, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo1 <- fun_check(data = geom.step.dir, options = c("vh", "hv", "mid"), na.contain = FALSE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = geom.step.dir, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom.step.dir ARGUMENT MUST BE A SINGLE CHARACTER STRING (\"vh\" OR \"hv\" OR \"mid\") OR A LIST OF THESE CHARACTER STRINGS") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(geom.stick.base)){ +tempo1 <- fun_check(data = geom.stick.base, class = "vector", mode = "numeric", na.contain = FALSE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = color, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom.stick.base ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = geom.stick.base, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo1 <- fun_check(data = alpha, prop = TRUE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = alpha, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": alpha ARGUMENT MUST BE A SINGLE NUMERIC VALUE BETWEEN 0 AND 1 OR A LIST OF SUCH VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo1 <- fun_check(data = dot.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) +tempo2 <- fun_check(data = dot.size, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.size ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo1 <- fun_check(data = dot.shape, class = "vector", length = 1, fun.name = function.name) +tempo2 <- fun_check(data = dot.shape, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.shape ARGUMENT MUST BE A SINGLE SHAPE VALUE OR A LIST OF SINGLE SHAPE VALUES (SEE https://ggplot2.tidyverse.org/articles/ggplot2-specs.html)") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo1 <- fun_check(data = dot.border.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) +tempo2 <- fun_check(data = dot.border.size, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.size ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(dot.border.color)){ +tempo1 <- fun_check(data = dot.border.color, class = "vector", mode = "character", length = 1, fun.name = function.name) +tempo2 <- fun_check(data = dot.border.color, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +# integer colors -> gg_palette +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.color MUST BE A SINGLE CHARACTER STRING OF COLOR OR A SINGLE INTEGER VALUE") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = dot.border.color, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo1 <- fun_check(data = line.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) +tempo2 <- fun_check(data = line.size, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo2$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": line.size ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +tempo1 <- fun_check(data = line.type, class = "vector", typeof = "integer", double.as.integer.allowed = FALSE, length = 1, fun.name = function.name) +tempo2 <- fun_check(data = line.type, class = "vector", mode = "character", length = 1, fun.name = function.name) +tempo3 <- fun_check(data = line.type, class = "list", na.contain = TRUE, fun.name = function.name) +checked.arg.names <- c(checked.arg.names, tempo3$object.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE & tempo3$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": line.type ARGUMENT MUST BE A SINGLE LINE KIND VALUE OR A LIST OF SINGLE LINE KIND VALUES (SEE https://ggplot2.tidyverse.org/articles/ggplot2-specs.html)") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +if( ! is.null(x.lim)){ +tempo <- fun_check(data = x.lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & any(x.lim %in% c(Inf, -Inf))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x.lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = x.lim, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(x.lab)){ +if(all(class(x.lab) %in% "expression")){ # to deal with math symbols +tempo <- fun_check(data = x.lab, class = "expression", length = 1, fun.name = function.name) ; eval(ee) +}else{ +tempo <- fun_check(data = x.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = x.lab, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = x.log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(x.tick.nb)){ +tempo <- fun_check(data = x.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & x.tick.nb < 0){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = x.tick.nb, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(x.second.tick.nb)){ +tempo <- fun_check(data = x.second.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & x.second.tick.nb <= 0){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x.second.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = x.second.tick.nb, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = x.include.zero, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.left.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.right.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = x.text.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) +if( ! is.null(y.lim)){ +tempo <- fun_check(data = y.lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & any(y.lim %in% c(Inf, -Inf))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y.lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.lim, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(y.lab)){ +if(all(class(y.lab) %in% "expression")){ # to deal with math symbols +tempo <- fun_check(data = y.lab, class = "expression", length = 1, fun.name = function.name) ; eval(ee) +}else{ +tempo <- fun_check(data = y.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.lab, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = y.log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(y.tick.nb)){ +tempo <- fun_check(data = y.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & y.tick.nb < 0){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.tick.nb, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if( ! is.null(y.second.tick.nb)){ +tempo <- fun_check(data = y.second.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE & y.second.tick.nb <= 0){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y.second.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = y.second.tick.nb, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = y.include.zero, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.top.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.bottom.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = y.text.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = raster, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = raster.ratio, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +if( ! is.null(raster.threshold)){ +tempo <- fun_check(data = raster.threshold, class = "vector", typeof = "integer", neg.values = FALSE, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = raster.threshold, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = legend.show, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(legend.width)){ +tempo <- fun_check(data = legend.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = legend.width, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = article, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = grid, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(add)){ +tempo <- fun_check(data = add, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = add, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +tempo <- fun_check(data = return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return.ggplot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = return.gtable, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = plot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) +if( ! is.null(lib.path)){ +tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) +if(tempo$problem == FALSE){ +if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA +tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) +text.check <- c(text.check, tempo.cat) +arg.check <- c(arg.check, TRUE) +} +} +}else{ +# no fun_check test here, it is just for checked.arg.names +tempo <- fun_check(data = lib.path, class = "vector") +checked.arg.names <- c(checked.arg.names, tempo$object.name) +} +if(any(arg.check) == TRUE){ +stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # +} +# source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() +# end argument primary checking + + +# second round of checking and data preparation +# management of NA arguments +tempo.arg <- names(arg.user.setting) # values provided by the user +tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length)== 1L # no argument provided by the user can be just NA +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NA arguments +# management of NULL arguments +tempo.arg <-c( +"data1", +# "x", # inactivated because of hline or vline +# "y", # inactivated because of hline or vline +"geom", +"geom.step.dir", +# "geom.stick.base", # inactivated because can be null +"alpha", +"dot.size", +"dot.shape", +"dot.border.size", +"line.size", +"line.type", +"x.log", +"x.include.zero", +"x.left.extra.margin", +"x.right.extra.margin", +"x.text.angle", +"y.log", +"y.include.zero", +"y.top.extra.margin", +"y.bottom.extra.margin", +"y.text.angle", +"raster", +"raster.ratio", +"text.size", +"title", +"title.text.size", +"legend.show", +# "legend.width", # inactivated because can be null +"article", +"grid", +"return", +"return.ggplot", +"return.gtable", +"plot", +"warn.print" +) +tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) +if(any(tempo.log) == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == +} +# end management of NULL arguments +# code that protects set.seed() in the global environment +# end code that protects set.seed() in the global environment +# warning initiation +ini.warning.length <- options()$warning.length +options(warning.length = 8170) +warn <- NULL +warn.count <- 0 +# end warning initiation +# other checkings +# check list lengths (and names of data1 compartments if present) +list.color <- NULL +list.geom <- NULL +list.geom.step.dir <- NULL +list.geom.stick.base <- NULL +list.alpha <- NULL +list.dot.size <- NULL +list.dot.shape <- NULL +list.dot.border.size <- NULL +list.dot.border.color <- NULL +list.line.size <- NULL +list.line.type <- NULL +if(all(class(data1) == "list")){ +if(length(data1) > 6){ +tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A LIST OF 6 DATA FRAMES MAXIMUM (6 OVERLAYS MAX)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if(is.null(names(data1))){ +names(data1) <- paste0("L", 1:length(data1)) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL NAME COMPARTMENT OF data1 LIST -> NAMES RESPECTIVELY ATTRIBUTED TO EACH COMPARTMENT:\n", paste(names(data1), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +if( ! is.null(x)){ +if( ! (all(class(x) == "list") & length(data1) == length(x))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +}else{ +x <- vector("list", length(data1)) +} +if( ! is.null(y)){ +if( ! (all(class(y) == "list") & length(data1) == length(y))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +}else{ +y <- vector("list", length(data1)) +} +if( ! is.null(categ)){ +if( ! (all(class(categ) == "list") & length(data1) == length(categ))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": categ ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if( ! is.null(categ.class.order)){ +if( ! (all(class(categ.class.order) == "list") & length(data1) == length(categ.class.order))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if( ! is.null(color)){ +if( ! ((all(class(color) == "list") & length(data1) == length(color)) | ((all(mode(color) == "character") | all(mode(color) == "numeric")) & length(color)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": color ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER STRING OR INTEGER") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if((all(mode(color) == "character") | all(mode(color) == "numeric")) & length(color)== 1L){ # convert the single value into a list of single value +list.color <- vector(mode = "list", length = length(data1)) +list.color[] <- color +} +} +if( ! ((all(class(geom) == "list") & length(data1) == length(geom)) | (all(mode(geom) == "character") & length(geom)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(geom) == "character") & length(geom)== 1L){ # convert the single value into a list of single value +list.geom <- vector(mode = "list", length = length(data1)) +list.geom[] <- geom +} +if( ! ((all(class(geom.step.dir) == "list") & length(data1) == length(geom.step.dir)) | (all(mode(geom.step.dir) == "character") & length(geom.step.dir)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": geom.step.dir ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(geom.step.dir) == "character") & length(geom.step.dir)== 1L){ # convert the single value into a list of single value +list.geom.step.dir <- vector(mode = "list", length = length(data1)) +list.geom.step.dir[] <- geom.step.dir +} +if( ! is.null(geom.stick.base)){ +if( ! ((all(class(geom.stick.base) == "list") & length(data1) == length(geom.stick.base)) | (all(mode(geom.stick.base) == "numeric") & length(geom.stick.base)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": geom.stick.base ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(geom.stick.base) == "numeric") & length(geom.stick.base)== 1L){ # convert the single value into a list of single value +list.geom.stick.base <- vector(mode = "list", length = length(data1)) +list.geom.stick.base[] <- geom.stick.base +} +} +if( ! ((all(class(alpha) == "list") & length(data1) == length(alpha)) | (all(mode(alpha) == "numeric") & length(alpha)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": alpha ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(alpha) == "numeric") & length(alpha)== 1L){ # convert the single value into a list of single value +list.alpha <- vector(mode = "list", length = length(data1)) +list.alpha[] <- alpha +} +if( ! ((all(class(dot.size) == "list") & length(data1) == length(dot.size)) | (all(mode(dot.size) == "numeric") & length(dot.size)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.size ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(dot.size) == "numeric") & length(dot.size)== 1L){ # convert the single value into a list of single value +list.dot.size <- vector(mode = "list", length = length(data1)) +list.dot.size[] <- dot.size +} +if( ! ((all(class(dot.shape) == "list") & length(data1) == length(dot.shape)) | (all(mode(dot.shape) != "list") & length(dot.shape)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.shape ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE SHAPE VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(dot.shape) != "list") & length(dot.shape)== 1L){ # convert the single value into a list of single value +list.dot.shape <- vector(mode = "list", length = length(data1)) +list.dot.shape[] <- dot.shape +} +if( ! ((all(class(dot.border.size) == "list") & length(data1) == length(dot.border.size)) | (all(mode(dot.border.size) == "numeric") & length(dot.border.size)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.size ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(dot.border.size) == "numeric") & length(dot.border.size)== 1L){ # convert the single value into a list of single value +list.dot.border.size <- vector(mode = "list", length = length(data1)) +list.dot.border.size[] <- dot.border.size +} +if( ! is.null(dot.border.color)){ +if( ! ((all(class(dot.border.color) == "list") & length(data1) == length(dot.border.color)) | ((all(mode(dot.border.color) == "character") | all(mode(dot.border.color) == "numeric")) & length(dot.border.color)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.color ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER STRING OR INTEGER") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if((all(mode(dot.border.color) == "character") | all(mode(dot.border.color) == "numeric")) & length(dot.border.color)== 1L){ # convert the single value into a list of single value +list.dot.border.color <- vector(mode = "list", length = length(data1)) +list.dot.border.color[] <- dot.border.color +} +} +if( ! ((all(class(line.size) == "list") & length(data1) == length(line.size)) | (all(mode(line.size) == "numeric") & length(line.size)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": line.size ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(line.size) == "numeric") & length(line.size)== 1L){ # convert the single value into a list of single value +list.line.size <- vector(mode = "list", length = length(data1)) +list.line.size[] <- line.size +} +if( ! ((all(class(line.type) == "list") & length(data1) == length(line.type)) | (all(mode(line.type) != "list") & length(line.type)== 1L))){ # list of same length as data1 or single value +tempo.cat <- paste0("ERROR IN ", function.name, ": line.type ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE LINE KIND VALUE") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(all(mode(line.type) != "list") & length(line.type)== 1L){ # convert the single value into a list of single value +list.line.type <- vector(mode = "list", length = length(data1)) +list.line.type[] <- line.type +} +if( ! is.null(legend.name)){ +if( ! (all(class(legend.name) == "list") & length(data1) == length(legend.name))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": legend.name ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +} +# end check list lengths (and names of data1 compartments if present) +# conversion into lists +if(all(is.data.frame(data1))){ +data1 <- list(L1 = data1) +if(all(class(x) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": x ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +x <- list(L1 = x) +} +if(all(class(y) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": y ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +y <- list(L1 = y) +} +if( ! is.null(categ)){ +if(all(class(categ) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": categ ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +categ <- list(L1 = categ) +} +} +if( ! is.null(categ.class.order)){ +if(all(class(categ.class.order) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +categ.class.order <- list(L1 = categ.class.order) +} +} +if( ! is.null(color)){ +if(all(class(color) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": color ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +color <- list(L1 = color) +} +} +if(all(class(geom) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +geom <- list(L1 = geom) +} +if(all(class(geom.step.dir) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom.step.dir ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +geom.step.dir <- list(L1 = geom.step.dir) +} +if( ! is.null(geom.stick.base)){ +if(all(class(geom.stick.base) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom.stick.base ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +geom.stick.base <- list(L1 = geom.stick.base) +} +} +if(all(class(alpha) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": alpha ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +alpha <- list(L1 = alpha) +} +if(all(class(dot.size) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.size ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +dot.size <- list(L1 = dot.size) +} +if(all(class(dot.shape) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.shape ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +dot.shape <- list(L1 = dot.shape) +} +if(all(class(dot.border.size) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.size ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +dot.border.size <- list(L1 = dot.border.size) +} +if( ! is.null(dot.border.color)){ +if(all(class(dot.border.color) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.color ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +dot.border.color <- list(L1 = dot.border.color) +} +} +if(all(class(line.size) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": line.size ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +line.size <- list(L1 = line.size) +} +if(all(class(line.type) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": line.type ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +line.type <- list(L1 = line.type) +} +if( ! is.null(legend.name)){ +if(all(class(legend.name) == "list")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": legend.name ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +legend.name <- list(L1 = legend.name) +} +} +}else if( ! all(sapply(data1, FUN = "class") == "data.frame")){ # if not a data frame, data1 can only be a list, as tested above +tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A DATA FRAME OR A LIST OF DATA FRAMES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +# single value converted into list now reattributed to the argument name +if( ! is.null(color)){ +if( ! is.null(list.color)){ +color <- list.color +} +} +if( ! is.null(list.geom)){ +geom <- list.geom +} +if( ! is.null(list.geom.step.dir)){ +geom.step.dir <- list.geom.step.dir +} +if( ! is.null(geom.stick.base)){ +if( ! is.null(list.geom.stick.base)){ +geom.stick.base <- list.geom.stick.base +} +} +if( ! is.null(list.alpha)){ +alpha <- list.alpha +} +if( ! is.null(list.dot.size)){ +dot.size <- list.dot.size +} +if( ! is.null(list.dot.shape)){ +dot.shape <- list.dot.shape +} +if( ! is.null(list.dot.border.size)){ +dot.border.size <- list.dot.border.size +} +if( ! is.null(dot.border.color)){ +if( ! is.null(list.dot.border.color)){ +dot.border.color <- list.dot.border.color +} +} +if( ! is.null(list.line.size)){ +line.size <- list.line.size +} +if( ! is.null(list.line.type)){ +line.type <- list.line.type +} +# end single value converted into list now reattributed to the argument name +# data, x, y, geom, alpha, dot.size, shape, dot.border.size, line.size, line.type, legend.name are list now +# if non-null, categ, categ.class.order, legend.name, color, dot.border.color are list now +# end conversion into lists +# verif of add +if( ! is.null(add)){ +if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by + +tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) + +}else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ # +tempo.cat <- paste0("ERROR IN ", function.name, ": FOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by ) +tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +# end verif of add +# management of add containing facet +facet.categ <- NULL +if( ! is.null(add)){ +facet.check <- TRUE +tempo <- unlist(strsplit(x = add, split = "\\s*\\+\\s*(ggplot2|lemon)\\s*::\\s*")) # +tempo <- sub(x = tempo, pattern = "^facet_wrap", replacement = "ggplot2::facet_wrap") +tempo <- sub(x = tempo, pattern = "^facet_grid", replacement = "ggplot2::facet_grid") +tempo <- sub(x = tempo, pattern = "^facet_rep", replacement = "lemon::facet_rep") +if(length(data1) > 1 & (any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")) | grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid"))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nfacet PANELS CANNOT BE USED IF MORE THAN ONE DATA FRAME IN THE data1 ARGUMENT\nPLEASE REWRITE THE add STRING AND RERUN") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +if(any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap"))){ +tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")]))) +facet.categ <- list(names(tempo1$params$facets)) # list of length 1 +tempo.text <- "facet_wrap OR facet_rep_wrap" +facet.check <- FALSE +}else if(grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")){ +tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")]))) +facet.categ <- list(c(names(tempo1$params$rows), names(tempo1$params$cols))) # list of length 1 +tempo.text <- "facet_grid OR facet_rep_grid" +facet.check <- FALSE +} +if(facet.check == FALSE & ! all(facet.categ %in% names(data1[[1]]))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL +tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1[[1]]), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +} +# if facet.categ is not NULL, it is a list of length 1 now +# end management of add containing facet +# legend name filling +if(is.null(legend.name) & ! is.null(categ)){ +legend.name <- categ +}else if(is.null(legend.name) & is.null(categ)){ +legend.name <- vector("list", length(data1)) # null list +} +# legend.name not NULL anymore (list) +# end legend name filling +# ini categ for legend display +fin.lg.disp <- vector("list", 6) # will be used at the end to display or not legends +fin.lg.disp[] <- FALSE +legend.disp <- vector("list", length(data1)) +if(is.null(categ) | legend.show == FALSE){ +legend.disp[] <- FALSE +}else{ +for(i2 in 1:length(data1)){ +if(is.null(categ[[i2]])){ +legend.disp[[i2]] <- FALSE +}else{ +legend.disp[[i2]] <- TRUE +} +} +} +# end ini categ for legend display +# integer colors into gg_palette +tempo.check.color <- NULL +for(i1 in 1:length(data1)){ +if(any(is.na(color[[i1]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), ": color ARGUMENT CANNOT CONTAIN NA") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +tempo.check.color <- c(tempo.check.color, fun_check(data = color[[i1]], data.name = ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name)$problem) +} +tempo.check.color <- ! tempo.check.color # invert TRUE and FALSE because if integer, then problem = FALSE +if(any(tempo.check.color == TRUE)){ # convert integers into colors +tempo.integer <- unlist(color[tempo.check.color]) +tempo.color <- fun_gg_palette(max(tempo.integer, na.rm = TRUE)) +for(i1 in 1:length(data1)){ +if(tempo.check.color[i1] == TRUE){ +color[[i1]] <-tempo.color[color[[i1]]] +} +} +} +# end integer colors into gg_palette +# loop (checking inside list compartment) +compart.null.color <- 0 # will be used to attribute a color when color is non-null but a compartment of color is NULL +data1.ini <- data1 # to report NA removal +removed.row.nb <- vector("list", length = length(data1)) # to report NA removal. Contains NULL +removed.rows <- vector("list", length = length(data1)) # to report NA removal. Contains NULL +for(i1 in 1:length(data1)){ +tempo <- fun_check(data = data1[[i1]], data.name = ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), class = "data.frame", na.contain = TRUE, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +# reserved word checking +if(any(names(data1[[i1]]) %in% reserved.words)){ # I do not use fun_name_change() because cannot control y before creating "fake_y". But ok because reserved are not that common +tempo.cat <- paste0("ERROR IN ", function.name, ": COLUMN NAMES OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " ARGUMENT CANNOT BE ONE OF THESE WORDS\n", paste(reserved.words, collapse = " "), "\nTHESE ARE RESERVED FOR THE ", function.name, " FUNCTION") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if( ! (is.null(add))){ +if(any(sapply(X = reserved.words, FUN = grepl, x = add))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF COLUMN NAMES OF data1 IN THE add ARGUMENT STRING, THAT CORRESPOND TO RESERVED STRINGS FOR ", function.name, "\nFOLLOWING COLUMN NAMES HAVE TO BE CHANGED:\n", paste(arg.names[sapply(X = reserved.words, FUN = grepl, x = add)], collapse = "\n"), "\nFOR INFORMATION, THE RESERVED WORDS ARE:\n", paste(reserved.words, collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(any(sapply(X = arg.names, FUN = grepl, x = add))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NAMES OF ", function.name, " ARGUMENTS DETECTED IN THE add STRING:\n", paste(arg.names[sapply(X = arg.names, FUN = grepl, x = add)], collapse = "\n"), "\nRISK OF WRONG OBJECT USAGE INSIDE ", function.name) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end reserved word checking +# check of geom now because required for y argument +tempo <- fun_check(data = geom[[i1]], data.name = ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), options = c("geom_point", "geom_line", "geom_path", "geom_step", "geom_hline", "geom_vline", "geom_stick"), length = 1, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if(geom[[i1]] == "geom_step" & is.null(geom.step.dir[[i1]])){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(geom.step.dir)== 1L, "geom.step.dir", paste0("ELEMENT ", i1, " OF geom.step.dir ARGUMENT")), ": geom.step.dir ARGUMENT CANNOT BE NULL IF ", ifelse(length(geom)== 1L, "geom", paste0("ELEMENT ", i1, " OF geom")), " ARGUMENT IS \"geom_step\"\nHERE geom.step.dir ARGUMENT IS: ", paste(geom.step.dir[[i1]], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(geom[[i1]] == "geom_step" & ! is.null(geom.step.dir[[i1]])){ +tempo <- fun_check(data = geom.step.dir[[i1]], data.name = ifelse(length(geom.step.dir)== 1L, "geom.step.dir", paste0("geom.step.dir NUMBER ", i1)), options = c("vh", "hv", "mid"), length = 1, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if( ! (is.null(geom.stick.base))){ +if(geom[[i1]] == "geom_stick" & ! is.null(geom.stick.base[[i1]])){ +tempo <- fun_check(data = geom.stick.base[[i1]], data.name = ifelse(length(geom.stick.base)== 1L, "geom.stick.base", paste0("geom.stick.base NUMBER ", i1)), mode = "numeric", length = 1, na.contain = FALSE, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +} +# end check of geom now because required for y argument +if(is.null(x[[i1]])){ +if(all(geom[[i1]] != "geom_hline")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": x ARGUMENT CANNOT BE NULL EXCEPT IF ", ifelse(length(geom)== 1L, "x", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_hline\"\nHERE geom ARGUMENT IS: ", paste(geom[[i1]], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +x[[i1]] <- "fake_x" +data1[[i1]] <- cbind(data1[[i1]], fake_x = NA, stringsAsFactors = TRUE) +data1[[i1]][, "fake_x"] <- as.numeric(data1[[i1]][, "fake_x"]) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x")), " ARGUMENT ASSOCIATED TO ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT ", geom[[i1]], " -> FAKE COLUMN ADDED TO DATA FRAME ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", NAMED \"fake_x\" FOR FINAL DRAWING") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(all(geom[[i1]] == "geom_hline")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": x ARGUMENT MUST BE NULL IF ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_hline\"") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +tempo <- fun_check(data = x[[i1]], data.name = ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), class = "vector", mode = "character", length = 1, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if(is.null(y[[i1]])){ +if(all(geom[[i1]] != "geom_vline")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": y ARGUMENT CANNOT BE NULL EXCEPT IF ", ifelse(length(geom)== 1L, "y", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_vline\"\nHERE geom ARGUMENT IS: ", paste(geom[[i1]], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +y[[i1]] <- "fake_y" +data1[[i1]] <- cbind(data1[[i1]], fake_y = NA, stringsAsFactors = TRUE) +data1[[i1]][, "fake_y"] <- as.numeric(data1[[i1]][, "fake_y"]) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y")), " ARGUMENT ASSOCIATED TO ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT ", geom[[i1]], " -> FAKE COLUMN ADDED TO DATA FRAME ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", NAMED \"fake_y\" FOR FINAL DRAWING") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else{ +if(all(geom[[i1]] == "geom_vline")){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": y ARGUMENT MUST BE NULL IF ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_vline\"") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +tempo <- fun_check(data = y[[i1]], data.name = ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), class = "vector", mode = "character", length = 1, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +# x[[i1]] and y[[i1]] not NULL anymore +if( ! (x[[i1]] %in% names(data1[[i1]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x")), " ARGUMENT MUST BE A COLUMN NAME OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT\nHERE IT IS: ", paste(x[[i1]], collapse = " ")))) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if( ! (y[[i1]] %in% names(data1[[i1]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y")), " ARGUMENT MUST BE A COLUMN NAME OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT\nHERE IT IS: ", paste(y[[i1]], collapse = " ")))) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +tempo <- fun_check(data = data1[[i1]][, x[[i1]]], data.name = ifelse(length(x)== 1L, "x ARGUMENT (AS COLUMN NAME OF data1 DATA FRAME)", paste0("ELEMENT ", i1, " OF x ARGUMENT", " (AS COLUMN NAME OF data1 DATA FRAME NUMBER ", i1, ")")), class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +tempo <- fun_check(data = data1[[i1]][, y[[i1]]], data.name = ifelse(length(y)== 1L, "y ARGUMENT (AS COLUMN NAME OF data1 DATA FRAME)", paste0("ELEMENT ", i1, " OF y ARGUMENT", " (AS COLUMN NAME OF data1 DATA FRAME NUMBER ", i1, ")")), class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if(x[[i1]] == "fake_x" & y[[i1]] == "fake_y"){ # because the code cannot accept to be both "fake_x" and "fake_y" at the same time +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2\nTHE CODE CANNOT ACCEPT x AND y TO BE \"fake_x\" AND \"fake_y\" IN THE SAME DATA FRAME ", i1, " ") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} + +if(( ! is.null(categ)) & ( ! is.null(categ[[i1]]))){ # is.null(categ[[i1]]) works even if categ is NULL # is.null(categ[[i1]]) works even if categ is NULL # if categ[[i1]] = NULL, fake_categ will be created later on +tempo <- fun_check(data = categ[[i1]], data.name = ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")),, class = "vector", mode = "character", length = 1, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if( ! (categ[[i1]] %in% names(data1[[i1]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ")), " ARGUMENT MUST BE A COLUMN NAME OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT\nHERE IT IS: ", paste(categ[[i1]], collapse = " ")))) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +tempo1 <- fun_check(data = data1[[i1]][, categ[[i1]]], data.name = ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) +tempo2 <- fun_check(data = data1[[i1]][, categ[[i1]]], data.name = ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), class = "factor", na.contain = TRUE, fun.name = function.name) +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " MUST BE A FACTOR OR CHARACTER VECTOR") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(tempo1$problem == FALSE){ +data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", THE CHARACTER COLUMN HAS BEEN CONVERTED TO FACTOR") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) + +} +if(geom[[i1]] == "geom_vline" | geom[[i1]] == "geom_hline"){ +if(length(unique(data1[[i1]][, categ[[i1]]])) != nrow(data1[[i1]])){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(geom)== 1L, "geom OF data1 ARGUMENT", paste0("geom NUMBER ", i1, " OF DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " ARGUMENT IS ", geom[[i1]], ", MEANING THAT ", ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " MUST HAVE A DIFFERENT CLASS PER LINE OF data1 (ONE x VALUE PER CLASS)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +}else if(( ! is.null(categ)) & is.null(categ[[i1]])){ # is.null(categ[[i1]]) works even if categ is NULL # if categ[[i1]] = NULL, fake_categ will be created. WARNING: is.null(categ[[i1]]) means no legend display (see above), because categ has not been precised. This also means a single color for data1[[i1]] +if(length(color[[i1]]) > 1){ # 0 means is.null(color[[i1]]) or is.null(color) and 1 is ok -> single color for data1[[i1]] +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ")), " ARGUMENT BUT CORRESPONDING COLORS IN ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " HAS LENGTH OVER 1\n", paste(color[[i1]], collapse = " "), "\nWHICH IS NOT COMPATIBLE WITH NULL CATEG -> COLOR RESET TO A SINGLE COLOR") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +color[i1] <- list(NULL) # will provide a single color below # Warning color[[i1]] <- NULL removes the compartment +} +categ[[i1]] <- "fake_categ" +data1[[i1]] <- cbind(data1[[i1]], fake_categ = "", stringsAsFactors = TRUE) +# inactivated because give a different color to different "Line_" categ while a single color for all the data1[[i1]] required. Thus, put back after the color management +# if(geom[[i1]] == "geom_hline" | geom[[i1]] == "geom_vline"){ +# data1[[i1]][, "fake_categ"] <- paste0("Line_", 1:nrow(data1[[i1]])) +# }else{ +data1[[i1]][, "fake_categ"] <- data1[[i1]][, "fake_categ"] # as.numeric("") create a vector of NA but class numeric +# } +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ")), " ARGUMENT -> FOR DATA FRAME ", ifelse(length(data1)== 1L, "data1 ARGUMENT:", paste0("NUMBER ", i1, " OF data1 ARGUMENT:")), "\n- FAKE \"fake_categ\" COLUMN ADDED FILLED WITH \"\"(OR WITH \"Line_...\" FOR LINES)\n- SINGLE COLOR USED FOR PLOTTING\n- NO LEGEND DISPLAYED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# OK: if categ is not NULL, all the non-null categ columns of data1 are factors from here + +# management of log scale and Inf removal +if(x[[i1]] != "fake_x"){ +if(any(( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") PRESENCE OF -Inf OR Inf VALUES IN ", ifelse(length(categ)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +if(y[[i1]] != "fake_y"){ +if(any(( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") PRESENCE OF -Inf OR Inf VALUES IN ", ifelse(length(categ)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# log conversion +if(x.log != "no"){ +tempo1 <- ! is.finite(data1[[i1]][, x[[i1]]]) # where are initial NA and Inf +data1[[i1]][, x[[i1]]] <- suppressWarnings(get(x.log)(data1[[i1]][, x[[i1]]]))# no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +if(any( ! (tempo1 | is.finite(data1[[i1]][, x[[i1]]])))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") LOG CONVERSION INTRODUCED -Inf OR Inf OR NaN VALUES IN ", ifelse(length(categ)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +if(y.log != "no"){ +tempo1 <- ! is.finite(data1[[i1]][, y[[i1]]]) # where are initial NA and Inf +data1[[i1]][, y[[i1]]] <- suppressWarnings(get(y.log)(data1[[i1]][, y[[i1]]]))# no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +if(any( ! (tempo1 | is.finite(data1[[i1]][, y[[i1]]])))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") LOG CONVERSION INTRODUCED -Inf OR Inf OR NaN VALUES IN ", ifelse(length(categ)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# Inf removal +# removed.row.nb[[i1]] <- NULL # already NULL and Warning this removes the compartment +removed.rows[[i1]] <- data.frame(stringsAsFactors = FALSE) +if(any(( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])) detects only Inf +removed.row.nb[[i1]] <- c(removed.row.nb[[i1]], which(( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])))) +} +if(any(( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])) detects only Inf +removed.row.nb[[i1]] <- c(removed.row.nb[[i1]], which(( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])))) +} +if( ! is.null(removed.row.nb[[i1]])){ +removed.row.nb[[i1]] <- unique(removed.row.nb[[i1]]) # to remove the duplicated positions (NA in both x and y) +removed.rows[[i1]] <- rbind(removed.rows[[i1]], data1.ini[[i1]][removed.row.nb[[i1]], ]) # here data1.ini used to have the y = O rows that will be removed because of Inf creation after log transformation +data1[[i1]] <- data1[[i1]][-removed.row.nb[[i1]], ] +data1.ini[[i1]] <- data1.ini[[i1]][-removed.row.nb[[i1]], ] # +} +# From here, data1 and data.ini have no more Inf +# end Inf removal +# x.lim and y.lim dealt later on, after the end f the loop +# end management of log scale and Inf removal +# na detection and removal +column.check <- unique(unlist(c( # unlist because creates a list +if(x[[i1]] == "fake_x"){NULL}else{x[[i1]]}, +if(y[[i1]] == "fake_y"){NULL}else{y[[i1]]}, +if( ! is.null(categ)){if(is.null(categ[[i1]])){NULL}else{categ[[i1]]}}, +if( ! is.null(facet.categ)){if(is.null(facet.categ[[i1]])){NULL}else{facet.categ[[i1]]}} +))) # dot.categ because can be a 3rd column of data1 +if(any(is.na(data1[[i1]][, column.check]))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NA DETECTED IN COLUMNS ", paste(column.check, collapse = " "), " OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +for(i3 in 1:length(column.check)){ +if(any(is.na(data1[[i1]][, column.check[i3]]))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("NA REMOVAL DUE TO COLUMN ", column.check[i3], " OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT"))) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +tempo <- unique(unlist(lapply(lapply(c(data1[[i1]][column.check]), FUN = is.na), FUN = which))) +removed.row.nb[[i1]] <- c(removed.row.nb[[i1]], tempo) +removed.rows[[i1]] <- rbind(removed.rows[[i1]], data1.ini[[i1]][tempo, ]) # # tempo used because removed.row.nb is not empty. Here data1.ini used to have the non NA rows that will be removed because of NAN creation after log transformation (neg values for instance) +column.check <- column.check[ ! (column.check == x[[i1]] | column.check == y[[i1]])] # remove x and y to keep quali columns +if(length(tempo) != 0){ +data1[[i1]] <- data1[[i1]][-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former +data1.ini[[i1]] <- data1.ini[[i1]][-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers than the former +for(i4 in 1:length(column.check)){ +if(any( ! unique(removed.rows[[i1]][, column.check[i4]]) %in% unique(data1[[i1]][, column.check[i4]]))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN COLUMN ", column.check[i4], " OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", THE FOLLOWING CLASSES HAVE DISAPPEARED AFTER NA REMOVAL\n(IF COLUMN USED IN THE PLOT, THIS CLASS WILL NOT BE DISPLAYED):\n", paste(unique(removed.rows[[i1]][, column.check[i4]])[ ! unique(removed.rows[[i1]][, column.check[i4]]) %in% unique(data1[[i1]][, column.check[i4]])], collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +tempo.levels <- levels(data1[[i1]][, column.check[i4]])[levels(data1[[i1]][, column.check[i4]]) %in% unique(as.character(data1[[i1]][, column.check[i4]]))] +data1[[i1]][, column.check[i4]] <- factor(as.character(data1[[i1]][, column.check[i4]]), levels = tempo.levels) +if(column.check[i4] %in% categ[[i1]] & ! is.null(categ.class.order)){ +categ.class.order[[i1]] <- levels(data1[[i1]][, column.check[i4]])[levels(data1[[i1]][, column.check[i4]]) %in% unique(data1[[i1]][, column.check[i4]])] # remove the absent class in the categ.class.order vector +data1[[i1]][, column.check[i4]] <- factor(as.character(data1[[i1]][, column.check[i4]]), levels = unique(categ.class.order[[i1]])) +} +} +} +} +} +# end na detection and removal +# From here, data1 and data.ini have no more NA or NaN in x, y, categ (if categ != NULL) and facet.categ (if categ != NULL) +if( ! is.null(categ.class.order)){ +# the following check will be done several times but I prefer to keep it here, after the creation of categ +if(is.null(categ[[i1]]) & ! is.null(categ.class.order[[i1]])){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i1, " OF categ ARGUMENT CANNOT BE NULL IF COMPARTMENT ", i1, " OF categ.class.order ARGUMENT IS NOT NULL: ", paste(categ.class.order, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +if(is.null(categ.class.order[[i1]])){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE categ.class.order COMPARTMENT ", i1, " IS NULL. ALPHABETICAL ORDER WILL BE APPLIED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +data1[[i1]][, categ[[i1]]] <- factor(as.character(data1[[i1]][, categ[[i1]]])) # if already a factor, change nothing, if characters, levels according to alphabetical order +categ.class.order[[i1]] <- levels(data1[[i1]][, categ[[i1]]]) # character vector that will be used later +}else{ +tempo <- fun_check(data = categ.class.order[[i1]], data.name = paste0("COMPARTMENT ", i1 , " OF categ.class.order ARGUMENT"), class = "vector", mode = "character", length = length(levels(data1[[i1]][, categ[[i1]]])), fun.name = function.name) # length(data1[, categ[i1]) -> if data1[, categ[i1] was initially character vector, then conversion as factor after the NA removal, thus class number ok. If data1[, categ[i1] was initially factor, no modification after the NA removal, thus class number ok +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if(any(duplicated(categ.class.order[[i1]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i1, " OF categ.class.order ARGUMENT CANNOT HAVE DUPLICATED CLASSES: ", paste(categ.class.order[[i1]], collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if( ! (all(categ.class.order[[i1]] %in% unique(data1[[i1]][, categ[[i1]]])) & all(unique(data1[[i1]][, categ[[i1]]]) %in% categ.class.order[[i1]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i1, " OF categ.class.order ARGUMENT MUST BE CLASSES OF COMPARTMENT ", i1, " OF categ ARGUMENT\nHERE IT IS:\n", paste(categ.class.order[[i1]], collapse = " "), "\nFOR COMPARTMENT ", i1, " OF categ.class.order AND IT IS:\n", paste(unique(data1[[i1]][, categ[[i1]]]), collapse = " "), "\nFOR COLUMN ", categ[[i1]], " OF data1 NUMBER ", i1) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]], levels = categ.class.order[[i1]]) # reorder the factor +} +names(categ.class.order)[i1] <- categ[[i1]] +} +} +# OK: if categ.class.order is not NULL, all the NULL categ.class.order columns of data1 are character from here + +if( ! is.null(legend.name[[i1]])){ +tempo <- fun_check(data = legend.name[[i1]], data.name = ifelse(length(legend.name)== 1L, "legend.name", paste0("legend.name NUMBER ", i1)),, class = "vector", mode = "character", length = 1, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if( ! is.null(color)){ # if color is NULL, will be filled later on +# check the nature of color +if(is.null(color[[i1]])){ +compart.null.color <- compart.null.color + 1 +color[[i1]] <- grey(compart.null.color / 8) # cannot be more than 7 overlays. Thus 7 different greys. 8/8 is excluded because white dots +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL COLOR IN ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " ASSOCIATED TO ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", SINGLE COLOR ", paste(color[[i1]], collapse = " "), " HAS BEEN ATTRIBUTED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +tempo1 <- fun_check(data = color[[i1]], data.name = ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) # na.contain = TRUE in case of colum of data1 +tempo2 <- fun_check(data = color[[i1]], data.name = ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), class = "factor", na.contain = TRUE, fun.name = function.name) # idem +if(tempo1$problem == TRUE & tempo2$problem == TRUE){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A FACTOR OR CHARACTER VECTOR OR INTEGER VECTOR") # integer possible because dealt above +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if( ! (all(color[[i1]] %in% colors() | grepl(pattern = "^#", color[[i1]])))){ # check that all strings of low.color start by # +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors(): ", paste(unique(color[[i1]]), collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if(any(is.na(color[[i1]]))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), ", THE COLORS:\n", paste(unique(color[[i1]]), collapse = " "), "\nCONTAINS NA") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +# end check the nature of color +# check the length of color +if(is.null(categ) & length(color[[i1]]) != 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A SINGLE COLOR IF categ IS NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if( ! is.null(categ)){ +# No problem of NA management by ggplot2 because already removed +if(categ[[i1]] == "fake_categ" & length(color[[i1]]) != 1){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A SINGLE COLOR IF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IS NULL") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(length(color[[i1]]) == length(unique(data1[[i1]][, categ[[i1]]]))){ # here length(color) is equal to the different number of categ +data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", THE FOLLOWING COLORS:\n", paste(color[[i1]], collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(factor(data1[[i1]][, categ[[i1]]])), collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else if(length(color[[i1]]) == length(data1[[i1]][, categ[[i1]]])){# here length(color) is equal to nrow(data1[[i1]]) -> Modif to have length(color) equal to the different number of categ (length(color) == length(levels(data1[[i1]][, categ[[i1]]]))) +data1[[i1]] <- cbind(data1[[i1]], color = color[[i1]], stringsAsFactors = TRUE) +tempo.check <- unique(data1[[i1]][ , c(categ[[i1]], "color")]) +if( ! (nrow(data1[[i1]]) == length(color[[i1]]) & nrow(tempo.check) == length(unique(data1[[i1]][ , categ[[i1]]])))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color")), " ARGUMENT HAS THE LENGTH OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), "\nBUT IS INCORRECTLY ASSOCIATED TO EACH CLASS OF THIS categ:\n", paste(unique(mapply(FUN = "paste", data1[[i1]][ ,categ[[i1]]], data1[[i1]][ ,"color"])), collapse = "\n")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order +color[[i1]] <- unique(color[[i1]][order(data1[[i1]][, categ[[i1]]])]) # Modif to have length(color) equal to the different number of categ (length(color) == length(levels(data1[[i1]][, categ[[i1]]]))) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count, ") FROM FUNCTION ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " HAS THE LENGTH OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " COLUMN VALUES\nCOLORS HAVE BEEN RESPECTIVELY ASSOCIATED TO EACH CLASS OF categ AS:\n", paste(levels(factor(data1[[i1]][, categ[[i1]]])), collapse = " "), "\n", paste(color[[i1]], collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else if(length(color[[i1]])== 1L){ +data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order +color[[i1]] <- rep(color[[i1]], length(levels(data1[[i1]][, categ[[i1]]]))) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", COLOR HAS LENGTH 1 MEANING THAT ALL THE DIFFERENT CLASSES OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), "\n", paste(levels(factor(data1[[i1]][, categ[[i1]]])), collapse = " "), "\nWILL HAVE THE SAME COLOR\n", paste(color[[i1]], collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE\n(1) LENGTH 1\nOR (2) THE LENGTH OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " COLUMN VALUES\nOR (3) THE LENGTH OF THE CLASSES IN THIS COLUMN\nHERE IT IS COLOR LENGTH ", length(color[[i1]]), " VERSUS CATEG LENGTH ", length(data1[[i1]][, categ[[i1]]]), " AND CATEG CLASS LENGTH ", length(unique(data1[[i1]][, categ[[i1]]])), "\nPRESENCE OF NA IN THE COLUMN x, y OR categ OF data1 COULD BE THE PROBLEME") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +} +if((geom[[i1]] == "geom_hline" | geom[[i1]] == "geom_vline") & ! is.null(categ[[i1]])){ # add here after the color management, to deal with the different lines to plot inside any data[[i1]] +if(categ[[i1]] == "fake_categ"){ +data1[[i1]][, "fake_categ"] <- factor(paste0("Line_", formatC(1:nrow(data1[[i2]]), width = nchar(nrow(data1[[i2]])), flag = "0"))) +} +} +tempo <- fun_check(data = alpha[[i1]], data.name = ifelse(length(alpha)== 1L, "alpha", paste0("alpha NUMBER ", i1)), prop = TRUE, length = 1, fun.name = function.name) +if(tempo$problem == TRUE){ +stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +# end loop (checking inside list compartment) +if(length(data1) > 1){ +if(length(unique(unlist(x)[ ! x == "fake_x"])) > 1){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE x ARGUMENT DOES NOT CONTAIN IDENTICAL COLUMN NAMES:\n", paste(unlist(x), collapse = " "), "\nX-AXIS OVERLAYING DIFFERENT VARIABLES?") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +if(length(data1) > 1){ +if(length(unique(unlist(y)[ ! y == "fake_y"])) > 1){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE y ARGUMENT DOES NOT CONTAIN IDENTICAL COLUMN NAMES:\n", paste(unlist(y), collapse = " "), "\nY-AXIS OVERLAYING DIFFERENT VARIABLES?") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +if(sum(geom %in% "geom_point") > 3){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT CANNOT HAVE MORE THAN THREE \"geom_point\" ELEMENTS") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(length(geom) - sum(geom %in% "geom_point") > 3){ +tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT CANNOT HAVE MORE THAN THREE LINE ELEMENTS") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +# x.lim management before transfo by x.log +if(x.log != "no" & ! is.null(x.lim)){ +if(any(x.lim <= 0)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE x.log ARGUMENT SET TO ", x.log, ":\n", paste(x.lim, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(any( ! is.finite(if(x.log == "log10"){log10(x.lim)}else{log2(x.lim)}))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lim ARGUMENT RETURNS INF/NA WITH THE x.log ARGUMENT SET TO ", x.log, "\nAS SCALE COMPUTATION IS ", ifelse(x.log == "log10", "log10", "log2"), ":\n", paste(if(x.log == "log10"){log10(x.lim)}else{log2(x.lim)}, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if(x.log != "no" & x.include.zero == TRUE){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") x.log ARGUMENT SET TO ", x.log, " AND x.include.zero ARGUMENT SET TO TRUE -> x.include.zero ARGUMENT RESET TO FALSE BECAUSE 0 VALUE CANNOT BE REPRESENTED IN LOG SCALE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +x.include.zero <- FALSE +} +# end x.lim management before transfo by x.log +# y.lim management before transfo by y.log +if(y.log != "no" & ! is.null(y.lim)){ +if(any(y.lim <= 0)){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(any( ! is.finite(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT RETURNS INF/NA WITH THE y.log ARGUMENT SET TO ", y.log, "\nAS SCALE COMPUTATION IS ", ifelse(y.log == "log10", "log10", "log2"), ":\n", paste(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if(y.log != "no" & y.include.zero == TRUE){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") y.log ARGUMENT SET TO ", y.log, " AND y.include.zero ARGUMENT SET TO TRUE -> y.include.zero ARGUMENT RESET TO FALSE BECAUSE 0 VALUE CANNOT BE REPRESENTED IN LOG SCALE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +y.include.zero <- FALSE +} +# end y.lim management before transfo by y.log +# end other checkings +# reserved word checking +#already done above +# end reserved word checking +# end second round of checking and data preparation + + +# package checking +fun_pack(req.package = c( +"gridExtra", +"ggplot2", +"lemon", +"scales" +), lib.path = lib.path) +# packages Cairo and grid tested by fun_gg_point_rast() +# end package checking + + + + +# main code +# axes management +if(is.null(x.lim)){ +if(any(unlist(mapply(FUN = "[[", data1, x, SIMPLIFY = FALSE)) %in% c(Inf, -Inf))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE x COLUMN IN data1 CONTAINS -Inf OR Inf VALUES THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +x.lim <- suppressWarnings(range(unlist(mapply(FUN = "[[", data1, x, SIMPLIFY = FALSE)), na.rm = TRUE, finite = TRUE)) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only. y.lim added here. If NULL, ok if y argument has values +}else if(x.log != "no"){ +x.lim <- get(x.log)(x.lim) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +} +if(x.log != "no"){ +if(any( ! is.finite(x.lim))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE x.log ARGUMENT SET TO ", x.log, ":\n", paste(x.lim, collapse = " "), "\nPLEASE, CHECK DATA VALUES (PRESENCE OF ZERO OR INF VALUES)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if(suppressWarnings(all(x.lim %in% c(Inf, -Inf)))){ # happen when x is only NULL +if(all(unlist(geom) %in% c("geom_vline", "geom_stick"))){ +tempo.cat <- paste0("ERROR IN ", function.name, " NOT POSSIBLE TO DRAW geom_vline OR geom_stick KIND OF LINES ALONE IF x.lim ARGUMENT IS SET TO NULL, SINCE NO X-AXIS DEFINED (", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x")), " ARGUMENT MUST BE NULL FOR THESE KIND OF LINES)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, " x.lim ARGUMENT MADE OF NA, -Inf OR Inf ONLY: ", paste(x.lim, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +x.lim.order <- order(x.lim) # to deal with inverse axis +# print(x.lim.order) +x.lim <- sort(x.lim) +x.lim[1] <- x.lim[1] - abs(x.lim[2] - x.lim[1]) * ifelse(diff(x.lim.order) > 0, x.right.extra.margin, x.left.extra.margin) # diff(x.lim.order) > 0 means not inversed axis +x.lim[2] <- x.lim[2] + abs(x.lim[2] - x.lim[1]) * ifelse(diff(x.lim.order) > 0, x.left.extra.margin, x.right.extra.margin) # diff(x.lim.order) > 0 means not inversed axis +if(x.include.zero == TRUE){ # no need to check x.log != "no" because done before +x.lim <- range(c(x.lim, 0), na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only +} +x.lim <- x.lim[x.lim.order] +if(any(is.na(x.lim))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 3") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if(is.null(y.lim)){ +if(any(unlist(mapply(FUN = "[[", data1, y, SIMPLIFY = FALSE)) %in% c(Inf, -Inf))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE y COLUMN IN data1 CONTAINS -Inf OR Inf VALUES THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +y.lim <- suppressWarnings(range(unlist(mapply(FUN = "[[", data1, y, SIMPLIFY = FALSE)), na.rm = TRUE, finite = TRUE)) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only. y.lim added here. If NULL, ok if y argument has values +}else if(y.log != "no"){ +y.lim <- get(y.log)(y.lim) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope +} +if(y.log != "no"){ +if(any( ! is.finite(y.lim))){ +tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " "), "\nPLEASE, CHECK DATA VALUES (PRESENCE OF ZERO OR INF VALUES)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +} +if(suppressWarnings(all(y.lim %in% c(Inf, -Inf)))){ # happen when y is only NULL +if(all(unlist(geom) == "geom_vline")){ +tempo.cat <- paste0("ERROR IN ", function.name, " NOT POSSIBLE TO DRAW geom_vline KIND OF LINES ALONE IF y.lim ARGUMENT IS SET TO NULL, SINCE NO Y-AXIS DEFINED (", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y")), " ARGUMENT MUST BE NULL FOR THESE KIND OF LINES)") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, " y.lim ARGUMENT MADE OF NA, -Inf OR Inf ONLY: ", paste(y.lim, collapse = " ")) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} } - - -######## fun_scale() #### select nice label numbers when setting number of ticks on an axis - - - - - -fun_scale <- function(n, lim, kind = "approx", lib.path = NULL){ - # AIM - # attempt to select nice scale numbers when setting n ticks on a lim axis range - # ARGUMENTS - # n: desired number of main ticks on the axis (integer above 0) - # lim: vector of 2 numbers indicating the limit range of the axis. Order of the 2 values matters (for inverted axis). Can be log transformed values - # kind: either "approx" (approximative), "strict" (strict) or "strict.cl" (strict clean). If "approx", use the scales::trans_breaks() function to provide an easy to read scale of approximately n ticks spanning the range of the lim argument. If "strict", cut the range of the lim argument into n + 1 equidistant part and return the n numbers at each boundary. This often generates numbers uneasy to read. If "strict.cl", provide an easy to read scale of exactly n ticks, but sometimes not completely spanning the range of the lim argument - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL - # RETURN - # a vector of numbers - # REQUIRED PACKAGES - # if kind = "approx": - # ggplot2 - # scales - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_round() - # EXAMPLES - # approximate number of main ticks - # ymin = 2 ; ymax = 3.101 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "approx") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) - # strict number of main ticks - # ymin = 2 ; ymax = 3.101 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "strict") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) - # strict "clean" number of main ticks - # ymin = 2 ; ymax = 3.101 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "strict.cl") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) - # approximate number of main ticks, scale inversion - # ymin = 3.101 ; ymax = 2 ; n = 5 ; scale <- fun_scale(n = n, lim = c(ymin, ymax), kind = "approx") ; scale ; par(yaxt = "n", yaxs = "i", las = 1) ; plot(ymin:ymax, ymin:ymax, xlim = range(scale, ymin, ymax)[order(c(ymin, ymax))], ylim = range(scale, ymin, ymax)[order(c(ymin, ymax))], xlab = "DEFAULT SCALE", ylab = "NEW SCALE") ; par(yaxt = "s") ; axis(side = 2, at = scale) - # DEBUGGING - # n = 9 ; lim = c(2, 3.101) ; kind = "approx" ; lib.path = NULL # for function debugging - # n = 10 ; lim = c(1e-4, 1e6) ; kind = "approx" ; lib.path = NULL # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # end initial argument checking - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_round", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_round() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & isTRUE(all.equal(n, 0))){ # isTRUE(all.equal(n, 0)) equivalent to n == 0 but deals with floats (approx ok) - tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT MUST BE A NON NULL AND POSITIVE INTEGER") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) # - } - tempo <- fun_check(data = lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & all(diff(lim) == 0L)){ # isTRUE(all.equal(diff(lim), rep(0, length(diff(lim))))) not used because we strictly need zero as a result - tempo.cat <- paste0("ERROR IN ", function.name, ": lim ARGUMENT HAS A NULL RANGE (2 IDENTICAL VALUES)") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & any(lim %in% c(Inf, -Inf))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = kind, options = c("approx", "strict", "strict.cl"), length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - lim.rank <- rank(lim) # to deal with inverted axis - lim <- sort(lim) - if(kind == "approx"){ - # package checking - fun_pack(req.package = c("ggplot2"), lib.path = lib.path) - fun_pack(req.package = c("scales"), lib.path = lib.path) - # end package checking - output <- ggplot2::ggplot_build(ggplot2::ggplot() + ggplot2::scale_y_continuous( - breaks = scales::trans_breaks( - trans = "identity", - inv = "identity", - n = n - ), - limits = lim - ))$layout$panel_params[[1]]$y$breaks # pretty() alone is not appropriate: tempo.pret <- pretty(seq(lim[1] ,lim[2], length.out = n)) ; tempo.pret[tempo.pret > = lim[1] & tempo.pret < = lim[2]]. # in ggplot 3.3.0, tempo.coord$y.major_source replaced by tempo.coord$y$breaks - if( ! is.null(attributes(output))){ # layout$panel_params[[1]]$y$breaks can be characters (labels of the axis). In that case, it has attributes that corresponds to positions - output <- unlist(attributes(output)) - } - output <- output[ ! is.na(output)] - }else if(kind == "strict"){ - output <- fun_round(seq(lim[1] ,lim[2], length.out = n), 2) - }else if(kind == "strict.cl"){ - tempo.range <- diff(sort(lim)) - tempo.max <- max(lim) - tempo.min <- min(lim) - mid <- tempo.min + (tempo.range/2) # middle of axis - tempo.inter <- tempo.range / (n + 1) # current interval between two ticks, between 0 and Inf - if(tempo.inter == 0L){ # isTRUE(all.equal(tempo.inter, rep(0, length(tempo.inter)))) not used because we strictly need zero as a result - tempo.cat <- paste0("ERROR IN ", function.name, ": THE INTERVAL BETWEEN TWO TICKS OF THE SCALE IS NULL. MODIFY THE lim OR n ARGUMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - log10.abs.lim <- 200 - log10.range <- (-log10.abs.lim):log10.abs.lim - log10.vec <- 10^log10.range - round.vec <- c(5, 4, 3, 2.5, 2, 1.25, 1) - dec.table <- outer(log10.vec, round.vec) # table containing the scale units (row: power of ten from -201 to +199, column: the 5, 2.5, 2, 1.25, 1 notches - - - - # recover the number of leading zeros in tempo.inter - ini.scipen <- options()$scipen - options(scipen = -1000) # force scientific format - if(any(grepl(pattern = "\\+", x = tempo.inter))){ # tempo.inter > 1 - power10.exp <- as.integer(substring(text = tempo.inter, first = (regexpr(pattern = "\\+", text = tempo.inter) + 1))) # recover the power of 10. Example recover 08 from 1e+08 - mantisse <- as.numeric(substr(x = tempo.inter, start = 1, stop = (regexpr(pattern = "\\+", text = tempo.inter) - 2))) # recover the mantisse. Example recover 1.22 from 1.22e+08 - }else if(any(grepl(pattern = "\\-", x = tempo.inter))){ # tempo.inter < 1 - power10.exp <- as.integer(substring(text = tempo.inter, first = (regexpr(pattern = "\\-", text = tempo.inter)))) # recover the power of 10. Example recover 08 from 1e+08 - mantisse <- as.numeric(substr(x = tempo.inter, start = 1, stop = (regexpr(pattern = "\\-", text = tempo.inter) - 2))) # recover the mantisse. Example recover 1.22 from 1.22e+08 - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - tempo.scale <- dec.table[log10.range == power10.exp, ] - # new interval - inter.select <- NULL - for(i1 in 1:length(tempo.scale)){ - tempo.first.tick <- trunc((tempo.min + tempo.scale[i1]) / tempo.scale[i1]) * (tempo.scale[i1]) # this would be use to have a number not multiple of tempo.scale[i1]: ceiling(tempo.min) + tempo.scale[i1] * 10^power10.exp - tempo.last.tick <- tempo.first.tick + tempo.scale[i1] * (n - 1) - if((tempo.first.tick >= tempo.min) & (tempo.last.tick <= tempo.max)){ - inter.select <- tempo.scale[i1] - break() - } - } - if(is.null(inter.select)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - options(scipen = ini.scipen) # restore the initial scientific penalty - # end new interval - # centering the new scale - tempo.mid <- trunc((mid + (-1:1) * inter.select) / inter.select) * inter.select # tempo middle tick closest to the middle axis - mid.tick <- tempo.mid[which.min(abs(tempo.mid - mid))] - if(isTRUE(all.equal(n, rep(1, length(n))))){ # isTRUE(all.equal(n, rep(1, length(n)))) is similar to n == 1L but deals with float - output <- mid.tick - }else if(isTRUE(all.equal(n, rep(2, length(n))))){ # isTRUE(all.equal(n, rep(0, length(n)))) is similar to n == 2L but deals with float - output <- mid.tick - tempo.min.dist <- mid.tick - inter.select - tempo.min - tempo.max.dist <- tempo.max - mid.tick + inter.select - if(tempo.min.dist <= tempo.max.dist){ # distance between lowest tick and bottom axis <= distance between highest tick and top axis. If yes, extra tick but at the top, otherwise at the bottom - output <- c(mid.tick, mid.tick + inter.select) - }else{ - output <- c(mid.tick - inter.select, mid.tick) - } - }else if((n / 2 - trunc(n / 2)) > 0.1){ # > 0.1 to avoid floating point. Because result can only be 0 or 0.5. Thus, > 0.1 means odd number - output <- c(mid.tick - (trunc(n / 2):1) * inter.select, mid.tick, mid.tick + (1:trunc(n / 2)) * inter.select) - }else if((n / 2 - trunc(n / 2)) < 0.1){ # < 0.1 to avoid floating point. Because result can only be 0 or 0.5. Thus, < 0.1 means even number - tempo.min.dist <- mid.tick - trunc(n / 2) * inter.select - tempo.min - tempo.max.dist <- tempo.max - mid.tick + trunc(n / 2) * inter.select - if(tempo.min.dist <= tempo.max.dist){ # distance between lowest tick and bottom axis <= distance between highest tick and top axis. If yes, extra tick but at the bottom, otherwise at the top - output <- c(mid.tick - ((trunc(n / 2) - 1):1) * inter.select, mid.tick, mid.tick + (1:trunc(n / 2)) * inter.select) - }else{ - output <- c(mid.tick - (trunc(n / 2):1) * inter.select, mid.tick, mid.tick + (1:(trunc(n / 2) - 1)) * inter.select) - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 3") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end centering the new scale - # last check - if(min(output) < tempo.min){ - output <- c(output[-1], max(output) + inter.select) # remove the lowest tick and add a tick at the top - }else if( max(output) > tempo.max){ - output <- c(min(output) - inter.select, output[-length(output)]) - } - if(min(output) < tempo.min | max(output) > tempo.max){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 4") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(is.na(output))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 5 (NA GENERATION)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end last check - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 6") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(diff(lim.rank) < 0){ - output <- rev(output) - } - return(output) +y.lim.order <- order(y.lim) # to deal with inverse axis +y.lim <- sort(y.lim) +y.lim[1] <- y.lim[1] - abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.bottom.extra.margin, y.top.extra.margin) # diff(y.lim.order) > 0 means not inversed axis +y.lim[2] <- y.lim[2] + abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.top.extra.margin, y.bottom.extra.margin) # diff(y.lim.order) > 0 means not inversed axis +if(y.include.zero == TRUE){ # no need to check y.log != "no" because done before +y.lim <- range(c(y.lim, 0), na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only } - - -######## fun_inter_ticks() #### define coordinates of secondary ticks - - -fun_inter_ticks <- function( - lim, - log = "log10", - breaks = NULL, - n = NULL, - warn.print = TRUE -){ - # AIM - # define coordinates and values of secondary ticks - # ARGUMENTS - # lim: vector of 2 numbers indicating the limit range of the axis. Order of the 2 values matters (for inverted axis). If log argument is "log2" or "log10", values in lim must be already log transformed. Thus, negative or zero values are allowed - # log: either "log2" (values in the lim argument are log2 transformed) or "log10" (values in the lim argument are log10 transformed), or "no" - # breaks: mandatory vector of numbers indicating the main ticks values/positions when log argument is "no". Ignored when log argument is "log2" or "log10" - # n: number of secondary ticks between each main tick when log argument is "no". Ignored when log argument is "log2" or "log10" - # warn.print: logical. Print potential warning messages at the end of the execution? If FALSE, warning messages are never printed, but can still be recovered in the returned list - # RETURN - # a list containing - # $log: value of the log argument used - # $coordinates: the coordinates of the secondary ticks on the axis, between the lim values - # $values: the corresponding values associated to each coordinate (with log scale, 2^$values or 10^$values is equivalent to the labels of the axis) - # $warn: the potential warning messages. Use cat() for proper display. NULL if no warning - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # no log scale - # fun_inter_ticks(lim = c(-4,4), log = "no", breaks = c(-2, 0, 2), n = 3) - # fun_inter_ticks(lim = c(10, 0), log = "no", breaks = c(10, 8, 6, 4, 2, 0), n = 4) - # log2 - # fun_inter_ticks(lim = c(-4,4), log = "log2") - # log10 - # fun_inter_ticks(lim = c(-2,3), log = "log10") - # DEBUGGING - # lim = c(2, 3.101) ; log = "no" ; breaks = NULL ; n = NULL ; warn.print = TRUE # for function debugging - # lim = c(0, 26.5) ; log = "no" ; breaks = c(0, 10, 20) ; n = 3 # for function debugging - # lim = c(10, 0); log = "no"; breaks = c(10, 8, 6, 4, 2, 0); n = 4 # for function debugging - # lim = c(-10, -20); log = "no"; breaks = c(-20, -15, -10); n = 4 # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - req.function <- c( - "fun_check" - ) - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED ", i1, "() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end required function checking - # argument primary checking - # arg with no default values - mandat.args <- c( - "lim" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # using fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(breaks)){ - tempo <- fun_check(data = breaks, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) - } - if( ! is.null(n)){ - tempo <- fun_check(data = n, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = warn.print, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end using fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # management of NA - if(any(is.na(lim)) | any(is.na(log)) | any(is.na(breaks)) | any(is.na(n)) | any(is.na(warn.print))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nNO ARGUMENT CAN HAVE NA VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA - # management of NULL - if(is.null(lim) | is.null(log)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nTHESE ARGUMENTS\nlim\nlog\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL - if(all(diff(lim) == 0L)){ # isTRUE(all.equal(diff(lim), rep(0, length(diff(lim))))) not used because we strictly need zero as a result - tempo.cat <- paste0("ERROR IN ", function.name, "\nlim ARGUMENT HAS A NULL RANGE (2 IDENTICAL VALUES): ", paste(lim, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(any(lim %in% c(Inf, -Inf))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nlim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(log == "no" & is.null(breaks)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nbreaks ARGUMENT CANNOT BE NULL IF log ARGUMENT IS \"no\"") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(breaks)){ - if(length(breaks) < 2){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nbreaks ARGUMENT MUST HAVE 2 VALUES AT LEAST (OTHERWISE, INTER TICK POSITIONS CANNOT BE COMPUTED): ", paste(breaks, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! isTRUE(all.equal(diff(sort(breaks)), rep(diff(sort(breaks))[1], length(diff(sort(breaks))))))){ # isTRUE(all.equal(n, 0)) equivalent to n == 0 but deals with floats (approx ok) - tempo.cat <- paste0("ERROR IN ", function.name, "\nbreaks ARGUMENT MUST HAVE EQUIDISTANT VALUES (OTHERWISE, EQUAL NUMBER OF INTER TICK BETWEEN MAIN TICKS CANNOT BE COMPUTED): ", paste(breaks, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if( ! is.null(n)){ - if(n <= 0){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nn ARGUMENT MUST BE A POSITIVE AND NON NULL INTEGER: ", paste(n, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end second round of checking and data preparation - # main code - ini.warning.length <- options()$warning.length - options(warning.length = 8170) - warn <- NULL - warn.count <- 0 - lim.rank <- rank(lim) # to deal with inverse axis - if(log != "no"){ - ini.scipen <- options()$scipen - options(scipen = -1000) # force scientific format - power10.exp <- as.integer(substring(text = 10^lim, first = (regexpr(pattern = "\\+|\\-", text = 10^lim)))) # recover the power of 10, i.e., integer part of lim. Example recover 08 from 1e+08. Works for log2 - # mantisse <- as.numeric(substr(x = 10^lim, start = 1, stop = (regexpr(pattern = "\\+|\\-", text = 10^lim) - 2))) # recover the mantisse. Example recover 1.22 from 1.22e+08 - options(scipen = ini.scipen) # restore the initial scientific penalty - tick.pos <- unique(as.vector(outer(2:10, ifelse(log == "log2", 2, 10)^((power10.exp[1] - ifelse(diff(lim.rank) > 0, 1, -1)):(power10.exp[2] + ifelse(diff(lim.rank) > 0, 1, -1)))))) # use log10(2:10) even if log2: it is to get log values between 0 and 1 - tick.pos <- sort(tick.pos, decreasing = ifelse(diff(lim.rank) > 0, FALSE, TRUE)) - if(log == "log2"){ - tick.values <- tick.pos[tick.pos >= min(2^lim) & tick.pos <= max(2^lim)] - tick.pos <- log2(tick.values) - }else if(log == "log10"){ - tick.values <- tick.pos[tick.pos >= min(10^lim) & tick.pos <= max(10^lim)] - tick.pos <- log10(tick.values) - } - }else{ - # if(length(breaks) > 1){ # not required because already checked above - breaks.rank <- rank(c(breaks[1], breaks[length(breaks)])) - if(diff(breaks.rank) != diff(lim.rank)){ - breaks <- sort(breaks, decreasing = ifelse(diff(lim.rank) < 0, TRUE, FALSE)) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") VALUES IN breaks ARGUMENT NOT IN THE SAME ORDER AS IN lim ARGUMENT -> VALUES REORDERED AS IN lim: ", paste(breaks, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - breaks.rank <- rank(c(breaks[1], breaks[length(breaks)])) - } - # } - main.tick.dist <- mean(diff(breaks), na.rm = TRUE) - tick.dist <- main.tick.dist / (n + 1) - tempo.extra.margin <- max(abs(diff(breaks)), na.rm = TRUE) - tick.pos <- seq( - if(diff(breaks.rank) > 0){breaks[1] - tempo.extra.margin}else{breaks[1] + tempo.extra.margin}, - if(diff(breaks.rank) > 0){breaks[length(breaks)] + tempo.extra.margin}else{breaks[length(breaks)] - tempo.extra.margin}, - by = tick.dist - ) - tick.pos <- tick.pos[tick.pos >= min(lim) & tick.pos <= max(lim)] - tick.values <- tick.pos - } - if(any(is.na(tick.pos) | ! is.finite(tick.pos))){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, ": NA or Inf GENERATED FOR THE INTER TICK POSITIONS: ", paste(tick.pos, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(tick.pos) == 0L){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NO INTER TICKS COMPUTED BETWEEN THE LIMITS INDICATED: ", paste(lim, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - output <- list(log = log, coordinates = tick.pos, values = tick.values, warn = warn) - if(warn.print == TRUE & ! is.null(warn)){ - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) # to recover the warning messages, see $warn - } - on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) - return(output) +y.lim <- y.lim[y.lim.order] +if(any(is.na(y.lim))){ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 4") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) } - - -######## fun_post_plot() #### set graph param after plotting (axes redesign for instance) - +# end axes management -fun_post_plot <- function( - x.side = 0, - x.log.scale = FALSE, - x.categ = NULL, - x.categ.pos = NULL, - x.lab = "", - x.axis.size = 1.5, - x.label.size = 1.5, - x.dist.legend = 0.5, - x.nb.inter.tick = 1, - y.side = 0, - y.log.scale = FALSE, - y.categ = NULL, - y.categ.pos = NULL, - y.lab = "", - y.axis.size = 1.5, - y.label.size = 1.5, - y.dist.legend = 0.5, - y.nb.inter.tick = 1, - text.angle = 90, - tick.length = 0.5, - sec.tick.length = 0.3, - bg.color = NULL, - grid.lwd = NULL, - grid.col = "white", - corner.text = "", - corner.text.size = 1, - just.label.add = FALSE, - par.reset = FALSE, - custom.par = NULL -){ - # AIM - # redesign axis. If x.side = 0, y.side = 0, the function just adds text at topright of the graph and reset par() for next graphics and provides outputs (see below) - # provide also positions for legend or additional text on the graph - # use fun_prior_plot() before this function for initial inactivation of the axis drawings - # ARGUMENTS - # x.side: axis at the bottom (1) or top (3) of the region figure. Write 0 for no change - # x.log.scale: Log scale for the x-axis? Either TRUE or FALSE - # x.categ: character vector representing the classes (levels()) to specify when the x-axis is qualititative(stripchart, boxplot) - # x.categ.pos: position of the classes names (numeric vector of identical length than x.categ). If left NULL, this will be 1:length(levels()) - # x.lab: label of the x-axis. If x.side == 0 and x.lab != "", then x.lab is printed - # x.axis.size: positive numeric. Increase or decrease the size of the x axis numbers. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2. Also control the size of displayed categories - # x.label.size: positive numeric. Increase or decrease the size of the x axis legend text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 - # x.dist.legend: increase the number to move x-axis legends away in inches (first number of mgp argument of par() but in inches) - # x.nb.inter.tick: number of secondary ticks between main ticks on x-axis (only if not log scale). 0 means no secondary ticks - # y.side: axis at the left (2) or right (4) of the region figure. Write 0 for no change - # y.log.scale: Log scale for the y-axis? Either TRUE or FALSE - # y.categ: classes (levels()) to specify when the y-axis is qualititative(stripchart, boxplot) - # y.categ.pos: position of the classes names (numeric vector of identical length than y.categ). If left NULL, this will be 1:length(levels()) - # y.lab: label of the y-axis. If y.side == 0 and y.lab != "", then y.lab is printed - # y.axis.size: positive numeric. Increase or decrease the size of the y axis numbers. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2. Also control the size of displayed categories - # y.label.size: positive numeric. Increase or decrease the size of the y axis legend text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 - # y.dist.legend: increase the number to move y-axis legends away in inches (first number of mgp argument of par() but in inches) - # y.nb.inter.tick: number of secondary ticks between main ticks on y-axis (only if not log scale). 0 means non secondary ticks - # text.angle: angle of the text when axis is qualitative - # tick.length: length of the main ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc., 0 for no ticks) - # sec.tick.length: length of the secondary ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc., 0 for no ticks) - # bg.color: background color of the plot region. NULL for no color. BEWARE: cover/hide an existing plot ! - # grid.lwd: if non NULL, activate the grid line (specify the line width) - # grid.col: grid line color (only if grid.lwd non NULL) - # corner.text: text to add at the top right corner of the window - # corner.text.size: positive numeric. Increase or decrease the size of the text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 - # par.reset: to reset all the graphics parameters. BEWARE: TRUE can generate display problems, mainly in graphic devices with multiple figure regions - # just.label.add: just add axis labels (legend)? Either TRUE or FALSE. If TRUE, at least (x.side == 0 & x.lab != "") or (y.side == 0 & y.lab != "") must be set to display the corresponding x.lab or y.lab - # custom.par: list that provides the parameters that reset all the graphics parameters. BEWARE: if NULL and par.reset == TRUE, the default par() parameters are used - # RETURN - # a list containing: - # $x.mid.left.dev.region: middle of the left margin of the device region, in coordinates of the x-axis - # $x.left.dev.region: left side of the left margin (including the potential margin of the device region), in coordinates of the x-axis - # $x.mid.right.dev.region: middle of the right margin of the device region, in coordinates of the x-axis - # $x.right.dev.region: right side of the right margin (including the potential margin of the device region), in coordinates of the x-axis - # $x.mid.left.fig.region: middle of the left margin of the figure region, in coordinates of the x-axis - # $x.left.fig.region: left side of the left margin, in coordinates of the x-axis - # $x.mid.right.fig.region: middle of the right margin of the figure region, in coordinates of the x-axis - # $x.right.fig.region: right side of the right margin, in coordinates of the x-axis - # $x.left.plot.region: left side of the plot region, in coordinates of the x-axis - # $x.right.plot.region: right side of the plot region, in coordinates of the x-axis - # $x.mid.plot.region: middle of the plot region, in coordinates of the x-axis - # $y.mid.bottom.dev.region: middle of the bottom margin of the device region, in coordinates of the y-axis - # $y.bottom.dev.region: bottom side of the bottom margin (including the potential margin of the device region), in coordinates of the y-axis - # $y.mid.top.dev.region: middle of the top margin of the device region, in coordinates of the y-axis - # $y.top.dev.region: top side of the top margin (including the potential margin of the device region), in coordinates of the y-axis - # $y.mid.bottom.fig.region: middle of the bottom margin of the figure region, in coordinates of the y-axis - # $y.bottom.fig.region: bottom of the bottom margin of the figure region, in coordinates of the y-axis - # $y.mid.top.fig.region: middle of the top margin of the figure region, in coordinates of the y-axis - # $y.top.fig.region: top of the top margin of the figure region, in coordinates of the y-axis - # $y.top.plot.region: top of the plot region, in coordinates of the y-axis - # $y.bottom.plot.region: bottom of the plot region, in coordinates of the y-axis - # $y.mid.plot.region: middle of the plot region, in coordinates of the y-axis - # $text: warning text - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_open() to reinitialize graph parameters if par.reset = TRUE and custom.par = NULL - # EXAMPLES - # Example of log axis with log y-axis and unmodified x-axis: - # prior.par <- fun_prior_plot(param.reinitial = TRUE, xlog.scale = FALSE, ylog.scale = TRUE, remove.label = TRUE, remove.x.axis = FALSE, remove.y.axis = TRUE, down.space = 1, left.space = 1, up.space = 1, right.space = 1, orient = 1, dist.legend = 0.5, tick.length = 0.5, box.type = "n", amplif.label = 1, amplif.axis = 1, display.extend = FALSE, return.par = TRUE) ; plot(1:100, log = "y") ; fun_post_plot(y.side = 2, y.log.scale = prior.par$ylog, x.lab = "Values", y.lab = "TEST", y.axis.size = 1.25, y.label.size = 1.5, y.dist.legend = 0.7, just.label.add = ! prior.par$ann) - # Example of log axis with redrawn x-axis and y-axis: - # prior.par <- fun_prior_plot(param.reinitial = TRUE) ; plot(1:100) ; fun_post_plot(x.side = 1, x.lab = "Values", y.side = 2, y.lab = "TEST", y.axis.size = 1, y.label.size = 2, y.dist.legend = 0.6) - # Example of title easily added to a plot: - # plot(1:100) ; para <- fun_post_plot(corner.text = "TITLE ADDED") # try also: par(xpd = TRUE) ; text(x = para$x.mid.left.fig.region, y = para$y.mid.top.fig.region, labels = "TITLE ADDED", cex = 0.5) - # example with margins in the device region: - # windows(5,5) ; fun_prior_plot(box.type = "o") ; par(mai=c(0.5,0.5,0.5,0.5), omi = c(0.25,0.25,1,0.25), xaxs = "i", yaxs = "i") ; plot(0:10) ; a <- fun_post_plot(x.side = 0, y.side = 0) ; x <- c(a$x.mid.left.dev.region, a$x.left.dev.region, a$x.mid.right.dev.region, a$x.right.dev.region, a$x.mid.left.fig.region, a$x.left.fig.region, a$x.mid.right.fig.region, a$x.right.fig.region, a$x.right.plot.region, a$x.left.plot.region, a$x.mid.plot.region) ; y <- c(a$y.mid.bottom.dev.region, a$y.bottom.dev.region, a$y.mid.top.dev.region, a$y.top.dev.region, a$y.mid.bottom.fig.region, a$y.bottom.fig.region, a$y.mid.top.fig.region, a$y.top.fig.region, a$y.top.plot.region, a$y.bottom.plot.region, a$y.mid.plot.region) ; par(xpd = NA) ; points(x = rep(5, length(y)), y = y, pch = 16, col = "red") ; text(x = rep(5, length(y)), y = y, c("y.mid.bottom.dev.region", "y.bottom.dev.region", "y.mid.top.dev.region", "y.top.dev.region", "y.mid.bottom.fig.region", "y.bottom.fig.region", "y.mid.top.fig.region", "y.top.fig.region", "y.top.plot.region", "y.bottom.plot.region", "y.mid.plot.region"), cex = 0.65, col = grey(0.25)) ; points(y = rep(5, length(x)), x = x, pch = 16, col = "blue") ; text(y = rep(5, length(x)), x = x, c("x.mid.left.dev.region", "x.left.dev.region", "x.mid.right.dev.region", "x.right.dev.region", "x.mid.left.fig.region", "x.left.fig.region", "x.mid.right.fig.region", "x.right.fig.region", "x.right.plot.region", "x.left.plot.region", "x.mid.plot.region"), cex = 0.65, srt = 90, col = grey(0.25)) - # DEBUGGING - # x.side = 0 ; x.log.scale = FALSE ; x.categ = NULL ; x.categ.pos = NULL ; x.lab = "" ; x.axis.size = 1.5 ; x.label.size = 1.5 ; x.dist.legend = 1 ; x.nb.inter.tick = 1 ; y.side = 0 ; y.log.scale = FALSE ; y.categ = NULL ; y.categ.pos = NULL ; y.lab = "" ; y.axis.size = 1.5 ; y.label.size = 1.5 ; y.dist.legend = 0.7 ; y.nb.inter.tick = 1 ; text.angle = 90 ; tick.length = 0.5 ; sec.tick.length = 0.3 ; bg.color = NULL ; grid.lwd = NULL ; grid.col = "white" ; corner.text = "" ; corner.text.size = 1 ; just.label.add = FALSE ; par.reset = FALSE ; custom.par = NULL # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_open", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_open() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = x.side, options = c(0, 1, 3), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.log.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(x.categ)){ - tempo <- fun_check(data = x.categ, class = "character", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - if( ! is.null(x.categ.pos)){ - tempo <- fun_check(data = x.categ.pos, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = x.lab, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.axis.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.label.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.nb.inter.tick, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.side, options = c(0, 2, 4), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.log.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(y.categ)){ - tempo <- fun_check(data = y.categ, class = "character", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - if( ! is.null(y.categ.pos)){ - tempo <- fun_check(data = y.categ.pos, class = "vector", mode = "numeric", fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = y.lab, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.axis.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.label.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.nb.inter.tick, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = text.angle, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = sec.tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - if( ! is.null(bg.color)){ - tempo <- fun_check(data = bg.color, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if( ! (bg.color %in% colors() | grepl(pattern = "^#", bg.color))){ # check color - tempo.cat <- paste0("ERROR IN ", function.name, ": bg.color ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # OR A COLOR NAME GIVEN BY colors()") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - if( ! is.null(grid.lwd)){ - tempo <- fun_check(data = grid.lwd, class = "vector", mode = "numeric", neg.values = FALSE, fun.name = function.name) ; eval(ee) - } - if( ! is.null(grid.col)){ - tempo <- fun_check(data = grid.col, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if( ! (grid.col %in% colors() | grepl(pattern = "^#", grid.col))){ # check color - tempo.cat <- paste0("ERROR IN ", function.name, ": grid.col ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # OR A COLOR NAME GIVEN BY colors()") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = corner.text, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = corner.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = just.label.add, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = par.reset, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(custom.par)){ - tempo <- fun_check(data = custom.par, typeof = "list", length = 1, fun.name = function.name) ; eval(ee) - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - text <- NULL - par(tcl = -par()$mgp[2] * tick.length) - if(x.log.scale == TRUE){ - grid.coord.x <- c(10^par("usr")[1], 10^par("usr")[2]) - }else{ - grid.coord.x <- c(par("usr")[1], par("usr")[2]) - } - if(y.log.scale == TRUE){ - grid.coord.y <- c(10^par("usr")[3], 10^par("usr")[4]) - }else{ - grid.coord.y <- c(par("usr")[3], par("usr")[4]) - } - if( ! is.null(bg.color)){ - rect(grid.coord.x[1], grid.coord.y[1], grid.coord.x[2], grid.coord.y[2], col = bg.color, border = NA) - } - if( ! is.null(grid.lwd)){ - grid(nx = NA, ny = NULL, col = grid.col, lty = 1, lwd = grid.lwd) - } - if(x.log.scale == TRUE){ - x.mid.left.dev.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) - x.left.dev.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1]) # in x coordinates - x.mid.right.dev.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) - x.right.dev.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2])) # in x coordinates - x.mid.left.fig.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) - x.left.fig.region <- 10^(par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1]) # in x coordinates - x.mid.right.fig.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) - x.right.fig.region <- 10^(par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2])) # in x coordinates - x.left.plot.region <- 10^par("usr")[1] # in x coordinates, left of the plot region (according to x scale) - x.right.plot.region <- 10^par("usr")[2] # in x coordinates, right of the plot region (according to x scale) - x.mid.plot.region <- 10^((par("usr")[2] + par("usr")[1]) / 2) # in x coordinates, right of the plot region (according to x scale) - }else{ - x.mid.left.dev.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) - x.left.dev.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1]) # in x coordinates - x.mid.right.dev.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) - x.right.dev.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) + ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * (1 - par("omd")[2])) # in x coordinates - x.mid.left.fig.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] / 2) # in x coordinates, to position axis labeling at the bottom of the graph (according to x scale) - x.left.fig.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1]) # in x coordinates - x.mid.right.fig.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2]) / 2) # in x coordinates, to position axis labeling at the top of the graph (according to x scale) - x.right.fig.region <- (par("usr")[2] + ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * (1 - par("plt")[2])) # in x coordinates - x.left.plot.region <- par("usr")[1] # in x coordinates, left of the plot region (according to x scale) - x.right.plot.region <- par("usr")[2] # in x coordinates, right of the plot region (according to x scale) - x.mid.plot.region <- (par("usr")[2] + par("usr")[1]) / 2 # in x coordinates, right of the plot region (according to x scale) - } - if(y.log.scale == TRUE){ - y.mid.bottom.dev.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (par("omd")[3] / 2)) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space - y.bottom.dev.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * par("omd")[3]) # in y coordinates - y.mid.top.dev.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space - y.top.dev.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4])) # in y coordinates - y.mid.bottom.fig.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] / 2) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space - y.bottom.fig.region <- 10^(par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3]) # in y coordinates - y.mid.top.fig.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space - y.top.fig.region <- 10^(par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4])) # in y coordinates - y.top.plot.region <- 10^par("usr")[4] # in y coordinates, top of the plot region (according to y scale) - y.bottom.plot.region <- 10^par("usr")[3] # in y coordinates, bottom of the plot region (according to y scale) - y.mid.plot.region <- (par("usr")[3] + par("usr")[4]) / 2 # in x coordinates, right of the plot region (according to x scale) - }else{ - y.mid.bottom.dev.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (par("omd")[3] / 2)) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space - y.bottom.dev.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] - ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * par("omd")[3]) # in y coordinates - y.mid.top.dev.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space - y.top.dev.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4])) # in y coordinates - y.mid.bottom.fig.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3] / 2) # in y coordinates, to position axis labeling at the bottom of the graph (according to y scale). Ex mid.bottom.space - y.bottom.fig.region <- (par("usr")[3] - ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * par("plt")[3]) # in y coordinates - y.mid.top.fig.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) / 2) # in y coordinates, to position axis labeling at the top of the graph (according to y scale). Ex mid.top.space - y.top.fig.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4])) # in y coordinates - y.top.plot.region <- par("usr")[4] # in y coordinates, top of the plot region (according to y scale) - y.bottom.plot.region <- par("usr")[3] # in y coordinates, bottom of the plot region (according to y scale) - y.mid.plot.region <- ((par("usr")[3] + par("usr")[4]) / 2) # in x coordinates, right of the plot region (according to x scale) - } - if(any(sapply(FUN = all.equal, c(1, 3), x.side) == TRUE)){ - par(xpd=FALSE, xaxt="s") - if(is.null(x.categ) & x.log.scale == TRUE){ - if(any(par()$xaxp[1:2] == 0L)){ # any(sapply(FUN = all.equal, par()$xaxp[1:2], 0) == TRUE) not used because we strictly need zero as a result. Beware: write "== TRUE", because the result is otherwise character and a warning message appears using any() - if(par()$xaxp[1] == 0L){ # isTRUE(all.equal(par()$xaxp[1], 0)) not used because we strictly need zero as a result - par(xaxp = c(10^-30, par()$xaxp[2:3])) # because log10(par()$xaxp[1] == 0) == -Inf - } - if(par()$xaxp[2] == 0L){ # isTRUE(all.equal(par()$xaxp[1], 0)) not used because we strictly need zero as a result - par(xaxp = c(par()$xaxp[1], 10^-30, par()$xaxp[3])) # because log10(par()$xaxp[2] == 0) == -Inf - } - } - axis(side = x.side, at = c(10^par()$usr[1], 10^par()$usr[2]), labels=rep("", 2), lwd=1, lwd.ticks = 0) # draw the axis line - mtext(side = x.side, text = x.lab, line = x.dist.legend / 0.2, las = 0, cex = x.label.size) - par(tcl = -par()$mgp[2] * sec.tick.length) # length of the secondary ticks are reduced - suppressWarnings(rug(10^outer(c((log10(par("xaxp")[1]) -1):log10(par("xaxp")[2])), log10(1:10), "+"), ticksize = NA, side = x.side)) # ticksize = NA to allow the use of par()$tcl value - par(tcl = -par()$mgp[2] * tick.length) # back to main ticks - axis(side = x.side, at = c(1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10), labels = expression(10^-15, 10^-14, 10^-13, 10^-12, 10^-11, 10^-10, 10^-9, 10^-8, 10^-7, 10^-6, 10^-5, 10^-4, 10^-3, 10^-2, 10^-1, 10^0, 10^1, 10^2, 10^3, 10^4, 10^5, 10^6, 10^7, 10^8, 10^9, 10^10), lwd = 0, lwd.ticks = 1, cex.axis = x.axis.size) - x.text <- 10^par("usr")[2] - }else if(is.null(x.categ) & x.log.scale == FALSE){ - axis(side=x.side, at=c(par()$usr[1], par()$usr[2]), labels=rep("", 2), lwd=1, lwd.ticks=0) # draw the axis line - axis(side=x.side, at=round(seq(par()$xaxp[1], par()$xaxp[2], length.out=par()$xaxp[3]+1), 2), cex.axis = x.axis.size) # axis(side=x.side, at=round(seq(par()$xaxp[1], par()$xaxp[2], length.out=par()$xaxp[3]+1), 2), labels = format(round(seq(par()$xaxp[1], par()$xaxp[2], length.out=par()$xaxp[3]+1), 2), big.mark=','), cex.axis = x.axis.size) # to get the 1000 comma separator - mtext(side = x.side, text = x.lab, line = x.dist.legend / 0.2, las = 0, cex = x.label.size) - if(x.nb.inter.tick > 0){ - inter.tick.unit <- (par("xaxp")[2] - par("xaxp")[1]) / par("xaxp")[3] - par(tcl = -par()$mgp[2] * sec.tick.length) # length of the ticks are reduced - suppressWarnings(rug(seq(par("xaxp")[1] - 10 * inter.tick.unit, par("xaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + x.nb.inter.tick)), ticksize = NA, x.side)) # ticksize = NA to allow the use of par()$tcl value - par(tcl = -par()$mgp[2] * tick.length) # back to main ticks - } - x.text <- par("usr")[2] - }else if(( ! is.null(x.categ)) & x.log.scale == FALSE){ - if(is.null(x.categ.pos)){ - x.categ.pos <- 1:length(x.categ) - }else if(length(x.categ.pos) != length(x.categ)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x.categ.pos MUST BE THE SAME LENGTH AS x.categ") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - par(xpd = TRUE) - if(isTRUE(all.equal(x.side, 1))){ #isTRUE(all.equal(x.side, 1)) is similar to x.side == 1L but deals with float - segments(x0 = x.left.plot.region, x1 = x.right.plot.region, y0 = y.bottom.plot.region, y1 = y.bottom.plot.region) # draw the line of the axis - text(x = x.categ.pos, y = y.mid.bottom.fig.region, labels = x.categ, srt = text.angle, cex = x.axis.size) - }else if(isTRUE(all.equal(x.side, 3))){ #isTRUE(all.equal(x.side, 1)) is similar to x.side == 3L but deals with float - segments(x0 = x.left.plot.region, x1 = x.right.plot.region, y0 = y.top.plot.region, y1 = y.top.plot.region) # draw the line of the axis - text(x = x.categ.pos, y = y.mid.top.fig.region, labels = x.categ, srt = text.angle, cex = x.axis.size) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": ARGUMENT x.side CAN ONLY BE 1 OR 3") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - par(xpd = FALSE) - x.text <- par("usr")[2] - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": PROBLEM WITH THE x.side (", x.side ,") OR x.log.scale (", x.log.scale,") ARGUMENTS") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else{ - x.text <- par("usr")[2] - } - if(any(sapply(FUN = all.equal, c(2, 4), y.side) == TRUE)){ - par(xpd=FALSE, yaxt="s") - if(is.null(y.categ) & y.log.scale == TRUE){ - if(any(par()$yaxp[1:2] == 0L)){ # any(sapply(FUN = all.equal, par()$yaxp[1:2], 0) == TRUE) not used because we strictly need zero as a result. Beware: write "== TRUE", because the result is otherwise character and a warning message appears using any() - if(par()$yaxp[1] == 0L){ # strict zero needed - par(yaxp = c(10^-30, par()$yaxp[2:3])) # because log10(par()$yaxp[1] == 0) == -Inf - } - if(par()$yaxp[2] == 0L){ # strict zero needed - par(yaxp = c(par()$yaxp[1], 10^-30, par()$yaxp[3])) # because log10(par()$yaxp[2] == 0) == -Inf - } - } - axis(side=y.side, at=c(10^par()$usr[3], 10^par()$usr[4]), labels=rep("", 2), lwd=1, lwd.ticks=0) # draw the axis line - par(tcl = -par()$mgp[2] * sec.tick.length) # length of the ticks are reduced - suppressWarnings(rug(10^outer(c((log10(par("yaxp")[1])-1):log10(par("yaxp")[2])), log10(1:10), "+"), ticksize = NA, side = y.side)) # ticksize = NA to allow the use of par()$tcl value - par(tcl = -par()$mgp[2] * tick.length) # back to main tick length - axis(side = y.side, at = c(1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10), labels = expression(10^-15, 10^-14, 10^-13, 10^-12, 10^-11, 10^-10, 10^-9, 10^-8, 10^-7, 10^-6, 10^-5, 10^-4, 10^-3, 10^-2, 10^-1, 10^0, 10^1, 10^2, 10^3, 10^4, 10^5, 10^6, 10^7, 10^8, 10^9, 10^10), lwd = 0, lwd.ticks = 1, cex.axis = y.axis.size) - y.text <- 10^(par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) - mtext(side = y.side, text = y.lab, line = y.dist.legend / 0.2, las = 0, cex = y.label.size) - }else if(is.null(y.categ) & y.log.scale == FALSE){ - axis(side=y.side, at=c(par()$usr[3], par()$usr[4]), labels=rep("", 2), lwd=1, lwd.ticks=0) # draw the axis line - axis(side=y.side, at=round(seq(par()$yaxp[1], par()$yaxp[2], length.out=par()$yaxp[3]+1), 2), cex.axis = y.axis.size) - mtext(side = y.side, text = y.lab, line = y.dist.legend / 0.2, las = 0, cex = y.label.size) - if(y.nb.inter.tick > 0){ - inter.tick.unit <- (par("yaxp")[2] - par("yaxp")[1]) / par("yaxp")[3] - par(tcl = -par()$mgp[2] * sec.tick.length) # length of the ticks are reduced - suppressWarnings(rug(seq(par("yaxp")[1] - 10 * inter.tick.unit, par("yaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + y.nb.inter.tick)), ticksize = NA, side=y.side)) # ticksize = NA to allow the use of par()$tcl value - par(tcl = -par()$mgp[2] * tick.length) # back to main tick length - } - y.text <- (par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) - }else if(( ! is.null(y.categ)) & y.log.scale == FALSE){ - if(is.null(y.categ.pos)){ - y.categ.pos <- 1:length(y.categ) - }else if(length(y.categ.pos) != length(y.categ)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y.categ.pos MUST BE THE SAME LENGTH AS y.categ") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - axis(side = y.side, at = y.categ.pos, labels = rep("", length(y.categ)), lwd=0, lwd.ticks=1) # draw the line of the axis - par(xpd = TRUE) - if(isTRUE(all.equal(y.side, 2))){ #isTRUE(all.equal(y.side, 2)) is similar to y.side == 2L but deals with float - text(x = x.mid.left.fig.region, y = y.categ.pos, labels = y.categ, srt = text.angle, cex = y.axis.size) - }else if(isTRUE(all.equal(y.side, 4))){ # idem - text(x = x.mid.right.fig.region, y = y.categ.pos, labels = y.categ, srt = text.angle, cex = y.axis.size) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": ARGUMENT y.side CAN ONLY BE 2 OR 4") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - par(xpd = FALSE) - y.text <- (par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": PROBLEM WITH THE y.side (", y.side ,") OR y.log.scale (", y.log.scale,") ARGUMENTS") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else{ - y.text <- (par("usr")[4] + (par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3]) * (1 - par("plt")[4])) - } - par(xpd=NA) - text(x = x.mid.right.fig.region, y = y.text, corner.text, adj=c(1, 1.1), cex = corner.text.size) # text at the topright corner. Replace x.right.fig.region by x.text if text at the right edge of the plot region - if(just.label.add == TRUE & isTRUE(all.equal(x.side, 0)) & x.lab != ""){ - text(x = x.mid.plot.region, y = y.mid.bottom.fig.region, x.lab, adj=c(0.5, 0.5), cex = x.label.size) # x label - } - if(just.label.add == TRUE & isTRUE(all.equal(y.side, 0)) & y.lab != ""){ - text(x = y.mid.plot.region, y = x.mid.left.fig.region, y.lab, adj=c(0.5, 0.5), cex = y.label.size) # x label - } - par(xpd=FALSE) - if(par.reset == TRUE){ - tempo.par <- fun_open(pdf = FALSE, return.output = TRUE) - invisible(dev.off()) # close the new window - if( ! is.null(custom.par)){ - if( ! names(custom.par) %in% names(tempo.par$ini.par)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": custom.par ARGUMENT SHOULD HAVE THE NAMES OF THE COMPARTMENT LIST COMING FROM THE par() LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - par(custom.par) - text <- c(text, "\nGRAPH PARAMETERS SET TO VALUES DEFINED BY custom.par ARGUMENT\n") - }else{ - par(tempo.par$ini.par) - text <- c(text, "\nGRAPH PARAMETERS RESET TO par() DEFAULT VALUES\n") - } - } - output <- list(x.mid.left.dev.region = x.mid.left.dev.region, x.left.dev.region = x.left.dev.region, x.mid.right.dev.region = x.mid.right.dev.region, x.right.dev.region = x.right.dev.region, x.mid.left.fig.region = x.mid.left.fig.region, x.left.fig.region = x.left.fig.region, x.mid.right.fig.region = x.mid.right.fig.region, x.right.fig.region = x.right.fig.region, x.left.plot.region = x.left.plot.region, x.right.plot.region = x.right.plot.region, x.mid.plot.region = x.mid.plot.region, y.mid.bottom.dev.region = y.mid.bottom.dev.region, y.bottom.dev.region = y.bottom.dev.region, y.mid.top.dev.region = y.mid.top.dev.region, y.top.dev.region = y.top.dev.region, y.mid.bottom.fig.region = y.mid.bottom.fig.region, y.bottom.fig.region = y.bottom.fig.region, y.mid.top.fig.region = y.mid.top.fig.region, y.top.fig.region = y.top.fig.region, y.top.plot.region = y.top.plot.region, y.bottom.plot.region = y.bottom.plot.region, y.mid.plot.region = y.mid.plot.region, text = text) - return(output) +# create a fake categ if NULL to deal with legend display +if(is.null(categ)){ +categ <- vector("list", length(data1)) +categ[] <- "fake_categ" +for(i2 in 1:length(data1)){ +data1[[i2]] <- cbind(data1[[i2]], fake_categ = "", stringsAsFactors = TRUE) +if(geom[[i2]] == "geom_hline" | geom[[i2]] == "geom_vline"){ +data1[[i2]][, "fake_categ"] <- factor(paste0("Line_", 1:nrow(data1[[i2]]))) } - - -######## fun_close() #### close specific graphic windows - - -fun_close <- function(kind = "pdf", return.text = FALSE){ - # AIM - # close only specific graphic windows (devices) - # ARGUMENTS: - # kind: vector, among c("windows", "quartz", "x11", "X11", "pdf", "bmp", "png", "tiff"), indicating the kind of graphic windows (devices) to close. BEWARE: either "windows", "quartz", "x11" or "X11" means that all the X11 GUI graphics devices will be closed, whatever the OS used - # return.text: print text regarding the kind parameter and the devices that were finally closed? - # RETURN - # text regarding the kind parameter and the devices that were finally closed - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # windows() ; windows() ; pdf() ; dev.list() ; fun_close(kind = c("pdf", "x11"), return.text = TRUE) ; dev.list() - # DEBUGGING - # kind = c("windows", "pdf") ; return.text = FALSE # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = kind, options = c("windows", "quartz", "x11", "X11", "pdf", "bmp", "png", "tiff"), fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return.text, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - text <- paste0("THE REQUIRED KIND OF GRAPHIC DEVICES TO CLOSE ARE ", paste(kind, collapse = " ")) - if(Sys.info()["sysname"] == "Windows"){ # Note that .Platform$OS.type() only says "unix" for macOS and Linux and "Windows" for Windows - if(any(kind %in% c("windows", "quartz", "x11", "X11"))){ - tempo <- kind %in% c("windows", "quartz", "x11", "X11") - kind[tempo] <- "windows" # term are replaced by what is displayed when using a <- dev.list() ; names(a) - } - }else if(Sys.info()["sysname"] == "Linux"){ - if(any(kind %in% c("windows", "quartz", "x11", "X11"))){ - tempo.device <- suppressWarnings(try(X11(), silent = TRUE))[] # open a X11 window to try to recover the X11 system used - if( ! is.null(tempo.device)){ - text <- paste0(text, "\nCANNOT CLOSE GUI GRAPHIC DEVICES AS REQUIRED BECAUSE THIS LINUX SYSTEM DOES NOT HAVE IT") - }else{ - tempo <- kind %in% c("windows", "quartz", "x11", "X11") - kind[tempo] <- names(dev.list()[length(dev.list())]) # term are replaced by what is displayed when using a <- dev.list() ; names(a) - invisible(dev.off()) # close the X11 opened by tempo - } - } - }else{ # for macOS - if(any(kind %in% c("windows", "quartz", "x11", "X11"))){ - tempo <- kind %in% c("windows", "quartz", "x11", "X11") - kind[tempo] <- "quartz" # term are replaced by what is displayed when using a <- dev.list() ; names(a) - } - } - kind <- unique(kind) - if(length(dev.list()) != 0){ - for(i in length(names(dev.list())):1){ - if(names(dev.list())[i] %in% kind){ - text <- paste0(text, "\n", names(dev.list())[i], " DEVICE NUMBER ", dev.list()[i], " HAS BEEN CLOSED") - invisible(dev.off(dev.list()[i])) - } - } - } - if(return.text == TRUE){ - return(text) - } } - - -################ Standard graphics - - -######## fun_empty_graph() #### text to display for empty graphs - - - - - -fun_empty_graph <- function( - text = NULL, - text.size = 1, - title = NULL, - title.size = 1.5 -){ - # AIM - # display an empty plot with a text in the middle of the window (for instance to specify that no plot can be drawn) - # ARGUMENTS - # text: character string of the message to display - # text.size: numeric value of the text size - # title: character string of the graph title - # title.size: numeric value of the title size (in points) - # RETURN - # an empty plot - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # simple example - # fun_empty_graph(text = "NO GRAPH") - # white page - # fun_empty_graph() # white page - # all the arguments - # fun_empty_graph(text = "NO GRAPH", text.size = 2, title = "GRAPH1", title.size = 1) - # DEBUGGING - # text = "NO GRAPH" ; title = "GRAPH1" ; text.size = 1 - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - if( ! is.null(text)){ - tempo <- fun_check(data = text, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(title)){ - tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = title.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - ini.par <- par(no.readonly = TRUE) # to recover the initial graphical parameters if required (reset). BEWARE: this command alone opens a pdf of GUI window if no window already opened. But here, protected with the code because always a tempo window opened - par(ann=FALSE, xaxt="n", yaxt="n", mar = rep(1, 4), bty = "n", xpd = NA) - plot(1, 1, type = "n") # no display with type = "n" - x.left.dev.region <- (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / ((par("omd")[2] - par("omd")[1]) * (par("plt")[2] - par("plt")[1]))) * par("omd")[1]) - y.top.dev.region <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par("omd")[4] - par("omd")[3]) * (par("plt")[4] - par("plt")[3]))) * (1 - par("omd")[4])) - if( ! is.null(text)){ - text(x = 1, y = 1, labels = text, cex = text.size) - } - if( ! is.null(title)){ - text(x = x.left.dev.region, y = y.top.dev.region, labels = title, adj=c(0, 1), cex = title.size) - } - par(ini.par) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL categ ARGUMENT -> FAKE \"fake_categ\" COLUMN ADDED TO EACH DATA FRAME OF data1, AND FILLED WITH \"\"") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) } - - -################ gg graphics - - -######## fun_gg_palette() #### ggplot2 default color palette - - - - - -fun_gg_palette <- function(n, kind = "std"){ - # AIM - # provide colors used by ggplot2 - # the interest is to use another single color that is not the red one used by default - # for ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html - # ARGUMENTS - # n: number of groups on the graph - # kind: either "std" for standard gg colors, "dark" for darkened gg colors, or "light" for pastel gg colors - # RETURN - # the vector of hexadecimal colors - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # output of the function - # fun_gg_palette(n = 2) - # the ggplot2 palette when asking for 7 different colors - # plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7)) - # selection of the 5th color of the ggplot2 palette made of 7 different colors - # plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7)[5]) - # the ggplot2 palette made of 7 darkened colors - # plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7, kind = "dark")) - # the ggplot2 palette made of 7 lighten colors - # plot(1:7, pch = 16, cex = 5, col = fun_gg_palette(n = 7, kind = "light")) - # DEBUGGING - # n = 0 - # kind = "std" - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = n, class = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & isTRUE(all.equal(n, 0))){ # isTRUE(all.equal(n, 0))) is similar to n == 0 but deals with float - tempo.cat <- paste0("ERROR IN ", function.name, ": n ARGUMENT MUST BE A NON ZERO INTEGER. HERE IT IS: ", paste(n, collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - tempo <- fun_check(data = kind, options = c("std", "dark", "light"), length = 1, fun.name = function.name) ; eval(ee) - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - hues = seq(15, 375, length = n + 1) - hcl(h = hues, l = if(kind == "std"){65}else if(kind == "dark"){35}else if(kind == "light"){85}, c = 100)[1:n] +# categ is not NULL anymore +if(is.null(categ.class.order)){ +categ.class.order <- vector("list", length = length(data1)) +tempo.categ.class.order <- NULL +for(i2 in 1:length(categ.class.order)){ +categ.class.order[[i2]] <- levels(data1[[i2]][, categ[[i2]]]) +names(categ.class.order)[i2] <- categ[[i2]] +tempo.categ.class.order <- c(tempo.categ.class.order, ifelse(i2 != 1, "\n", ""), categ.class.order[[i2]]) } - - -######## fun_gg_just() #### ggplot2 justification of the axis labeling, depending on angle - - - - - -fun_gg_just <- function(angle, pos, kind = "axis"){ - # AIM - # provide correct justification for text labeling, depending on the chosen angle - # WARNINGS - # justification behave differently on plot, depending whether it is used for annotayed text or for axis labelling. Indeed the latter has labelling constrained - # Of note, a bug in ggplot2: vjust sometimes does not work, i.e., the same justification result is obtained whatever the value used. This is the case with angle = 90, pos = "top", kind = "axis". While everything is fine with angle = 90, pos = "bottom", kind = "axis". At least, everything seems fine for kind = "axis" and pos = c("left", "bottom") - # ARGUMENTS - # angle: integer value of the text angle, using the same rules as in ggplot2. Positive values for counterclockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Negative values for clockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. - # pos: where text is? Either "top", "right", "bottom" or "left" of the elements to justify from - # kind: kind of text? Either "axis" or "text". In the first case, the pos argument refers to the axis position, and in the second to annotated text (using ggplot2::annotate() or ggplot2::geom_text()) - # RETURN - # a list containing: - # $angle: the submitted angle (value potentially reduced to fit the [-360 ; 360] interval, e.g., 460 -> 100, without impact on the final angle displayed) - # $pos: the selected position (argument pos) - # $kind: the selected kind of text (argument kind) - # $hjust: the horizontal justification - # $vjust: the vertical justification - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_gg_just(angle = 45, pos = "bottom") - # fun_gg_just(angle = (360*2 + 45), pos = "left") - # output <- fun_gg_just(angle = 45, pos = "bottom") ; obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; ggplot2::ggplot() + ggplot2::geom_bar(data = obs1, mapping = ggplot2::aes(x = group, y = time), stat = "identity") + ggplot2::theme(axis.text.x = ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)) - # output <- fun_gg_just(angle = -45, pos = "left") ; obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; ggplot2::ggplot() + ggplot2::geom_bar(data = obs1, mapping = ggplot2::aes(x = group, y = time), stat = "identity") + ggplot2::theme(axis.text.y = ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)) + ggplot2::coord_flip() - # output1 <- fun_gg_just(angle = 90, pos = "bottom") ; output2 <- fun_gg_just(angle = -45, pos = "left") ; obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; ggplot2::ggplot() + ggplot2::geom_bar(data = obs1, mapping = ggplot2::aes(x = group, y = time), stat = "identity") + ggplot2::theme(axis.text.x = ggplot2::element_text(angle = output1$angle, hjust = output1$hjust, vjust = output1$vjust), axis.text.y = ggplot2::element_text(angle = output2$angle, hjust = output2$hjust, vjust = output2$vjust)) - # output <- fun_gg_just(angle = -45, pos = "left") ; obs1 <- data.frame(time = 1, km = 1, bird = "pigeon", stringsAsFactors = FALSE) ; ggplot2::ggplot(data = obs1, mapping = ggplot2::aes(x = time, y = km)) + ggplot2::geom_point() + ggplot2::geom_text(mapping = ggplot2::aes(label = bird), angle = output$angle, hjust = output$hjust, vjust = output$vjust) - # obs1 <- data.frame(time = 1:10, km = 1:10, bird = c(NA, NA, NA, "pigeon", NA, "cat", NA, NA, NA, NA), stringsAsFactors = FALSE) ; fun_open(width = 4, height = 4) ; for(i0 in c("text", "axis")){for(i1 in c("top", "right", "bottom", "left")){for(i2 in c(0, 45, 90, 135, 180, 225, 270, 315, 360)){output <- fun_gg_just(angle = i2, pos = i1, kind = i0) ; title <- paste0("kind: ", i0, " | pos: ", i1, " | angle = ", i2, " | hjust: ", output$hjust, " | vjust: ", output$vjust) ; if(i0 == "text"){print(ggplot2::ggplot(data = obs1, mapping = ggplot2::aes(x = time, y = km)) + ggplot2::geom_point(color = fun_gg_palette(1), alpha = 0.5) + ggplot2::ggtitle(title) + ggplot2::geom_text(mapping = ggplot2::aes(label = bird), angle = output$angle, hjust = output$hjust, vjust = output$vjust) + ggplot2::theme(title = ggplot2::element_text(size = 5)))}else{print(ggplot2::ggplot(data = obs1, mapping = ggplot2::aes(x = time, y = km)) + ggplot2::geom_point(color = fun_gg_palette(1), alpha = 0.5) + ggplot2::ggtitle(title) + ggplot2::geom_text(mapping = ggplot2::aes(label = bird)) + ggplot2::scale_x_continuous(position = ifelse(i1 == "top", "top", "bottom")) + ggplot2::scale_y_continuous(position = ifelse(i1 == "right", "right", "left")) + ggplot2::theme(title = ggplot2::element_text(size = 5), axis.text.x = if(i1 %in% c("top", "bottom")){ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)}, axis.text.y = if(i1 %in% c("right", "left")){ggplot2::element_text(angle = output$angle, hjust = output$hjust, vjust = output$vjust)}))}}}} ; dev.off() - # DEBUGGING - # angle = 45 ; pos = "left" ; kind = "axis" - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument primary checking - # arg with no default values - mandat.args <- c( - "angle", - "pos" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # using fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = angle, class = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = pos, options = c("left", "top", "right", "bottom"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = kind, options = c("axis", "text"), length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end using fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <- c( - "angle", - "pos", - "kind" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # end second round of checking and data preparation - # main code - # to get angle between -360 and 360 - while(angle > 360){ - angle <- angle - 360 - } - while(angle < -360){ - angle <- angle + 360 - } - # end to get angle between -360 and 360 - # justifications - if(pos %in% c("bottom", "top")){ - # code below is for if(pos == "bottom"){ - if(any(sapply(FUN = all.equal, c(-360, -180, 0, 180, 360), angle) == TRUE)){ # equivalent of angle == -360 | angle == -180 | angle == 0 | angle == 180 | angle == 360 but deals with floats - hjust <- 0.5 - if(kind == "text"){ - if(any(sapply(FUN = all.equal, c(-360, 0, 360), angle) == TRUE)){ - vjust <- 1 - }else if(any(sapply(FUN = all.equal, c(-180, 180), angle) == TRUE)){ - vjust <- 0 - } - }else{ - vjust <- 0.5 - } - }else if(any(sapply(FUN = all.equal, c(-270, 90), angle) == TRUE)){ - hjust <- 1 - vjust <- 0.5 - }else if(any(sapply(FUN = all.equal, c(-90, 270), angle) == TRUE)){ - hjust <- 0 - vjust <- 0.5 - }else if((angle > -360 & angle < -270) | (angle > 0 & angle < 90)){ - hjust <- 1 - vjust <- 1 - }else if((angle > -270 & angle < -180) | (angle > 90 & angle < 180)){ - hjust <- 1 - vjust <- 0 - }else if((angle > -180 & angle < -90) | (angle > 180 & angle < 270)){ - hjust <- 0 - vjust <- 0 - if(kind == "text" & pos == "top"){ - hjust <- 1 - } - }else if((angle > -90 & angle < 0) | (angle > 270 & angle < 360)){ - hjust <- 0 - vjust <- 1 - } - if(pos == "top"){ - if( ! ((angle > -180 & angle < -90) | (angle > 180 & angle < 270))){ - hjust <- 1 - hjust - } - vjust <- 1 - vjust - } - }else if(pos %in% c("left", "right")){ - # code below is for if(pos == "left"){ - if(any(sapply(FUN = all.equal, c(-270, -90, 90, 270), angle) == TRUE)){ # equivalent of angle == -270 | angle == -90 | angle == 90 | angle == 270 but deals with floats - hjust <- 0.5 - if(kind == "text"){ - if(any(sapply(FUN = all.equal, c(-90, 90), angle) == TRUE)){ - vjust <- 0 - }else if(any(sapply(FUN = all.equal, c(-270, 270), angle) == TRUE)){ - vjust <- 1 - } - }else{ - vjust <- 0.5 - } - }else if(any(sapply(FUN = all.equal, c(-360, 0, 360), angle) == TRUE)){ - hjust <- 1 - vjust <- 0.5 - }else if(any(sapply(FUN = all.equal, c(-180, 180), angle) == TRUE)){ - hjust <- 0 - vjust <- 0.5 - }else if((angle > -360 & angle < -270) | (angle > 0 & angle < 90)){ - hjust <- 1 - vjust <- 0 - }else if((angle > -270 & angle < -180) | (angle > 90 & angle < 180)){ - hjust <- 0 - vjust <- 0 - }else if((angle > -180 & angle < -90) | (angle > 180 & angle < 270)){ - hjust <- 0 - vjust <- 1 - }else if((angle > -90 & angle < 0) | (angle > 270 & angle < 360)){ - hjust <- 1 - vjust <- 1 - } - if(pos == "right"){ - hjust <- 1 - hjust - if( ! (((angle > -270 & angle < -180) | (angle > 90 & angle < 180)) | ((angle > -180 & angle < -90) | (angle > 180 & angle < 270)))){ - vjust <- 1 - vjust - } - } - } - # end justifications - output <- list(angle = angle, pos = pos, kind = kind, hjust = hjust, vjust = vjust) - return(output) +if(any(unlist(legend.disp))){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") THE categ.class.order SETTING IS NULL. ALPHABETICAL ORDER WILL BE APPLIED FOR CLASS ORDERING:\n", paste(tempo.categ.class.order, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) } - - -######## fun_gg_get_legend() #### get the legend of ggplot objects - - - - - -fun_gg_get_legend <- function(ggplot_built, fun.name = NULL, lib.path = NULL){ - # AIM - # get legend of ggplot objects - # # from https://stackoverflow.com/questions/12539348/ggplot-separate-legend-and-plot - # ARGUMENTS - # ggplot_built: a ggplot build object - # fun.name: single character string indicating the name of the function using fun_gg_get_legend() for warning and error messages. Ignored if NULL - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL - # RETURN - # a list of class c("gtable", "gTree", "grob", "gDesc"), providing legend information of ggplot_built objet, or NULL if the ggplot_built object has no legend - # REQUIRED PACKAGES - # ggplot2 - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_pack() - # EXAMPLES - # Simple example - # obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; p <- ggplot2::ggplot() + ggplot2::geom_point(data = obs1, mapping = ggplot2::aes(x = group, y = time, fill = group)) ; fun_gg_get_legend(ggplot_built = ggplot2::ggplot_build(p)) - # Error message because no legend in the ggplot - # obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; p <- ggplot2::ggplot() + ggplot2::geom_point(data = obs1, mapping = ggplot2::aes(x = group, y = time)) ; fun_gg_get_legend(ggplot_built = ggplot2::ggplot_build(p)) - # DEBUGGING - # obs1 <- data.frame(time = 1:20, group = rep(c("CLASS_1", "CLASS_2"), times = 10), stringsAsFactors = TRUE) ; p <- ggplot2::ggplot() + ggplot2::geom_point(data = obs1, mapping = ggplot2::aes(x = group, y = time)) ; ggplot_built = ggplot2::ggplot_build(p) ; fun.name = NULL ; lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - req.function <- c( - "fun_check", - "fun_pack" - ) - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED ", i1, "() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end required function checking - # argument primary checking - # arg with no default values - mandat.args <- c( - "ggplot_built" - ) - tempo <- eval(parse(text = paste0("c(missing(", paste0(mandat.args, collapse = "), missing("), "))"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(sum(tempo, na.rm = TRUE) > 1, "S HAVE", " HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args[tempo], collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # using fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = ggplot_built, class = "ggplot_built", mode = "list", fun.name = function.name) ; eval(ee) - if( ! is.null(fun.name)){ - tempo <- fun_check(data = fun.name, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - } - if( ! is.null(arg.check)){ - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - } - # end using fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking - # management of NA - if(any(is.na(ggplot_built)) | any(is.na(fun.name)) | any(is.na(lib.path))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": NO ARGUMENT CAN HAVE NA VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA - # management of NULL - if(is.null(ggplot_built)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nggplot_built ARGUMENT CANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL - if( ! is.null(lib.path)){ - if( ! all(dir.exists(lib.path))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end second round of checking - # package checking - fun_pack(req.package = c("ggplot2"), lib.path = lib.path) - # end package checking - # main code - win.nb <- dev.cur() - pdf(file = NULL) - tmp <- ggplot2::ggplot_gtable(ggplot_built) - # BEWARE with ggplot_gtable : open a blanck device https://stackoverflow.com/questions/17012518/why-does-this-r-ggplot2-code-bring-up-a-blank-display-device - invisible(dev.off()) - if(win.nb > 1){ # to go back to the previous active device, if == 1 means no opened device - dev.set(win.nb) - } - leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box") - if(length(leg) == 0L){ - legend <- NULL - }else{ - legend <- tmp$grobs[[leg]] - } - return(legend) } - - -######## fun_gg_point_rast() #### ggplot2 raster scatterplot layer - - - - - -fun_gg_point_rast <- function( - data = NULL, - mapping = NULL, - stat = "identity", - position = "identity", - ..., - na.rm = FALSE, - show.legend = NA, - inherit.aes = TRUE, - raster.width = NULL, - raster.height = NULL, - raster.dpi = 300, - inactivate = TRUE, - lib.path = NULL -){ - # AIM - # equivalent to ggplot2::geom_point() but in raster mode - # use it like ggplot2::geom_point() with the main raster.dpi additional argument - # WARNINGS - # can be long to generate the plot - # use a square plot region. Otherwise, the dots will have ellipsoid shape - # solve the transparency problems with some GUI - # this function is derived from the geom_point_rast() function, created by Viktor Petukhov , and present in the ggrastr package (https://rdrr.io/github/VPetukhov/ggrastr/src/R/geom-point-rast.R, MIT License, Copyright (c) 2017 Viktor Petukhov). Has been placed here to minimize package dependencies - # ARGUMENTS - # classical arguments of geom_point(), shown here https://rdrr.io/github/VPetukhov/ggrastr/man/geom_point_rast.html - # raster.width : width of the result image (in inches). Default: deterined by the current device parameters - # raster.height: height of the result image (in inches). Default: deterined by the current device parameters - # raster.dpi: resolution of the result image - # inactivate: logical. Inactivate the fun.name argument of the fun_check() function? If TRUE, the name of the fun_check() function in error messages coming from this function. Use TRUE if fun_gg_point_rast() is used like this: eval(parse(text = "fun_gg_point_rast")) - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL - # RETURN - # a raster scatter plot - # REQUIRED PACKAGES - # ggplot2 - # grid (included in the R installation packages but not automatically loaded) - # Cairo - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_pack() - # EXAMPLES - # Two pdf in the current directory - # set.seed(1) ; data1 = data.frame(x = rnorm(100000), y = rnorm(10000), stringsAsFactors = TRUE) ; fun_open(pdf.name = "Raster") ; ggplot2::ggplot() + fun_gg_point_rast(data = data1, mapping = ggplot2::aes(x = x, y = y)) ; fun_open(pdf.name = "Vectorial") ; ggplot2::ggplot() + ggplot2::geom_point(data = data1, mapping = ggplot2::aes(x = x, y = y)) ; dev.off() ; dev.off() - # DEBUGGING - # - # function name - if(all(inactivate == FALSE)){ # inactivate has to be used here but will be fully checked below - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - }else if(all(inactivate == TRUE)){ - function.name <- NULL - }else{ - tempo.cat <- paste0("ERROR IN fun_gg_point_rast(): CODE INCONSISTENCY 1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_pack", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - if( ! is.null(data)){ - tempo <- fun_check(data = data, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - if( ! is.null(mapping)){ - tempo <- fun_check(data = mapping, class = "uneval", typeof = "list", fun.name = function.name) ; eval(ee) # aes() is tested - } - # stat and position not tested because too complicate - tempo <- fun_check(data = na.rm, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = show.legend, class = "vector", mode = "logical", length = 1, na.contain = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = inherit.aes, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(raster.width)){ - tempo <- fun_check(data = raster.width, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - } - if( ! is.null(raster.height)){ - tempo <- fun_check(data = raster.height, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = raster.dpi, class = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = inactivate, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # package checking - fun_pack(req.package = c("ggplot2"), lib.path = lib.path) - fun_pack(req.package = c("grid"), lib.path = lib.path) - fun_pack(req.package = c("Cairo"), lib.path = lib.path) - # end package checking - # additional functions - DrawGeomPointRast <- function(data, panel_params, coord, na.rm = FALSE, raster.width = NULL, raster.height= NULL, raster.dpi = raster.dpi){ - if (is.null(raster.width)){ - raster.width <- par('fin')[1] - } - if (is.null(raster.height)){ - raster.height <- par('fin')[2] - } - prev_dev_id <- dev.cur() - p <- ggplot2::GeomPoint$draw_panel(data, panel_params, coord) - dev_id <- Cairo::Cairo(type='raster', width = raster.width*raster.dpi, height = raster.height*raster.dpi, dpi = raster.dpi, units = 'px', bg = "transparent")[1] - grid::pushViewport(grid::viewport(width = 1, height = 1)) - grid::grid.points(x = p$x, y = p$y, pch = p$pch, size = p$size, - name = p$name, gp = p$gp, vp = p$vp, draw = T) - grid::popViewport() - cap <- grid::grid.cap() - invisible(dev.off(dev_id)) - invisible(dev.set(prev_dev_id)) - grid::rasterGrob(cap, x = 0, y = 0, width = 1, height = 1, default.units = "native", just = c("left","bottom")) - } - # end additional functions - # main code - GeomPointRast <- ggplot2::ggproto("GeomPointRast", ggplot2::GeomPoint, draw_panel = DrawGeomPointRast) - ggplot2::layer( - data = data, - mapping = mapping, - stat = stat, - geom = GeomPointRast, - position = position, - show.legend = show.legend, - inherit.aes = inherit.aes, - params = list( - na.rm = na.rm, - raster.width = raster.width, - raster.height = raster.height, - raster.dpi = raster.dpi, - ... - ) - ) - # end main code +# end create a fake categ if NULL to deal with legend display +# categ.class.order is not NULL anymore + + +# vector of color with length as in levels(categ) of data1 +if(is.null(color)){ +color <- vector("list", length(data1)) +length.categ.list <- lapply(lapply(mapply(FUN = "[[", data1, categ, SIMPLIFY = FALSE), FUN = unique), FUN = function(x){length(x[ ! is.na(x)])}) +length.categ.list[sapply(categ, FUN = "==", "fake_categ")] <- 1 # when is.null(color), a single color for all the dots or lines of data[[i1]] that contain "fake_categ" category +total.categ.length <- sum(unlist(length.categ.list), na.rm = TRUE) +tempo.color <- fun_gg_palette(total.categ.length) +tempo.count <- 0 +for(i2 in 1:length(data1)){ +color[[i2]] <- tempo.color[(1:length.categ.list[[i2]]) + tempo.count] +tempo.count <- tempo.count + length.categ.list[[i2]] +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") NULL color ARGUMENT -> COLORS RESPECTIVELY ATTRIBUTED TO EACH CLASS OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i2, " OF categ ARGUMENT")), " (", categ[[i2]], ") IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i2, " OF data1 ARGUMENT")), ":\n", paste(color[[i2]], collapse = " "), "\n", paste(if(all(levels(data1[[i2]][, categ[[i2]]]) == "")){'\"\"'}else{levels(data1[[i2]][, categ[[i2]]])}, collapse = " ")) +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) } - - -######## fun_gg_boxplot() #### ggplot2 boxplot + background dots if required - - - - -######## fun_gg_scatter() #### ggplot2 scatterplot + lines (up to 6 overlays totally) - - - - -######## fun_gg_heatmap() #### ggplot2 heatmap + overlaid mask if required - - -#test plot.margin = margin(up.space.mds, right.space.mds, down.space.mds, left.space.mds, "inches") to set the dim of the region plot ? -# if matrix is full of zero (or same value I guess), heatmap is complicate. Test it and error message - -fun_gg_heatmap <- function( - data1, - legend.name1 = "", - low.color1 = "blue", - mid.color1 = "white", - high.color1 = "red", - limit1 = NULL, - midpoint1 = NULL, - data2 = NULL, - color2 = "black", - alpha2 = 0.5, - invert2 = FALSE, - text.size = 12, - title = "", - title.text.size = 12, - show.scale = TRUE, - rotate = FALSE, - return = FALSE, - plot = TRUE, - add = NULL, - warn.print = FALSE, - lib.path = NULL -){ - # AIM - # ggplot2 heatmap with the possibility to overlay a mask - # see also: - # draw : http://www.sthda.com/english/wiki/ggplot2-quick-correlation-matrix-heatmap-r-software-and-data-visualization - # same range scale : https://stackoverflow.com/questions/44655723/r-ggplot2-heatmap-fixed-scale-color-between-graphs - # for ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html - # ARGUMENTS - # data1: numeric matrix or data frame resulting from the conversion of the numeric matrix by reshape2::melt() - # legend.name1: character string of the data1 heatmap scale legend - # low.color1: character string of the color (i.e., "blue" or "#0000FF") of the lowest scale value - # mid.color1: same as low.color1 but for the middle scale value. If NULL, the middle color is the default color between low.color1 and high.color1. BEWARE: argument midpoint1 is not ignored, even if mid.color1 is NULL, meaning that the default mid color can still be controled - # high.color1: same as low.color1 but for the highest scale value - # limit1: 2 numeric values defining the lowest and higest color scale values. If NULL, take the range of data1 values - # midpoint1: single numeric value defining the value corresponding to the mid.color1 argument. A warning message is returned if midpoint1 does not correspond to the mean of limit1 values, because the color scale is not linear anymore. If NULL, takes the mean of limit1 values. Mean of data1, instead of mean of limit1, can be used here if required - # data2: binary mask matrix (made of 0 and 1) of same dimension as data1 or a data frame resulting from the conversion of the binary mask matrix by reshape2::melt(). Value 1 of data2 will correspond to color2 argument (value 0 will be NA color), and the opposite if invert2 argument is TRUE (inverted mask) - # color2: color of the 1 values of the binary mask matrix. The 0 values will be color NA - # alpha2: numeric value (from 0 to 1) of the mask transparency - # invert2: logical. Invert the mask (1 -> 0 and 0 -> 1)? - # text.size: numeric value of the size of the texts in scale - # title: character string of the graph title - # title.text.size: numeric value of the title size (in points) - # show.scale: logical. Show color scale? - # rotate: logical. Rotate the heatmap 90° clockwise? - # return: logical. Return the graph parameters? - # plot: logical. Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting - # add: character string allowing to add more ggplot2 features (dots, lines, themes, etc.). BEWARE: (1) must start with "+" just after the simple or double opening quote (no space, end of line, carriage return, etc., allowed), (2) must finish with ")" just before the simple or double closing quote (no space, end of line, carriage return, etc., allowed) and (3) each function must be preceded by "ggplot2::" (for instance: "ggplot2::coord_flip()). If the character string contains the "ggplot2::theme" string, then internal ggplot2 theme() and theme_classic() functions will be inactivated to be reused by add. BEWARE: handle this argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions - # warn.print: logical. Print warnings at the end of the execution? No print if no warning messages - # lib.path: absolute path of the required packages, if not in the default folders - # RETURN - # a heatmap if plot argument is TRUE - # a list of the graph info if return argument is TRUE: - # $data: a list of the graphic info - # $axes: a list of the axes info - # $scale: the scale info (lowest, mid and highest values) - # $warn: the warning messages. Use cat() for proper display. NULL if no warning - # REQUIRED PACKAGES - # ggplot2 - # reshape2 - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_pack() - # fun_round() - # EXAMPLES - # fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), title = "GRAPH 1") - # fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), return = TRUE) - # fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), legend.name1 = "VALUE", title = "GRAPH 1", text.size = 5, data2 = matrix(rep(c(1,0,0,0), 4), ncol = 4), invert2 = FALSE, return = TRUE) - # diagonal matrix - # fun_gg_heatmap(data1 = matrix(c(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1), ncol = 4)) - # fun_gg_heatmap(data1 = reshape2::melt(matrix(c(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1), ncol = 4))) - # error message - # fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), data2 = matrix(rep(c(1,0,0,0), 5), ncol = 5)) - # fun_gg_heatmap(data1 = matrix(1:16, ncol = 4), data2 = reshape2::melt(matrix(rep(c(1,0,0,0), 4), ncol = 4))) - # fun_gg_heatmap(data1 = reshape2::melt(matrix(1:16, ncol = 4)), data2 = reshape2::melt(matrix(rep(c(1,0,0,0), 4), ncol = 4))) - # DEBUGGING - # data1 = matrix(1:16, ncol = 4) ; legend.name1 = "" ; low.color1 = "blue" ; mid.color1 = "white" ; high.color1 = "red" ; limit1 = NULL ; midpoint1 = NULL ; data2 = matrix(rep(c(1,0,0,0), 4), ncol = 4) ; color2 = "black" ; alpha2 = 0.5 ; invert2 = FALSE ; text.size = 12 ; title = "" ; title.text.size = 12 ; show.scale = TRUE ; rotate = FALSE ; return = FALSE ; plot = TRUE ; add = NULL ; warn.print = TRUE ; lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_pack", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_round", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_round() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # no reserved words required for this function - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - if(all(is.matrix(data1))){ - tempo <- fun_check(data = data1, class = "matrix", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) - }else if(all(is.data.frame(data1))){ - tempo <- fun_check(data = data1, class = "data.frame", length = 3, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - # structure of reshape2::melt() data frame - tempo <- fun_check(data = data1[, 1], data.name = "COLUMN 1 OF data1 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = data1[, 2], data.name = "COLUMN 2 OF data1 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = data1[, 3], data.name = "COLUMN 3 OF data1 (reshape2::melt() DATA FRAME)", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT MUST BE A NUMERIC MATRIX OR A DATA FRAME OUTPUT OF THE reshape::melt() FUNCTION") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = legend.name1, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = low.color1, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (all(low.color1 %in% colors() | grepl(pattern = "^#", low.color1)))){ # check that all strings of low.color1 start by # - tempo.cat <- paste0("ERROR IN ", function.name, ": low.color1 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(mid.color1)){ - tempo <- fun_check(data = mid.color1, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (all(mid.color1 %in% colors() | grepl(pattern = "^#", mid.color1)))){ # check that all strings of mid.color1 start by # - tempo.cat <- paste0("ERROR IN ", function.name, ": mid.color1 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = high.color1, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (all(high.color1 %in% colors() | grepl(pattern = "^#", high.color1)))){ # check that all strings of high.color1 start by # - tempo.cat <- paste0("ERROR IN ", function.name, ": high.color1 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(limit1)){ - tempo <- fun_check(data = limit1, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & any(limit1 %in% c(Inf, -Inf))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": limit1 ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - if( ! is.null(midpoint1)){ - tempo <- fun_check(data = midpoint1, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - } - if( ! is.null(data2)){ - if(all(is.matrix(data2))){ - tempo <- fun_check(data = data2, class = "matrix", mode = "numeric", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! all(unique(data2) %in% c(0,1))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": MATRIX IN data2 MUST BE MADE OF 0 AND 1 ONLY (MASK MATRIX)") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & all(is.matrix(data1)) & ! identical(dim(data1), dim(data2))){ # matrix and matrix - tempo.cat <- paste0("ERROR IN ", function.name, ": MATRIX DIMENSION IN data2 MUST BE IDENTICAL AS MATRIX DIMENSION IN data1. HERE IT IS RESPECTIVELY:\n", paste(dim(data2), collapse = " "), "\n", paste(dim(data1), collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & all(is.data.frame(data1)) & nrow(data1) != prod(dim(data2))){ # reshape2 and matrix - tempo.cat <- paste0("ERROR IN ", function.name, ": DATA FRAME IN data2 MUST HAVE ROW NUMBER EQUAL TO PRODUCT OF DIMENSIONS OF data1 MATRIX. HERE IT IS RESPECTIVELY:\n", paste(nrow(data1), collapse = " "), "\n", paste(prod(dim(data2)), collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else if(all(is.data.frame(data2))){ - tempo <- fun_check(data = data2, class = "data.frame", length = 3, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - # structure of reshape2::melt() data frame - tempo <- fun_check(data = data2[, 1], data.name = "COLUMN 1 OF data2 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = data2[, 2], data.name = "COLUMN 2 OF data2 (reshape2::melt() DATA FRAME)", typeof = "integer", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = data2[, 3], data.name = "COLUMN 3 OF data2 (reshape2::melt() DATA FRAME)", mode = "numeric", fun.name = function.name) ; eval(ee) - } - if(tempo$problem == FALSE & ! all(unique(data2[, 3]) %in% c(0,1))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THIRD COLUMN OF DATA FRAME IN data2 MUST BE MADE OF 0 AND 1 ONLY (MASK DATA FRAME)") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & all(is.data.frame(data1)) & ! identical(dim(data1), dim(data2))){ # data frame and data frame - tempo.cat <- paste0("ERROR IN ", function.name, ": DATA FRAME DIMENSION IN data2 MUST BE IDENTICAL TO DATA FRAME DIMENSION IN data1. HERE IT IS RESPECTIVELY:\n", paste(dim(data2), collapse = " "), "\n", paste(dim(data1), collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & all(is.matrix(data1)) & nrow(data2) != prod(dim(data1))){ # reshape2 and matrix - tempo.cat <- paste0("ERROR IN ", function.name, ": DATA FRAME IN data2 MUST HAVE ROW NUMBER EQUAL TO PRODUCT OF DIMENSION OF data1 MATRIX. HERE IT IS RESPECTIVELY:\n", paste(nrow(data2), collapse = " "), "\n", paste(prod(dim(data1)), collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE data2 ARGUMENT MUST BE A NUMERIC MATRIX OR A DATA FRAME OUTPUT OF THE reshape::melt() FUNCTION") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = color2, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (all(color2 %in% colors() | grepl(pattern = "^#", color2)))){ # check that all strings of color2 start by # - tempo.cat <- paste0("ERROR IN ", function.name, ": color2 ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors()") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = alpha2, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = invert2, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = title, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = show.scale, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = plot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(add)){ - tempo <- fun_check(data = add, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! grepl(pattern = "^\\+", add)){ # check that the add string start by + - tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & ! grepl(pattern = "ggplot2::", add)){ # - tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST CONTAIN \"ggplot2::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & ! grepl(pattern = ")$", add)){ # check that the add string finished by ) - tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # package checking - fun_pack(req.package = c("reshape2", "ggplot2"), lib.path = lib.path) - # end package checking - # main code - ini.warning.length <- options()$warning.length - options(warning.length = 8170) - warn <- NULL - warn.count <- 0 - if(all(is.matrix(data1))){ - data1 <- reshape2::melt(data1) # transform a matrix into a data frame with 2 coordinates columns and the third intensity column - } - if(rotate == TRUE){ - data1[, 1] <- rev(data1[, 1]) - } - if(is.null(limit1)){ - if(any(data1[, 3] %in% c(Inf, -Inf))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE data1 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE THIRD COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - limit1 <- range(data1[, 3], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE limit1 ARGUMENT IS NULL -> RANGE OF data1 ARGUMENT HAS BEEN TAKEN: ", paste(fun_round(limit1), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - if(suppressWarnings(any(limit1 %in% c(Inf, -Inf)))){ - tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED LIMIT CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(is.null(midpoint1)){ - midpoint1 <- mean(limit1, na.rm = TRUE) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE midpoint1 ARGUMENT IS NULL -> MEAN OF limit1 ARGUMENT HAS BEEN TAKEN: ", paste(fun_round(midpoint1), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else if(fun_round(midpoint1, 9) != fun_round(mean(limit1), 9)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE midpoint1 ARGUMENT (", fun_round(mean(midpoint1), 9), ") DOES NOT CORRESPOND TO THE MEAN OF THE limit1 ARGUMENT (", fun_round(mean(limit1), 9), "). COLOR SCALE IS NOT LINEAR") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data2)){ - if(all(is.matrix(data2))){ - data2 <- reshape2::melt(data2) # transform a matrix into a data frame with 2 coordinates columns and the third intensity column - } - if(rotate == TRUE){ - data2[, 1] <- rev(data2[, 1]) - } - data2[, 3] <- factor(data2[, 3]) # to converte continuous scale into discrete scale - } - tempo.gg.name <- "gg.indiv.plot." - tempo.gg.count <- 0 # to facilitate debugging - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggplot()) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_raster(data = data1, mapping = ggplot2::aes_string(x = names(data1)[ifelse(rotate == FALSE, 2, 1)], y = names(data1)[ifelse(rotate == FALSE, 1, 2)], fill = names(data1)[3]), show.legend = show.scale)) # show.legend option do not remove the legend, only the aesthetic of the legend (dot, line, etc.) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_gradient2(low = low.color1, high = high.color1, mid = mid.color1, midpoint = midpoint1, limit = limit1, breaks = c(limit1[1], midpoint1, limit1[2]), labels = fun_round(c(limit1[1], midpoint1, limit1[2])), name = legend.name1)) - if( ! is.null(data2)){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_raster(data = data2, mapping = ggplot2::aes_string(x = names(data2)[ifelse(rotate == FALSE, 2, 1)], y = names(data2)[ifelse(rotate == FALSE, 1, 2)], alpha = names(data2)[3]), fill = color2, show.legend = FALSE)) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", values = if(invert2 == FALSE){c(0, alpha2)}else{c(alpha2, 0)}, guide = FALSE)) - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_raster(data = data2, mapping = ggplot2::aes_string(x = names(data2)[ifelse(rotate == FALSE, 2, 1)], y = names(data2)[ifelse(rotate == FALSE, 1, 2)], group = names(data2)[3]), fill = data2[, 3], alpha = alpha2, show.legend = FALSE)) # BEWARE: this does not work if NA present, because geom_raster() has a tendency to complete empty spaces, and thus, behave differently than geom_tile(). See https://github.com/tidyverse/ggplot2/issues/3025 - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_fixed()) # x = y - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_y_reverse()) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) - add.check <- TRUE - if( ! is.null(add)){ # if add is NULL, then = 0 - if(grepl(pattern = "ggplot2::theme", add) == TRUE){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT -> INTERNAL GGPLOT2 THEME FUNCTIONS theme() AND theme_classic() HAVE BEEN INACTIVATED, TO BE USED BY THE USER") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - add.check <- FALSE - } - } - if(add.check == TRUE){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_classic(base_size = text.size)) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme( - text = ggplot2::element_text(size = text.size), - plot.title = ggplot2::element_text(size = title.text.size), # stronger than text - line = ggplot2::element_blank(), - axis.title = ggplot2::element_blank(), - axis.text = ggplot2::element_blank(), - axis.ticks = ggplot2::element_blank(), - panel.background = ggplot2::element_blank() - )) - } - if(plot == TRUE){ - # suppressWarnings( - print(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add})))) - # ) - }else{ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") PLOT NOT SHOWN AS REQUESTED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if(warn.print == TRUE & ! is.null(warn)){ - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) - } - on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) - if(return == TRUE){ - output <- ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + ")))) - output <- output$data - names(output)[1] <- "heatmap" - if( ! is.null(data2)){ - names(output)[2] <- "mask" - } - return(list(data = output, axes = output$layout$panel_params[[1]], scale = c(limit1[1], midpoint1, limit1[2]), warn = warn)) - } } - - -######## fun_gg_empty_graph() #### text to display for empty graphs +# end vector of color with length as in levels(categ) of data1 +# color is not NULL anymore -fun_gg_empty_graph <- function( - text = NULL, - text.size = 12, - title = NULL, - title.size = 8, - lib.path = NULL -){ - # AIM - # display an empty ggplot2 plot with a text in the middle of the window (for instance to specify that no plot can be drawn) - # ARGUMENTS - # text: character string of the message to display - # text.size: numeric value of the text size (in points) - # title: character string of the graph title - # title.size: numeric value of the title size (in points) - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL - # RETURN - # an empty plot - # REQUIRED PACKAGES - # ggplot2 - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_pack() - # EXAMPLES - ### simple example - # fun_gg_empty_graph(text = "NO GRAPH") - ### white page - # fun_gg_empty_graph() - ### all the arguments - # fun_gg_empty_graph(text = "NO GRAPH", text.size = 8, title = "GRAPH1", title.size = 10, lib.path = NULL) - # DEBUGGING - # text = "NO GRAPH" ; text.size = 12 ; title = "GRAPH1" ; title.size = 8 ; lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_pack", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - if( ! is.null(text)){ - tempo <- fun_check(data = text, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(title)){ - tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = title.size, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # package checking - fun_pack(req.package = c("ggplot2"), lib.path = lib.path) - # end package checking - # main code - tempo.gg.name <- "gg.indiv.plot." - tempo.gg.count <- 0 - # no need loop part - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggplot()) - if( ! is.null(text)){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text(data = data.frame(x = 1, y = 1, stringsAsFactors = TRUE), ggplot2::aes(x = x, y = y, label = text), size = text.size)) - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_void()) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( - plot.title = ggplot2::element_text(size = title.size) # stronger than text - )) - suppressWarnings(print(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))) +# last check +for(i1 in 1:length(data1)){ +if(categ[[i1]] != "fake_categ" & length(color[[i1]]) != length(unique(data1[[i1]][, categ[[i1]]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, " LAST CHECK: ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST HAVE THE LENGTH OF LEVELS OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), "\nHERE IT IS COLOR LENGTH ", length(color[[i1]]), " VERSUS CATEG LEVELS LENGTH ", length(unique(data1[[i1]][, categ[[i1]]])), "\nREMINDER: A SINGLE COLOR PER CLASS OF CATEG AND A SINGLE CLASS OF CATEG PER COLOR MUST BE RESPECTED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else if(categ[[i1]] == "fake_categ" & length(color[[i1]]) != 1){ +tempo.cat <- paste0("ERROR IN ", function.name, " LAST CHECK: ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST HAVE LENGTH 1 WHEN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IS NULL\nHERE IT IS COLOR LENGTH ", length(color[[i1]])) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) } +} +# end last check -################ Graphic extraction -######## fun_trim() #### display values from a quantitative variable and trim according to defined cut-offs -# Add name of the variable in the graph -# not max and min for boxplot but 1.5IQR -fun_trim <- function( - data, - displayed.nb = NULL, - single.value.display = FALSE, - trim.method = "", - trim.cutoffs = c(0.05, - 0.975), - interval.scale.disp = TRUE, - down.space = 0.75, - left.space = 0.75, - up.space = 0.3, - right.space = 0.25, - orient = 1, - dist.legend = 0.37, - box.type = "l", - amplif.label = 1.25, - amplif.axis = 1.25, - std.x.range = TRUE, - std.y.range = TRUE, - cex.pt = 0.2, - col.box = hsv(0.55, - 0.8, - 0.8), - x.nb.inter.tick = 4, - y.nb.inter.tick = 0, - tick.length = 1, - sec.tick.length = 0.75, - corner.text = "", - amplif.legend = 1, - corner.text.size = 0.75, - trim.return = FALSE -){ - # AIM - # trim and display values from a numeric vector or matrix - # plot 4 graphs: stripchart of values, stripchart of rank of values, histogram and normal QQPlot - # different kinds of intervals are displayed on the top of graphes to facilitate the analysis of the variable and a trimming setting - # the trimming interval chosen is displayed on top of graphs - # both trimmed and not trimmed values are returned in a list - # ARGUMENTS - # data: values to plot (either a numeric vector or a numeric matrix) - # displayed.nb: number of values displayed. If NULL, all the values are displayed. Otherwise, if the number of values is over displayed.nb, then displayed.nb values are displayed after random selection - # single.value.display: provide the 4 graphs if data is made of a single (potentially repeated value)? If FALSE, an empty graph is displayed if data is made of a single (potentially repeated value). And the return list is made of NULL compartments - # trim.method: Write "" if not required. write "mean.sd" if mean +/- sd has to be displayed as a trimming interval (only recommanded for normal distribution). Write "quantile" to display a trimming interval based on quantile cut-offs. No other possibility allowed. See trim.cutoffs below - # trim.cutoffs: 2 values cutoff for the trimming interval displayed, each value between 0 and 1. Not used if trim.method == "".The couple of values c(lower, upper) represents the lower and upper boundaries of the trimming interval (in proportion), which represent the interval of distribution kept (between 0 and 1). Example: trim.cutoffs = c(0.05, 0.975). What is strictly kept for the display is ]lower , upper[, boundaries excluded. Using the "mean.sd" method, 0.025 and 0.975 represent 95% CI which is mean +/- 1.96 * sd - # interval.scale.disp: display sd and quantiles intervals on top of graphs ? - # down.space: lower vertical margin (in inches, mai argument of par()) - # left.space: left horizontal margin (in inches, mai argument of par()) - # up.space: upper vertical margin between plot region and grapical window (in inches, mai argument of par()) - # right.space: right horizontal margin (in inches, mai argument of par()) - # orient: scale number orientation (las argument of par()). 0, always parallel to the axis; 1, always horizontal; 2, always perpendicular to the axis; 3, always vertical - # dist.legend: numeric value that moves axis legends away in inches (first number of mgp argument of par() but in inches thus / 0.2) - # box.type: bty argument of par(). Either "o", "l", "7", "c", "u", "]", the resulting box resembles the corresponding upper case letter. A value of "n" suppresses the box - # amplif.label: increase or decrease the size of the text in legends - # amplif.axis: increase or decrease the size of the scale numbers in axis - # std.x.range: standard range on the x-axis? TRUE (no range extend) or FALSE (4% range extend). Controls xaxs argument of par() (TRUE is xaxs = "i", FALSE is xaxs = "r") - # std.y.range: standard range on the y-axis? TRUE (no range extend) or FALSE (4% range extend). Controls yaxs argument of par() (TRUE is yaxs = "i", FALSE is yaxs = "r") - # cex.pt: size of points in stripcharts (in inches, thus cex.pt will be thereafter / 0.2) - # col.box: color of boxplot - # x.nb.inter.tick: number of secondary ticks between main ticks on x-axis (only if not log scale). Zero means non secondary ticks - # y.nb.inter.tick: number of secondary ticks between main ticks on y-axis (only if not log scale). Zero means non secondary ticks - # tick.length: length of the ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc. 0 means no tick - # sec.tick.length: length of the secondary ticks (1 means complete the distance between the plot region and the axis numbers, 0.5 means half the length, etc., 0 for no ticks) - # corner.text: text to add at the top right corner of the window - # amplif.legend: increase or decrease the size of the text of legend - # corner.text.size: positive numeric. Increase or decrease the size of the text. Value 1 does not change it, 0.5 decreases by half, 2 increases by 2 - # trim.return: return the trimmed and non trimmed values? NULL returned for trimmed and non trimmed values if trim.method == "" - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # RETURN - # a list containing: - # $trim.method: correspond to trim.method above - # $trim.cutoffs: correspond to trim.cutoffs above - # $real.trim.cutoffs: the two boundary values (in the unit of the numeric vector or numeric matrix analyzed). NULL - # $trimmed.values: the values outside of the trimming interval as defined in trim.cutoffs above - # $kept.values: the values inside the trimming interval as defined in trim.cutoffs above - # EXAMPLES - # fun_trim(data = c(1:100, 1:10), displayed.nb = NULL, single.value.display = FALSE, trim.method = "mean.sd", trim.cutoffs = c(0.05, 0.975), interval.scale.disp = TRUE, down.space = 0.75, left.space = 0.75, up.space = 0.3, right.space = 0.25, orient = 1, dist.legend = 0.37, box.type = "l", amplif.label = 1.25, amplif.axis = 1.25, std.x.range = TRUE, std.y.range = TRUE, cex.pt = 0.2, col.box = hsv(0.55, 0.8, 0.8), x.nb.inter.tick = 4, y.nb.inter.tick = 0, tick.length = 0.5, sec.tick.length = 0.3, corner.text = "", amplif.legend = 1, corner.text.size = 0.75, trim.return = TRUE) - # DEBUGGING - # data = c(1:100, 1:10) ; displayed.nb = NULL ; single.value.display = FALSE ; trim.method = "quantile" ; trim.cutoffs = c(0.05, 0.975) ; interval.scale.disp = TRUE ; down.space = 1 ; left.space = 1 ; up.space = 0.5 ; right.space = 0.25 ; orient = 1 ; dist.legend = 0.5 ; box.type = "l" ; amplif.label = 1 ; amplif.axis = 1 ; std.x.range = TRUE ; std.y.range = TRUE ; cex.pt = 0.1 ; col.box = hsv(0.55, 0.8, 0.8) ; x.nb.inter.tick = 4 ; y.nb.inter.tick = 0 ; tick.length = 0.5 ; sec.tick.length = 0.3 ; corner.text = "" ; amplif.legend = 1 ; corner.text.size = 0.75 ; trim.return = TRUE # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - # argument checking without fun_check() - if( ! (all(class(data) == "numeric") | all(class(data) == "integer") | (all(class(data) %in% c("matrix", "array")) & base::mode(data) == "numeric"))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data ARGUMENT MUST BE A NUMERIC VECTOR OR NUMERIC MATRIX") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end argument checking without fun_check() - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - if( ! is.null(displayed.nb)){ - tempo <- fun_check(data = displayed.nb, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if(displayed.nb < 2){ - tempo.cat <- paste0("ERROR IN ", function.name, ": displayed.nb ARGUMENT MUST BE A SINGLE INTEGER VALUE GREATER THAN 1 AND NOT: ", paste(displayed.nb, collapse = " ")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = single.value.display, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = trim.method, options = c("", "mean.sd", "quantile"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = trim.cutoffs, class = "vector", mode = "numeric", length = 2, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = interval.scale.disp, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = down.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = left.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = up.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = right.space, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = orient, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = dist.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.type, options = c("o", "l", "7", "c", "u", "]", "n"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = amplif.label, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = amplif.axis, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = std.x.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = std.y.range, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = cex.pt, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = col.box, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.nb.inter.tick, class = "integer", length = 1, neg.values = FALSE, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.nb.inter.tick, class = "integer", length = 1, neg.values = FALSE, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = sec.tick.length, class = "vector", mode = "numeric", length = 1, prop = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = corner.text, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = amplif.legend, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = corner.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = trim.return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - if(all(is.na(data) | ! is.finite(data))){ - tempo.cat <- paste0("ERROR IN fun_trim FUNCTION\ndata ARGUMENT CONTAINS ONLY NA OR Inf") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end argument checking - # main code - if(all(class(data)%in% c("matrix", "array"))){ - data <- as.vector(data) - } - na.nb <- NULL - if(any(is.na(data))){ - na.nb <- sum(c(is.na(data))) - data <- data[ ! is.na(data)] - } - color.cut <- hsv(0.75, 1, 1) # color of interval selected - col.mean <- hsv(0.25, 1, 0.8) # color of interval using mean+/-sd - col.quantile <- "orange" # color of interval using quantiles - quantiles.selection <- c(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.975, 0.99) # quantiles used in axis to help for choosing trimming cutoffs - if(single.value.display == FALSE & length(unique(data)) == 1L){ - par(bty = "n", xaxt = "n", yaxt = "n", xpd = TRUE) - plot(1, pch = 16, col = "white", xlab = "", ylab = "") - text(x = 1, y = 1, paste0("No graphic displayed\nBecause data made of a single different value (", formatC(as.double(table(data))), ")"), cex = 2) - output <- list(trim.method = NULL, trim.cutoffs = NULL, real.trim.cutoffs = NULL, trimmed.values = NULL, kept.values = NULL) - }else{ - output <- list(trim.method = trim.method, trim.cutoffs = trim.cutoffs, real.trim.cutoffs = NULL, trimmed.values = NULL, kept.values = NULL) - fun.rug <- function(sec.tick.length.f = sec.tick.length, x.nb.inter.tick.f = x.nb.inter.tick, y.nb.inter.tick.f = y.nb.inter.tick){ - if(x.nb.inter.tick.f > 0){ - inter.tick.unit <- (par("xaxp")[2] - par("xaxp")[1]) / par("xaxp")[3] - par.ini <- par()[c("xpd", "tcl")] - par(xpd = FALSE) - par(tcl = -par()$mgp[2] * sec.tick.length.f) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) - suppressWarnings(rug(seq(par("xaxp")[1] - 10 * inter.tick.unit, par("xaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + x.nb.inter.tick.f)), ticksize = NA, side = 1)) # ticksize = NA to allow the use of par()$tcl value - par(par.ini) - rm(par.ini) - } - if(y.nb.inter.tick.f > 0){ - inter.tick.unit <- (par("yaxp")[2] - par("yaxp")[1]) / par("yaxp")[3] - par.ini <- par()[c("xpd", "tcl")] - par(xpd = FALSE) - par(tcl = -par()$mgp[2] * sec.tick.length.f) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) - suppressWarnings(rug(seq(par("yaxp")[1] - 10 * inter.tick.unit, par("yaxp")[2] + 10 * inter.tick.unit, by = inter.tick.unit / (1 + y.nb.inter.tick.f)), ticksize = NA, side = 2)) # ticksize = NA to allow the use of par()$tcl value - par(par.ini) - rm(par.ini) - } - } - fun.add.cut <- function(data.f, trim.method.f = trim.method, trim.cutoffs.f = trim.cutoffs, color.cut.f = color.cut, return.f = FALSE){ - # DEBUGGING - # data.f = data ; trim.method.f = "mean.sd"; trim.cutoffs.f = trim.cutoffs ; color.cut.f = color.cut ; return.f = TRUE - real.trim.cutoffs.f <- NULL - if(trim.method.f != ""){ - data.f <- sort(data.f) - par.ini <- par()$xpd - par(xpd = FALSE) - if(trim.method.f == "mean.sd"){ - real.trim.cutoffs.f <- qnorm(trim.cutoffs.f, mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)) - abline(v = qnorm(trim.cutoffs.f, mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)), col = color.cut.f) - segments(qnorm(trim.cutoffs.f[1], mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)), par()$usr[4] * 0.75, qnorm(trim.cutoffs.f[2], mean(data.f, na.rm = TRUE), sd(data.f, na.rm = TRUE)), par()$usr[4] * 0.75, col = color.cut.f) - } - if(trim.method.f == "quantile"){ - real.trim.cutoffs.f <- quantile(data.f, probs = trim.cutoffs.f, type = 7, na.rm = TRUE) - abline(v = quantile(data.f, probs = trim.cutoffs.f, type = 7, na.rm = TRUE), col = color.cut.f) - segments(quantile(data.f, probs = trim.cutoffs.f[1], type = 7, na.rm = TRUE), par()$usr[4] * 0.75, quantile(data.f, probs = trim.cutoffs.f[2], type = 7, na.rm = TRUE), par()$usr[4] * 0.75, col = color.cut.f) - } - par(par.ini) - if(return.f == TRUE){ - trimmed.values.f <- data.f[data.f <= real.trim.cutoffs.f[1] | data.f >= real.trim.cutoffs.f[2]] - kept.values.f <- data.f[data.f > real.trim.cutoffs.f[1] & data.f < real.trim.cutoffs.f[2]] - } - }else{ - real.trim.cutoffs.f <- NULL - trimmed.values.f <- NULL - kept.values.f <- NULL - } - if(return.f == TRUE){ - output <- list(trim.method = trim.method.f, trim.cutoffs = trim.cutoffs.f, real.trim.cutoffs = real.trim.cutoffs.f, trimmed.values = trimmed.values.f, kept.values = kept.values.f) - return(output) - } - } - fun.interval.scale.display <- function(data.f, col.quantile.f = col.quantile, quantiles.selection.f = quantiles.selection, col.mean.f = col.mean){ # intervals on top of graphs - par.ini <- par()[c("mgp", "xpd")] - par(mgp = c(0.25, 0.25, 0), xpd = NA) - axis(side = 3, at = c(par()$usr[1], par()$usr[2]), labels = rep("", 2), col = col.quantile.f, lwd.ticks = 0) - par(xpd = FALSE) - axis(side = 3, at = quantile(as.vector(data.f), probs = quantiles.selection.f, type = 7, na.rm = TRUE), labels = quantiles.selection.f, col.axis = col.quantile.f, col = col.quantile.f) - par(mgp = c(1.75, 1.75, 1.5), xpd = NA) - axis(side = 3, at = c(par()$usr[1], par()$usr[2]), labels = rep("", 2), col = col.mean.f, lwd.ticks = 0) - par(xpd = FALSE) - axis(side = 3, at = m + s * qnorm(quantiles.selection.f), labels = formatC(round(qnorm(quantiles.selection.f), 2)), col.axis = col.mean.f, col = col.mean.f, lwd.ticks = 1) - par(par.ini) - } - zone<-matrix(1:4, ncol=2) - layout(zone) - par(omi = c(0, 0, 1.5, 0), mai = c(down.space, left.space, up.space, right.space), las = orient, mgp = c(dist.legend / 0.2, 0.5, 0), xpd = FALSE, bty= box.type, cex.lab = amplif.label, cex.axis = amplif.axis, xaxs = ifelse(std.x.range, "i", "r"), yaxs = ifelse(std.y.range, "i", "r")) - par(tcl = -par()$mgp[2] * tick.length) # tcl gives the length of the ticks as proportion of line text, knowing that mgp is in text lines. So the main ticks are a 0.5 of the distance of the axis numbers by default. The sign provides the side of the tick (negative for outside of the plot region) - if(is.null(displayed.nb)){ - sampled.data <- as.vector(data) - if(corner.text == ""){ - corner.text <- paste0("ALL VALUES OF THE DATASET DISPLAYED") - }else{ - corner.text <- paste0(corner.text, "\nALL VALUES OF THE DATASET DISPLAYED") - } - }else{ - if(length(as.vector(data)) > displayed.nb){ - sampled.data <- sample(as.vector(data), displayed.nb, replace = FALSE) - if(corner.text == ""){ - corner.text <- paste0("WARNING: ONLY ", displayed.nb, " VALUES ARE DISPLAYED AMONG THE ", length(as.vector(data)), " VALUES OF THE DATASET ANALYZED") - }else{ - corner.text <- paste0(corner.text, "\nWARNING: ONLY ", displayed.nb, " VALUES ARE DISPLAYED AMONG THE ", length(as.vector(data)), " VALUES OF THE DATASET ANALYZED") - } - }else{ - sampled.data <- as.vector(data) - if(corner.text == ""){ - corner.text <- paste0("WARNING: THE DISPLAYED NUMBER OF VALUES PARAMETER ", deparse(substitute(displayed.nb)), " HAS BEEN SET TO ", displayed.nb, " WHICH IS ABOVE THE NUMBER OF VALUES OF THE DATASET ANALYZED -> ALL VALUES DISPLAYED") - }else{ - corner.text <- paste0(corner.text, "\nWARNING: THE DISPLAYED NUMBER OF VALUES PARAMETER ", deparse(substitute(displayed.nb)), " HAS BEEN SET TO ", displayed.nb, " WHICH IS ABOVE THE NUMBER OF VALUES OF THE DATASET ANALYZED -> ALL VALUES DISPLAYED") - } - } - } - if( ! is.null(na.nb)){ - if(corner.text == ""){ - corner.text <- paste0("WARNING: NUMBER OF NA REMOVED IS ", na.nb) - }else{ - corner.text <- paste0("WARNING: NUMBER OF NA REMOVED IS ", na.nb) - } - } - stripchart(sampled.data, method="jitter", jitter=0.4, vertical=FALSE, ylim=c(0.5, 1.5), group.names = "", xlab = "Value", ylab="", pch=1, cex = cex.pt / 0.2) - fun.rug(y.nb.inter.tick.f = 0) - boxplot(as.vector(data), horizontal=TRUE, add=TRUE, boxwex = 0.4, staplecol = col.box, whiskcol = col.box, medcol = col.box, boxcol = col.box, range = 0, whisklty = 1) - m <- mean(as.vector(data), na.rm = TRUE) - s <- sd(as.vector(data), na.rm = TRUE) - segments(m, 0.8, m, 1, lwd=2, col="red") # mean - segments(m -1.96 * s, 0.9, m + 1.96 * s, 0.9, lwd=1, col="red") # mean - graph.xlim <- par()$usr[1:2] # for hist() and qqnorm() below - if(interval.scale.disp == TRUE){ - fun.interval.scale.display(data.f = data) - if(corner.text == ""){ - corner.text <- paste0("MULTIPLYING FACTOR DISPLAYED (MEAN +/- SD) ON SCALES: ", paste(formatC(round(qnorm(quantiles.selection), 2))[-(1:(length(quantiles.selection) - 1) / 2)], collapse = ", "), "\nQUANTILES DISPLAYED ON SCALES: ", paste(quantiles.selection, collapse = ", ")) - }else{ - corner.text <- paste0(corner.text, "\nMULTIPLYING FACTOR DISPLAYED (MEAN +/- SD) ON SCALES: ", paste(formatC(round(qnorm(quantiles.selection), 2))[-(1:(length(quantiles.selection) - 1) / 2)], collapse = ", "), "\nQUANTILES DISPLAYED ON SCALES: ", paste(quantiles.selection, collapse = ", ")) - } - } - output.tempo <- fun.add.cut(data.f = data, return.f = TRUE) # to recover real.trim.cutoffs - if(trim.return == TRUE){ - output <- output.tempo - } - par(xpd = NA) - if(trim.method != ""){ - if(corner.text == ""){ - corner.text <- paste0("SELECTED CUT-OFFS (PROPORTION): ", paste(trim.cutoffs, collapse = ", "), "\nSELECTED CUT-OFFS: ", paste(output.tempo$real.trim.cutoffs, collapse = ", ")) - }else{ - corner.text <- paste0(corner.text, "\nSELECTED CUT-OFFS (PROPORTION): ", paste(trim.cutoffs, collapse = ", "), "\nSELECTED CUT-OFFS: ", paste(output.tempo$real.trim.cutoffs, collapse = ", ")) - } - if(interval.scale.disp == TRUE){ - legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- 1.96sd", paste0("Trimming interval: ", paste0(trim.cutoffs, collapse = " , ")), "Mean +/- sd multiplying factor", "Quantile"), yjust = 0, lty=1, col=c(col.box, "red", color.cut, col.mean, col.quantile), bty="n", cex = amplif.legend) - }else{ - legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- 1.96sd", paste0("Trimming interval: ", paste0(trim.cutoffs, collapse = " , "))), yjust = 0, lty=1, col=c(col.box, "red", color.cut), bty="n", cex = amplif.legend, y.intersp=1.25) - } - }else{ - if(interval.scale.disp == TRUE){ - legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- sd", "Mean +/- sd multiplying factor", "Quantile"), yjust = 0, lty=1, col=c(col.box, "red", col.mean, col.quantile), bty="n", cex = amplif.legend) - }else{ - legend(x = (par("usr")[1] - ((par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1])) * par("plt")[1] - ((par("usr")[2] - par("usr")[1]) / (par("omd")[2] - par("omd")[1])) * par("omd")[1]), y = (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / (par("omd")[4] - par("omd")[3])) * (1 - par("omd")[4]) / 2), legend = c(c("min, Q1, Median, Q3, max"), "mean +/- sd"), yjust = 0, lty=1, col=c(col.box, "red"), bty="n", cex = amplif.legend, y.intersp=1.25) - } - } - par(xpd = FALSE, xaxs = ifelse(std.x.range, "i", "r"), yaxs = ifelse(std.y.range, "i", "r")) - hist(as.vector(data), main = "", xlim = graph.xlim, xlab = "Value", ylab="Density", col = grey(0.25)) # removed: breaks = seq(min(as.vector(data), na.rm = TRUE), max(as.vector(data), na.rm = TRUE), length.out = length(as.vector(data)) / 10) - abline(h = par()$usr[3]) - fun.rug() - if(interval.scale.disp == TRUE){ - fun.interval.scale.display(data.f = data) - } - fun.add.cut(data.f = data) - par(xaxs = ifelse(std.x.range, "i", "r")) - stripchart(rank(sampled.data), method="stack", vertical=FALSE, ylim=c(0.99, 1.3), group.names = "", xlab = "Rank of values", ylab="", pch=1, cex = cex.pt / 0.2) - fun.rug(y.nb.inter.tick.f = 0) - x.text <- par("usr")[2] + (par("usr")[2] - par("usr")[1]) / (par("plt")[2] - par("plt")[1]) * (1 - par("plt")[2]) / 2 - y.text <- (par("usr")[4] + ((par("usr")[4] - par("usr")[3]) / (par("plt")[4] - par("plt")[3])) * (1 - par("plt")[4]) + ((par("usr")[4] - par("usr")[3]) / ((par()$omd[4] / 2) * ((par("plt")[4] - par("plt")[3])))) * (1 - par("omd")[4])) # BEWARE. Here in "(par()$omd[4] / 2", division by two because there are 2 graphs staked on the y axis, and not one - par(xpd=NA) - text(x = x.text, y = y.text, paste0(corner.text), adj=c(1, 1.1), cex = corner.text.size) # text at the topright corner - par(xpd=FALSE) - par(xaxs = ifelse(std.x.range, "i", "r"), yaxs = ifelse(std.y.range, "i", "r")) - qqnorm(as.vector(sampled.data), main = "", datax = TRUE, ylab = "Value", pch = 1, col = "red", cex = cex.pt / 0.2) - fun.rug() - if(diff(quantile(as.vector(data), probs = c(0.25, 0.75), na.rm = TRUE)) != 0){ # otherwise, error generated - qqline(as.vector(data), datax = TRUE) - } - if(interval.scale.disp == TRUE){ - fun.interval.scale.display(data.f = data) - } - fun.add.cut(data.f = data) - } - if(trim.return == TRUE){ - return(output) - } +# conversion of geom_hline and geom_vline +for(i1 in 1:length(data1)){ +if(geom[[i1]] == "geom_hline" | geom[[i1]] == "geom_vline"){ +final.data.frame <- data.frame() +for(i3 in 1:nrow(data1[[i1]])){ +tempo.data.frame <- rbind(data1[[i1]][i3, ], data1[[i1]][i3, ], stringsAsFactors = TRUE) +if(geom[[i1]] == "geom_hline"){ +tempo.data.frame[, x[[i1]]] <- x.lim +}else if(geom[[i1]] == "geom_vline"){ +tempo.data.frame[, y[[i1]]] <- y.lim +}else{ +tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 5") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) } - - -######## fun_segmentation() #### segment a dot cloud on a scatterplot and define the dots from another cloud outside the segmentation - - -fun_segmentation <- function( - data1, - x1, - y1, - x.range.split = NULL, - x.step.factor = 10, - y.range.split = NULL, - y.step.factor = 10, - error = 0, - data2 = NULL, - x2, - y2, - data2.pb.dot = "unknown", - xy.cross.kind = "&", - plot = FALSE, - graph.in.file = FALSE, - raster = TRUE, - warn.print = FALSE, - lib.path = NULL -){ - # AIM - # if data1 is a data frame corresponding to the data set of a scatterplot (with a x column for x-axis values and a y column for the y-axis column), then fun_segmentation() delimits a frame around the dots cloud using a sliding window set by x.range.split and x.step.factor to frame the top and bottom part of the cloud, and set by y.range.split and y.step.factor to frame the left and right part of the cloud - # if a second data frame is provided, corresponding to the data set of a scatterplot (with a x column for x-axis values and a y column for the y-axis column), then fun_segmentation() defines the dots of this data frame, outside of the frame of the first data frame - # WARNINGS - # if dots from data2 look significant on the graph (outside the frame) but are not (not black on the last figure), this is probably because the frame is flat on the zero coordinate (no volume inside the frame at this position). Thus, no way to conclude that data2 dots here are significant. These dots are refered to as "unknown". The pb.dot argument deals with such dots - # dots that are sometimes inside and outside the frame, depending on the sliding window, are treated differently: they are removed. Such dots are neither classified as "signif", "non signif" or "unknown", but as "inconsistent" - # unknown dots are treated as finally significant, not significant, or unknown (data2.pb.dot argument) for each x-axis and y-axis separately. Then, the union or intersection of significant dots is performed (argument xy.cross.kind). See the example section - # ARGUMENTS - # data1: a data frame containing a column of x-axis values and a column of y-axis values - # x1: character string of the data1 column name for x-axis (first column of data1 by default) - # y1: character string of the data1 column name for y-axis (second column of data1 by default) - # x.range.split: positive non null numeric value giving the number of interval on the x value range. if x.range is the range of the dots on the x-axis, then abs(diff(x.range) / x.range.split) gives the window size. Window size decreases when range.split increases. In unit of x-axis. Write NULL if not required. At least one of the x.range.split and y.range.split must be non NULL - # x.step.factor: positive non null numeric value giving the shift step of the window. If x.step.factor = 1, no overlap during the sliding (when the window slides from position n to position n+1, no overlap between the two positions). If x.step.factor = 2, 50% of overlap (when the window slides from position n to position n+1, the window on position n+1 overlap 50% of the window when it was on position n) - # y.range.split: same as x.range.split for the y-axis. At least one of the x.range.split and y.range.split must be non NULL - # y.step.factor: same as x.step.factor for the y-axis - # error: proportion (from 0 to 1) of false positives (i.e., proportion of dots from data1 outside of the frame). 0.05 means 5% of the dots from data1 outside of the frame - # data2: a data frame containing a column of x-axis values and a column of y-axis values, for which outside dots of the data1 cloud has to be determined. Write NULL if not required - # x2: character string of the data1 column name for x-axis (first column of data1 by default) - # y2: character string of the data1 column name for y-axis (second column of data1 by default) - # data2.pb.dot: unknown dots are explain in the warning section above. If "signif", then the unknown dots are finally considered as significant (outside the frame). If "not.signif", then the unknown dots are finally considered as non significant (inside the frame). If "unknown", no conclusion are drawn from these dots. See the examples below - # xy.cross.kind: if data2 is non null and if both x.range.split and y.range.split are non null, which dots are finally significants? Write "&" for intersection of outside dots on x and on y. Write "|" for union of outside dots on x and on y. See the examples below - # plot: logical. Print graphs that check the frame? - # graph.in.file: logical. Graphs sent into a graphic device already opened? If FALSE, GUI are opened for each graph. If TRUE, no GUI are opended. The graphs are displayed on the current active graphic device. Ignored if plot is FALSE - # raster: logical. Dots in raster mode? If FALSE, dots from each geom_point from geom argument are in vectorial mode (bigger pdf and long to display if millions of dots). If TRUE, dots from each geom_point from geom argument are in matricial mode (smaller pdf and easy display if millions of dots, but long to generate the layer). If TRUE, the region plot will be square to avoid a bug in fun_gg_point_rast(). If TRUE, solve the transparency problem with some GUI. Not considered if plot is FALSE - # warn.print: logical. Print warnings at the end of the execution? No print if no warning messages - # lib.path: character vector specifying the absolute pathways of the directories containing the required packages if not in the default directories. Ignored if NULL. Ignored if plot is FALSE - # RETURN - # several graphs if plot is TRUE - # a list containing: - # $data1.removed.row.nb: which rows have been removed due to NA; NaN, -Inf or Inf detection in x1 or y1 columns (NULL if no row removed) - # $data1.removed.rows: removed rows (NULL if no row removed) - # $data2.removed.row.nb: which rows have been removed due to NA; NaN, -Inf or Inf detection in x2 or y2 columns (NULL if no row removed) - # $data2.removed.rows: removed rows (NULL if no row removed) - # $hframe: x and y coordinates of the bottom and top frames for frame plotting (frame1 for the left step and frame2 for the right step) - # $vframe: x and y coordinates of the left and right frames for frame plotting (frame1 for the down step and frame2 for the top step) - # $data1.signif.dot: the significant dots of data1 (i.e., dots outside the frame). A good segmentation should not have any data1.signif.dot - # $data1.non.signif.dot: the non significant dots of data1 (i.e., dots inside the frame) - # $data1.inconsistent.dot: see the warning section above - # $data2.signif.dot: the significant dots of data2 if non NULL (i.e., dots outside the frame) - # $data2.non.signif.dot: the non significant dots of data2 (i.e., dots inside the frame) - # $data2.unknown.dot: the problematic dots of data2 (i.e., data2 dots outside of the range of data1, or data2 dots in a sliding window without data1 dots). Is systematically NULL except if argument data2.pb.dot = "unknown" and some data2 dots are in such situation. Modifying the segmentation x.range.split, x.step.factor, y.range.split, y.step.factor arguments can solve this problem - # $data2.inconsistent.dot: see the warning section above - # $axes: the x-axis and y-axis info - # $warn: the warning messages. Use cat() for proper display. NULL if no warning - # REQUIRED PACKAGES - # ggplot2 if plot is TRUE - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # if plot is TRUE: - # fun_pack() - # fun_open() - # fun_gg_palette() - # fun_gg_scatter() - # fun_gg_empty_graph() - # fun_close() - # EXAMPLES - # example explaining the unknown and inconsistent dots, and the cross - - # set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data1[5:7, 2] <- NA ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; data2[11:13, 1] <- Inf ; set.seed(NULL) ; fun_segmentation(data1 = data1, x1 = names(data1)[1], y1 = names(data1)[2], x.range.split = 20, x.step.factor = 10, y.range.split = 23, y.step.factor = 10, error = 0, data2 = data2, x2 = names(data2)[1], y2 = names(data2)[2], data2.pb.dot = "not.signif", xy.cross.kind = "|", plot = TRUE, graph.in.file = FALSE, raster = FALSE, lib.path = NULL) - # set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; fun_segmentation(data1 = data1, x1 = names(data1)[1], y1 = names(data1)[2], x.range.split = NULL, x.step.factor = 10, y.range.split = 23, y.step.factor = 10, error = 0, data2 = data2, x2 = names(data2)[1], y2 = names(data2)[2], data2.pb.dot = "unknown", xy.cross.kind = "|", plot = TRUE, graph.in.file = FALSE, raster = FALSE, lib.path = NULL) - # set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; fun_segmentation(data1 = data1, x1 = names(data1)[1], y1 = names(data1)[2], x.range.split = 20, x.step.factor = 10, y.range.split = NULL, y.step.factor = 10, error = 0, data2 = data2, x2 = names(data2)[1], y2 = names(data2)[2], data2.pb.dot = "unknown", xy.cross.kind = "&", plot = TRUE, graph.in.file = FALSE, raster = FALSE, lib.path = NULL) - # DEBUGGING - # set.seed(1) ; data1 = data.frame(x = rnorm(50), y = rnorm(50), stringsAsFactors = TRUE) ; data1[5:7, 2] <- NA ; x1 = names(data1)[1] ; y1 = names(data1)[2] ; x.range.split = 5 ; x.step.factor = 10 ; y.range.split = 5 ; y.step.factor = 10 ; error = 0 ; data2 = data.frame(x = rnorm(50, 0, 2), y = rnorm(50, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; x2 = names(data2)[1] ; y2 = names(data2)[2] ; data2.pb.dot = "unknown" ; xy.cross.kind = "|" ; plot = TRUE ; graph.in.file = FALSE ; raster = FALSE ; warn.print = TRUE ; lib.path = NULL - # set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; x1 = names(data1)[1] ; y1 = names(data1)[2] ; x.range.split = 20 ; x.step.factor = 10 ; y.range.split = 23 ; y.step.factor = 10 ; error = 0 ; x2 = names(data2)[1] ; y2 = names(data2)[2] ; data2.pb.dot = "not.signif" ; xy.cross.kind = "|" ; plot = TRUE ; graph.in.file = FALSE ; raster = FALSE ; warn.print = TRUE ; lib.path = NULL - # set.seed(1) ; data1 = data.frame(x = rnorm(500), y = rnorm(500), stringsAsFactors = TRUE) ; data2 = data.frame(x = rnorm(500, 0, 2), y = rnorm(500, 0, 2), stringsAsFactors = TRUE) ; set.seed(NULL) ; x1 = names(data1)[1] ; y1 = names(data1)[2] ; x.range.split = 20 ; x.step.factor = 10 ; y.range.split = NULL ; y.step.factor = 10 ; error = 0 ; x2 = names(data2)[1] ; y2 = names(data2)[2] ; data2.pb.dot = "unknown" ; xy.cross.kind = "&" ; plot = TRUE ; graph.in.file = FALSE ; raster = FALSE ; warn.print = TRUE ; lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - ini.warning.length <- options()$warning.length - warn <- NULL - warn.count <- 0 - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data1, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & length(data1) < 2){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A DATA FRAME OF AT LEAST 2 COLUMNS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = x1, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (x1 %in% names(data1))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x1 ARGUMENT MUST BE A COLUMN NAME OF data1") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & x1 %in% names(data1)){ - tempo <- fun_check(data = data1[, x1], data.name = "x1 COLUMN OF data1", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = y1, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (y1 %in% names(data1))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y1 ARGUMENT MUST BE A COLUMN NAME OF data1") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & y1 %in% names(data1)){ - tempo <- fun_check(data = data1[, y1], data.name = "y1 COLUMN OF data1", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - if(is.null(x.range.split) & is.null(y.range.split)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": AT LEAST ONE OF THE x.range.split AND y.range.split ARGUMENTS MUST BE NON NULL") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(x.range.split)){ - tempo <- fun_check(data = x.range.split, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & x.range.split < 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x.range.split ARGUMENT CANNOT BE LOWER THAN 1") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - if( ! is.null(y.range.split)){ - tempo <- fun_check(data = y.range.split, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & y.range.split < 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y.range.split ARGUMENT CANNOT BE LOWER THAN 1") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = x.step.factor, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & x.step.factor < 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x.step.factor ARGUMENT CANNOT BE LOWER THAN 1") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = y.step.factor, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & y.step.factor < 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y.step.factor ARGUMENT CANNOT BE LOWER THAN 1") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = error, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(data2)){ - if(is.null(x2) | is.null(y2)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x2 AND y2 ARGUMENTS CANNOT BE NULL IF data2 ARGUMENT IS NON NULL") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = data2, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & length(data2) < 2){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data2 ARGUMENT MUST BE A DATA FRAME OF AT LEAST 2 COLUMNS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(x2)){ - tempo <- fun_check(data = x2, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (x2 %in% names(data2))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x2 ARGUMENT MUST BE A COLUMN NAME OF data2") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & x2 %in% names(data2)){ - tempo <- fun_check(data = data2[, x2], data.name = "x2 COLUMN OF data2", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - } - if( ! is.null(y2)){ - tempo <- fun_check(data = y2, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & ! (y2 %in% names(data2))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y2 ARGUMENT MUST BE A COLUMN NAME OF data2") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & y2 %in% names(data2)){ - tempo <- fun_check(data = data2[, y2], data.name = "y2 COLUMN OF data2", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) ; eval(ee) - } - } - } - if( ! is.null(data2)){ - tempo <- fun_check(data = data2.pb.dot, options = c("signif", "not.signif", "unknown"), length = 1, fun.name = function.name) ; eval(ee) - } - if( ! (is.null(x.range.split)) & ! (is.null(y.range.split))){ - tempo <- fun_check(data = xy.cross.kind, options = c("&", "|"), length = 1, fun.name = function.name) ; eval(ee) - } - tempo <- fun_check(data = plot, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & plot == TRUE){ - tempo <- fun_check(data = raster, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = graph.in.file, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & graph.in.file == TRUE & is.null(dev.list())){ - tempo.cat <- paste0("ERROR IN ", function.name, ": \ngraph.in.file PARAMETER SET TO TRUE BUT NO ACTIVE GRAPHIC DEVICE DETECTED") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo$problem == FALSE & graph.in.file == TRUE & ! is.null(dev.list())){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHS PRINTED IN THE CURRENT DEVICE (TYPE ", toupper(names(dev.cur())), ")") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # other required function checking - if(plot == TRUE){ - if(length(utils::find("fun_pack", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_open", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_open() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_gg_palette", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_gg_palette() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_gg_empty_graph", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_gg_empty_graph() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_gg_scatter", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_gg_scatter() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_close", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_close() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end other required function checking - # package checking - if(plot == TRUE){ - fun_pack(req.package = c("ggplot2"), lib.path = lib.path) - } - # end package checking - # main code - # na and Inf detection and removal (done now to be sure of the correct length of categ) - data1.removed.row.nb <- NULL - data1.removed.rows <- NULL - data2.removed.row.nb <- NULL - data2.removed.rows <- NULL - if(any(is.na(data1[, c(x1, y1)])) | any(is.infinite(data1[, x1])) | any(is.infinite(data1[, y1]))){ - tempo.na <- unlist(lapply(lapply(c(data1[c(x1, y1)]), FUN = is.na), FUN = which)) - tempo.inf <- unlist(lapply(lapply(c(data1[c(x1, y1)]), FUN = is.infinite), FUN = which)) - data1.removed.row.nb <- sort(unique(c(tempo.na, tempo.inf))) - if(length(data1.removed.row.nb) > 0){ - data1.removed.rows <- data1[data1.removed.row.nb, ] - } - if(length(data1.removed.row.nb) == nrow(data1)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": AT LEAST ONE NA, NaN, -Inf OR Inf DETECTED IN EACH ROW OF data1. FUNCTION CANNOT BE USED ON EMPTY DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(data1.removed.row.nb) > 0){ - data1 <- data1[-data1.removed.row.nb, ] - } - if(nrow(data1) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x1, y1), collapse = " "), " OF data1 AND CORRESPONDING ROWS REMOVED (SEE $data1.removed.row.nb AND $data1.removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NO NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x1, y1), collapse = " "), " OF data1. NO ROW REMOVED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data2)){ - if(any(is.na(data2[, c(x2, y2)])) | any(is.infinite(data2[, x2])) | any(is.infinite(data2[, y2]))){ - tempo.na <- unlist(lapply(lapply(c(data2[c(x2, y2)]), FUN = is.na), FUN = which)) - tempo.inf <- unlist(lapply(lapply(c(data2[c(x2, y2)]), FUN = is.infinite), FUN = which)) - data2.removed.row.nb <- sort(unique(c(tempo.na, tempo.inf))) - if(length(data2.removed.row.nb) > 0){ - data2.removed.rows <- data2[data2.removed.row.nb, ] - } - if(length(data2.removed.row.nb) == nrow(data2)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": AT LEAST ONE NA, NaN, -Inf OR Inf DETECTED IN EACH ROW OF data2. FUNCTION CANNOT BE USED ON EMPTY DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(data2.removed.row.nb) > 0){ - data2 <- data2[-data2.removed.row.nb, ] - } - if(nrow(data2) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x2, y2), collapse = " "), " OF data2 AND CORRESPONDING ROWS REMOVED (SEE $data2.removed.row.nb AND $data2.removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NO NA, NaN, -Inf OR Inf DETECTED IN COLUMN ", paste(c(x2, y2), collapse = " "), " OF data2. NO ROW REMOVED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end na and Inf detection and removal (done now to be sure of the correct length of categ) - # row annotation (dot number) - # data1 <- data1[ ! duplicated(data1[, c(x1, y1)]), ] # do not remove the dots that have same x and y values, because they will have different dot number -> not the same position on the matrices (so true for symmetric matrices) - data1 <- cbind(data1, DOT_NB = 1:nrow(data1), stringsAsFactors = TRUE) - if( ! is.null(data2)){ - # data2 <- data2[ ! duplicated(data2[, c(x2, y2)]), ] # do not remove the dots that have same x and y values, because they will have different dot number -> not the same position on the matrices (so true for symmetric matrices) - data2 <- cbind(data2, DOT_NB = 1:nrow(data2), stringsAsFactors = TRUE) - } - # end row annotation (dot number) - - - - - # Method using x unit interval - # may be create vector of each column to increase speed - x.data1.l <- NULL # x coord of the y upper and lower limits defined on the data1 cloud for left step line - x.data1.r <- NULL # x coord of the y upper and lower limits defined on the data1 cloud for right step line - y.data1.down.limit.l <- NULL # lower limit of the data1 cloud for left step line - y.data1.top.limit.l <- NULL # upper limit of the data1 cloud for left step line - y.data1.down.limit.r <- NULL # lower limit of the data1 cloud for right step line - y.data1.top.limit.r <- NULL # upper limit of the data1 cloud for left step line - if(any(data1[, x1] %in% c(Inf, -Inf))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE data1 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE x1 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - x.range <- range(data1[, x1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - if(suppressWarnings(any(x.range %in% c(Inf, -Inf)))){ - tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED x.range CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data1[, y1] %in% c(Inf, -Inf))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE data1 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE y1 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - y.range <- range(data1[, y1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - if(suppressWarnings(any(x.range %in% c(Inf, -Inf)))){ - tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED y.range CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - x.range.plot <- range(data1[, x1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - y.range.plot <- range(data1[, y1], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - if( ! is.null(data2)){ - if(any(data2[, x2] %in% c(Inf, -Inf))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE data2 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE x2 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - x.range.plot <- range(data1[, x1], data2[, x2], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - if(any(data2[, y2] %in% c(Inf, -Inf))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE data2 ARGUMENT CONTAINS -Inf OR Inf VALUES IN THE y2 COLUMN, THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - y.range.plot <- range(data1[, y1], data2[, y2], na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - } - if(suppressWarnings(any(x.range.plot %in% c(Inf, -Inf)))){ - tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED x.range.plot CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 (AND data2?) ARGUMENTS ARE NA OR Inf ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(suppressWarnings(any(y.range.plot %in% c(Inf, -Inf)))){ - tempo.cat <- paste0("ERROR IN ", function.name, " COMPUTED y.range.plot CONTAINS Inf VALUES, BECAUSE VALUES FROM data1 (AND data2?) ARGUMENTS ARE NA OR Inf ONLY") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(x.range.split)){ - # data.frame ordering to slide the window from small to big values + sliding window definition - data1 <- data1[order(data1[, x1], na.last = TRUE), ] - if( ! is.null(data2)){ - data2 <- data2[order(data2[, x2], na.last = TRUE), ] - } - x.win.size <- abs(diff(x.range) / x.range.split) # in unit of x-axis - step <- x.win.size / x.step.factor - # end data.frame ordering to slide the window from small to big values + sliding window definition - # x-axis sliding and y-axis limits of the data1 cloud -> y significant data2 - loop.nb <- ceiling((diff(x.range) - x.win.size) / step) # x.win.size + n * step covers the x range if x.win.size + n * step >= diff(x.range), thus if n >= (diff(x.range) - x.win.size) / step - y.outside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are upper or lower than the frame - y.inside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are not upper or lower than the frame - y.data1.median <- median(data1[, y1], na.rm = TRUE) # will be used for sliding windows without data1 in it - if( ! is.null(data2)){ - y.outside.data2.dot.nb <- integer() # vector that will contain the selected 1D coordinates (i.e., dots) of data2 that are upper or lower than the data1 frame - y.inside.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are not upper or lower than the data1 frame - y.unknown.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are problematic: data2 dots outside of the range of data1, or data2 dots in a sliding window without data1 dots - # recover data2 dots outside the range of data1 - if(any(data2[, x2] < x.range[1])){ - y.unknown.data2.dot.nb <- c(y.unknown.data2.dot.nb, data2$DOT_NB[data2[, x2] < x.range[1]]) - #tempo.warn & indicate the interval - } - if(any(data2[, x2] > x.range[2])){ - y.unknown.data2.dot.nb <- c(y.unknown.data2.dot.nb, data2$DOT_NB[data2[, x2] > x.range[2]]) - #tempo.warn & indicate the interval - } - # end recover data2 dots outside the range of data1 - } - # loop.ini.time <- as.numeric(Sys.time()) - for(i1 in 0:(loop.nb + 1)){ - min.pos <- x.range[1] + step * i1 # lower position of the sliding window in data1 - max.pos <- min.pos + x.win.size # upper position of the sliding window in data1 - x.data1.l <- c(x.data1.l, min.pos, min.pos + step) # min.pos + step to make the steps - x.data1.r <- c(x.data1.r, max.pos, max.pos + step) # max.pos + step to make the steps - x.data1.dot.here <- data1[, x1] >= min.pos & data1[, x1] < max.pos # is there data1 dot present in the sliding window, considering the x axis? - if( ! is.null(data2)){ - x.data2.dot.here <- data2[, x2] >= min.pos & data2[, x2] < max.pos # is there data2 dot present in the sliding window, considering the x axis? - } - # recover the data1 dots outside the frame - if(any(x.data1.dot.here == TRUE)){ - tempo.y.data1.top.limit <- quantile(data1[x.data1.dot.here, y1], probs = 1 - error, na.rm = TRUE) - tempo.y.data1.down.limit <- quantile(data1[x.data1.dot.here, y1], probs = 0 + error, na.rm = TRUE) - y.data1.top.limit.l <- c(y.data1.top.limit.l, tempo.y.data1.top.limit, tempo.y.data1.top.limit) - y.data1.down.limit.l <- c(y.data1.down.limit.l, tempo.y.data1.down.limit, tempo.y.data1.down.limit) - y.data1.top.limit.r <- c(y.data1.top.limit.r, tempo.y.data1.top.limit, tempo.y.data1.top.limit) - y.data1.down.limit.r <- c(y.data1.down.limit.r, tempo.y.data1.down.limit, tempo.y.data1.down.limit) - y.data1.dot.signif <- ( ! ((data1[, y1] <= tempo.y.data1.top.limit) & (data1[, y1] >= tempo.y.data1.down.limit))) & x.data1.dot.here # is there data1 dot present in the sliding window, above or below the data1 limits, considering the y axis? - y.data1.dot.not.signif <- x.data1.dot.here & ! y.data1.dot.signif - y.outside.data1.dot.nb <- c(y.outside.data1.dot.nb, data1$DOT_NB[y.data1.dot.signif]) # recover the row number of data1 - y.outside.data1.dot.nb <- unique(y.outside.data1.dot.nb) - y.inside.data1.dot.nb <- c(y.inside.data1.dot.nb, data1$DOT_NB[y.data1.dot.not.signif]) - y.inside.data1.dot.nb <- unique(y.inside.data1.dot.nb) - }else{ - y.data1.top.limit.l <- c(y.data1.top.limit.l, y.data1.median, y.data1.median) - y.data1.down.limit.l <- c(y.data1.down.limit.l, y.data1.median, y.data1.median) - y.data1.top.limit.r <- c(y.data1.top.limit.r, y.data1.median, y.data1.median) - y.data1.down.limit.r <- c(y.data1.down.limit.r, y.data1.median, y.data1.median) - } - # end recover the data1 dots outside the frame - # recover the data2 dots outside the frame - if( ! is.null(data2)){ - if(any(x.data1.dot.here == TRUE) & any(x.data2.dot.here == TRUE)){ - y.data2.dot.signif <- ( ! ((data2[, y2] <= tempo.y.data1.top.limit) & (data2[, y2] >= tempo.y.data1.down.limit))) & x.data2.dot.here # is there data2 dot present in the sliding window, above or below the data1 limits, considering the y axis? - y.data2.dot.not.signif <- x.data2.dot.here & ! y.data2.dot.signif - y.outside.data2.dot.nb <- c(y.outside.data2.dot.nb, data2$DOT_NB[y.data2.dot.signif]) - y.outside.data2.dot.nb <- unique(y.outside.data2.dot.nb) - y.inside.data2.dot.nb <- c(y.inside.data2.dot.nb, data2$DOT_NB[y.data2.dot.not.signif]) - y.inside.data2.dot.nb <- unique(y.inside.data2.dot.nb) - }else if(any(x.data1.dot.here == FALSE) & any(x.data2.dot.here == TRUE)){ # problem: data2 dots in the the window but no data1 dots to generates the quantiles - y.unknown.data2.dot.nb <- c(y.unknown.data2.dot.nb, data2$DOT_NB[x.data2.dot.here]) - y.unknown.data2.dot.nb <- unique(y.unknown.data2.dot.nb) - #tempo.warn & indicate the interval - - - - - # tempo.warn <- paste0("FROM FUNCTION ", function.name, ": THE [", round(min.pos, 3), " ; ", round(max.pos, 3), "] INTERVAL DOES NOT CONTAIN data1 X VALUES BUT CONTAINS data2 X VALUES WHICH CANNOT BE EVALUATED.\nTHE CONCERNED data2 ROW NUMBERS ARE:\n", paste(which(x.data1.dot.here == FALSE & x.data2.dot.here == TRUE), collapse = "\n")) - # warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end recover the data2 dots outside the frame - # if(any(i1 == seq(1, loop.nb, 500))){ - # loop.fin.time <- as.numeric(Sys.time()) # time of process end - # cat(paste0("COMPUTATION TIME OF LOOP ", i1, " / ", loop.nb, ": ", as.character(lubridate::seconds_to_period(round(loop.fin.time - loop.ini.time))), "\n")) - # } - } - if(max.pos < x.range[2]){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE SLIDING WINDOW HAS NOT REACHED THE MAX VALUE OF data1 ON THE X-AXIS: ", max.pos, " VERSUS ", x.range[2]) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - y.incon.data1.dot.nb.final <- unique(c(y.outside.data1.dot.nb[y.outside.data1.dot.nb %in% y.inside.data1.dot.nb], y.inside.data1.dot.nb[y.inside.data1.dot.nb %in% y.outside.data1.dot.nb])) # inconsistent dots: if a row number of y.inside.data1.dot.nb is present in y.outside.data1.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list - y.outside.data1.dot.nb.final <- y.outside.data1.dot.nb[ ! (y.outside.data1.dot.nb %in% y.incon.data1.dot.nb.final)] # inconsistent dots removed from the outside list - y.inside.data1.dot.nb.final <- y.inside.data1.dot.nb[ ! (y.inside.data1.dot.nb %in% y.incon.data1.dot.nb.final)] # inconsistent dots removed from the inside list - if( ! is.null(data2)){ - # if some unknown dots are also inside, and/or outside, they are put in the inside and/or outside. Ok, because then the intersection between inside and outside is treated -> inconsistent dots - tempo.unknown.out <- y.unknown.data2.dot.nb[y.unknown.data2.dot.nb %in% y.outside.data2.dot.nb] - y.outside.data2.dot.nb <- unique(c(y.outside.data2.dot.nb, tempo.unknown.out)) # if a row number of y.unknown.data2.dot.nb is present in y.outside.data2.dot.nb, it is put into outside - tempo.unknown.in <- y.unknown.data2.dot.nb[y.unknown.data2.dot.nb %in% y.inside.data2.dot.nb] - y.inside.data2.dot.nb <- unique(c(y.inside.data2.dot.nb, tempo.unknown.in)) # if a row number of y.unknown.data2.dot.nb is present in y.inside.data2.dot.nb, it is put into inside - y.unknown.data2.dot.nb.final <- y.unknown.data2.dot.nb[ ! (y.unknown.data2.dot.nb %in% c(y.outside.data2.dot.nb, y.inside.data2.dot.nb))] # then dots also in inside and outside are remove from unknown - y.incon.data2.dot.nb.final <- unique(c(y.outside.data2.dot.nb[y.outside.data2.dot.nb %in% y.inside.data2.dot.nb], y.inside.data2.dot.nb[y.inside.data2.dot.nb %in% y.outside.data2.dot.nb])) # inconsistent dots: if a row number of y.inside.data2.dot.nb is present in y.outside.data2.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list - y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb[ ! (y.outside.data2.dot.nb %in% y.incon.data2.dot.nb.final)] # inconsistent dots removed from the outside list - y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb[ ! (y.inside.data2.dot.nb %in% y.incon.data2.dot.nb.final)] # inconsistent dots removed from the inside list - } - # end x-axis sliding and y-axis limits of the data1 cloud -> y significant data2 - } - # end Method using x unit interval - - - - - # Method using y unit interval - y.data1.d <- NULL # y coord of the x upper and lower limits defined on the data1 cloud for down step line - y.data1.t <- NULL # y coord of the x upper and lower limits defined on the data1 cloud for top step line - x.data1.left.limit.d <- NULL # left limit of the data1 cloud for down step line - x.data1.right.limit.d <- NULL # right limit of the data1 cloud for down step line - x.data1.left.limit.t <- NULL # left limit of the data1 cloud for top step line - x.data1.right.limit.t <- NULL # right limit of the data1 cloud for top step line - if( ! is.null(y.range.split)){ - # data.frame ordering to slide the window from small to big values + sliding window definition - data1 <- data1[order(data1[, y1], na.last = TRUE), ] - if( ! is.null(data2)){ - data2 <- data2[order(data2[, y2], na.last = TRUE), ] - } - y.win.size <- abs(diff(y.range) / y.range.split) # in unit of y-axis - step <- y.win.size / y.step.factor - # end data.frame ordering to slide the window from small to big values + sliding window definition - # y-axis sliding and x-axis limits of the data1 cloud -> x significant data2 - loop.nb <- ceiling((diff(y.range) - y.win.size) / step) # y.win.size + n * step covers the y range if y.win.size + n * step >= diff(y.range), thus if n >= (diff(y.range) - y.win.size) / step - x.outside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are upper or lower than the frame - x.inside.data1.dot.nb <- integer() # vector that will contain the selected rows numbers of data1 that are not upper or lower than the frame - x.data1.median <- median(data1[, x1], na.rm = TRUE) # will be used for sliding window without data1 in it - if( ! is.null(data2)){ - x.outside.data2.dot.nb <- integer() # vector that will contain the selected 1D coordinates (i.e., dots) of data2 that are upper or lower than the data1 frame - x.inside.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are not upper or lower than the data1 frame - x.unknown.data2.dot.nb <- integer() # vector that will contain the 1D coordinates (i.e., dots) of data2 that are problematic: data2 dots outside of the range of data1, or data2 dots in a sliding window without data1 dots - # recover data2 dots outside the range of data1 - if(any(data2[, y2] < y.range[1])){ - x.unknown.data2.dot.nb <- c(x.unknown.data2.dot.nb, data2$DOT_NB[data2[, y2] < y.range[1]]) - } - if(any(data2[, y2] > y.range[2])){ - x.unknown.data2.dot.nb <- c(x.unknown.data2.dot.nb, data2$DOT_NB[data2[, y2] > y.range[2]]) - } - # end recover data2 dots outside the range of data1 - } - # loop.ini.time <- as.numeric(Sys.time()) - for(i1 in 0:(loop.nb + 1)){ - min.pos <- y.range[1] + step * i1 # lower position of the sliding window in data1 - max.pos <- min.pos + y.win.size # upper position of the sliding window in data1 - y.data1.d <- c(y.data1.d, min.pos, min.pos + step) # min.pos + step to make the steps - y.data1.t <- c(y.data1.t, max.pos, max.pos + step) # max.pos + step to make the steps - y.data1.dot.here <- data1[, y1] >= min.pos & data1[, y1] < max.pos # is there data1 dot present in the sliding window, considering the y axis? - if( ! is.null(data2)){ - y.data2.dot.here <- data2[, y2] >= min.pos & data2[, y2] < max.pos # is there data2 dot present in the sliding window, considering the y axis? - } - # recover the data1 dots outside the frame - if(any(y.data1.dot.here == TRUE)){ - tempo.x.data1.right.limit <- quantile(data1[y.data1.dot.here, x1], probs = 1 - error, na.rm = TRUE) - tempo.x.data1.left.limit <- quantile(data1[y.data1.dot.here, x1], probs = 0 + error, na.rm = TRUE) - x.data1.right.limit.d <- c(x.data1.right.limit.d, tempo.x.data1.right.limit, tempo.x.data1.right.limit) - x.data1.left.limit.d <- c(x.data1.left.limit.d, tempo.x.data1.left.limit, tempo.x.data1.left.limit) - x.data1.right.limit.t <- c(x.data1.right.limit.t, tempo.x.data1.right.limit, tempo.x.data1.right.limit) - x.data1.left.limit.t <- c(x.data1.left.limit.t, tempo.x.data1.left.limit, tempo.x.data1.left.limit) - x.data1.dot.signif <- ( ! ((data1[, x1] <= tempo.x.data1.right.limit) & (data1[, x1] >= tempo.x.data1.left.limit))) & y.data1.dot.here # is there data2 dot present in the sliding window, above or below the data1 limits, considering the x axis? - x.data1.dot.not.signif <- y.data1.dot.here & ! x.data1.dot.signif - x.outside.data1.dot.nb <- c(x.outside.data1.dot.nb, data1$DOT_NB[x.data1.dot.signif]) # recover the row number of data1 - x.outside.data1.dot.nb <- unique(x.outside.data1.dot.nb) - x.inside.data1.dot.nb <- c(x.inside.data1.dot.nb, data1$DOT_NB[x.data1.dot.not.signif]) - x.inside.data1.dot.nb <- unique(x.inside.data1.dot.nb) - }else{ - x.data1.right.limit.d <- c(x.data1.right.limit.d, x.data1.median, x.data1.median) - x.data1.left.limit.d <- c(x.data1.left.limit.d, x.data1.median, x.data1.median) - x.data1.right.limit.t <- c(x.data1.right.limit.t, x.data1.median, x.data1.median) - x.data1.left.limit.t <- c(x.data1.left.limit.t, x.data1.median, x.data1.median) - } - # end recover the data1 dots outside the frame - # recover the data2 dots outside the frame - if( ! is.null(data2)){ - if(any(y.data1.dot.here == TRUE) & any(y.data2.dot.here == TRUE)){ - x.data2.dot.signif <- ( ! ((data2[, x2] <= tempo.x.data1.right.limit) & (data2[, x2] >= tempo.x.data1.left.limit))) & y.data2.dot.here # is there data2 dot present in the sliding window, above or below the data1 limits, considering the x axis? - x.data2.dot.not.signif <- y.data2.dot.here & ! x.data2.dot.signif - x.outside.data2.dot.nb <- c(x.outside.data2.dot.nb, data2$DOT_NB[x.data2.dot.signif]) - x.outside.data2.dot.nb <- unique(x.outside.data2.dot.nb) - x.inside.data2.dot.nb <- c(x.inside.data2.dot.nb, data2$DOT_NB[x.data2.dot.not.signif]) - x.inside.data2.dot.nb <- unique(x.inside.data2.dot.nb) - }else if(any(y.data1.dot.here == FALSE) & any(y.data2.dot.here == TRUE)){ # recover the data2 dots outside the range of the data1 cloud - x.unknown.data2.dot.nb <- c(x.unknown.data2.dot.nb, data2$DOT_NB[y.data2.dot.here]) - x.unknown.data2.dot.nb <- unique(x.unknown.data2.dot.nb) - - - - # tempo.warn <- paste0("FROM FUNCTION ", function.name, ": THE [", round(min.pos, 3), " ; ", round(max.pos, 3), "] INTERVAL DOES NOT CONTAIN data1 Y VALUES BUT CONTAINS data2 Y VALUES WHICH CANNOT BE EVALUATED.\nTHE CONCERNED data2 ROW NUMBERS ARE:\n", paste(which(y.data1.dot.here == FALSE & y.data2.dot.here == TRUE), collapse = "\n")) - # warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end recover the data2 dots outside the frame - # if(any(i1 == seq(1, loop.nb, 500))){ - # loop.fin.time <- as.numeric(Sys.time()) # time of process end - # cat(paste0("COMPUTATION TIME OF LOOP ", i1, " / ", loop.nb, ": ", as.character(lubridate::seconds_to_period(round(loop.fin.time - loop.ini.time))), "\n")) - # } - } - if(max.pos < y.range[2]){ - tempo.cat <- paste0("ERROR IN ", function.name, ": THE SLIDING WINDOW HAS NOT REACHED THE MAX VALUE OF data1 ON THE Y-AXIS: ", max.pos, " VERSUS ", y.range[2]) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - x.incon.data1.dot.nb.final <- unique(c(x.outside.data1.dot.nb[x.outside.data1.dot.nb %in% x.inside.data1.dot.nb], x.inside.data1.dot.nb[x.inside.data1.dot.nb %in% x.outside.data1.dot.nb])) # inconsistent dots: if a row number of x.inside.data1.dot.nb is present in x.outside.data1.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list - x.outside.data1.dot.nb.final <- x.outside.data1.dot.nb[ ! (x.outside.data1.dot.nb %in% x.incon.data1.dot.nb.final)] # inconsistent dots removed from the outside list - x.inside.data1.dot.nb.final <- x.inside.data1.dot.nb[ ! (x.inside.data1.dot.nb %in% x.incon.data1.dot.nb.final)] # inconsistent dots removed from the inside list - if( ! is.null(data2)){ - # if some unknown dots are also inside, and/or outside, they are put in the inside and/or outside. Ok, because then the intersection between inside and outside is treated -> inconsistent dots - tempo.unknown.out <- x.unknown.data2.dot.nb[x.unknown.data2.dot.nb %in% x.outside.data2.dot.nb] - x.outside.data2.dot.nb <- unique(c(x.outside.data2.dot.nb, tempo.unknown.out)) # if a row number of x.unknown.data2.dot.nb is present in x.outside.data2.dot.nb, it is put into outside - tempo.unknown.in <- x.unknown.data2.dot.nb[x.unknown.data2.dot.nb %in% x.inside.data2.dot.nb] - x.inside.data2.dot.nb <- unique(c(x.inside.data2.dot.nb, tempo.unknown.in)) # if a row number of x.unknown.data2.dot.nb is present in x.inside.data2.dot.nb, it is put into inside - x.unknown.data2.dot.nb.final <- x.unknown.data2.dot.nb[ ! (x.unknown.data2.dot.nb %in% c(x.outside.data2.dot.nb, x.inside.data2.dot.nb))] # then dots also in inside and outside are remove from unknown - x.incon.data2.dot.nb.final <- unique(c(x.outside.data2.dot.nb[x.outside.data2.dot.nb %in% x.inside.data2.dot.nb], x.inside.data2.dot.nb[x.inside.data2.dot.nb %in% x.outside.data2.dot.nb])) # inconsistent dots: if a row number of x.inside.data2.dot.nb is present in x.outside.data2.dot.nb (and vice versa), it means that during the sliding, a dot has been sometime inside, sometime outside -> removed from the outside list - x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb[ ! (x.outside.data2.dot.nb %in% x.incon.data2.dot.nb.final)] # inconsistent dots removed from the outside list - x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb[ ! (x.inside.data2.dot.nb %in% x.incon.data2.dot.nb.final)] # inconsistent dots removed from the inside list - } - # end y-axis sliding and x-axis limits of the data1 cloud -> x significant data2 - } - # end Method using y unit interval - - - - # recovering the frame coordinates - hframe = rbind( - data.frame( - x = if(is.null(x.data1.l)){NULL}else{x.data1.l}, - y = if(is.null(x.data1.l)){NULL}else{y.data1.down.limit.l}, - kind = if(is.null(x.data1.l)){NULL}else{"down.frame1"}, - stringsAsFactors = TRUE - ), - data.frame( - x = if(is.null(x.data1.r)){NULL}else{x.data1.r}, - y = if(is.null(x.data1.r)){NULL}else{y.data1.down.limit.r}, - kind = if(is.null(x.data1.r)){NULL}else{"down.frame2"}, - stringsAsFactors = TRUE - ), - data.frame( - x = if(is.null(x.data1.l)){NULL}else{x.data1.l}, - y = if(is.null(x.data1.l)){NULL}else{y.data1.top.limit.l}, - kind = if(is.null(x.data1.l)){NULL}else{"top.frame1"}, - stringsAsFactors = TRUE - ), - data.frame( - x = if(is.null(x.data1.r)){NULL}else{x.data1.r}, - y = if(is.null(x.data1.r)){NULL}else{y.data1.top.limit.r}, - kind = if(is.null(x.data1.r)){NULL}else{"top.frame2"}, - stringsAsFactors = TRUE - ), - stringsAsFactors = TRUE - ) - vframe = rbind( - data.frame( - x = if(is.null(y.data1.d)){NULL}else{x.data1.left.limit.d}, - y = if(is.null(y.data1.d)){NULL}else{y.data1.d}, - kind = if(is.null(y.data1.d)){NULL}else{"left.frame1"}, - stringsAsFactors = TRUE - ), - data.frame( - x = if(is.null(y.data1.t)){NULL}else{x.data1.left.limit.t}, - y = if(is.null(y.data1.t)){NULL}else{y.data1.t}, - kind = if(is.null(y.data1.t)){NULL}else{"left.frame2"}, - stringsAsFactors = TRUE - ), - data.frame( - x = if(is.null(y.data1.d)){NULL}else{x.data1.right.limit.d}, - y = if(is.null(y.data1.d)){NULL}else{y.data1.d}, - kind = if(is.null(y.data1.d)){NULL}else{"right.frame1"}, - stringsAsFactors = TRUE - ), - data.frame( - x = if(is.null(y.data1.t)){NULL}else{x.data1.right.limit.t}, - y = if(is.null(y.data1.t)){NULL}else{y.data1.t}, - kind = if(is.null(y.data1.t)){NULL}else{"right.frame2"}, - stringsAsFactors = TRUE - ), - stringsAsFactors = TRUE - ) - # end recovering the frame coordinates - # recovering the dot coordinates - data1.signif.dot <- NULL - data1.non.signif.dot <- NULL - data1.incon.dot <- NULL - data2.signif.dot <- NULL - data2.non.signif.dot <- NULL - data2.unknown.dot <- NULL - data2.incon.dot <- NULL - if(( ! is.null(x.range.split)) & ( ! is.null(y.range.split))){ - # inconsistent dots recovery - if(length(unique(c(x.incon.data1.dot.nb.final, y.incon.data1.dot.nb.final))) > 0){ - data1.incon.dot <- data1[data1$DOT_NB %in% unique(c(x.incon.data1.dot.nb.final, y.incon.data1.dot.nb.final)), ] # if a dot in inconsistent in x or y -> classified as inconsistent (so unique() used) - # removal of the inconsistent dot in the other classifications - x.inside.data1.dot.nb.final <- x.inside.data1.dot.nb.final[ ! x.inside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] - y.inside.data1.dot.nb.final <- y.inside.data1.dot.nb.final[ ! y.inside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] - x.outside.data1.dot.nb.final <- x.outside.data1.dot.nb.final[ ! x.outside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] - y.outside.data1.dot.nb.final <- y.outside.data1.dot.nb.final[ ! y.outside.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] - x.unknown.data1.dot.nb.final <- x.unknown.data1.dot.nb.final[ ! x.unknown.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] - y.unknown.data1.dot.nb.final <- y.unknown.data1.dot.nb.final[ ! y.unknown.data1.dot.nb.final %in% data1.incon.dot$DOT_NB] - # end removal of the inconsistent dot in the other classifications - } - if( ! is.null(data2)){ - if(length(unique(c(x.incon.data2.dot.nb.final, y.incon.data2.dot.nb.final))) > 0){ - data2.incon.dot <- data2[data2$DOT_NB %in% unique(c(x.incon.data2.dot.nb.final, y.incon.data2.dot.nb.final)), ] - # removal of the inconsistent dot in the other classifications - x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb.final[ ! x.inside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] - y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb.final[ ! y.inside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] - x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb.final[ ! x.outside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] - y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb.final[ ! y.outside.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] - x.unknown.data2.dot.nb.final <- x.unknown.data2.dot.nb.final[ ! x.unknown.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] - y.unknown.data2.dot.nb.final <- y.unknown.data2.dot.nb.final[ ! y.unknown.data2.dot.nb.final %in% data2.incon.dot$DOT_NB] - # end removal of the inconsistent dot in the other classifications - } - } - # end inconsistent dots recovery - # unknown dots recovery - if( ! is.null(data2)){ - if(data2.pb.dot == "signif"){ - x.outside.data2.dot.nb.final <- unique(c(x.outside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) - x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb.final[ ! x.inside.data2.dot.nb.final %in% x.unknown.data2.dot.nb.final] # remove x.unknown.data2.dot.nb.final from x.inside.data2.dot.nb.final - y.outside.data2.dot.nb.final <- unique(c(y.outside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) - y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb.final[ ! y.inside.data2.dot.nb.final %in% y.unknown.data2.dot.nb.final] # remove y.unknown.data2.dot.nb.final from y.inside.data2.dot.nb.final - x.unknown.data2.dot.nb.final <- NULL - y.unknown.data2.dot.nb.final <- NULL - data2.unknown.dot <- NULL - }else if(data2.pb.dot == "not.signif"){ - x.inside.data2.dot.nb.final <- unique(c(x.inside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) - x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb.final[ ! x.outside.data2.dot.nb.final %in% x.unknown.data2.dot.nb.final] # remove x.unknown.data2.dot.nb.final from x.outside.data2.dot.nb.final - y.inside.data2.dot.nb.final <- unique(c(y.inside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) - y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb.final[ ! y.outside.data2.dot.nb.final %in% y.unknown.data2.dot.nb.final] # remove y.unknown.data2.dot.nb.final from y.outside.data2.dot.nb.final - x.unknown.data2.dot.nb.final <- NULL - y.unknown.data2.dot.nb.final <- NULL - data2.unknown.dot <- NULL - }else if(data2.pb.dot == "unknown"){ - if(length(unique(c(x.unknown.data2.dot.nb.final, y.unknown.data2.dot.nb.final))) > 0){ - data2.unknown.dot <- data2[data2$DOT_NB %in% unique(c(x.unknown.data2.dot.nb.final, y.unknown.data2.dot.nb.final)), ] # if a dot in unknown in x or y -> classified as unknown (so unique() used) - x.outside.data2.dot.nb.final <- x.outside.data2.dot.nb.final[ ! x.outside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove x.unknown.data2.dot.nb.final from x.outside.data2.dot.nb.final - x.inside.data2.dot.nb.final <- x.inside.data2.dot.nb.final[ ! x.inside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove x.unknown.data2.dot.nb.final from x.inside.data2.dot.nb.final - y.outside.data2.dot.nb.final <- y.outside.data2.dot.nb.final[ ! y.outside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove y.unknown.data2.dot.nb.final from y.outside.data2.dot.nb.final - y.inside.data2.dot.nb.final <- y.inside.data2.dot.nb.final[ ! y.inside.data2.dot.nb.final %in% data2.unknown.dot$DOT_NB] # remove y.unknown.data2.dot.nb.final from y.inside.data2.dot.nb.final - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 3") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end unknown dots recovery - # sign and non sign dot recovery - if(xy.cross.kind == "|"){ # here the problem is to deal with significant dots depending on x and y. Thus I start with that, recover dots finally non significant in outside and put them in inside (when &), and remove from inside the dots in outside - if(length(unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final))) > 0){ - tempo.outside <- unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final)) # union so unique() used - tempo.inside <- unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)) - tempo.inside <- tempo.inside[ ! tempo.inside %in% tempo.outside] - data1.signif.dot <- data1[data1$DOT_NB %in% tempo.outside, ] - data1.non.signif.dot <- data1[data1$DOT_NB %in% tempo.inside, ] - }else{ - data1.non.signif.dot <- data1[unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it - } - }else if(xy.cross.kind == "&"){ - if(sum(x.outside.data1.dot.nb.final %in% y.outside.data1.dot.nb.final) > 0){ # that is intersection - tempo.outside <- unique(x.outside.data1.dot.nb.final[x.outside.data1.dot.nb.final %in% y.outside.data1.dot.nb.final]) # intersection - tempo.outside.removed <- unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final))[ ! unique(c(x.outside.data1.dot.nb.final, y.outside.data1.dot.nb.final)) %in% tempo.outside] - tempo.inside <- unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)) - data1.signif.dot <- data1[data1$DOT_NB %in% tempo.outside, ] - data1.non.signif.dot <- data1[data1$DOT_NB %in% tempo.inside, ] - }else{ - data1.non.signif.dot <- data1[unique(c(x.inside.data1.dot.nb.final, y.inside.data1.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 4") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(data2)){ - if(xy.cross.kind == "|"){ # here the problem is to deal with significant dots depending on x and y. Thus I start with that, recover dots finally non significant in outside and put them in inside (when &), and remove from inside the dots in outside - if(length(unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final))) > 0){ - tempo.outside <- unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final)) # union so unique() used - tempo.inside <- unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)) - tempo.inside <- tempo.inside[ ! tempo.inside %in% tempo.outside] - data2.signif.dot <- data2[data2$DOT_NB %in% tempo.outside, ] - data2.non.signif.dot <- data2[data2$DOT_NB %in% tempo.inside, ] - }else{ - data2.non.signif.dot <- data2[unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it - } - }else if(xy.cross.kind == "&"){ - if(sum(x.outside.data2.dot.nb.final %in% y.outside.data2.dot.nb.final) > 0){ # that is intersection - tempo.outside <- unique(x.outside.data2.dot.nb.final[x.outside.data2.dot.nb.final %in% y.outside.data2.dot.nb.final]) # intersection - tempo.outside.removed <- unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final))[ ! unique(c(x.outside.data2.dot.nb.final, y.outside.data2.dot.nb.final)) %in% tempo.outside] - tempo.inside <- unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)) - data2.signif.dot <- data2[data2$DOT_NB %in% tempo.outside, ] - data2.non.signif.dot <- data2[data2$DOT_NB %in% tempo.inside, ] - }else{ - data2.non.signif.dot <- data2[unique(c(x.inside.data2.dot.nb.final, y.inside.data2.dot.nb.final)), ] # if no outside dots, I recover all the inside dots and that's it - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 5") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end sign and non sign dot recovery - }else if(( ! is.null(x.range.split)) & is.null(y.range.split)){ - # inconsistent dots recovery - if(length(y.incon.data1.dot.nb.final) > 0){ - data1.incon.dot <- data1[data1$DOT_NB %in% y.incon.data1.dot.nb.final, ] - } - if( ! is.null(data2)){ - if(length(y.incon.data2.dot.nb.final) > 0){ - data2.incon.dot <- data2[data2$DOT_NB %in% y.incon.data2.dot.nb.final, ] - } - }# end inconsistent dots recovery - # unknown dots recovery - if( ! is.null(data2)){ - if(data2.pb.dot == "signif"){ - y.outside.data2.dot.nb.final <- unique(c(y.outside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) - }else if(data2.pb.dot == "not.signif"){ - y.inside.data2.dot.nb.final <- unique(c(y.inside.data2.dot.nb.final, y.unknown.data2.dot.nb.final)) - }else if(data2.pb.dot == "unknown"){ - if(length(y.unknown.data2.dot.nb.final) > 0){ - data2.unknown.dot <- data2[data2$DOT_NB %in% y.unknown.data2.dot.nb.final, ] - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 6") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end unknown dots recovery - # sign and non sign dot recovery - if(length(y.outside.data1.dot.nb.final) > 0){ - data1.signif.dot <- data1[data1$DOT_NB %in% y.outside.data1.dot.nb.final, ] - } - if(length(y.inside.data1.dot.nb.final) > 0){ - data1.non.signif.dot <- data1[data1$DOT_NB %in% y.inside.data1.dot.nb.final, ] - } - if( ! is.null(data2)){ - if(length(y.outside.data2.dot.nb.final) > 0){ - data2.signif.dot <- data2[data2$DOT_NB %in% y.outside.data2.dot.nb.final, ] - } - if(length(y.inside.data2.dot.nb.final) > 0){ - data2.non.signif.dot <- data2[data2$DOT_NB %in% y.inside.data2.dot.nb.final, ] - } - } - # end sign and non sign dot recovery - }else if(is.null(x.range.split) & ( ! is.null(y.range.split))){ - # inconsistent dots recovery - if(length(x.incon.data1.dot.nb.final) > 0){ - data1.incon.dot <- data1[data1$DOT_NB %in% x.incon.data1.dot.nb.final, ] - } - if( ! is.null(data2)){ - if(length(x.incon.data2.dot.nb.final) > 0){ - data2.incon.dot <- data2[data2$DOT_NB %in% x.incon.data2.dot.nb.final, ] - } - }# end inconsistent dots recovery - # unknown dots recovery - if( ! is.null(data2)){ - if(data2.pb.dot == "signif"){ - x.outside.data2.dot.nb.final <- unique(c(x.outside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) - }else if(data2.pb.dot == "not.signif"){ - x.inside.data2.dot.nb.final <- unique(c(x.inside.data2.dot.nb.final, x.unknown.data2.dot.nb.final)) - }else if(data2.pb.dot == "unknown"){ - if(length(x.unknown.data2.dot.nb.final) > 0){ - data2.unknown.dot <- data2[data2$DOT_NB %in% x.unknown.data2.dot.nb.final, ] - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 7") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end unknown dots recovery - # sign and non sign dot recovery - if(length(x.outside.data1.dot.nb.final) > 0){ - data1.signif.dot <- data1[data1$DOT_NB %in% x.outside.data1.dot.nb.final, ] - } - if(length(x.inside.data1.dot.nb.final) > 0){ - data1.non.signif.dot <- data1[data1$DOT_NB %in% x.inside.data1.dot.nb.final, ] - } - if( ! is.null(data2)){ - if(length(x.outside.data2.dot.nb.final) > 0){ - data2.signif.dot <- data2[data2$DOT_NB %in% x.outside.data2.dot.nb.final, ] - } - if(length(x.inside.data2.dot.nb.final) > 0){ - data2.non.signif.dot <- data2[data2$DOT_NB %in% x.inside.data2.dot.nb.final, ] - } - } - # end sign and non sign dot recovery - } - # end recovering the dot coordinates - # verif - if(any(data1.signif.dot$DOT_NB %in% data1.non.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", FUNCTION.NAME, ": CODE INCONSISTENCY 8") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data1.non.signif.dot$DOT_NB %in% data1.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", FUNCTION.NAME, ": CODE INCONSISTENCY 9") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data1.signif.dot$DOT_NB %in% data1.incon.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 10") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data1.incon.dot$DOT_NB %in% data1.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 11") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data1.non.signif.dot$DOT_NB %in% data1.incon.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 12") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data1.incon.dot$DOT_NB %in% data1.non.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 13") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(data2)){ - if(any(data2.signif.dot$DOT_NB %in% data2.non.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 14") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.non.signif.dot$DOT_NB %in% data2.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 15") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.signif.dot$DOT_NB %in% data2.unknown.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 16") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.unknown.dot$DOT_NB %in% data2.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 17") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.signif.dot$DOT_NB %in% data2.incon.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 18") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.incon.dot$DOT_NB %in% data2.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 19") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.non.signif.dot$DOT_NB %in% data2.unknown.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 20") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.unknown.dot$DOT_NB %in% data2.non.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 21") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.non.signif.dot$DOT_NB %in% data2.incon.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 22") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.incon.dot$DOT_NB %in% data2.non.signif.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 23") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.unknown.dot$DOT_NB %in% data2.incon.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 24") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(data2.incon.dot$DOT_NB %in% data2.unknown.dot$DOT_NB)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 25") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end verif - # plot - # recovering the axes data whatever plot or not - if(is.null(data2)){ - axes <- fun_gg_scatter(data1 = list(data1), x = list(x1), y = list(y1), categ = list(NULL), color = list(fun_gg_palette(2)[2]), geom = list("geom_point"), alpha = list(0.5), x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, plot = FALSE, return = TRUE)$axes - }else{ - axes <- fun_gg_scatter(data1 = list(data1, data2), x = list(x1, x2), y = list(y1, y2), categ = list(NULL, NULL), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1]), geom = list("geom_point", "geom_point"), alpha = list(0.5, 0.5), x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, plot = FALSE, return = TRUE)$axes - } - # end recovering the axes data whatever plot or not - if(plot == TRUE){ - # add a categ for plot legend - tempo.df.name <- c("data1", "data1.signif.dot", "data1.incon.dot", "data2", "data2.signif.dot", "data2.unknown.dot", "data2.incon.dot") - tempo.class.name <- c("data1", "data1", "data1", "data2", "data2", "data2", "data2") - for(i2 in 1:length(tempo.df.name)){ - if( ! is.null(get(tempo.df.name[i2], env = sys.nframe(), inherit = FALSE))){ - assign(tempo.df.name[i2], data.frame(get(tempo.df.name[i2], env = sys.nframe(), inherit = FALSE), kind = tempo.class.name[i2]), - stringsAsFactors = TRUE) - } - } - # end add a categ for plot legend - if(( ! is.null(x.range.split)) & ( ! is.null(y.range.split))){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, vframe), x = list(x1, "x", "x"), y = list(y1, "y", "y"), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5), title = "DATA1", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data1.signif.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, vframe, data1.signif.dot), x = list(x1, "x", "x", x1), y = list(y1, "y", "y", y1), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME" , "VERT FRAME", "SIGNIF DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), "black"), geom = list("geom_point", "geom_path", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA1 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA1 SIGNIFICANT DOTS") - } - if( ! is.null(data1.incon.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, vframe, data1.incon.dot), x = list(x1, "x", "x", x1), y = list(y1, "y", "y", y1), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME" , "VERT FRAME", "INCONSISTENT DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), fun_gg_palette(7)[6]), geom = list("geom_point", "geom_path", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA1 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1\nINCONSISTENT DOTS", text.size = 8, title = "DATA1 + DATA1 INCONSISTENT DOTS") - } - if( ! is.null(data2)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, hframe , vframe), x = list(x1, x2, "x", "x"), y = list(y1, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data2.signif.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.signif.dot, hframe , vframe), x = list(x1, x2, x2, "x", "x"), y = list(y1, y2, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "SIGNIF DOTS", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], "black", rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS") - } - if( ! is.null(data2.incon.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.incon.dot, hframe , vframe), x = list(x1, x2, x2, "x", "x"), y = list(y1, y2, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "INCONSISTENT DOTS", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[6], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nINCONSISTENT DOTS", text.size = 8, title = "DATA2 + DATA2 INCONSISTENT DOTS") - } - if( ! is.null(data2.unknown.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.unknown.dot, hframe , vframe), x = list(x1, x2, x2, "x", "x"), y = list(y1, y2, y2, "y", "y"), categ = list("kind", "kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "UNKNOWN DOTS", "HORIZ FRAME" , "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[5], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 UNKNOWN DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nUNKNOWN DOTS", text.size = 12, title = "DATA2 + DATA2 UNKNOWN DOTS") - } - } - }else if(( ! is.null(x.range.split)) & is.null(y.range.split)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe), x = list(x1, "x"), y = list(y1, "y"), categ = list("kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_path"), alpha = list(0.5, 0.5), title = "DATA1", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data1.signif.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, data1.signif.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME", "SIGNIF DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), "black"), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA1 SIGNIFICANT DOTS") - } - if( ! is.null(data1.incon.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, hframe, data1.incon.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "HORIZ FRAME", "INCONSISTENT DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2), fun_gg_palette(7)[6]), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1\nINCONSISTENT DOTS", text.size = 8, title = "DATA1 + DATA1 INCONSISTENT DOTS") - } - if( ! is.null(data2)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, hframe), x = list(x1, x2, "x"), y = list(y1, y2, "y"), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA2", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data2.signif.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.signif.dot, hframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "SIGNIF DOTS", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], "black", rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS") - } - if( ! is.null(data2.incon.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.incon.dot, hframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "INCONSISTENT DOTS", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[6], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nINCONSISTENT DOTS", text.size = 8, title = "DATA2 + DATA2 INCONSISTENT DOTS") - } - if( ! is.null(data2.unknown.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.unknown.dot, hframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "UNKNOWN DOTS", "HORIZ FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[5], rep(hsv(h = c(0.1, 0.15), v = c(0.75, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 UNKNOWN DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nUNKNOWN DOTS", text.size = 8, title = "DATA2 + DATA2 UNKNOWN DOTS") - } - } - }else if(is.null(x.range.split) & ( ! is.null(y.range.split))){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, vframe), x = list(x1, "x"), y = list(y1, "y"), categ = list("kind", "kind"), legend.name = list("DATASET", "VERT FRAME"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_path"), alpha = list(0.5, 0.5), title = "DATA1", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data1.signif.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, vframe, data1.signif.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "VERT FRAME", "SIGNIF DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), "black"), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA1 SIGNIFICANT DOTS") - } - if( ! is.null(data1.incon.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, vframe, data1.incon.dot), x = list(x1, "x", x1), y = list(y1, "y", y1), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "VERT FRAME", "INCONSISTENT DOTS"), color = list(fun_gg_palette(2)[2], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2), fun_gg_palette(7)[6]), geom = list("geom_point", "geom_path", "geom_point"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA1 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA1\nINCONSISTENT DOTS", text.size = 8, title = "DATA1 + DATA1 INCONSISTENT DOTS") - } - if( ! is.null(data2)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, vframe), x = list(x1, x2, "x"), y = list(y1, y2, "y"), categ = list("kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5), title = "DATA1 + DATA2", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(data2.signif.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.signif.dot, vframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "SIGNIF DOTS", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], "black", rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2 DOTS\nOUTSIDE THE FRAMES", text.size = 8, title = "DATA1 + DATA2 + DATA2 SIGNIFICANT DOTS") - } - if( ! is.null(data2.incon.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.incon.dot, vframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "INCONSISTENT DOTS", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[6], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 INCONSISTENT DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nINCONSISTENT DOTS", text.size = 8, title = "DATA2 + DATA2 INCONSISTENT DOTS") - } - if( ! is.null(data2.unknown.dot)){ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - tempo.graph <- fun_gg_scatter(data1 = list(data1, data2, data2.unknown.dot, vframe), x = list(x1, x2, x2, "x"), y = list(y1, y2, y2, "y"), categ = list("kind", "kind", "kind", "kind"), legend.name = list("DATASET", "DATASET", "UNKNOWN DOTS", "VERT FRAME"), color = list(fun_gg_palette(2)[2], fun_gg_palette(2)[1], fun_gg_palette(7)[5], rep(hsv(h = c(0.5, 0.6), v = c(0.9, 1)), 2)), geom = list("geom_point", "geom_point", "geom_point", "geom_path"), alpha = list(0.5, 0.5, 0.5, 0.5), title = "DATA1 + DATA2 + DATA2 UNKNOWN DOTS", x.lim = x.range.plot, y.lim = y.range.plot, raster = raster, return = TRUE) - if( ! is.null(tempo.graph$warn)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") FROM fun_gg_scatter():\n", tempo.graph$warn) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(graph.in.file == FALSE){ - fun_open(pdf = FALSE) - } - fun_gg_empty_graph(text = "NO PLOT\nBECAUSE\nNO DATA2\nUNKNOWN DOTS", text.size = 8, title = "DATA2 + DATA2 UNKNOWN DOTS") - } - } - } - } - # end plot - if(warn.print == TRUE & ! is.null(warn)){ - options(warning.length = 8170) - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) - } - on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) - tempo.list <- list(data1.removed.row.nb = data1.removed.row.nb, data1.removed.rows = data1.removed.rows, data2.removed.row.nb = data2.removed.row.nb, data2.removed.rows = data2.removed.rows, hframe = hframe, vframe = vframe, data1.signif.dot = data1.signif.dot, data1.non.signif.dot = data1.non.signif.dot, data1.inconsistent.dot = data1.incon.dot, data2.signif.dot = data2.signif.dot, data2.non.signif.dot = data2.non.signif.dot, data2.unknown.dot = data2.unknown.dot, data2.inconsistent.dot = data2.incon.dot, axes = axes, warn = warn) - return(tempo.list) +# 3 lines below inactivated because I put that above +# if(is.null(categ[[i1]])){ +# data1[, "fake_categ"] <- paste0("Line_", i3) +# } +final.data.frame <- rbind(final.data.frame, tempo.data.frame, stringsAsFactors = TRUE) } - - -################ Import - - -######## fun_pack() #### check if R packages are present and import into the working environment - - -fun_pack <- function( - req.package, - load = FALSE, - lib.path = NULL -){ - # AIM - # check if the specified R packages are present in the computer and import them into the working environment - # ARGUMENTS - # req.package: character vector of package names to import - # load: logical. Load the package into the environement (using library())? Interesting if packages are not in default folders or for checking the functions names of packages using search() - # lib.path: optional character vector specifying the absolute pathways of the directories containing some of the listed packages in the req.package argument, if not in the default directories. Ignored if NULL - # RETURN - # nothing - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_pack(req.package = "nopackage") - # fun_pack(req.package = "ggplot2") - # fun_pack(req.package = "ggplot2", lib.path = "blablabla") - # DEBUGGING - # req.package = "ggplot2" ; lib.path = "C:/Program Files/R/R-3.5.1/library" - # req.package = "serpentine" ; lib.path = "C:/users/gael/appdata/roaming/python/python36/site-packages" - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = req.package, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = load, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - if(is.null(lib.path)){ - lib.path <- .libPaths() # .libPaths(new = lib.path) # or .libPaths(new = c(.libPaths(), lib.path)) - }else{ - .libPaths(new = sub(x = lib.path, pattern = "/$|\\\\$", replacement = "")) # .libPaths(new = ) add path to default path. BEWARE: .libPaths() does not support / at the end of a submitted path. Thus check and replace last / or \\ in path - lib.path <- .libPaths() - } - tempo <- NULL - for(i1 in 1:length(req.package)){ - if( ! req.package[i1] %in% rownames(utils::installed.packages(lib.loc = lib.path))){ - tempo <- c(tempo, req.package[i1]) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0( - "ERROR IN ", - function.name, - ": PACKAGE", - ifelse(length(tempo) == 1L, paste0("\n\n", tempo, "\n\n"), paste0("S\n", paste(tempo, collapse = "\n"), "\n")), - "MUST BE INSTALLED IN", - ifelse(length(lib.path) == 1L, "", " ONE OF THESE FOLDERS"), - ":\n", - paste(lib.path, collapse = "\n") - ) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(load == TRUE){ - for(i2 in 1:length(req.package)){ - suppressMessages(suppressWarnings(suppressPackageStartupMessages(library(req.package[i2], lib.loc = lib.path, quietly = TRUE, character.only = TRUE)))) - } - } +data1[[i1]] <- final.data.frame +geom[[i1]] <- "geom_line" +if(length(color[[i1]])== 1L){ +color[[i1]] <- rep(color[[i1]], length(unique(data1[[i1]][ , categ[[i1]]]))) +}else if(length(color[[i1]]) != length(unique(data1[[i1]][ , categ[[i1]]]))){ +tempo.cat <- paste0("ERROR IN ", function.name, " geom_hline AND geom_vline CONVERSION TO FIT THE XLIM AND YLIM LIMITS OF THE DATA: ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST HAVE THE LENGTH OF LEVELS OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), "\nHERE IT IS COLOR LENGTH ", length(color[[i1]]), " VERSUS CATEG LEVELS LENGTH ", length(unique(data1[[i1]][, categ[[i1]]]))) +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) } - - -######## fun_python_pack() #### check if python packages are present - - -fun_python_pack <- function( - req.package, - python.exec.path = NULL, - lib.path = NULL, - R.lib.path = NULL -){ - # AIM - # check if the specified python packages are present in the computer (no import) - # WARNINGS - # for python 3.7. Previous versions return an error "Error in sys$stdout$flush() : attempt to apply non-function" - # ARGUMENTS - # req.package: character vector of package names to import - # python.exec.path: optional character vector specifying the absolute pathways of the executable python file to use (associated to the packages to use). If NULL, the reticulate::import_from_path() function used in fun_python_pack() seeks for an available version of python.exe, and then uses python_config(python_version, required_module, python_versions). But might not be the correct one for the lib.path parameter specified. Thus, it is recommanded to do not leave NULL, notably when using computing clusters - # lib.path: optional character vector specifying the absolute pathways of the directories containing some of the listed packages in the req.package argument, if not in the default directories - # R.lib.path: absolute path of the reticulate packages, if not in the default folders - # RETURN - # nothing - # REQUIRED PACKAGES - # reticulate - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # fun_pack() - # EXAMPLES - # example of error message - # fun_python_pack(req.package = "nopackage") - # example without error message (require the installation of the python serpentine package from https://github.com/koszullab/serpentine - # fun_python_pack(req.package = "serpentine", python.exec.path = "C:/ProgramData/Anaconda3/python.exe", lib.path = "c:/programdata/anaconda3/lib/site-packages/") - # another example of error message - # fun_python_pack(req.package = "serpentine", lib.path = "blablabla") - # DEBUGGING - # req.package = "serpentine" ; python.exec.path = "C:/ProgramData/Anaconda3/python.exe" ; lib.path = "c:/programdata/anaconda3/lib/site-packages/" ; R.lib.path = NULL - # req.package = "bad" ; lib.path = NULL ; R.lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(utils::find("fun_pack", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_pack() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = req.package, class = "character", fun.name = function.name) ; eval(ee) - if( ! is.null(python.exec.path)){ - tempo <- fun_check(data = python.exec.path, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(file.exists(python.exec.path))){ # separation to avoid the problem of tempo$problem == FALSE and python.exec.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": FILE PATH INDICATED IN THE python.exec.path ARGUMENT DOES NOT EXISTS:\n", paste(python.exec.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if( ! is.null(R.lib.path)){ - tempo <- fun_check(data = R.lib.path, class = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(R.lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and R.lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE R.lib.path ARGUMENT DOES NOT EXISTS:\n", paste(R.lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # package checking - fun_pack(req.package = "reticulate", lib.path = R.lib.path) - # end package checking - # main code - if(is.null(python.exec.path)){ - python.exec.path <- reticulate::py_run_string(" -import sys ; -path_lib = sys.path -") # python string - python.exec.path <- python.exec.path$path_lib - } - if(is.null(lib.path)){ - lib.path <- reticulate::py_run_string(" -import sys ; -path_lib = sys.path -") # python string - lib.path <- lib.path$path_lib - } - reticulate::use_python(Sys.which(python.exec.path), required = TRUE) # required to avoid the use of erratic python exec by reticulate::import_from_path() - for(i1 in 1:length(req.package)){ - tempo.try <- vector("list", length = length(lib.path)) - for(i2 in 1:length(lib.path)){ - tempo.try[[i2]] <- suppressWarnings(try(reticulate::import_from_path(req.package[i1], path = lib.path[i2]), silent = TRUE)) - tempo.try[[i2]] <- suppressWarnings(try(reticulate::import_from_path(req.package[i1], path = lib.path[i2]), silent = TRUE)) # done twice to avoid the error message about flushing present the first time but not the second time. see https://stackoverflow.com/questions/57357001/reticulate-1-13-error-in-sysstdoutflush-attempt-to-apply-non-function - } - if(all(sapply(tempo.try, FUN = grepl, pattern = "[Ee]rror"))){ - print(tempo.try) - tempo.cat <- paste0("ERROR IN ", function.name, ": PACKAGE ", req.package[i1], " MUST BE INSTALLED IN THE MENTIONNED DIRECTORY:\n", paste(lib.path, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } # else{ - # suppressMessages(suppressWarnings(suppressPackageStartupMessages(assign(req.package[i1], reticulate::import(req.package[i1]))))) # not required because try() already evaluates - # } - } } - - -################ Print / Exporting results (text & tables) - - -######## fun_report() #### print string or data object into output file - - -fun_report <- function( - data, - output = "results.txt", - path = "C:/Users/Gael/Desktop/", - overwrite = FALSE, - rownames.kept = FALSE, - vector.cat = FALSE, - noquote = TRUE, - sep = 2 -){ - # AIM - # log file function: print a character string or a data object into a same output file - # ARGUMENTS - # data: object to print in the output file. If NULL, nothing is done, with no warning - # output: name of the output file - # path: location of the output file - # overwrite: (logical) if output file already exists, defines if the printing is appended (default FALSE) or if the output file content is erased before printing (TRUE) - # rownames.kept: (logical) defines whether row names have to be removed or not in small tables (less than length.rows rows) - # vector.cat (logical). If TRUE print a vector of length > 1 using cat() instead of capture.output(). Otherwise (default FALSE) the opposite - # noquote: (logical). If TRUE no quote are present for the characters - # sep: number of separating lines after printed data (must be integer) - # RETURN - # nothing - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_report() - # fun_report(data = 1:3, output = "results.txt", path = "C:/Users/Gael/Desktop", overwrite = TRUE, rownames.kept = FALSE, vector.cat = FALSE, noquote = FALSE, sep = 2) - # DEBUGGING - # data = 1:3 ; output = "results.txt" ; path = "C:/Users/Gael/Desktop" ; overwrite = TRUE ; rownames.kept = FALSE ; vector.cat = FALSE ; noquote = FALSE ; sep = 2 # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # argument checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = output, class = "character", length = 1, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & output == ""){ - tempo.cat <- paste0("ERROR IN ", function.name, ": output ARGUMENT AS \"\" DOES NOT CORRESPOND TO A VALID FILE NAME") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo <- fun_check(data = path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": path ARGUMENT DOES NOT CORRESPOND TO EXISTING DIRECTORY\n", paste(path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = overwrite, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = rownames.kept, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = vector.cat, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = noquote, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = sep, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # the 4 next lines are inactivated but kept because at a time, I might have a problem with data (solved with data = NULL). These 4 lines are just to know how to detect a missing argument. Important here because if data is not provided, print the code of the data function - # arg.user.list <- as.list(match.call(expand.dots = FALSE))[-1] # recover all the arguments provided by the function user (excluding the argument with defaults values not provided by the user. Thus, it is really the list indicated by the user) - # default.arg.list <- formals(fun = sys.function(sys.parent())) # list of all the arguments of the function with their default values (not the values of the user !). It seems that ls() as first line of the function provide the names of the arguments (empty, called, etc., or not) - # arg.without.default.value <- sapply(default.arg.list, is.symbol) & sapply(sapply(default.arg.list, as.character), identical, "") # logical to detect argument without default values (these are typeof "symbol" and class "name" and empty character - # if( ! all(names(default.arg.list)[arg.without.default.value] %in% names(arg.user.list))){ # test that the arguments with no null values are provided by the user - # tempo.cat <- paste0("ERROR IN ", function.name, ": VALUE REQUIRED FOR THESE ARGUMENTS WITH NO DEFAULTS VALUES: ", paste(names(default.arg.list)[arg.without.default.value][ ! names(default.arg.list)[arg.without.default.value] %in% names(arg.user.list)], collapse = " ")) - # stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - # } - # end argument checking - # main code - if( ! is.null(data)){ - if(all(class(data) == "data.frame") | all(class(data) == "table") | all(class(data) %in% c("matrix", "array"))){ # before R4.0.0, it was all(class(data) %in% c("matrix", "data.frame", "table")) - if(rownames.kept == FALSE & all(class(data) == "data.frame") & nrow(data) != 0 & nrow(data) <= 4){ # for data frames with nrows <= 4 - rownames.output.tables <- "" - length.rows <- nrow(data) - for(i in 1:length.rows){ # replace the rownames of the first 4 rows by increasing number of spaces (because identical row names not allowed in data frames). This method cannot be extended to more rows as the printed data frame is shifted on the right because of "big empty rownames" - rownames.output.tables <- c(rownames.output.tables, paste0(rownames.output.tables[i]," ", collapse="")) - } - row.names(data) <- rownames.output.tables[1:length.rows] - }else if(rownames.kept == FALSE & (all(class(data) == "table") | all(class(data) %in% c("matrix", "array")))){ # before R4.0.0, it was & all(class(data) %in% c("matrix", "table")) - rownames(data) <- rep("", nrow(data)) # identical row names allowed in matrices and tables - } - if(noquote == TRUE){ - utils::capture.output(noquote(data), file=paste0(path, "/", output), append = ! overwrite) - }else{ - utils::capture.output(data, file=paste0(path, "/", output), append = ! overwrite) - } - }else if(is.vector(data) & all(class(data) != "list") & (length(data) == 1L | vector.cat == TRUE)){ - if(noquote == TRUE){ - cat(noquote(data), file= paste0(path, "/", output), append = ! overwrite) - }else{ - cat(data, file= paste0(path, "/", output), append = ! overwrite) - } - }else if(all(base::mode(data) == "character")){ # characters (array, list, factor or vector with vector.cat = FALSE) - if(noquote == TRUE){ - utils::capture.output(noquote(data), file=paste0(path, "/", output), append = ! overwrite) - }else{ - utils::capture.output(data, file=paste0(path, "/", output), append = ! overwrite) - } - }else{ # other object (S4 for instance, which do not like noquote() - utils::capture.output(data, file=paste0(path, "/", output), append = ! overwrite) - } - sep.final <- paste0(rep("\n", sep), collapse = "") - write(sep.final, file= paste0(path, "/", output), append = TRUE) # add a sep - } } +# end conversion of geom_hline and geom_vline -######## fun_get_message() #### return error/warning/other messages of an expression (that can be exported) -fun_get_message <- function( - data, - kind = "error", - header = TRUE, - print.no = FALSE, - text = NULL, - env = NULL -){ - # AIM - # evaluate an instruction written between "" and return the first of the error, or warning or standard (non error non warning) messages if ever exist - # using argument print.no = FALSE, return NULL if no message, which is convenient in some cases - # WARNINGS - # Only the first message is returned - # Always use the env argument when fun_get_message() is used inside functions - # The function does not prevent printing if print() is used inside the instruction tested. To prevent that, use tempo <- capture.output(error <- fun_get_message(data = "fun_check(data = 'a', class = mean, neg.values = FALSE, print = TRUE)")). The return of fun_get_message() is assigned into error and the printed messages are captured by capture.output() and assigned into tempo. See the examples - # ARGUMENTS - # data: character string to evaluate - # kind: character string. Either "error" to get error messages, or "warning" to get warning messages, or "message" to get non error and non warning messages - # header: logical. Add a header in the returned message? - # print.no: logical. Print a message saying that no message reported? - # text: character string added to the output message (even if no message exists and print.no is TRUE). Inactivated if header is FALSE - # env: the name of an existing environment. NULL if not required - # RETURN - # the message or NULL if no message and print.no is FALSE - # REQUIRED PACKAGES - # none - # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION - # fun_check() - # EXAMPLES - # fun_get_message(data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)", kind = "error", print.no = TRUE, text = "IN A") - # fun_get_message(data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)", kind = "warning", print.no = TRUE, text = "IN A") - # fun_get_message(data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)", kind = "message", print.no = TRUE, text = "IN A") - # fun_get_message(data = "wilcox.test()", kind = "error", print.no = TRUE, text = "IN A") - # fun_get_message(data = "sum(1)", kind = "error", print.no = TRUE, text = "IN A") - # fun_get_message(data = "message('ahah')", kind = "error", print.no = TRUE, text = "IN A") - # fun_get_message(data = "message('ahah')", kind = "message", print.no = TRUE, text = "IN A") - # fun_get_message(data = "ggplot2::ggplot(data = data.frame(X = 1:10, stringsAsFactors = TRUE), mapping = ggplot2::aes(x = X)) + ggplot2::geom_histogram()", kind = "message", print.no = TRUE, text = "IN FUNCTION 1") - # set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; fun_get_message(data = 'fun_gg_boxplot(data = obs1, y = "Time", categ = "Group1")', kind = "message", print.no = TRUE, text = "IN FUNCTION 1") - # DEBUGGING - # data = "wilcox.test(c(1,1,3), c(1, 2, 4), paired = TRUE)" ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging - # data = "sum(1)" ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging - # set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Group1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; data = 'fun_gg_boxplot(data1 = obs1, y = "Time", categ = "Group1")' ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging - # data = "message('ahah')" ; kind = "error" ; header = TRUE ; print.no = TRUE ; text = "IN A" ; env = NULL - # data = 'ggplot2::ggplot(data = data.frame(X = "a", stringsAsFactors = TRUE), mapping = ggplot2::aes(x = X)) + ggplot2::geom_histogram()' ; kind = "message" ; header = TRUE ; print.no = FALSE ; text = NULL # for function debugging - # data = 'ggplot2::ggplot(data = data.frame(X = "a", stringsAsFactors = TRUE), mapping = ggplot2::aes(x = X)) + ggplot2::geom_histogram()' ; kind = "warning" ; header = TRUE ; print.no = FALSE ; text = NULL # for function debugging - # data = "emmeans::emmeans(object = emm.rg, specs = contrast.var)" ; kind = "message" ; header = TRUE ; print.no = FALSE ; text = NULL ; env = NULL # for function debugging - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - # end function name - # required function checking - if(length(utils::find("fun_check", mode = "function")) == 0L){ - tempo.cat <- paste0("ERROR IN ", function.name, ": REQUIRED fun_check() FUNCTION IS MISSING IN THE R ENVIRONMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # no need to use reserved words to avoid bugs, because it is local, and exists("tempo.warning", inherit = FALSE), never use the scope - # argument checking - # argument checking with fun_check() - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data, class = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = kind, options = c("error", "warning", "message"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = print.no, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = header, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(text)){ - tempo <- fun_check(data = text, class = "character", length = 1, fun.name = function.name) ; eval(ee) - } - if( ! is.null(env)){ - tempo <- fun_check(data = env, class = "environment", fun.name = function.name) ; eval(ee) # - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # end argument checking with fun_check() - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument checking - # main code - pdf(file = NULL) # send plots into a NULL file, no pdf file created - window.nb <- dev.cur() - invisible(dev.set(window.nb)) - # last warning cannot be used because suppressWarnings() does not modify last.warning present in the base evironment (created at first warning in a new R session), or warnings() # to reset the warning history : unlockBinding("last.warning", baseenv()) ; assign("last.warning", NULL, envir = baseenv()) - output <- NULL - tempo.error <- try(suppressMessages(suppressWarnings(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env}))), silent = TRUE) # get error message, not warning or messages - if(any(class(tempo.error) %in% c("gg", "ggplot"))){ - tempo.error <- try(suppressMessages(suppressWarnings(ggplot2::ggplot_build(tempo.error))), silent = TRUE)[1] - } - if(exists("tempo.error", inherit = FALSE) == TRUE){ # inherit = FALSE avoid the portee lexical and thus the declared word - if( ! all(class(tempo.error) == "try-error")){ # deal with NULL and S4 objects. Old code: ! (all(class(tempo.error) == "try-error") & any(grepl(x = tempo.error, pattern = "^Error|^error|^ERROR"))) but problem with S4 objects. Old code : if((length(tempo.error) > 0 & ! any(grepl(x = tempo.error, pattern = "^Error|^error|^ERROR"))) | (length(tempo.error) == 0) ){ but problem when tempo.error is a list but added this did not work: | ! all(class(tempo.error) == "character") - tempo.error <- NULL - } - }else{ - tempo.error <- NULL - } - if(kind == "error" & ! is.null(tempo.error)){ # - if(header == TRUE){ - tempo.error[1] <- gsub(x = tempo.error[1], pattern = "^Error i|^error i|^ERROR I", replacement = "I") - output <- paste0("ERROR MESSAGE REPORTED", ifelse(is.null(text), "", " "), text, ":\n", tempo.error[1]) # - }else{ - output <- tempo.error[1] # - } - }else if(kind == "error" & is.null(tempo.error) & print.no == TRUE){ - output <- paste0("NO ERROR MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) - }else if(kind != "error" & ( ! is.null(tempo.error)) & print.no == TRUE){ - output <- paste0("NO ", ifelse(kind == "warning", "WARNING", "STANDARD (NON ERROR AND NON WARNING)"), " MESSAGE BECAUSE OF ERROR MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) - }else if(is.null(tempo.error)){ - fun.warning.capture <- function(expr){ - # from demo(error.catching) typed in the R console, coming from ?tryCatch - # see also http://mazamascience.com/WorkingWithData/?p=912 - # return a character string or NULL - # expr <- wilcox.test.default(c(1, 1, 3), c(1, 2, 4), paired = TRUE) - W <- NULL - w.handler <- function(w){ # warning handler - W <<- w # send to the above env, i.e., the inside of the fun.warning.capture function - invokeRestart("muffleWarning") # here w.handler() muffles all the warnings. See http://romainfrancois.blog.free.fr/index.php?post/2009/05/20/Disable-specific-warnings to muffle specific warnings and print others - } - output <- list( - value = suppressMessages(withCallingHandlers(tryCatch(expr, error = function(e){e}), warning = w.handler)), # BEWARE: w.handler is a function written without (), like in other functions with FUN argument - warning = W # processed by w.handler() - ) - return(if(is.null(output$warning)){NULL}else{as.character(output$warning)}) - } - tempo.warn <- fun.warning.capture(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env})) - # warn.options.ini <- options()$warn ; options(warn = 1) ; tempo.warn <- utils::capture.output({tempo <- suppressMessages(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env}))}, type = "message") ; options(warn = warn.options.ini) # this recover warnings not messages and not errors but does not work in all enviroments - tempo.message <- utils::capture.output({ - tempo <- suppressMessages(suppressWarnings(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env}))) - if(any(class(tempo) %in% c("gg", "ggplot"))){ - tempo <- ggplot2::ggplot_build(tempo) - }else{ - tempo <- suppressWarnings(eval(parse(text = data), envir = if(is.null(env)){parent.frame()}else{env})) - } - }, type = "message") # recover messages not warnings and not errors - if(kind == "warning" & ! is.null(tempo.warn)){ - if(length(tempo.warn) > 0){ # to avoid character(0) - if( ! any(sapply(tempo.warn, FUN = "grepl", pattern = "() FUNCTION:$"))){ - tempo.warn <- paste(unique(tempo.warn), collapse = "\n") # if FALSE, means that the tested data is a special function. If TRUE, means that the data is a standard function. In that case, the output of capture.output() is two strings per warning messages: if several warning messages -> identical first string, which is removed in next messages by unique() - }else{ - tempo.warn <- paste(tempo.warn, collapse = "\n") - } - if(header == TRUE){ - if(any(grepl(x = tempo.warn[[1]], pattern = "^simpleWarning i"))){ - tempo.warn[[1]] <- gsub(x = tempo.warn[[1]], pattern = "^Warning i", replacement = "I") - } - if(any(grepl(x = tempo.warn[[1]], pattern = "^Warning i"))){ - tempo.warn[[1]] <- gsub(x = tempo.warn[[1]], pattern = "^Warning i", replacement = "I") - } - output <- paste0("WARNING MESSAGE REPORTED", ifelse(is.null(text), "", " "), text, ":\n", tempo.warn) # - }else{ - output <- tempo.warn # - } - }else{ - if(print.no == TRUE){ - output <- paste0("NO WARNING MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) - } # no need else{} here because output is already NULL at first - } - }else if(kind == "warning" & is.null(tempo.warn) & print.no == TRUE){ - output <- paste0("NO WARNING MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) - }else if(kind == "message" & exists("tempo.message", inherit = FALSE) == TRUE){ # inherit = FALSE avoid the portee lexical and thus the declared word - if(length(tempo.message) > 0){ # if something is returned by capture.ouptput() (only in this env) with a length more than 1 - if(header == TRUE){ - output <- paste0("STANDARD (NON ERROR AND NON WARNING) MESSAGE REPORTED", ifelse(is.null(text), "", " "), text, ":\n", tempo.message) # - }else{ - output <- tempo.message # - } - }else{ - if(print.no == TRUE){ - output <- paste0("NO STANDARD (NON ERROR AND NON WARNING) MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) - } # no need else{} here because output is already NULL at first - } - }else if(kind == "message" & exists("tempo.message", inherit = FALSE) == FALSE & print.no == TRUE){ - output <- paste0("NO STANDARD (NON ERROR AND NON WARNING) MESSAGE REPORTED", ifelse(is.null(text), "", " "), text) - } # no need else{} here because output is already NULL at first - } # no need else{} here because output is already NULL at first - invisible(dev.off(window.nb)) # end send plots into a NULL file - return(output) # do not use cat() because the idea is to reuse the message +# kind of geom_point (vectorial or raster) +scatter.kind <- vector("list", length = length(data1)) # list of same length as data1, that will be used to use either ggplot2::geom_point() (vectorial dot layer) or fun_gg_point_rast() (raster dot layer) +fix.ratio <- FALSE +if(is.null(raster.threshold)){ +if(raster == TRUE){ +scatter.kind[] <- "fun_gg_point_rast" # not important to fill everything: will be only used when geom == "geom_point" +fix.ratio <- TRUE +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") RASTER PLOT GENERATED -> ASPECT RATIO OF THE PLOT REGION SET BY THE raster.ratio ARGUMENT (", fun_round(raster.ratio, 2), ") TO AVOID A BUG OF ELLIPSOID DOT DRAWING") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +}else{ +scatter.kind[] <- "ggplot2::geom_point" } +}else{ +for(i2 in 1:length(data1)){ +if(geom[[i2]] == "geom_point"){ +if(nrow(data1[[i2]]) <= raster.threshold){ +scatter.kind[[i2]] <- "ggplot2::geom_point" +}else{ +scatter.kind[[i2]] <- "fun_gg_point_rast" +fix.ratio <- TRUE +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i2, " OF data1 ARGUMENT")), " LAYER AS RASTER (NOT VECTORIAL)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +} +if(any(unlist(scatter.kind) == "fun_gg_point_rast")){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") RASTER PLOT GENERATED -> ASPECT RATIO OF THE PLOT REGION SET BY THE raster.ratio ARGUMENT (", fun_round(raster.ratio, 2), ") TO AVOID A BUG OF ELLIPSOID DOT DRAWING") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +} +# end kind of geom_point (vectorial or raster) + + + + +# no need loop part +coord.names <- NULL +tempo.gg.name <- "gg.indiv.plot." +tempo.gg.count <- 0 +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::xlab(if(is.null(x.lab)){x[[1]]}else{x.lab})) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ylab(if(is.null(y.lab)){y[[1]]}else{y.lab})) +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) +# text angle management +x.tempo.just <- fun_gg_just(angle = x.text.angle, pos = "bottom", kind = "axis") +y.tempo.just <- fun_gg_just(angle = y.text.angle, pos = "left", kind = "axis") +# end text angle management +add.check <- TRUE +if( ! is.null(add)){ # if add is NULL, then = 0 +if(grepl(pattern = "ggplot2::theme", add) == TRUE){ +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT\n-> INTERNAL GGPLOT2 THEME FUNCTIONS theme() AND theme_classic() HAVE BEEN INACTIVATED, TO BE USED BY THE USER\n-> article ARGUMENT WILL BE IGNORED\nIT IS RECOMMENDED TO USE \"+ theme(aspect.ratio = raster.ratio)\" IF RASTER MODE IS ACTIVATED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +add.check <- FALSE +} +} +if(add.check == TRUE & article == TRUE){ +# WARNING: not possible to add several times theme(). NO message but the last one overwrites the others +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_classic(base_size = text.size)) +if(grid == TRUE){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( +text = ggplot2::element_text(size = text.size), +plot.title = ggplot2::element_text(size = title.text.size), # stronger than text +legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend +line = ggplot2::element_line(size = 0.5), +axis.line.y.left = ggplot2::element_line(colour = "black"), # draw lines for the y axis +axis.line.x.bottom = ggplot2::element_line(colour = "black"), # draw lines for the x axis +panel.grid.major.x = ggplot2::element_line(colour = "grey85", size = 0.75), +panel.grid.minor.x = ggplot2::element_line(colour = "grey90", size = 0.25), +panel.grid.major.y = ggplot2::element_line(colour = "grey85", size = 0.75), +panel.grid.minor.y = ggplot2::element_line(colour = "grey90", size = 0.25), +axis.text.x = ggplot2::element_text(angle = x.tempo.just$angle, hjust = x.tempo.just$hjust, vjust = x.tempo.just$vjust), +axis.text.y = ggplot2::element_text(angle = y.tempo.just$angle, hjust = y.tempo.just$hjust, vjust = y.tempo.just$vjust), +aspect.ratio = if(fix.ratio == TRUE){raster.ratio}else{NULL} # for raster +)) +}else{ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( +text = ggplot2::element_text(size = text.size), +plot.title = ggplot2::element_text(size = title.text.size), # stronger than text +line = ggplot2::element_line(size = 0.5), +legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend +axis.line.y.left = ggplot2::element_line(colour = "black"), +axis.line.x.bottom = ggplot2::element_line(colour = "black"), +axis.text.x = ggplot2::element_text(angle = x.tempo.just$angle, hjust = x.tempo.just$hjust, vjust = x.tempo.just$vjust), +axis.text.y = ggplot2::element_text(angle = y.tempo.just$angle, hjust = y.tempo.just$hjust, vjust = y.tempo.just$vjust), +aspect.ratio = if(fix.ratio == TRUE){raster.ratio}else{NULL} # for raster +)) +} +}else if(add.check == TRUE & article == FALSE){ +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( +text = ggplot2::element_text(size = text.size), +plot.title = ggplot2::element_text(size = title.text.size), # stronger than text +line = ggplot2::element_line(size = 0.5), +legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend +panel.background = ggplot2::element_rect(fill = "grey95"), +axis.line.y.left = ggplot2::element_line(colour = "black"), +axis.line.x.bottom = ggplot2::element_line(colour = "black"), +panel.grid.major.x = ggplot2::element_line(colour = "grey85", size = 0.75), +panel.grid.minor.x = ggplot2::element_line(colour = "grey90", size = 0.25), +panel.grid.major.y = ggplot2::element_line(colour = "grey85", size = 0.75), +panel.grid.minor.y = ggplot2::element_line(colour = "grey90", size = 0.25), +strip.background = ggplot2::element_rect(fill = "white", colour = "black"), +axis.text.x = ggplot2::element_text(angle = x.tempo.just$angle, hjust = x.tempo.just$hjust, vjust = x.tempo.just$vjust), +axis.text.y = ggplot2::element_text(angle = y.tempo.just$angle, hjust = y.tempo.just$hjust, vjust = y.tempo.just$vjust), +aspect.ratio = if(fix.ratio == TRUE){raster.ratio}else{NULL} # for raster +# do not work -> legend.position = "none" # to remove the legend completely: https://www.datanovia.com/en/blog/how-to-remove-legend-from-a-ggplot/ +)) +} +# end no need loop part + + +# loop part +point.count <- 0 +line.count <- 0 +lg.order <- vector(mode = "list", length = 6) # order of the legend +lg.order <- lapply(lg.order, as.numeric) # order of the legend +lg.color <- vector(mode = "list", length = 6) # color of the legend +lg.dot.shape <- vector(mode = "list", length = 6) # etc. +lg.dot.size <- vector(mode = "list", length = 6) # etc. +lg.dot.size <- lapply(lg.dot.size, as.numeric) # etc. +lg.dot.border.size <- vector(mode = "list", length = 6) # etc. +lg.dot.border.size <- lapply(lg.dot.border.size, as.numeric) # etc. +lg.dot.border.color <- vector(mode = "list", length = 6) # etc. +lg.line.size <- vector(mode = "list", length = 6) # etc. +lg.line.size <- lapply(lg.line.size, as.numeric) # etc. +lg.line.type <- vector(mode = "list", length = 6) # etc. +lg.alpha <- vector(mode = "list", length = 6) # etc. +lg.alpha <- lapply(lg.alpha, as.numeric) # etc. +for(i1 in 1:length(data1)){ +if(geom[[i1]] == "geom_point"){ +point.count <- point.count + 1 +if(point.count== 1L){ +fin.lg.disp[[1]] <- legend.disp[[point.count + line.count]] +lg.order[[1]] <- point.count + line.count +lg.color[[1]] <- color[[i1]] # if color == NULL -> NULL +lg.dot.shape[[1]] <- dot.shape[[i1]] +lg.dot.size[[1]] <- dot.size[[i1]] +lg.dot.border.size[[1]] <- dot.border.size[[i1]] +lg.dot.border.color[[1]] <- dot.border.color[[i1]] # if dot.border.color == NULL -> NULL +if(plot == TRUE & fin.lg.disp[[1]] == TRUE & dot.shape[[1]] %in% 0:14 & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE DOTS (DOT LAYER NUMBER ", point.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +lg.alpha[[1]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf +}else{ +lg.alpha[[1]] <- alpha[[i1]] +} +class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) +for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same +tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = scatter.kind[[i1]]))(data = tempo.data.frame, mapping = ggplot2::aes_string(x = x[[i1]], y = y[[i1]], fill = categ[[i1]]), shape = dot.shape[[i1]], size = dot.size[[i1]], stroke = dot.border.size[[i1]], color = if(dot.shape[[i1]] %in% 21:24 & ! is.null(dot.border.color)){dot.border.color[[i1]]}else{color[[i1]][i5]}, alpha = alpha[[i1]], show.legend = if(i5== 1L){TRUE}else{FALSE})) # WARNING: a single color allowed for color argument outside aesthetic, but here a single color for border --> loop could be inactivated but kept for commodity # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency +coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_manual(name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = as.character(color[[i1]]), breaks = class.categ)) # values are the values of fill, breaks reorder the classes according to class.categ in the legend, order argument of guide_legend determines the order of the different aesthetics in the legend (not order of classes). See guide_legend settings of scale_..._manual below +} +if(point.count== 2L){ +fin.lg.disp[[2]] <- legend.disp[[point.count + line.count]] +lg.order[[2]] <- point.count + line.count +lg.color[[2]] <- color[[i1]] # if color == NULL -> NULL +lg.dot.shape[[2]] <- dot.shape[[i1]] +lg.dot.size[[2]] <- dot.size[[i1]] +lg.dot.border.size[[2]] <- dot.border.size[[i1]] +lg.dot.border.color[[2]] <- dot.border.color[[i1]] # if dot.border.color == NULL -> NULL +if(plot == TRUE & fin.lg.disp[[2]] == TRUE & dot.shape[[2]] %in% 0:14 & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE DOTS (DOT LAYER NUMBER ", point.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +lg.alpha[[2]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf +}else{ +lg.alpha[[2]] <- alpha[[i1]] +} +class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) +for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same +tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = scatter.kind[[i1]]))(data = tempo.data.frame, mapping = ggplot2::aes_string(x = x[[i1]], y = y[[i1]], shape = categ[[i1]]), size = dot.size[[i1]], stroke = dot.border.size[[i1]], fill = color[[i1]][i5], color = if(dot.shape[[i1]] %in% 21:24 & ! is.null(dot.border.color)){dot.border.color[[i1]]}else{color[[i1]][i5]}, alpha = alpha[[i1]], show.legend = FALSE)) # WARNING: a single color allowed for fill argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency +coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_shape_manual(name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(dot.shape[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of shape, breaks reorder the classes according to class.categ in the legend. See guide_legend settings of scale_..._manual below - - - -# Error: class order not good when a class is removed due to NA -# Error: line 136 in check 20201126 with add argument -# Solve this: sometimes error messages can be more than the max display (8170). Thus, check every paste0("ERROR IN ", function.name, and trunck the message if to big. In addition, add at the begining of the warning message that it is too long and see the $warn output for complete message. Add also this into fun_scatter -# add dot.shape ? See with available aesthetic layers -# rasterise: https://cran.r-project.org/web/packages/ggrastr/vignettes/Raster_geoms.html -# add horizontal argument and deal any conflict with vertical argument. Start with horizontal = NULL as default. If ! is.null() -> convert vertical if required - -fun_gg_boxplot <- function( - data1, - y, - categ, - categ.class.order = NULL, - categ.color = NULL, - box.legend.name = NULL, - box.fill = FALSE, - box.width = 0.5, - box.space = 0.1, - box.line.size = 0.75, - box.notch = FALSE, - box.alpha = 1, - box.mean = TRUE, - box.whisker.kind = "std", - box.whisker.width = 0, - dot.color = grey(0.25), - dot.categ = NULL, - dot.categ.class.order = NULL, - dot.legend.name = NULL, - dot.tidy = FALSE, - dot.tidy.bin.nb = 50, - dot.jitter = 0.5, - dot.seed = 2, - dot.size = 3, - dot.alpha = 0.5, - dot.border.size = 0.5, - dot.border.color = NULL, - x.lab = NULL, - x.angle = 0, - y.lab = NULL, - y.lim = NULL, - y.log = "no", - y.tick.nb = NULL, - y.second.tick.nb = 1, - y.include.zero = FALSE, - y.top.extra.margin = 0.05, - y.bottom.extra.margin = 0.05, - stat.pos = "top", - stat.mean = FALSE, - stat.size = 4, - stat.dist = 5, - stat.angle = 0, - vertical = TRUE, - text.size = 12, - title = "", - title.text.size = 8, - legend.show = TRUE, - legend.width = 0.5, - article = TRUE, - grid = FALSE, - add = NULL, - return = FALSE, - return.ggplot = FALSE, - return.gtable = TRUE, - plot = TRUE, - warn.print = FALSE, - lib.path = NULL -){ - # AIM - # Plot ggplot2 boxplots + dots + means - # For ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html - # WARNINGS - # Rows containing NA in data1[, c(y, categ)] will be removed before processing, with a warning (see below) - # Hinges are not computed like in the classical boxplot() function of R. See https://ggplot2.tidyverse.org/reference/geom_boxplot.html - # To have a single box, please create a factor column with a single class and specify the name of this column in the categ argument. For a single set of grouped boxes, create a factor column with a single class and specify this column in categ argument as first element (i.e., as categ1, knowing that categ2 must also be specified in this situation). See categ argument below - # The dot.alpha argument can alter the display of the color boxes when using pdf output - # Size arguments (box.line.size, dot.size, dot.border.size, stat.size, text.size and title.text.size) are in mm. See Hadley comment in https://stackoverflow.com/questions/17311917/ggplot2-the-unit-of-size. See also http://sape.inf.usi.ch/quick-reference/ggplot2/size). Unit object are not accepted, but conversion can be used (e.g., grid::convertUnit(grid::unit(0.2, "inches"), "mm", valueOnly = TRUE)) - # Display seems to be done twice on Windows devices (like a blink). However, no double plots on pdf devices. Thus, the blink remains mysterious - # To remove boxes and have only dots, use box.alpha = 0 - # ARGUMENTS - # data1: data frame containing one column of quantitative values (see the y argument below) and one or two columns of categories (see the categ argument below). Duplicated column names are not allowed - # y: character string of the data1 column name for y-axis (column containing numeric values). Numeric values will be split according to the classes of the column names indicated in the categ argument to generate the boxes and will also be used to plot the dots - # categ: vector of character strings of the data1 column name for categories (column of characters or factors). Must be either one or two column names. If a single column name (further referred to as categ1), then one box per class of categ1. If two column names (further referred to as categ1 and categ2), then one box per class of categ2, which form a group of boxes in each class of categ1. WARNING: no empty classes allowed. To have a single box, create a factor column with a single class and specify the name of this column in the categ argument (here, no categ2 in categ argument). For a single set of grouped boxes, create a factor column with a single class and specify this column in categ argument as first element (i.e., as categ1), in addition to the already used category (as categ2 in this situation) - # categ.class.order: list indicating the order of the classes of categ1 and categ2 represented on the boxplot (the first compartment for categ1 and and the second for categ2). If categ.class.order == NULL, classes are represented according to the alphabetical order. Some compartments can be NULL and others not. See the categ argument for categ1 and categ2 description - # categ.color: vector of color character string for box frames (see the categ argument for categ1 and categ2 description) - # If categ.color == NULL, default colors of ggplot2, whatever categ1 and categ2 - # If categ.color is non-null and only categ1 in categ argument, categ.color can be either: - # (1) a single color string. All the boxes will have this color, whatever the number of classes of categ1 - # (2) a vector of string colors, one for each class of categ1. Each color will be associated according to categ.class.order of categ1 - # (3) a vector or factor of string colors, like if it was one of the column of data1 data frame. WARNING: a single color per class of categ1 and a single class of categ1 per color must be respected - # Color functions, like grey(), hsv(), etc., are also accepted - # Positive integers are also accepted instead of character strings, as long as above rules about length are respected. Integers will be processed by fun_gg_palette() using the maximal integer value among all the integers in categ.color (see fun_gg_palette()) - # If categ.color is non-null and categ1 and categ2 are specified, all the rules described above will apply to categ2 instead of categ1 (colors will be determined for boxes inside a group of boxes) - # box.legend.name: character string of the legend title. If box.legend.name is NULL, then box.legend.name <- categ1 if only categ1 is present, and box.legend.name <- categ2 if categ1 and categ2 are present in the categ argument. Write "" if no legend required. See the categ argument for categ1 and categ2 description - # box.fill: logical. Fill the box? If TRUE, the categ.color argument will be used to generate filled boxplots (the box frames being black) as well as filled outlier dots (the dot border being controlled by the dot.border.color argument). If all the dots are plotted (argument dot.color other than NULL), they will be over the boxes. If FALSE, the categ.color argument will be used to color the box frames and the outlier dot borders. If all the dots are plotted, they will be beneath the boxes - # box.width: single numeric value (from 0 to 1) of width of either boxes or group of boxes - # When categ argument has a single categ1 element (i.e., separate boxes. See the categ argument for categ1 and categ2 description), then each class of categ1 is represented by a single box. In that case, box.width argument defines each box width, from 0 (no box width) to 1 (max box width), but also the space between boxes (the code uses 1 - box.width for the box spaces). Of note, xmin and xmax of the fun_gg_boxplot() output report the box boundaries (around x-axis unit 1, 2, 3, etc., for each box) - # When categ argument has a two categ1 and categ2 elements (i.e., grouped boxes), box.width argument defines the width allocated for each set of grouped boxes, from 0 (no group width) to 1 (max group width), but also the space between grouped boxes (the code uses 1 - box.width for the spaces). Of note, xmin and xmax of the fun_gg_boxplot() output report the box boundaries (around x-axis unit 1, 2, 3, etc., for each set of grouped box) - # box.space: single numeric value (from 0 to 1) indicating the box separation inside grouped boxes, when categ argument has a two categ1 and categ2 elements. 0 means no space and 1 means boxes shrunk to a vertical line. Ignored if categ argument has a single categ1 element - # box.line.size: single numeric value of line width of boxes and whiskers in mm - # box.notch: logical. Notched boxplot? It TRUE, display notched boxplot, notches corresponding approximately to the 95% confidence interval of the median (the notch interval is exactly 1.58 x Inter Quartile Range (IQR) / sqrt(n), with n the number of values that made the box). If notch intervals between two boxes do not overlap, it can be interpreted as significant median differences - # box.alpha: single numeric value (from 0 to 1) of box transparency (full transparent to full opaque, respectively). To remove boxplots, use box.alpha = 0 - # box.mean: logical. Add mean value? If TRUE, a diamond-shaped dot, with the horizontal diagonal corresponding to the mean value, is displayed over each boxplot - # box.whisker.kind: range of the whiskers. Either "no" (no whiskers), or "std" (length of each whisker equal to 1.5 x Inter Quartile Range (IQR)), or "max" (length of the whiskers up or down to the most distant dot) - # box.whisker.width: single numeric value (from 0 to 1) of the whisker width, with 0 meaning no whiskers and 1 meaning a width equal to the box width - # dot.color: vector of color character string ruling the dot colors and the dot display. See the example section below for easier understanding of the rules described here - # If NULL, no dots plotted - # If "same", the dots will have the same colors as the respective boxplots - # Otherwise, as in the rule (1), (2) or (3) described in the categ.color argument, except that in the possibility (3), the rule "a single color per class of categ and a single class of categ per color", does not have to be respected (for instance, each dot can have a different color). Colors will also depend on the dot.categ argument. If dot.categ is NULL, then colors will be applied to each class of the last column name specified in categ. If dot.categ is non-NULL, colors will be applied to each class of the column name specified in dot.categ. See examples - # dot.categ: optional single character string of a column name (further referred to as categ3) of the data1 argument. This column of data1 will be used to generate a legend for dots, in addition to the legend for boxes. See the dot.color argument for details about the way the legend is built using the two dot.categ and dot.color arguments. If NULL, no legend created and the colors of dots will depend on dot.color and categ arguments (as explained in the dot.color argument) - # dot.categ.class.order: optional vector of character strings indicating the order of the classes of categ3 (see the dot.categ argument). If dot.categ is non-NULL and dot.categ.class.order is NULL, classes are displayed in the legend according to the alphabetical order. Ignored if dot.categ is NULL - # dot.legend.name: optional character string of the legend title for categ3 (see the dot.categ argument). If dot.legend.name == NULL, dot.categ value is used (name of the column in data1). Write "" if no legend required. Ignored if dot.categ is NULL - # dot.tidy: logical. Nice dot spreading? If TRUE, use the geom_dotplot() function for a nice representation. WARNING: change the true quantitative coordinates of dots (i.e., y-axis values for vertical display) because of binning. Thus, the gain in aestheticism is associated with a loss in precision that can be very important. If FALSE, dots are randomly spread on the qualitative axis, using the dot.jitter argument (see below) keeping the true quantitative coordinates - # dot.tidy.bin.nb: positive integer indicating the number of bins (i.e., nb of separations) of the y.lim range. Each dot will then be put in one of the bin, with a diameter of the width of the bin. In other words, increase the number of bins to have smaller dots. Not considered if dot.tidy is FALSE - # dot.jitter: numeric value (from 0 to 1) of random dot horizontal dispersion (for vertical display), with 0 meaning no dispersion and 1 meaning dispersion in the corresponding box width interval. Not considered if dot.tidy is TRUE - # dot.seed: integer value that set the random seed. Using the same number will generate the same dot jittering. Write NULL to have different jittering each time the same instruction is run. Ignored if dot.tidy is TRUE - # dot.size: numeric value of dot diameter in mm. Not considered if dot.tidy is TRUE - # dot.alpha: numeric value (from 0 to 1) of dot transparency (full transparent to full opaque, respectively) - # dot.border.size: numeric value of border dot width in mm. Write zero for no dot border. If dot.tidy is TRUE, value 0 remove the border and other values leave the border without size control (geom_doplot() feature) - # dot.border.color: single character color string defining the color of the dot border (same color for all the dots, whatever their categories). If dot.border.color == NULL, the border color will be the same as the dot color. A single integer is also accepted instead of a character string, that will be processed by fun_gg_palette() - # x.lab: a character string or expression for x-axis legend. If NULL, character string of categ1 (see the categ argument for categ1 and categ2 description) - # x.angle: integer value of the text angle for the x-axis numbers, using the same rules as in ggplot2. Positive values for counterclockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Negative values for clockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. - # y.lab: a character string or expression for y-axis legend. If NULL, character string of the y argument - # y.lim: 2 numeric values indicating the range of the y-axis. Order matters (for inverted axis). If NULL, the range of the x column name of data1 will be used. - # y.log: either "no", "log2" (values in the y argument column of the data1 data frame will be log2 transformed and y-axis will be log2 scaled) or "log10" (values in the y argument column of the data1 data frame will be log10 transformed and y-axis will be log10 scaled). WARNING: not possible to have horizontal boxes with a log axis, due to a bug in ggplot2 (see https://github.com/tidyverse/ggplot2/issues/881) - # y.tick.nb: approximate number of desired values labeling the y-axis (i.e., main ticks, see the n argument of the the cute::fun_scale() function). If NULL and if y.log is "no", then the number of labeling values is set by ggplot2. If NULL and if y.log is "log2" or "log10", then the number of labeling values corresponds to all the exposant integers in the y.lim range (e.g., 10^1, 10^2 and 10^3, meaning 3 main ticks for y.lim = c(9, 1200)). WARNING: if non-NULL and if y.log is "log2" or "log10", labeling can be difficult to read (e.g., ..., 10^2, 10^2.5, 10^3, ...) - # y.second.tick.nb: number of desired secondary ticks between main ticks. Ignored if y.log is other than "no" (log scale plotted). Use argument return = TRUE and see $plot$y.second.tick.values to have the values associated to secondary ticks. IF NULL, no secondary ticks - # y.include.zero: logical. Does y.lim range include 0? Ignored if y.log is "log2" or "log10" - # y.top.extra.margin: single proportion (between 0 and 1) indicating if extra margins must be added to y.lim. If different from 0, add the range of the axis multiplied by y.top.extra.margin (e.g., abs(y.lim[2] - y.lim[1]) * y.top.extra.margin) to the top of y-axis - # y.bottom.extra.margin: idem as y.top.extra.margin but to the bottom of y-axis - # stat.pos: add the median number above the corresponding box. Either NULL (no number shown), "top" (at the top of the plot region) or "above" (above each box) - # stat.mean: logical. Display mean numbers instead of median numbers? Ignored if stat.pos is NULL - # stat.size: numeric value of the stat font size in mm. Ignored if stat.pos is NULL - # stat.dist: numeric value of the stat distance in percentage of the y-axis range (stat.dist = 5 means move the number displayed at 5% of the y-axis range). Ignored if stat.pos is NULL or "top" - # stat.angle: integer value of the angle of stat, using the same rules as in ggplot2. Positive values for counterclockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Negative values for clockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. - # vertical: logical. Vertical boxes? WARNING: will be automatically set to TRUE if y.log argument is other than "no". Indeed, not possible to have horizontal boxes with a log axis, due to a bug in ggplot2 (see https://github.com/tidyverse/ggplot2/issues/881) - # text.size: numeric value of the font size of the (1) axis numbers, (2) axis labels and (3) texts in the graphic legend (in mm) - # title: character string of the graph title - # title.text.size: numeric value of the title font size in mm - # legend.show: logical. Show legend? Not considered if categ argument is NULL, because this already generate no legend, excepted if legend.width argument is non-NULL. In that specific case (categ is NULL, legend.show is TRUE and legend.width is non-NULL), an empty legend space is created. This can be useful when desiring graphs of exactly the same width, whatever they have legends or not - # legend.width: single proportion (between 0 and 1) indicating the relative width of the legend sector (on the right of the plot) relative to the width of the plot. Value 1 means that the window device width is split in 2, half for the plot and half for the legend. Value 0 means no room for the legend, which will overlay the plot region. Write NULL to inactivate the legend sector. In such case, ggplot2 will manage the room required for the legend display, meaning that the width of the plotting region can vary between graphs, depending on the text in the legend - # article: logical. If TRUE, use an article theme (article like). If FALSE, use a classic related ggplot theme. Use the add argument (e.g., add = "+ggplot2::theme_classic()" for the exact classic ggplot theme - # grid: logical. Draw lines in the background to better read the box values? Not considered if article == FALSE (grid systematically present) - # add: character string allowing to add more ggplot2 features (dots, lines, themes, facet, etc.). Ignored if NULL - # WARNING: (1) the string must start with "+", (2) the string must finish with ")" and (3) each function must be preceded by "ggplot2::". Example: "+ ggplot2::coord_flip() + ggplot2::theme_bw()" - # If the character string contains the "ggplot2::theme" string, then the article argument of fun_gg_boxplot() (see above) is ignored with a warning. In addition, some arguments can be overwritten, like x.angle (check all the arguments) - # Handle the add argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions - # WARNING: the call of objects inside the quotes of add can lead to an error if the name of these objects are some of the fun_gg_boxplot() arguments. Indeed, the function will use the internal argument instead of the global environment object. Example article <- "a" in the working environment and add = '+ ggplot2::ggtitle(article)'. The risk here is to have TRUE as title. To solve this, use add = '+ ggplot2::ggtitle(get("article", envir = .GlobalEnv))' - # return: logical. Return the graph parameters? - # return.ggplot: logical. Return the ggplot object in the output list? Ignored if return argument is FALSE. WARNING: always assign the fun_gg_boxplot() function (e.g., a <- fun_gg_boxplot()) if return.ggplot argument is TRUE, otherwise, double plotting is performed. See $ggplot in the RETURN section below for more details - # return.gtable: logical. Return the ggplot object as gtable of grobs in the output list? Ignored if plot argument is FALSE. Indeed, the graph must be plotted to get the grobs dispositions. See $gtable in the RETURN section below for more details - # plot: logical. Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting - # warn.print: logical. Print warnings at the end of the execution? ? If FALSE, warning messages are never printed, but can still be recovered in the returned list. Some of the warning messages (those delivered by the internal ggplot2 functions) are not apparent when using the argument plot = FALSE - # lib.path: character string indicating the absolute path of the required packages (see below). if NULL, the function will use the R library default folders - # RETURN - # A boxplot if plot argument is TRUE - # A list of the graph info if return argument is TRUE: - # $data: the initial data with graphic information added - # $stat: the graphic statistics (mostly equivalent to ggplot_build()$data[[2]]) - # $removed.row.nb: which rows have been removed due to NA/Inf detection in y and categ columns (NULL if no row removed) - # $removed.rows: removed rows (NULL if no row removed) - # $plot: the graphic box and dot coordinates - # $dots: dot coordinates - # $main.box: coordinates of boxes - # $median: median coordinates - # $sup.whisker: coordinates of top whiskers (y for base and y.end for extremities) - # $inf.whisker: coordinates of bottom whiskers (y for base and y.end for extremities) - # $sup.whisker.edge: coordinates of top whisker edges (x and xend) - # $inf.whisker.edge: coordinates of bottom whisker edges(x and xend) - # $mean: diamond mean coordinates (only if box.mean argument is TRUE) - # $stat.pos: coordinates of stat numbers (only if stat.pos argument is not NULL) - # y.second.tick.positions: coordinates of secondary ticks (only if y.second.tick.nb argument is non-NULL or if y.log argument is different from "no") - # y.second.tick.values: values of secondary ticks. NULL except if y.second.tick.nb argument is non-NULL or if y.log argument is different from "no") - # $panel: the variable names used for the panels (NULL if no panels). WARNING: NA can be present according to ggplot2 upgrade to v3.3.0 - # $axes: the x-axis and y-axis info - # $warn: the warning messages. Use cat() for proper display. NULL if no warning. WARNING: warning messages delivered by the internal ggplot2 functions are not apparent when using the argument plot = FALSE - # $ggplot: ggplot object that can be used for reprint (use print(...$ggplot) or update (use ...$ggplot + ggplot2::...). NULL if return.ggplot argument is FALSE. Of note, a non-NULL $ggplot in the output list is sometimes annoying as the manipulation of this list prints the plot - # $gtable: gtable object that can be used for reprint (use gridExtra::grid.arrange(...$ggplot) or with additionnal grobs (see the grob decomposition in the examples). NULL if return.ggplot argument is FALSE. Contrary to $ggplot, a non-NULL $gtable in the output list is not annoying as the manipulation of this list does not print the plot - # REQUIRED PACKAGES - # ggplot2 - # gridExtra - # lemon (in case of use in the add argument) - # scales - # REQUIRED FUNCTIONS FROM THE cute PACKAGE - # fun_check() - # fun_comp_1d() - # fun_comp_2d() - # fun_gg_just() - # fun_gg_palette() - # fun_inter_ticks() - # fun_name_change() - # fun_pack() - # fun_round() - # fun_scale() - # EXAMPLE - # set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(20, 100, 10), rnorm(20, 200, 50), rnorm(20, 500, 60), rnorm(20, 100, 50)), Categ1 = rep(c("CAT", "DOG"), times = 40), Categ2 = rep(c("A", "B", "C", "D"), each = 20), Color1 = rep(c("coral", "lightblue"), times = 40), Color2 = rep(c("#9F2108", "#306100", "#007479", "#8500C0"), each = 20), stringsAsFactors = TRUE) ; set.seed(NULL) ; fun_gg_boxplot(data1 = obs1, y = "Time", categ = "Categ1") - # see http - # DEBUGGING - # set.seed(1) ; obs1 <- data.frame(Time = c(rnorm(10), rnorm(10) + 2), Categ1 = rep(c("G", "H"), each = 10), stringsAsFactors = TRUE) ; set.seed(NULL) ; obs1$Time[1:10] <- NA ; data1 = obs1 ; y = "Time" ; categ = c("Categ1") ; categ.class.order = NULL ; categ.color = NULL ; box.legend.name = NULL ; box.fill = FALSE ; box.width = 0.5 ; box.space = 0.1 ; box.line.size = 0.75 ; box.notch = FALSE ; box.alpha = 1 ; box.mean = TRUE ; box.whisker.kind = "std" ; box.whisker.width = 0 ; dot.color = grey(0.25) ; dot.categ = NULL ; dot.categ.class.order = NULL ; dot.legend.name = NULL ; dot.tidy = FALSE ; dot.tidy.bin.nb = 50 ; dot.jitter = 0.5 ; dot.seed = 2 ; dot.size = 3 ; dot.alpha = 0.5 ; dot.border.size = 0.5 ; dot.border.color = NULL ; x.lab = NULL ; x.angle = 0 ; y.lab = NULL ; y.lim = NULL ; y.log = "no" ; y.tick.nb = NULL ; y.second.tick.nb = 1 ; y.include.zero = FALSE ; y.top.extra.margin = 0.05 ; y.bottom.extra.margin = 0.05 ; stat.pos = "top" ; stat.mean = FALSE ; stat.size = 4 ; stat.dist = 5 ; stat.angle = 0 ; vertical = TRUE ; text.size = 12 ; title = "" ; title.text.size = 8 ; legend.show = TRUE ; legend.width = 0.5 ; article = TRUE ; grid = FALSE ; add = NULL ; return = FALSE ; return.ggplot = FALSE ; return.gtable = TRUE ; plot = TRUE ; warn.print = FALSE ; lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots = FALSE))[[1]], "()") - arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments - arg.user.setting <- as.list(match.call(expand.dots = FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - req.function <- c( - "fun_comp_2d", - "fun_gg_just", - "fun_gg_palette", - "fun_name_change", - "fun_pack", - "fun_check", - "fun_round", - "fun_scale", - "fun_inter_ticks" - ) - tempo <- NULL - for(i1 in req.function){ - if(length(find(i1, mode = "function")) == 0L){ - tempo <- c(tempo, i1) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # reserved words to avoid bugs (names of dataframe columns used in this function) - reserved.words <- c("categ.check", "categ.color", "dot.color", "dot.categ", "dot.max", "dot.min", "group", "PANEL", "group.check", "MEAN", "tempo.categ1", "tempo.categ2", "text.max.pos", "text.min.pos", "x", "x.y", "y", "y.check", "y_from.dot.max", "ymax", "tidy_group", "binwidth") - # end reserved words to avoid bugs (used in this function) - # arg with no default values - mandat.args <- c( - "data1", - "y", - "categ" - ) - tempo <- eval(parse(text = paste0("missing(", paste0(mandat.args, collapse = ") | missing("), ")"))) - if(any(tempo)){ # normally no NA for missing() output - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", "HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo <- fun_check(data = data1, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = categ, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if( ! is.null(categ.class.order)){ - tempo <- fun_check(data = categ.class.order, class = "list", fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = categ.class.order, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(box.legend.name)){ - tempo <- fun_check(data = box.legend.name, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = box.legend.name, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(categ.color)){ - tempo1 <- fun_check(data = categ.color, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = categ.color, class = "factor", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.check.color <- fun_check(data = categ.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, neg.values = FALSE, fun.name = function.name)$problem - if(tempo.check.color == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(any(categ.color == 0L, na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = categ.color, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = box.fill, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.space, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.line.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.notch, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.alpha, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.mean, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.whisker.kind, options = c("no", "std", "max"), length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = box.whisker.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(dot.color)){ - tempo1 <- fun_check(data = dot.color, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = dot.color, class = "factor", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.check.color <- fun_check(data = dot.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, neg.values = FALSE, fun.name = function.name)$problem - if(tempo.check.color == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(any(dot.color == 0L, na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT MUST BE A FACTOR OR CHARACTER VECTOR OR POSITVE INTEGER VECTOR") # integer possible because dealt above - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = dot.color, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(dot.categ)){ - tempo <- fun_check(data = dot.categ, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = dot.categ, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(dot.categ.class.order)){ - tempo <- fun_check(data = dot.categ.class.order, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = dot.categ.class.order, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(dot.legend.name)){ - tempo <- fun_check(data = dot.legend.name, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = dot.legend.name, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = dot.tidy, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = dot.tidy.bin.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = FALSE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if(dot.tidy.bin.nb == 0L){ # length and NA checked above - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.tidy.bin.nb ARGUMENT MUST BE A NON-NULL AND POSITVE INTEGER VALUE") # integer possible because dealt above - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - tempo <- fun_check(data = dot.jitter, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(dot.seed)){ - tempo <- fun_check(data = dot.seed, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, neg.values = TRUE, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = dot.seed, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = dot.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = dot.alpha, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = dot.border.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - if( ! is.null(dot.border.color)){ - tempo1 <- fun_check(data = dot.border.color, class = "vector", mode = "character", length = 1, fun.name = function.name) - tempo2 <- fun_check(data = dot.border.color, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.border.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR STRING STARTING BY #, OR (2) A COLOR NAME GIVEN BY colors(), OR (3) AN INTEGER VALUE") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - }else if(tempo1$problem == FALSE & tempo2$problem == TRUE){ - if( ! all(dot.border.color %in% colors() | grepl(pattern = "^#", dot.border.color), na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.border.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR STRING STARTING BY #, OR (2) A COLOR NAME GIVEN BY colors(), OR (3) AN INTEGER VALUE") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = dot.border.color, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(x.lab)){ - tempo1 <- fun_check(data = x.lab, class = "expression", length = 1, fun.name = function.name) - tempo2 <- fun_check(data = x.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lab ARGUMENT MUST BE A SINGLE CHARACTER STRING OR EXPRESSION") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = x.lab, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = x.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) - if( ! is.null(y.lab)){ - tempo1 <- fun_check(data = y.lab, class = "expression", length = 1, fun.name = function.name) - tempo2 <- fun_check(data = y.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lab ARGUMENT MUST BE A SINGLE CHARACTER STRING OR EXPRESSION") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.lab, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(y.lim)){ - tempo <- fun_check(data = y.lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if(any(is.infinite(y.lim))){ # normally no NA for is.infinite() output - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.lim, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = y.log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(y.tick.nb)){ - tempo <- fun_check(data = y.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if(y.tick.nb < 0){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.tick.nb ARGUMENT MUST BE A NON NULL POSITIVE INTEGER") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.tick.nb, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(y.second.tick.nb)){ - tempo <- fun_check(data = y.second.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if(y.second.tick.nb <= 0){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.second.tick.nb ARGUMENT MUST BE A NON NULL POSITIVE INTEGER") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.second.tick.nb, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = y.include.zero, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.top.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.bottom.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(stat.pos)){ - tempo <- fun_check(data = stat.pos, options = c("top", "above"), length = 1, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = stat.pos, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = stat.mean, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = stat.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = stat.dist, class = "vector", mode = "numeric", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = stat.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = vertical, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = legend.show, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(legend.width)){ - tempo <- fun_check(data = legend.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = legend.width, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = article, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = grid, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(add)){ - tempo <- fun_check(data = add, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = add, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return.ggplot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return.gtable, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = plot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path), na.rm = TRUE)){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, "\nDIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = lib.path, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if(any(arg.check) == TRUE){ # normally no NA - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - # second round of checking and data preparation - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length) == 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ # normally no NA because is.na() used here - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "data1", - "y", - "categ", - "box.fill", - "box.width", - "box.space", - "box.line.size", - "box.notch", - "box.alpha", - "box.mean", - "box.whisker.kind", - "box.whisker.width", - # "dot.color", # inactivated because can be null - "dot.tidy", - "dot.tidy.bin.nb", - "dot.jitter", - # "dot.seed", # inactivated because can be null - "dot.size", - "dot.alpha", - "dot.border.size", - "x.angle", - "y.log", - # "y.second.tick.nb", # inactivated because can be null - "y.include.zero", - "y.top.extra.margin", - "y.bottom.extra.margin", - # "stat.pos", # inactivated because can be null - "stat.mean", - "stat.size", - "stat.dist", - "stat.angle", - "vertical", - "text.size", - "title", - "title.text.size", - "legend.show", - # "legend.width", # inactivated because can be null - "article", - "grid", - "return", - "return.ggplot", - "return.gtable", - "plot", - "warn.print" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){# normally no NA with is.null() - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # code that protects set.seed() in the global environment - # see also Protocol 100-rev0 Parallelization in R.docx - if(exists(".Random.seed", envir = .GlobalEnv)){ # if .Random.seed does not exists, it means that no random operation has been performed yet in any R environment - tempo.random.seed <- .Random.seed - on.exit(assign(".Random.seed", tempo.random.seed, env = .GlobalEnv)) - }else{ - on.exit(set.seed(NULL)) # inactivate seeding -> return to complete randomness - } - set.seed(dot.seed) - # end code that protects set.seed() in the global environment - # warning initiation - ini.warning.length <- options()$warning.length - options(warning.length = 8170) - warn <- NULL - warn.count <- 0 - # end warning initiation - # other checkings - if(any(duplicated(names(data1)), na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nDUPLICATED COLUMN NAMES OF data1 ARGUMENT NOT ALLOWED:\n", paste(names(data1)[duplicated(names(data1))], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! (y %in% names(data1))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny ARGUMENT MUST BE A COLUMN NAME OF data1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - tempo <- fun_check(data = data1[, y], data.name = "y COLUMN OF data1", class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) - if(tempo$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny ARGUMENT MUST BE NUMERIC COLUMN IN data1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(length(categ) > 2){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg ARGUMENT CANNOT HAVE MORE THAN 2 COLUMN NAMES OF data1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if( ! all(categ %in% names(data1))){ # all() without na.rm -> ok because categ cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg ARGUMENT MUST BE COLUMN NAMES OF data1. HERE IT IS:\n", paste(categ, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(length(dot.categ) > 1){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT CANNOT HAVE MORE THAN 1 COLUMN NAMES OF data1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if( ! all(dot.categ %in% names(data1))){ # all() without na.rm -> ok because dot.categ cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT MUST BE COLUMN NAMES OF data1. HERE IT IS:\n", paste(dot.categ, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # reserved word checking - if(any(names(data1) %in% reserved.words, na.rm = TRUE)){ - if(any(duplicated(names(data1)), na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nDUPLICATED COLUMN NAMES OF data1 ARGUMENT NOT ALLOWED:\n", paste(names(data1)[duplicated(names(data1))], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(dot.categ)){ - if(dot.categ %in% categ){ - reserved.words <- c(reserved.words, paste0(dot.categ, "_DOT")) # paste0(dot.categ, "_DOT") is added to the reserved words because in such situation, a new column will be added to data1 that is named paste0(dot.categ, "_DOT") - } - } - tempo.output <- fun_name_change(names(data1), reserved.words) - for(i2 in 1:length(tempo.output$ini)){ # a loop to be sure to take the good ones - names(data1)[names(data1) == tempo.output$ini[i2]] <- tempo.output$post[i2] - if(any(y == tempo.output$ini[i2])){ # any() without na.rm -> ok because y cannot be NA (tested above) - y[y == tempo.output$ini[i2]] <- tempo.output$post[i2] - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN y ARGUMENT (COLUMN NAMES OF data1 ARGUMENT),\n", tempo.output$ini[i2], " HAS BEEN REPLACED BY ", tempo.output$post[i2], "\nBECAUSE RISK OF BUG AS SOME NAMES IN y ARGUMENT ARE RESERVED WORD USED BY THE ", function.name, " FUNCTION") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # WARNING: names of y argument potentially replaced - if(any(categ == tempo.output$ini[i2])){ # any() without na.rm -> ok because categ cannot be NA (tested above) - categ[categ == tempo.output$ini[i2]] <- tempo.output$post[i2] - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN categ ARGUMENT (COLUMN NAMES OF data1 ARGUMENT),\n", tempo.output$ini[i2], " HAS BEEN REPLACED BY ", tempo.output$post[i2], "\nBECAUSE RISK OF BUG AS SOME NAMES IN categ ARGUMENT ARE RESERVED WORD USED BY THE ", function.name, " FUNCTION") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # WARNING: names of categ argument potentially replaced - if( ! is.null(dot.categ)){ - if(any(dot.categ == tempo.output$ini[i2])){ # any() without na.rm -> ok because dot.categ cannot be NA (tested above) - dot.categ[dot.categ == tempo.output$ini[i2]] <- tempo.output$post[i2] - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN dot.categ ARGUMENT (COLUMN NAMES OF data1 ARGUMENT),\n", tempo.output$ini[i2], " HAS BEEN REPLACED BY ", tempo.output$post[i2], "\nBECAUSE RISK OF BUG AS SOME NAMES IN dot.categ ARGUMENT ARE RESERVED WORD USED BY THE ", function.name, " FUNCTION") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # WARNING: names of dot.categ argument potentially replaced - } - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") REGARDING COLUMN NAMES REPLACEMENT, THE NAMES\n", paste(tempo.output$ini, collapse = " "), "\nHAVE BEEN REPLACED BY\n", paste(tempo.output$post, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - if( ! (is.null(add) | is.null(tempo.output$ini))){ - if(grepl(x = add, pattern = paste(tempo.output$ini, collapse = "|"))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF COLUMN NAMES OF data1 IN THE add ARGUMENT STRING, THAT CORRESPOND TO RESERVED STRINGS FOR ", function.name, "\nCOLUMN NAMES HAVE TO BE CHANGED\nTHE PROBLEMATIC COLUMN NAMES ARE SOME OF THESE NAMES:\n", paste(tempo.output$ini, collapse = " "), "\nIN THE DATA FRAME OF data1 AND IN THE STRING OF add ARGUMENT, TRY TO REPLACE NAMES BY:\n", paste(tempo.output$post, collapse = " "), "\n\nFOR INFORMATION, THE RESERVED WORDS ARE:\n", paste(reserved.words, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - } - if( ! (is.null(add))){ - if(any(sapply(X = arg.names, FUN = grepl, x = add), na.rm = TRUE)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NAMES OF ", function.name, " ARGUMENTS DETECTED IN THE add STRING:\n", paste(arg.names[sapply(X = arg.names, FUN = grepl, x = add)], collapse = "\n"), "\nRISK OF WRONG OBJECT USAGE INSIDE ", function.name) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end reserved word checking - # verif of add - if( ! is.null(add)){ - if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by + - tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ # - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by ) - tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end verif of add - # management of add containing facet - facet.categ <- NULL - if( ! is.null(add)){ - facet.check <- TRUE - tempo <- unlist(strsplit(x = add, split = "\\s*\\+\\s*(ggplot2|lemon)\\s*::\\s*")) # - tempo <- sub(x = tempo, pattern = "^facet_wrap", replacement = "ggplot2::facet_wrap") - tempo <- sub(x = tempo, pattern = "^facet_grid", replacement = "ggplot2::facet_grid") - tempo <- sub(x = tempo, pattern = "^facet_rep", replacement = "lemon::facet_rep") - if(any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap"), na.rm = TRUE)){ - tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")]))) - facet.categ <- names(tempo1$params$facets) - tempo.text <- "facet_wrap OR facet_rep_wrap" - facet.check <- FALSE - }else if(grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")){ - tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")]))) - facet.categ <- c(names(tempo1$params$rows), names(tempo1$params$cols)) - tempo.text <- "facet_grid OR facet_rep_grid" - facet.check <- FALSE - } - if(facet.check == FALSE & ! all(facet.categ %in% names(data1))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL # all() without na.rm -> ok because facet.categ cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end management of add containing facet - # conversion of categ columns in data1 into factors - for(i1 in 1:length(categ)){ - tempo1 <- fun_check(data = data1[, categ[i1]], data.name = paste0("categ NUMBER ", i1, " OF data1"), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = data1[, categ[i1]], data.name = paste0("categ NUMBER ", i1, " OF data1"), class = "factor", na.contain = TRUE, fun.name = function.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\n", paste0("categ NUMBER ", i1, " OF data1"), " MUST BE A FACTOR OR CHARACTER VECTOR") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(tempo1$problem == FALSE){ # character vector - if(box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN categ NUMBER ", i1, " IN data1, THE CHARACTER COLUMN HAS BEEN CONVERTED TO FACTOR, WITH LEVELS ACCORDING TO THE ALPHABETICAL ORDER") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - data1[, categ[i1]] <- factor(data1[, categ[i1]]) # if already a factor, change nothing, if characters, levels according to alphabetical order - } - # OK: all the categ columns of data1 are factors from here - # end conversion of categ columns in data1 into factors - - - - # management of log scale and Inf removal - if(any(( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf # normally no NA with is.finite0() and is.na() - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") PRESENCE OF -Inf OR Inf VALUES IN THE ", y, " COLUMN OF THE data1 ARGUMENT AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - data1.ini <- data1 # strictly identical to data1 except that in data1 y is log converted if and only if y.log != "no" - if(y.log != "no"){ - tempo1 <- ! is.finite(data1[, y]) # where are initial NA and Inf - data1[, y] <- suppressWarnings(get(y.log)(data1[, y]))# no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - if(any( ! (tempo1 | is.finite(data1[, y])))){ # normally no NA with is.finite - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") LOG CONVERSION INTRODUCED -Inf OR Inf OR NaN VALUES IN THE ", y, " COLUMN OF THE data1 ARGUMENT AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # Inf removal - if(any(( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf # normally no NA with is.finite - removed.row.nb <- which(( ! is.finite(data1[, y])) & ( ! is.na(data1[, y]))) - removed.rows <- data1.ini[removed.row.nb, ] # here data1.ini used to have the y = O rows that will be removed because of Inf creation after log transformation - data1 <- data1[-removed.row.nb, ] # - data1.ini <- data1.ini[-removed.row.nb, ] # - }else{ - removed.row.nb <- NULL - removed.rows <- data.frame(stringsAsFactors = FALSE) - } - # From here, data1 and data.ini have no more Inf - # end Inf removal - if(y.log != "no" & ! is.null(y.lim)){ - if(any(y.lim <= 0)){ # any() without na.rm -> ok because y.lim cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(any( ! is.finite(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}))){ # normally no NA with is.finite - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT RETURNS INF/NA WITH THE y.log ARGUMENT SET TO ", y.log, "\nAS SCALE COMPUTATION IS ", ifelse(y.log == "log10", "log10", "log2"), ":\n", paste(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(y.log != "no" & y.include.zero == TRUE){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") y.log ARGUMENT SET TO ", y.log, " AND y.include.zero ARGUMENT SET TO TRUE -> y.include.zero ARGUMENT RESET TO FALSE BECAUSE 0 VALUE CANNOT BE REPRESENTED IN LOG SCALE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - y.include.zero <- FALSE - } - if(y.log != "no" & vertical == FALSE){ - vertical <- TRUE - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") BECAUSE OF A BUG IN ggplot2, CANNOT FLIP BOXES HORIZONTALLY WITH A Y.LOG SCALE -> vertical ARGUMENT RESET TO TRUE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # end management of log scale and Inf removal - # na detection and removal (done now to be sure of the correct length of categ) - column.check <- unique(c(y, categ, if( ! is.null(dot.color) & ! is.null(dot.categ)){dot.categ}, if( ! is.null(facet.categ)){facet.categ})) # dot.categ because can be a 3rd column of data1, categ.color and dot.color will be tested later - if(any(is.na(data1[, column.check]))){ # data1 used here instead of data1.ini in case of new NaN created by log conversion (neg values) # normally no NA with is.na - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NA DETECTED IN COLUMNS OF data1 AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - for(i2 in 1:length(column.check)){ - if(any(is.na(data1[, column.check[i2]]))){ # normally no NA with is.na - tempo.warn <- paste0("NA REMOVAL DUE TO COLUMN ", column.check[i2], " OF data1") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n", tempo.warn))) - } - } - tempo <- unique(unlist(lapply(lapply(c(data1[column.check]), FUN = is.na), FUN = which))) - removed.row.nb <- c(removed.row.nb, tempo) # removed.row.nb created to remove Inf - removed.rows <- rbind(removed.rows, data1.ini[tempo, ], stringsAsFactors = FALSE) # here data1.ini used to have the non NA rows that will be removed because of NAN creation after log transformation (neg values for instance) - column.check <- column.check[ ! column.check == y] # remove y to keep quali columns - if(length(tempo) != 0){ - data1 <- data1[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former - data1.ini <- data1.ini[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers than the former - for(i3 in 1:length(column.check)){ - if(any( ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]]), na.rm = TRUE)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN COLUMN ", column.check[i3], " OF data1, THE FOLLOWING CLASSES HAVE DISAPPEARED AFTER NA/Inf REMOVAL (IF COLUMN USED IN THE PLOT, THIS CLASS WILL NOT BE DISPLAYED):\n", paste(unique(removed.rows[, column.check[i3]])[ ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]])], collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - } - count.categ <- 0 - for(i2 in 1:length(column.check)){ - if(column.check[i2] %in% categ){ - count.categ <- count.categ + 1 - } - if(column.check[i2] == categ[count.categ]){ - categ.class.order[count.categ] <- list(levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])]) # remove the absent color in the character vector - data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(categ.class.order[[count.categ]])) - } - if( ! is.null(dot.color) & ! is.null(dot.categ)){ # reminder : dot.categ cannot be a column name of categ anymore (because in that case dot.categ name is changed into "..._DOT" - if(column.check[i2] == dot.categ){ - dot.categ.class.order <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])] # remove the absent color in the character vector - data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(dot.categ.class.order)) - } - } - if(column.check[i2] %in% facet.categ){ # works if facet.categ == NULL this method should keep the order of levels when removing some levels - tempo.levels <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(as.character(data1[, column.check[i2]]))] - data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = tempo.levels) - } - } - } - # end na detection and removal (done now to be sure of the correct length of categ) - # From here, data1 and data.ini have no more NA or NaN in y, categ, dot.categ (if dot.color != NULL) and facet.categ - - - - if( ! is.null(categ.class.order)){ - if(length(categ.class.order) != length(categ)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.class.order ARGUMENT MUST BE A LIST OF LENGTH EQUAL TO LENGTH OF categ\nHERE IT IS LENGTH: ", length(categ.class.order), " VERSUS ", length(categ)) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - for(i3 in 1:length(categ.class.order)){ - if(is.null(categ.class.order[[i3]])){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE categ.class.order COMPARTMENT ", i3, " IS NULL. ALPHABETICAL ORDER WILL BE APPLIED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - data1[, categ[i3]] <- factor(as.character(data1[, categ[i3]])) # if already a factor, change nothing, if characters, levels according to alphabetical order - categ.class.order[[i3]] <- levels(data1[, categ[i3]]) # character vector that will be used later - }else{ - tempo <- fun_check(data = categ.class.order[[i3]], data.name = paste0("COMPARTMENT ", i3 , " OF categ.class.order ARGUMENT"), class = "vector", mode = "character", length = length(levels(data1[, categ[i3]])), fun.name = function.name) # length(data1[, categ[i1]) -> if data1[, categ[i1] was initially character vector, then conversion as factor after the NA removal, thus class number ok. If data1[, categ[i1] was initially factor, no modification after the NA removal, thus class number ok - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(any(duplicated(categ.class.order[[i3]]), na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i3, " OF categ.class.order ARGUMENT CANNOT HAVE DUPLICATED CLASSES: ", paste(categ.class.order[[i3]], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if( ! (all(categ.class.order[[i3]] %in% unique(data1[, categ[i3]]), na.rm = TRUE) & all(unique(data1[, categ[i3]]) %in% categ.class.order[[i3]], na.rm = TRUE))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i3, " OF categ.class.order ARGUMENT MUST BE CLASSES OF ELEMENT ", i3, " OF categ ARGUMENT\nHERE IT IS:\n", paste(categ.class.order[[i3]], collapse = " "), "\nFOR COMPARTMENT ", i3, " OF categ.class.order AND IT IS:\n", paste(unique(data1[, categ[i3]]), collapse = " "), "\nFOR COLUMN ", categ[i3], " OF data1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - data1[, categ[i3]] <- factor(data1[, categ[i3]], levels = categ.class.order[[i3]]) # reorder the factor - - } - names(categ.class.order)[i3] <- categ[i3] - } - } - }else{ - categ.class.order <- vector("list", length = length(categ)) - tempo.categ.class.order <- NULL - for(i2 in 1:length(categ.class.order)){ - categ.class.order[[i2]] <- levels(data1[, categ[i2]]) - names(categ.class.order)[i2] <- categ[i2] - tempo.categ.class.order <- c(tempo.categ.class.order, ifelse(i2 != 1, "\n", ""), categ.class.order[[i2]]) - } - if(box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE categ.class.order SETTING IS NULL. ALPHABETICAL ORDER WILL BE APPLIED FOR BOX ORDERING:\n", paste(tempo.categ.class.order, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # categ.class.order not NULL anymore (list) - if(is.null(box.legend.name) & box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE box.legend.name SETTING IS NULL. NAMES OF categ WILL BE USED: ", paste(categ, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - box.legend.name <- categ[length(categ)] # if only categ1, then legend name of categ1, if length(categ) == 2L, then legend name of categ2 - } - # box.legend.name not NULL anymore (character string) - # management of categ.color - if( ! is.null(categ.color)){ - # check the nature of color - # integer colors into gg_palette - tempo.check.color <- fun_check(data = categ.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name)$problem - if(tempo.check.color == FALSE){ - # convert integers into colors - categ.color <- fun_gg_palette(max(categ.color, na.rm = TRUE))[categ.color] - } - # end integer colors into gg_palette - if( ! (all(categ.color %in% colors() | grepl(pattern = "^#", categ.color)))){ # check that all strings of low.color start by #, # all() without na.rm -> ok because categ.color cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors(): ", paste(unique(categ.color), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(is.na(categ.color)) & box.alpha != 0){ # normally no NA with is.na - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") categ.color ARGUMENT CONTAINS NA") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # end check the nature of color - # check the length of color - categ.len <- length(categ) # if only categ1, then colors for classes of categ1, if length(categ) == 2L, then colors for classes of categ2 - if(length(data1[, categ[categ.len]]) == length(levels(data1[, categ[categ.len]])) & length(categ.color) == length(data1[, categ[categ.len]])){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE NUMBER OF CLASSES OF THE COLUMN ", categ[categ.len], " THE NUMBER OF ROWS OF THIS COLUMN AND THE NUMBER OF COLORS OF THE categ.color ARGUMENT ARE ALL EQUAL. BOX COLORS WILL BE ATTRIBUTED ACCORDING THE LEVELS OF ", categ[categ.len], ", NOT ACCORDING TO THE ROWS OF ", categ[categ.len]) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if(length(categ.color) == length(levels(data1[, categ[categ.len]]))){ # here length(categ.color) is equal to the different number of categ - # data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor - data1 <- data.frame(data1, categ.color = data1[, categ[categ.len]], stringsAsFactors = TRUE) # no need stringsAsFactors here for stat.nolog as factors remain factors - data1$categ.color <- factor(data1$categ.color, labels = categ.color) # replace the characters of data1[, categ[categ.len]] put in the categ.color column by the categ.color (can be write like this because categ.color is length of levels of data1[, categ[categ.len]]) - if(box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN ", categ[categ.len], " OF categ ARGUMENT, THE FOLLOWING COLORS:\n", paste(categ.color, collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else if(length(categ.color) == length(data1[, categ[categ.len]])){# here length(categ.color) is equal to nrow(data1) -> Modif to have length(categ.color) equal to the different number of categ (length(categ.color) == length(levels(data1[, categ[categ.len]]))) - data1 <- data.frame(data1, categ.color = categ.color, stringsAsFactors = TRUE) - tempo.check <- unique(data1[ , c(categ[categ.len], "categ.color")]) - if( ! (nrow(tempo.check) == length(unique(categ.color)) & nrow(tempo.check) == length(unique(data1[ , categ[categ.len]])))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT HAS THE LENGTH OF data1 ROW NUMBER\nBUT IS INCORRECTLY ASSOCIATED TO EACH CLASS OF categ ", categ[categ.len], ":\n", paste(unique(mapply(FUN = "paste", data1[ ,categ[categ.len]], data1[ ,"categ.color"])), collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - # data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor - categ.color <- unique(data1$categ.color[order(data1[, categ[categ.len]])]) # Modif to have length(categ.color) equal to the different number of categ (length(categ.color) == length(levels(data1[, categ[categ.len]]))) - if(box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") categ.color ARGUMENT HAS THE LENGTH OF data1 ROW NUMBER\nCOLORS HAVE BEEN RESPECTIVELY ASSOCIATED TO EACH CLASS OF categ ", categ[categ.len], " AS:\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " "), "\n", paste(categ.color, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - }else if(length(categ.color) == 1L){ - # data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor - data1 <- data.frame(data1, categ.color = categ.color, stringsAsFactors = TRUE) - categ.color <- rep(categ.color, length(levels(data1[, categ[categ.len]]))) - if(box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") categ.color ARGUMENT HAS LENGTH 1, MEANING THAT ALL THE DIFFERENT CLASSES OF ", categ[categ.len], "\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " "), "\nWILL HAVE THE SAME COLOR\n", paste(categ.color, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg.color ARGUMENT MUST BE (1) LENGTH 1, OR (2) THE LENGTH OF data1 NROWS AFTER NA/Inf REMOVAL, OR (3) THE LENGTH OF THE CLASSES IN THE categ ", categ[categ.len], " COLUMN. HERE IT IS COLOR LENGTH ", length(categ.color), " VERSUS CATEG LENGTH ", length(data1[, categ[categ.len]]), " AND CATEG CLASS LENGTH ", length(unique(data1[, categ[categ.len]])), "\nPRESENCE OF NA/Inf COULD BE THE PROBLEM") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else{ - categ.len <- length(categ) # if only categ1, then colors for classes of categ1, if length(categ) == 2L, then colors for classes of categ2 - # data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor - categ.color <- fun_gg_palette(length(levels(data1[, categ[categ.len]]))) - data1 <- data.frame(data1, categ.color = data1[, categ[categ.len]], stringsAsFactors = TRUE) - data1$categ.color <- factor(data1$categ.color, labels = categ.color) # replace the characters of data1[, categ[categ.len]] put in the categ.color column by the categ.color (can be write like this because categ.color is length of levels of data1[, categ[categ.len]]) - if(box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL categ.color ARGUMENT -> COLORS RESPECTIVELY ATTRIBUTED TO EACH CLASS OF ", categ[categ.len], " IN data1:\n", paste(categ.color, collapse = " "), "\n", paste(levels(data1[, categ[categ.len]]), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # categ.color not NULL anymore - categ.color <- as.character(categ.color) - # categ.color is a character string representing the diff classes - data1$categ.color <- factor(data1$categ.color, levels = unique(categ.color)) # ok because if categ.color is a character string, the order make class 1, class 2, etc. unique() because no duplicates allowed - # data1$categ.color is a factor with order of levels -> categ.color - # end management of categ.color - # management of dot.color - if( ! is.null(dot.color)){ - # optional legend of dot colors - if( ! is.null(dot.categ)){ - ini.dot.categ <- dot.categ - if( ! dot.categ %in% names(data1)){ # no need to use all() because length(dot.categ) = 1 - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT MUST BE A COLUMN NAME OF data1. HERE IT IS:\n", dot.categ) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(dot.categ %in% categ){ # no need to use all() because length(dot.categ) = 1. Do not use dot.categ %in% categ[length(categ)] -> error - # management of dot legend if dot.categ %in% categ (because legends with the same name are joined in ggplot2) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE COLUMN NAME OF data1 INDICATED IN THE dot.categ ARGUMENT (", dot.categ, ") HAS BEEN REPLACED BY ", paste0(dot.categ, "_DOT"), " TO AVOID MERGED LEGEND BY GGPLOT2") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - data1 <- data.frame(data1, dot.categ = data1[, dot.categ], stringsAsFactors = TRUE) # dot.categ is not a column name of data1 (checked above with reserved words) - dot.categ <- paste0(dot.categ, "_DOT") - names(data1)[names(data1) == "dot.categ"] <- dot.categ # paste0(dot.categ, "_DOT") is not a column name of data1 (checked above with reserved words) - # tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ ARGUMENT CANNOT BE A COLUMN NAME OF data1 ALREADY SPECIFIED IN THE categ ARGUMENT:\n", dot.categ, "\nINDEED, dot.categ ARGUMENT IS MADE TO HAVE MULTIPLE DOT COLORS NOT RELATED TO THE BOXPLOT CATEGORIES") - # stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - tempo1 <- fun_check(data = data1[, dot.categ], data.name = paste0(dot.categ, " COLUMN OF data1"), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = data1[, dot.categ], data.name = paste0(dot.categ, " COLUMN OF data1"), class = "factor", na.contain = TRUE, fun.name = function.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ COLUMN MUST BE A FACTOR OR CHARACTER VECTOR") # - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - data1[, dot.categ] <- factor(data1[, dot.categ]) # if already a factor, change nothing, if characters, levels according to alphabetical order - # dot.categ column of data1 is factor from here - if( ! is.null(dot.categ.class.order)){ - if(any(duplicated(dot.categ.class.order), na.rm = TRUE)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ.class.order ARGUMENT CANNOT HAVE DUPLICATED CLASSES: ", paste(dot.categ.class.order, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if( ! (all(dot.categ.class.order %in% levels(data1[, dot.categ])) & all(levels(data1[, dot.categ]) %in% dot.categ.class.order, na.rm = TRUE))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.categ.class.order ARGUMENT MUST BE CLASSES OF dot.categ ARGUMENT\nHERE IT IS:\n", paste(dot.categ.class.order, collapse = " "), "\nFOR dot.categ.class.order AND IT IS:\n", paste(levels(data1[, dot.categ]), collapse = " "), "\nFOR dot.categ COLUMN (", ini.dot.categ, ") OF data1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - data1[, dot.categ] <- factor(data1[, dot.categ], levels = dot.categ.class.order) # reorder the factor - } - }else{ - if(all(dot.color == "same") & length(dot.color)== 1L){ # all() without na.rm -> ok because dot.color cannot be NA (tested above) - dot.categ.class.order <- unlist(categ.class.order[length(categ)]) - data1[, dot.categ] <- factor(data1[, dot.categ], levels = dot.categ.class.order) # reorder the factor - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE dot.categ.class.order SETTING IS NULL AND dot.color IS \"same\". ORDER OF categ.class.order WILL BE APPLIED FOR LEGEND DISPLAY: ", paste(dot.categ.class.order, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - dot.categ.class.order <- sort(levels(data1[, dot.categ])) - data1[, dot.categ] <- factor(data1[, dot.categ], levels = dot.categ.class.order) # reorder the factor - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE dot.categ.class.order SETTING IS NULL. ALPHABETICAL ORDER WILL BE APPLIED FOR LEGEND DISPLAY: ", paste(dot.categ.class.order, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # dot.categ.class.order not NULL anymore (character string) if dot.categ is not NULL - if(all(dot.color == "same") & length(dot.color)== 1L){ # all() without na.rm -> ok because dot.color cannot be NA (tested above) - if( ! identical(ini.dot.categ, categ[length(categ)])){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nWHEN dot.color ARGUMENT IS \"same\", THE COLUMN NAME IN dot.categ ARGUMENT MUST BE IDENTICAL TO THE LAST COLUMN NAME IN categ ARGUMENT. HERE IT IS:\ndot.categ: ", paste(ini.dot.categ, collapse = " "), "\ncateg: ", paste(categ, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if( ! fun_comp_1d(unlist(categ.class.order[length(categ)]), dot.categ.class.order)$identical.content){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nWHEN dot.color ARGUMENT IS \"same\",\nLAST COMPARTMENT OF categ.class.order ARGUMENT AND dot.categ.class.order ARGUMENT CANNOT BE DIFFERENT:\nLAST COMPARTMENT OF categ.class.order: ", paste(unlist(categ.class.order[length(categ)]), collapse = " "), "\ndot.categ.class.order: ", paste(dot.categ.class.order, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - for(i3 in 1:length(categ)){ - if(identical(categ[i3], ini.dot.categ) & ! identical(unlist(categ.class.order[i3]), dot.categ.class.order) & identical(sort(unlist(categ.class.order[i3])), sort(dot.categ.class.order))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE dot.categ ARGUMENT SETTING IS PRESENT IN THE categ ARGUMENT SETTING, BUT ORDER OF THE CLASSES IS NOT THE SAME:\ncateg.class.order: ", paste(unlist(categ.class.order[i3]), collapse = " "), "\ndot.categ.class.order: ", paste(dot.categ.class.order, collapse = " "), "\nNOTE THAT ORDER OF categ.class.order IS THE ONE USED FOR THE AXIS REPRESENTATION") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - if(is.null(dot.legend.name)){ - dot.legend.name <- if(ini.dot.categ %in% categ[length(categ)]){dot.categ}else{ini.dot.categ} # - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE dot.legend.name SETTING IS NULL -> ", dot.legend.name, " WILL BE USED AS LEGEND TITLE OF DOTS") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # dot.legend.name not NULL anymore (character string) - }else{ - if( ! is.null(dot.categ.class.order)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE dot.categ.class.order ARGUMENT IS NOT NULL, BUT IS THE dot.categ ARGUMENT\n-> dot.categ.class.order NOT CONSIDERED AS NO LEGEND WILL BE DRAWN") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # But dot.categ.class.order will be converted to NULL below (not now) - } - # end optional legend of dot colors - # check the nature of color - # integer colors into gg_palette - tempo.check.color <- fun_check(data = dot.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name)$problem - if(tempo.check.color == FALSE){ - # convert integers into colors - dot.color <- fun_gg_palette(max(dot.color, na.rm = TRUE))[dot.color] - } - # end integer colors into gg_palette - if(all(dot.color == "same") & length(dot.color)== 1L){# all() without na.rm -> ok because dot.color cannot be NA (tested above) - dot.color <- categ.color # same color of the dots as the corresponding box color - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") dot.color ARGUMENT HAS BEEN SET TO \"same\"\nTHUS, DOTS WILL HAVE THE SAME COLORS AS THE CORRESPONDING BOXPLOT") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else if( ! (all(dot.color %in% colors() | grepl(pattern = "^#", dot.color)))){ # check that all strings of low.color start by #, # all() without na.rm -> ok because dot.color cannot be NA (tested above) - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR VECTOR STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) INTEGERS, OR THE STRING \"same\"\nHERE IT IS: ", paste(unique(dot.color), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(any(is.na(dot.color))){ # normally no NA with is.finite - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") dot.color ARGUMENT CONTAINS NA") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # end check the nature of color - # check the length of color - if( ! is.null(dot.categ)){ - # optional legend of dot colors - if(length(data1[, dot.categ]) == length(levels(data1[, dot.categ])) & length(dot.color) == length(data1[, dot.categ])){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE NUMBER OF CLASSES OF THE COLUMN ", dot.categ, " THE NUMBER OF ROWS OF THIS COLUMN AND THE NUMBER OF COLORS OF THE dot.color ARGUMENT ARE ALL EQUAL. DOT COLORS WILL BE ATTRIBUTED ACCORDING THE LEVELS OF ", dot.categ, ", NOT ACCORDING TO THE ROWS OF ", dot.categ) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if(length(dot.color) > 1 & ! (length(dot.color) == length(unique(data1[, dot.categ])) | length(dot.color) == length(data1[, dot.categ]))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nWHEN LENGTH OF THE dot.color ARGUMENT IS MORE THAN 1, IT MUST BE EQUAL TO THE NUMBER OF 1) ROWS OR 2) LEVELS OF dot.categ COLUMN (", dot.categ, "):\ndot.color: ", paste(dot.color, collapse = " "), "\ndot.categ LEVELS: ", paste(levels(data1[, dot.categ]), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else if(length(dot.color) > 1 & length(dot.color) == length(unique(data1[, dot.categ]))){ - data1 <- data.frame(data1, dot.color = data1[, dot.categ], stringsAsFactors = TRUE) - data1$dot.color <- factor(data1$dot.color, labels = dot.color) # do not use labels = unique(dot.color). Otherwise, we can have green1 green2 when dot.color is c("green", "green") - }else if(length(dot.color) > 1 & length(dot.color) == length(data1[, dot.categ])){ - data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) - }else if(length(dot.color)== 1L){ # to deal with single color. Warning: & length(dot.categ.class.order) > 1 removed because otherwise, the data1 is not with dot.color column when length(dot.categ.class.order) == 1 - data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) - } - dot.color <- as.character(unique(data1$dot.color[order(data1[, dot.categ])])) # reorder the dot.color character vector - if(length(dot.color)== 1L & length(dot.categ.class.order) > 1){ # to deal with single color - dot.color <- rep(dot.color, length(dot.categ.class.order)) - } - tempo.check <- unique(data1[ , c(dot.categ, "dot.color")]) - if(length(unique(data1[ , "dot.color"])) > 1 & ( ! (nrow(tempo.check) == length(unique(data1[ , "dot.color"])) & nrow(tempo.check) == length(unique(data1[ , dot.categ]))))){ # length(unique(data1[ , "dot.color"])) > 1 because if only one color, can be attributed to each class of dot.categ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT IS INCORRECTLY ASSOCIATED TO EACH CLASS OF dot.categ (", dot.categ, ") COLUMN:\n", paste(unique(mapply(FUN = "paste", data1[ , dot.categ], data1[ ,"dot.color"])), collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN dot.categ ARGUMENT (", ini.dot.categ, "), THE FOLLOWING COLORS OF DOTS:\n", paste(dot.color, collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(data1[, dot.categ]), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # dot.color is a character string representing the diff classes of dot.categ - # data1$dot.color is a factor with order of levels -> dot.categ - # end optional legend of dot colors - }else{ - categ.len <- length(categ) # if only categ1, then colors for classes of categ1, if length(categ) == 2L, then colors for classes of categ2 - if(length(dot.color) == length(levels(data1[, categ[categ.len]]))){ # here length(dot.color) is equal to the different number of categ - # data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor - data1 <- data.frame(data1, dot.color = data1[, categ[categ.len]], stringsAsFactors = TRUE) - data1$dot.color <- factor(data1$dot.color, labels = dot.color) - if(box.alpha != 0){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN ", categ[categ.len], " OF categ ARGUMENT, THE FOLLOWING COLORS:\n", paste(dot.color, collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else if(length(dot.color) == length(data1[, categ[categ.len]])){# here length(dot.color) is equal to nrow(data1) -> Modif to have length(dot.color) equal to the different number of categ (length(dot.color) == length(levels(data1[, categ[categ.len]]))) - data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) - }else if(length(dot.color)== 1L & ! all(dot.color == "same")){ # all() without na.rm -> ok because dot.color cannot be NA (tested above) - # data1[, categ[categ.len]] <- factor(data1[, categ[categ.len]]) # not required because sure that is is a factor - data1 <- data.frame(data1, dot.color = dot.color, stringsAsFactors = TRUE) - dot.color <- rep(dot.color, length(levels(data1[, categ[categ.len]]))) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") dot.color ARGUMENT HAS LENGTH 1, MEANING THAT ALL THE DIFFERENT CLASSES OF ", categ[categ.len], "\n", paste(levels(factor(data1[, categ[categ.len]])), collapse = " "), "\nWILL HAVE THE SAME COLOR\n", paste(dot.color, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, "\ndot.color ARGUMENT MUST BE (1) LENGTH 1, OR (2) THE LENGTH OF data1 NROWS AFTER NA/Inf REMOVAL, OR (3) THE LENGTH OF THE CLASSES IN THE categ ", categ[categ.len], " COLUMN. HERE IT IS COLOR LENGTH ", length(dot.color), " VERSUS CATEG LENGTH ", length(data1[, categ[categ.len]]), " AND CATEG CLASS LENGTH ", length(unique(data1[, categ[categ.len]])), "\nPRESENCE OF NA/Inf COULD BE THE PROBLEM") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end check the length of color - dot.color <- as.character(dot.color) - # dot.color is a character string representing the diff classes - data1$dot.color <- factor(data1$dot.color, levels = unique(dot.color)) # ok because if dot.color is a character string, the order make class 1, class 2, etc. If dot.color is a column of data1, then levels will be created, without incidence, except if dot.categ specified (see below). unique() because no duplicates allowed - # data1$dot.color is a factor with order of levels -> dot.color - } - # end optional legend of dot colors - }else if(is.null(dot.color) & ! (is.null(dot.categ) & is.null(dot.categ.class.order) & is.null(dot.legend.name))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") dot.categ OR dot.categ.class.order OR dot.legend.name ARGUMENT HAS BEEN SPECIFIED BUT dot.color ARGUMENT IS NULL (NO DOT PLOTTED)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # dot.color either NULL (no dot plotted) or character string (potentially representing the diff classes of dot.categ) - # data1$dot.color is either NA or a factor (with order of levels -> depending on dot.categ or categ[length(categ)], or other - if(is.null(dot.categ)){ - dot.categ.class.order <- NULL # because not used anyway - } - # dot.categ.class.order either NULL if dot.categ is NULL (no legend displayed) or character string (potentially representing the diff classes of dot.categ) - # end management of dot.color - if(is.null(dot.color) & box.fill == FALSE & dot.alpha <= 0.025){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE FOLLOWING ARGUMENTS WERE SET AS:\ndot.color = NULL (NOT ALL DOTS BUT ONLY POTENTIAL OUTLIER DOTS DISPLAYED)\nbox.fill = FALSE (NO FILLING COLOR FOR BOTH BOXES AND POTENTIAL OUTLIER DOTS)\ndot.alpha = ", fun_round(dot.alpha, 4), "\n-> POTENTIAL OUTLIER DOTS MIGHT NOT BE VISIBLE BECAUSE ALMOST TRANSPARENT") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if(is.null(dot.color) & box.fill == FALSE & dot.border.size == 0){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE FOLLOWING ARGUMENTS WERE SET AS:\ndot.color = NULL (NOT ALL DOTS BUT ONLY POTENTIAL OUTLIER DOTS DISPLAYED)\nbox.fill = FALSE (NO FILLING COLOR FOR BOTH BOXES AND POTENTIAL OUTLIER DOTS)\ndot.border.size = 0 (NO BORDER FOR POTENTIAL OUTLIER DOTS)\n-> THESE SETTINGS ARE NOT ALLOWED BECAUSE THE POTENTIAL OUTLIER DOTS WILL NOT BE VISIBLE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # integer dot.border.color into gg_palette - if( ! is.null(dot.border.color)){ - tempo <- fun_check(data = dot.border.color, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) - if(tempo$problem == FALSE){ # convert integers into colors - dot.border.color <- fun_gg_palette(max(dot.border.color, na.rm = TRUE))[dot.border.color] - } - } - # end integer dot.border.color into gg_palette - # na detection and removal (done now to be sure of the correct length of categ) - column.check <- c("categ.color", if( ! is.null(dot.color)){"dot.color"}) # - if(any(is.na(data1[, column.check]))){ # data1 used here instead of data1.ini in case of new NaN created by log conversion (neg values) # normally no NA with is.na - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NA DETECTED IN COLUMNS ", paste(column.check, collapse = " "), " OF data1 AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - for(i2 in 1:length(column.check)){ - if(any(is.na(data1[, column.check[i2]]))){ # normally no NA with is.na - tempo.warn <- paste0("NA REMOVAL DUE TO COLUMN ", column.check[i2], " OF data1") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n", tempo.warn))) - } - } - tempo <- unique(unlist(lapply(lapply(c(data1[column.check]), FUN = is.na), FUN = which))) - removed.row.nb <- c(removed.row.nb, tempo) - removed.rows <- rbind(removed.rows, data1[tempo, ], stringsAsFactors = FALSE) # here data1 used because categorical columns tested - if(length(tempo) != 0){ - data1 <- data1[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former - data1.ini <- data1.ini[-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former - for(i3 in 1:length(column.check)){ - if(any( ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]]), na.rm = TRUE)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN COLUMN ", column.check[i3], " OF data1, THE FOLLOWING CLASSES HAVE DISAPPEARED AFTER NA/Inf REMOVAL (IF COLUMN USED IN THE PLOT, THIS CLASS WILL NOT BE DISPLAYED):\n", paste(unique(removed.rows[, column.check[i3]])[ ! unique(removed.rows[, column.check[i3]]) %in% unique(data1[, column.check[i3]])], collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - } - for(i2 in 1:length(column.check)){ - if(column.check[i2] == "categ.color"){ - categ.color <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])] # remove the absent color in the character vector - if(length(categ.color)== 1L & length(unlist(categ.class.order[length(categ)])) > 1){ # to deal with single color - categ.color <- rep(categ.color, length(unlist(categ.class.order[length(categ)]))) - } - data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(categ.color)) - } - if(column.check[i2] == "dot.color"){ - dot.color <- levels(data1[, column.check[i2]])[levels(data1[, column.check[i2]]) %in% unique(data1[, column.check[i2]])] # remove the absent color in the character vector - if(length(dot.color)== 1L & length(dot.categ.class.order) > 1){ # to deal with single color. If dot.categ.class.order == NULL (which is systematically the case if dot.categ == NULL), no rep(dot.color, length(dot.categ.class.order) - dot.color <- rep(dot.color, length(dot.categ.class.order)) - } - data1[, column.check[i2]] <- factor(as.character(data1[, column.check[i2]]), levels = unique(dot.color)) - } - } - } - # end na detection and removal (done now to be sure of the correct length of categ) - # From here, data1 and data.ini have no more NA or NaN - # end other checkings - # reserved word checking - #already done above - # end reserved word checking - # end second round of checking and data preparation - - - # package checking - fun_pack(req.package = c( - "ggplot2", - "gridExtra", - "lemon", - "scales" - ), lib.path = lib.path) - # end package checking - - - - - - # main code - # y coordinates recovery (create ini.box.coord, dot.coord and modify data1) - if(length(categ)== 1L){ - # width commputations - box.width2 <- box.width - box.space <- 0 # to inactivate the shrink that add space between grouped boxes, because no grouped boxes here - # end width commputations - # data1 check categ order for dots coordinates recovery - data1 <- data.frame(data1, categ.check = data1[, categ[1]], stringsAsFactors = TRUE) - data1$categ.check <- as.integer(data1$categ.check) # to check that data1[, categ[1]] and dot.coord$group are similar, during merging - # end data1 check categ order for dots coordinates recovery - # per box dots coordinates recovery - tempo.gg.name <- "gg.indiv.plot." - tempo.gg.count <- 0 - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[1]), stroke = dot.border.size, size = dot.size, alpha = dot.alpha, shape = 21)) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = if(is.null(categ.color)){rep(NA, length(unique(data1[, categ[1]])))}else if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[1]])))}else{categ.color})) # categ.color used for dot colors because at that stage, we do not care about colors - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, fill = categ[1]), coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf})) # fill because this is what is used with geom_box # to easily have the equivalent of the grouped boxes - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[1]])))}else{categ.color})) - # end per box dots coordinates recovery - }else if(length(categ) == 2L){ - # width commputations - box.width2 <- box.width / length(unique(data1[, categ[length(categ)]])) # real width of each box in x-axis unit, among the set of grouped box. Not relevant if no grouped boxes length(categ)== 1L - # end width commputations - # data1 check categ order for dots coordinates recovery - tempo.factor <- paste0(data1[order(data1[, categ[2]], data1[, categ[1]]), categ[2]], "_", data1[order(data1[, categ[2]], data1[, categ[1]]), categ[1]]) - data1 <- data.frame(data1[order(data1[, categ[2]], data1[, categ[1]]), ], categ.check = factor(tempo.factor, levels = unique(tempo.factor)), stringsAsFactors = TRUE) - data1$categ.check <- as.integer(data1$categ.check) - # end data1 check categ order for dots coordinates recovery - # per box dots coordinates recovery - tempo.gg.name <- "gg.indiv.plot." - tempo.gg.count <- 0 - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[2]), stroke = dot.border.size, size = dot.size, alpha = dot.alpha, shape = 21)) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = if(is.null(categ.color)){rep(NA, length(unique(data1[, categ[2]])))}else if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[2]])))}else{categ.color})) # categ.color used for dot colors because at that stage, we do not care about colors - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, fill = categ[2]), coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf})) # fill because this is what is used with geom_box # to easily have the equivalent of the grouped boxes - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[2]])))}else{categ.color})) - # end per box dots coordinates recovery - }else{ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if( ! is.null(stat.pos)){ - stat.just <- fun_gg_just( - angle = stat.angle, - pos = ifelse( - vertical == TRUE, - ifelse(stat.pos == "top", "bottom", "top"), # "bottom" because we want justification for text that are below the ref point which is the top of the graph. The opposite for "above" - ifelse(stat.pos == "top", "left", "right") # "left" because we want justification for text that are on the left of the ref point which is the right border of the graph. The opposite for "above" - ), - kind = "text" - ) - } - # has in fact no interest because ggplot2 does not create room for geom_text() - tempo.data.max <- data1[which.max(data1[, y]), ] - tempo.data.max <- data.frame(tempo.data.max, label = formatC(tempo.data.max[, y], digit = 2, drop0trailing = TRUE, format = "f"), stringsAsFactors = TRUE) - # end has in fact no interest because ggplot2 does not create room for geom_text() - tempo.graph.info.ini <- ggplot2::ggplot_build(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if( ! is.null(stat.pos)){' + ggplot2::geom_text(data = tempo.data.max, mapping = ggplot2::aes_string(x = 1, y = y, label = "label"), size = stat.size, color = "black", angle = stat.angle, hjust = stat.just$hjust, vjust = stat.just$vjust)'})))) # added here to have room for annotation - dot.coord <- tempo.graph.info.ini$data[[1]] - dot.coord$x <- as.numeric(dot.coord$x) # because weird class - dot.coord$PANEL <- as.numeric(dot.coord$PANEL) # because numbers as levels. But may be a problem is facet are reordered ? - tempo.mean <- aggregate(x = dot.coord$y, by = list(dot.coord$group, dot.coord$PANEL), FUN = mean, na.rm = TRUE) - names(tempo.mean)[names(tempo.mean) == "x"] <- "MEAN" - names(tempo.mean)[names(tempo.mean) == "Group.1"] <- "BOX" - names(tempo.mean)[names(tempo.mean) == "Group.2"] <- "PANEL" - dot.coord <- data.frame( - dot.coord[order(dot.coord$group, dot.coord$y), ], # dot.coord$PANEL deals below - y.check = as.double(data1[order(data1$categ.check, data1[, y]), y]), - categ.check = data1[order(data1$categ.check, data1[, y]), "categ.check"], - dot.color = if(is.null(dot.color)){NA}else{data1[order(data1$categ.check, data1[, y]), "dot.color"]}, - data1[order(data1$categ.check, data1[, y]), ][categ], # avoid the renaming below - stringsAsFactors = TRUE - ) # y.check to be sure that the order is the same between the y of data1 and the y of dot.coord - # names(dot.coord)[names(dot.coord) == "tempo.categ1"] <- categ[1] - if( ! is.null(dot.categ)){ - dot.coord <- data.frame(dot.coord, data1[order(data1$categ.check, data1[, y]), ][dot.categ], stringsAsFactors = TRUE) # avoid the renaming - } - if( ! is.null(facet.categ)){ - dot.coord <- data.frame(dot.coord, data1[order(data1$categ.check, data1[, y]), ][facet.categ], stringsAsFactors = TRUE) # for facet panels - tempo.test <- NULL - for(i2 in 1:length(facet.categ)){ - tempo.test <- paste0(tempo.test, ".", formatC(as.numeric(dot.coord[, facet.categ[i2]]), width = nchar(max(as.numeric(dot.coord[, facet.categ[i2]]), na.rm = TRUE)), flag = "0")) # convert factor into numeric with leading zero for proper ranking # merge the formatC() to create a new factor. The convertion to integer should recreate the correct group number. Here as.numeric is used and not as.integer in case of numeric in facet.categ (because comes from add and not checked by fun_check, contrary to categ) - } - tempo.test <- as.integer(factor(tempo.test)) - if( ! identical(as.integer(dot.coord$PANEL), tempo.test)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nas.integer(dot.coord$PANEL) AND tempo.test MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(dot.tidy == TRUE){ - if( ! is.null(dot.categ)){ - dot.coord <- data.frame(dot.coord, tidy_group = data1[order(data1$categ.check, data1[, y]), ][, dot.categ], stringsAsFactors = TRUE) # avoid the renaming - # tidy_group_coord is to be able to fuse table when creating the table for dot coordinates - if(dot.categ %in% categ){ - dot.coord <- data.frame(dot.coord, tidy_group_coord = dot.coord$group, stringsAsFactors = TRUE) - }else{ - dot.coord <- data.frame(dot.coord, tidy_group_coord = as.integer(factor(paste0( - formatC(as.integer(dot.coord[, categ[1]]), width = nchar(max(as.integer(dot.coord[, categ[1]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking - ".", - if(length(categ) == 2L){formatC(as.integer(dot.coord[, categ[2]]), width = nchar(max(as.integer(dot.coord[, categ[2]]), na.rm = TRUE)), flag = "0")}, # convert factor into numeric with leading zero for proper ranking - if(length(categ) == 2L){"."}, - formatC(as.integer(dot.coord[, dot.categ]), width = nchar(max(as.integer(dot.coord[, dot.categ]), na.rm = TRUE)), flag = "0") # convert factor into numeric with leading zero for proper ranking - )), stringsAsFactors = TRUE) # merge the 2 or 3 formatC() to create a new factor. The convertion to integer should recreate the correct group number - ) # for tidy dot plots - } - }else{ - dot.coord <- data.frame(dot.coord, tidy_group = if(length(categ)== 1L){ - dot.coord[, categ]}else{as.integer(factor(paste0( - formatC(as.integer(dot.coord[, categ[1]]), width = nchar(max(as.integer(dot.coord[, categ[1]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking - ".", - formatC(as.integer(dot.coord[, categ[2]]), width = nchar(max(as.integer(dot.coord[, categ[2]]), na.rm = TRUE)), flag = "0")# convert factor into numeric with leading zero for proper ranking - )), stringsAsFactors = TRUE) # merge the 2 formatC() to create a new factor. The convertion to integer should recreate the correct group number - }) # for tidy dot plots - # tidy_group_coord is to be able to fuse table when creating the table for dot coordinates - dot.coord <- data.frame(dot.coord, tidy_group_coord = dot.coord$group, stringsAsFactors = TRUE) - } - } - if( ! (identical(dot.coord$y, dot.coord$y.check) & identical(dot.coord$group, dot.coord$categ.check))){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\n(dot.coord$y AND dot.coord$y.check) AS WELL AS (dot.coord$group AND dot.coord$categ.check) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - if( ! identical(tempo.mean[order(tempo.mean$BOX, tempo.mean$PANEL), ]$BOX, unique(dot.coord[order(dot.coord$group, dot.coord$PANEL), c("group", "PANEL")])$group)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\n(tempo.mean$BOX, tempo.mean$PANEL) AND (dot.coord$group, dot.coord$PANEL) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - tempo <- unique(dot.coord[order(dot.coord$group, dot.coord$PANEL), c(categ, if( ! is.null(dot.color) & ! is.null(dot.categ)){if(dot.categ != ini.dot.categ){dot.categ}}, if( ! is.null(facet.categ)){facet.categ}), drop = FALSE]) - # names(tempo) <- paste0(names(tempo), ".mean") - tempo.mean <- data.frame(tempo.mean[order(tempo.mean$BOX, tempo.mean$PANEL), ], tempo, stringsAsFactors = TRUE) - } - } - # at that stage, categ color and dot color are correctly attributed in data1, box.coord and dot.coord - # end y dot coordinates recovery (create ini.box.coord, dot.coord and modify data1) - # ylim range - if(is.null(y.lim)){ - y.lim <- tempo.graph.info.ini$layout$panel_params[[1]]$y.range # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - if(any(( ! is.finite(y.lim)) | is.na(y.lim)) | length(y.lim) != 2){ # kept but normally no more Inf in data1 # normally no NA with is.finite, etc. - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\ntempo.graph.info.ini$layout$panel_params[[1]]$y.range[1] CONTAINS NA OR Inf OR HAS LENGTH 1") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else if(y.log != "no"){ - y.lim <- get(y.log)(y.lim) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - } - if(y.log != "no"){ - # normally this control is not necessary anymore - if(any( ! is.finite(y.lim))){ # normally no NA with is.finite - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " "), "\nPLEASE, CHECK DATA VALUES (PRESENCE OF ZERO OR INF VALUES)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - if(suppressWarnings(all(y.lim %in% c(Inf, -Inf)))){ # all() without na.rm -> ok because y.lim cannot be NA (tested above) - # normally this control is not necessary anymore - tempo.cat <- paste0("ERROR IN ", function.name, " y.lim CONTAINS Inf VALUES, MAYBE BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY OR BECAUSE OF LOG SCALE REQUIREMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - if(suppressWarnings(any(is.na(y.lim)))){ # normally no NA with is.na - # normally this control is not necessary anymore - tempo.cat <- paste0("ERROR IN ", function.name, " y.lim CONTAINS NA OR NaN VALUES, MAYBE BECAUSE VALUES FROM data1 ARGUMENTS ARE NA OR Inf ONLY OR BECAUSE OF LOG SCALE REQUIREMENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - y.lim.order <- order(y.lim) # to deal with inverse axis - y.lim <- sort(y.lim) - y.lim[1] <- y.lim[1] - abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.bottom.extra.margin, y.top.extra.margin) # diff(y.lim.order) > 0 medians not inversed axis - y.lim[2] <- y.lim[2] + abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.top.extra.margin, y.bottom.extra.margin) # diff(y.lim.order) > 0 medians not inversed axis - if(y.include.zero == TRUE){ # no need to check y.log != "no" because done before - y.lim <- range(c(y.lim, 0), na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - } - y.lim <- y.lim[y.lim.order] - if(any(is.na(y.lim))){ # normally no NA with is.na - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 2") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end ylim range - - - - - - - # drawing - # constant part - tempo.gg.name <- "gg.indiv.plot." - tempo.gg.count <- 0 - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add is directly put here to deal with additional variable of data, like when using facet_grid. No problem if add is a theme, will be dealt below - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::xlab(if(is.null(x.lab)){categ[1]}else{x.lab})) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ylab(if(is.null(y.lab)){y}else{y.lab})) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) - # text angle management - axis.just <- fun_gg_just(angle = x.angle, pos = ifelse(vertical == TRUE, "bottom", "left"), kind = "axis") - # end text angle management - add.check <- TRUE - if( ! is.null(add)){ # if add is NULL, then = 0 - if(grepl(pattern = "ggplot2\\s*::\\s*theme", add) == TRUE){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT\n-> INTERNAL GGPLOT2 THEME FUNCTIONS theme() AND theme_classic() HAVE BEEN INACTIVATED, TO BE USED BY THE USER\n-> article ARGUMENT WILL BE IGNORED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - add.check <- FALSE - } - } - if(add.check == TRUE & article == TRUE){ - # WARNING: not possible to add theme()several times. NO message but the last one overwrites the others - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_classic(base_size = text.size)) - if(grid == TRUE){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( - text = ggplot2::element_text(size = text.size), - plot.title = ggplot2::element_text(size = title.text.size), # stronger than text - line = ggplot2::element_line(size = 0.5), - legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend - axis.line.y.left = ggplot2::element_line(colour = "black"), # draw lines for the y axis - axis.line.x.bottom = ggplot2::element_line(colour = "black"), # draw lines for the x axis - panel.grid.major.x = if(vertical == TRUE){NULL}else{ggplot2::element_line(colour = "grey85", size = 0.75)}, - panel.grid.major.y = if(vertical == TRUE){ggplot2::element_line(colour = "grey85", size = 0.75)}else{NULL}, - panel.grid.minor.y = if(vertical == TRUE){ggplot2::element_line(colour = "grey90", size = 0.25)}else{NULL}, - axis.text.x = if(vertical == TRUE){ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}else{NULL}, - axis.text.y = if(vertical == TRUE){NULL}else{ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}, - strip.background = ggplot2::element_rect(fill = NA, colour = NA) # for facet background - )) - }else{ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( - text = ggplot2::element_text(size = text.size), - plot.title = ggplot2::element_text(size = title.text.size), # stronger than text - line = ggplot2::element_line(size = 0.5), - legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend - axis.line.y.left = ggplot2::element_line(colour = "black"), - axis.line.x.bottom = ggplot2::element_line(colour = "black"), - axis.text.x = if(vertical == TRUE){ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}else{NULL}, - axis.text.y = if(vertical == TRUE){NULL}else{ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}, - strip.background = ggplot2::element_rect(fill = NA, colour = NA) - )) - } - }else if(add.check == TRUE & article == FALSE){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( - text = ggplot2::element_text(size = text.size), - plot.title = ggplot2::element_text(size = title.text.size), # stronger than text - line = ggplot2::element_line(size = 0.5), - legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend - panel.background = ggplot2::element_rect(fill = "grey95"), - axis.line.y.left = ggplot2::element_line(colour = "black"), - axis.line.x.bottom = ggplot2::element_line(colour = "black"), - panel.grid.major.x = ggplot2::element_line(colour = "grey85", size = 0.75), - panel.grid.major.y = ggplot2::element_line(colour = "grey85", size = 0.75), - panel.grid.minor.x = ggplot2::element_blank(), - panel.grid.minor.y = ggplot2::element_line(colour = "grey90", size = 0.25), - strip.background = ggplot2::element_rect(fill = NA, colour = NA), - axis.text.x = if(vertical == TRUE){ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)}else{NULL}, - axis.text.y = if(vertical == TRUE){NULL}else{ggplot2::element_text(angle = axis.just$angle, hjust = axis.just$hjust, vjust = axis.just$vjust)} - )) - } - # Contrary to fun_gg_bar(), cannot plot the boxplot right now, because I need the dots plotted first - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, group = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), color = NA, width = box.width, fill = NA)) # this is to set the graph (i.e., a blanck boxplot to be able to use x coordinates to plot dots before boxes) - # end constant part - - - - - # graphic info recovery (including means) - tempo.graph.info <- ggplot2::ggplot_build(eval(parse(text = paste0(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), ' + ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, fill = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), width = box.width, notch = box.notch, coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf}) + ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[length(categ)]])))}else{categ.color})')))) # will be recovered later again, when ylim will be considered - tempo.yx.ratio <- (tempo.graph.info$layout$panel_params[[1]]$y.range[2] - tempo.graph.info$layout$panel_params[[1]]$y.range[1]) / (tempo.graph.info$layout$panel_params[[1]]$x.range[2] - tempo.graph.info$layout$panel_params[[1]]$x.range[1]) - box.coord <- tempo.graph.info$data[[2]] # to have the summary statistics of the plot. Contrary to ini.box.plot, now integrates ylim Here because can be required for stat.pos when just box are plotted - box.coord$x <- as.numeric(box.coord$x) # because x is of special class that block comparison of values using identical - box.coord$PANEL <- as.numeric(box.coord$PANEL) # because numbers as levels. But may be a problem is facet are reordered ? - box.coord <- box.coord[order(box.coord$group, box.coord$PANEL), ] - if( ! (identical(tempo.mean$BOX, box.coord$group) & identical(tempo.mean$PANEL, box.coord$PANEL))){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nidentical(tempo.mean$BOX, box.coord$group) & identical(tempo.mean$PANEL, box.coord$PANEL) DO NOT HAVE THE SAME VALUE ORDER") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - # tempo <- c(categ, if( ! is.null(dot.color) & ! is.null(dot.categ)){if(dot.categ != ini.dot.categ){dot.categ}}, if( ! is.null(facet.categ)){facet.categ}) - if(any(names(tempo.mean) %in% names(box.coord), na.rm = TRUE)){ - names(tempo.mean)[names(tempo.mean) %in% names(box.coord)] <- paste0(names(tempo.mean)[names(tempo.mean) %in% names(box.coord)], ".mean") - } - box.coord <- data.frame(box.coord, tempo.mean, stringsAsFactors = TRUE) - } - # end graphic info recovery (including means) - - - - # stat output (will also serve for boxplot and mean display) - # x not added now (to do not have them in stat.nolog) - stat <- data.frame( - MIN = box.coord$ymin_final, - QUART1 = box.coord$lower, - MEDIAN = box.coord$middle, - MEAN = box.coord$MEAN, - QUART3 = box.coord$upper, - MAX = box.coord$ymax_final, - WHISK_INF = box.coord$ymin, - BOX_INF = box.coord$lower, - NOTCH_INF = box.coord$notchlower, - NOTCH_SUP = box.coord$notchupper, - BOX_SUP = box.coord$upper, - WHISK_SUP = box.coord$ymax, - OUTLIERS = box.coord["outliers"], - tempo.mean[colnames(tempo.mean) != "MEAN"], - COLOR = box.coord$fill, - stringsAsFactors = TRUE - ) # box.coord["outliers"] written like this because it is a list. X coordinates not put now because several features to set - names(stat)[names(stat) == "outliers"] <- "OUTLIERS" - stat.nolog <- stat # stat.nolog ini will serve for outputs - if(y.log != "no"){ - stat.nolog[c("MIN", "QUART1", "MEDIAN", "MEAN", "QUART3", "MAX", "WHISK_INF", "BOX_INF", "NOTCH_INF", "NOTCH_SUP", "BOX_SUP", "WHISK_SUP")] <- ifelse(y.log == "log2", 2, 10)^(stat.nolog[c("MIN", "QUART1", "MEDIAN", "MEAN", "QUART3", "MAX", "WHISK_INF", "BOX_INF", "NOTCH_INF", "NOTCH_SUP", "BOX_SUP", "WHISK_SUP")]) - stat.nolog$OUTLIERS <- lapply(stat.nolog$OUTLIERS, FUN = function(X){ifelse(y.log == "log2", 2, 10)^X}) - } - # end stat output (will also serve for boxplot and mean display) - - - - - - - # x coordinates management (for random plotting and for stat display) - # width commputations - width.ini <- c(box.coord$xmax - box.coord$xmin)[1] # all the box widths are equal here. Only the first one taken - width.correct <- width.ini * box.space / 2 - if( ! (identical(stat$BOX, box.coord$group) & identical(stat$PANEL, box.coord$PANEL))){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nidentical(stat$BOX, box.coord$group) & identical(stat$PANEL, box.coord$PANEL) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - stat <- data.frame( - stat, - X = box.coord$x, - X_BOX_INF = box.coord$xmin + width.correct, - X_BOX_SUP = box.coord$xmax - width.correct, - X_NOTCH_INF = box.coord$x - (box.coord$x - (box.coord$xmin + width.correct)) / 2, - X_NOTCH_SUP = box.coord$x + (box.coord$x - (box.coord$xmin + width.correct)) / 2, - X_WHISK_INF = box.coord$x - (box.coord$x - (box.coord$xmin + width.correct)) * box.whisker.width, - X_WHISK_SUP = box.coord$x + (box.coord$x - (box.coord$xmin + width.correct)) * box.whisker.width, - # tempo.mean[colnames(tempo.mean) != "MEAN"], # already added above - stringsAsFactors = TRUE - ) - stat$COLOR <- factor(stat$COLOR, levels = unique(categ.color)) - if( ! all(stat$NOTCH_SUP < stat$BOX_SUP & stat$NOTCH_INF > stat$BOX_INF, na.rm = TRUE) & box.notch == TRUE){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") SOME NOTCHES ARE BEYOND BOX HINGES. TRY ARGUMENT box.notch = FALSE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - dot.jitter <- c((box.coord$xmax - width.correct) - (box.coord$xmin + width.correct))[1] * dot.jitter # real dot.jitter. (box.coord$xmin + width.correct) - (box.coord$xmax - width.correct))[1] is the width of the box. Is equivalent to (box.coord$x - (box.coord$xmin + width.correct))[1] * 2 - # end width commputations - if( ! is.null(dot.color)){ - # random dots - if(dot.tidy == FALSE){ - dot.coord.rd1 <- merge(dot.coord, box.coord[c("fill", "PANEL", "group", "x")], by = c("PANEL", "group"), sort = FALSE) # rd for random. Send the coord of the boxes into the coord data.frame of the dots (in the column x.y). WARNING: by = c("PANEL", "group") without fill column because PANEL & group columns are enough as only one value of x column per group number in box.coord. Thus, no need to consider fill column - if(nrow(dot.coord.rd1) != nrow(dot.coord)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.rd1 DATA FRAME. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - sampled.dot.jitter <- if(nrow(dot.coord.rd1)== 1L){runif(n = nrow(dot.coord.rd1), min = - dot.jitter / 2, max = dot.jitter / 2)}else{sample(x = runif(n = nrow(dot.coord.rd1), min = - dot.jitter / 2, max = dot.jitter / 2), size = nrow(dot.coord.rd1), replace = FALSE)} - dot.coord.rd2 <- data.frame(dot.coord.rd1, dot.x = dot.coord.rd1$x.y + sampled.dot.jitter, stringsAsFactors = TRUE) # set the dot.jitter thanks to runif and dot.jitter range. Then, send the coord of the boxes into the coord data.frame of the dots (in the column x.y) - if(length(categ)== 1L){ - tempo.data1 <- unique(data.frame(data1[categ[1]], group = as.integer(data1[, categ[1]]), stringsAsFactors = TRUE)) # categ[1] is factor - names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") - verif <- paste0(categ[1], ".check") - }else if(length(categ) == 2L){ - tempo.data1 <- unique( - data.frame( - data1[c(categ[1], categ[2])], - group = as.integer(factor(paste0( - formatC(as.integer(data1[, categ[2]]), width = nchar(max(as.integer(data1[, categ[2]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking - ".", - formatC(as.integer(data1[, categ[1]]), width = nchar(max(as.integer(data1[, categ[1]]), na.rm = TRUE)), flag = "0")# convert factor into numeric with leading zero for proper ranking - )), stringsAsFactors = TRUE) # merge the 2 formatC() to create a new factor. The convertion to integer should recreate the correct group number - ) - ) # categ[2] first if categ[2] is used to make the categories in ggplot and categ[1] is used to make the x-axis - names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") - names(tempo.data1)[names(tempo.data1) == categ[2]] <- paste0(categ[2], ".check") - verif <- c(paste0(categ[1], ".check"), paste0(categ[2], ".check")) - }else{ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 3") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - dot.coord.rd3 <- merge(dot.coord.rd2, tempo.data1, by = intersect("group", "group"), sort = FALSE) # send the factors of data1 into coord. WARNING: I have replaced by = "group" by intersect("group", "group") because of an error due to wrong group group merging in dot.coord.rd3 - if(nrow(dot.coord.rd3) != nrow(dot.coord) | ( ! fun_comp_2d(dot.coord.rd3[categ], dot.coord.rd3[verif])$identical.content)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.rd3 DATA FRAME. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end random dots - } - # tidy dots - # coordinates are recovered during plotting (see dot.coord.tidy1 below) - # end tidy dots - } - # end x coordinates management (for random plotting and for stat display) - - - - - - # boxplot display before dot display if box.fill = TRUE - coord.names <- NULL - # creation of the data frame for (main box + legend) and data frame for means - if(box.notch == FALSE){ - for(i3 in 1:length(categ)){ - if(i3== 1L){ - tempo.polygon <- data.frame(GROUPX = c(t(stat[, rep(categ[i3], 5)])), stringsAsFactors = TRUE) - }else{ - tempo.polygon <- cbind(tempo.polygon, c(t(stat[, rep(categ[i3], 5)])), stringsAsFactors = TRUE) - } - } - names(tempo.polygon) <- categ - tempo.polygon <- data.frame(X = c(t(stat[, c("X_BOX_INF", "X_BOX_SUP", "X_BOX_SUP", "X_BOX_INF", "X_BOX_INF")])), Y = c(t(stat[, c("BOX_INF", "BOX_INF", "BOX_SUP", "BOX_SUP", "BOX_INF")])), COLOR = c(t(stat[, c("COLOR", "COLOR", "COLOR", "COLOR", "COLOR")])), BOX = as.character(c(t(stat[, c("BOX", "BOX", "BOX", "BOX", "BOX")]))), tempo.polygon, stringsAsFactors = TRUE) - if( ! is.null(facet.categ)){ - for(i4 in 1:length(facet.categ)){ - tempo.polygon <- data.frame(tempo.polygon, c(t(stat[, c(facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4])])), stringsAsFactors = TRUE) - names(tempo.polygon)[length(names(tempo.polygon))] <- facet.categ[i4] - } - } - }else{ - for(i3 in 1:length(categ)){ - if(i3== 1L){ - tempo.polygon <- data.frame(GROUPX = c(t(stat[, rep(categ[i3], 11)])), stringsAsFactors = TRUE) - }else{ - tempo.polygon <- cbind(tempo.polygon, c(t(stat[, rep(categ[i3], 11)])), stringsAsFactors = TRUE) - } - } - names(tempo.polygon) <- categ - tempo.polygon <- data.frame(X = c(t(stat[, c("X_BOX_INF", "X_BOX_SUP", "X_BOX_SUP", "X_NOTCH_SUP", "X_BOX_SUP", "X_BOX_SUP", "X_BOX_INF", "X_BOX_INF", "X_NOTCH_INF", "X_BOX_INF", "X_BOX_INF")])), Y = c(t(stat[, c("BOX_INF", "BOX_INF", "NOTCH_INF", "MEDIAN", "NOTCH_SUP", "BOX_SUP", "BOX_SUP", "NOTCH_SUP", "MEDIAN", "NOTCH_INF", "BOX_INF")])), COLOR = c(t(stat[, c("COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR", "COLOR")])), BOX = as.character(c(t(stat[, c("BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX", "BOX")]))), tempo.polygon, stringsAsFactors = TRUE) - if( ! is.null(facet.categ)){ - for(i4 in 1:length(facet.categ)){ - tempo.polygon <- data.frame(tempo.polygon, c(t(stat[, c(facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4], facet.categ[i4])])), stringsAsFactors = TRUE) - names(tempo.polygon)[length(names(tempo.polygon))] <- facet.categ[i4] - } - } - } - tempo.polygon$COLOR <- factor(tempo.polygon$COLOR, levels = unique(categ.color)) - if( ! is.null(categ.class.order)){ - for(i3 in 1:length(categ)){ - tempo.polygon[, categ[i3]] <- factor(tempo.polygon[, categ[i3]], levels = categ.class.order[[i3]]) - } - } - # modified name of dot.categ column (e.g., "Categ1_DOT") must be included for boxplot using ridy dots - if( ! is.null(dot.color) & ! is.null(dot.categ)){ - if(dot.categ != ini.dot.categ){ - tempo.polygon <- data.frame(tempo.polygon, GROUPX = tempo.polygon[, ini.dot.categ], stringsAsFactors = TRUE) - names(tempo.polygon)[names(tempo.polygon) == "GROUPX"] <- dot.categ - - } - } - tempo.diamon.mean <- data.frame(X = c(t(stat[, c("X", "X_NOTCH_INF", "X", "X_NOTCH_SUP", "X")])), Y = c(t(cbind(stat["MEAN"] - (stat[, "X"] - stat[, "X_NOTCH_INF"]) * tempo.yx.ratio, stat["MEAN"], stat["MEAN"] + (stat[, "X"] - stat[, "X_NOTCH_INF"]) * tempo.yx.ratio, stat["MEAN"], stat["MEAN"] - (stat[, "X"] - stat[, "X_NOTCH_INF"]) * tempo.yx.ratio, stringsAsFactors = TRUE))), COLOR = c(t(stat[, c("COLOR", "COLOR", "COLOR", "COLOR", "COLOR")])), GROUP = c(t(stat[, c("BOX", "BOX", "BOX", "BOX", "BOX")])), stringsAsFactors = TRUE) # stringsAsFactors = TRUE for cbind() because stat["MEAN"] is a data frame. Otherwise, stringsAsFactors is not an argument for cbind() on vectors - if( ! is.null(facet.categ)){ - for(i3 in 1:length(facet.categ)){ - tempo.diamon.mean <- data.frame(tempo.diamon.mean, c(t(stat[, c(facet.categ[i3], facet.categ[i3], facet.categ[i3], facet.categ[i3], facet.categ[i3])])), stringsAsFactors = TRUE) - names(tempo.diamon.mean)[length(names(tempo.diamon.mean))] <- facet.categ[i3] - } - } - tempo.diamon.mean$COLOR <- factor(tempo.diamon.mean$COLOR, levels = unique(categ.color)) - # end creation of the data frame for (main box + legend) and data frame for means - if(box.fill == TRUE){ - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[length(categ)], fill = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), width = box.width, size = box.line.size, notch = box.notch, coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf}, alpha = box.alpha, outlier.shape = if( ! is.null(dot.color)){NA}else{21}, outlier.color = if( ! is.null(dot.color)){NA}else{dot.border.color}, outlier.fill = if( ! is.null(dot.color)){NA}else{NULL}, outlier.size = if( ! is.null(dot.color)){NA}else{dot.size}, outlier.stroke = if( ! is.null(dot.color)){NA}else{dot.border.size}, outlier.alpha = if( ! is.null(dot.color)){NA}else{dot.alpha})) # the color, size, etc. of the outliers are dealt here. outlier.color = NA to do not plot outliers when dots are already plotted. Finally, boxplot redrawn (see below) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_polygon( - data = tempo.polygon, - mapping = ggplot2::aes_string(x = "X", y = "Y", group = "BOX", fill = categ[length(categ)], color = categ[length(categ)]), - size = box.line.size, - alpha = box.alpha # works only for fill, not for color - )) - coord.names <- c(coord.names, "main.box") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha)) # - coord.names <- c(coord.names, "sup.whisker") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_INF, yend = WHISK_INF, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha)) # - coord.names <- c(coord.names, "inf.whisker") - if(box.whisker.width > 0){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha, lineend = "round")) # - coord.names <- c(coord.names, "sup.whisker.edge") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_INF, yend = WHISK_INF, group = categ[length(categ)]), color = "black", size = box.line.size, alpha = box.alpha, lineend = "round")) # - coord.names <- c(coord.names, "inf.whisker.edge") - } - if(box.mean == TRUE){ - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = stat, mapping = ggplot2::aes_string(x = "X", y = "MEAN", group = categ[length(categ)]), shape = 23, stroke = box.line.size * 2, fill = stat$COLOR, size = box.mean.size, color = "black", alpha = box.alpha)) # group used in aesthetic to do not have it in the legend - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_polygon( - data = tempo.diamon.mean, - mapping = ggplot2::aes(x = X, y = Y, group = GROUP), - fill = tempo.diamon.mean[, "COLOR"], - color = hsv(0, 0, 0, alpha = box.alpha), # outline of the polygon in black but with alpha - size = box.line.size, - alpha = box.alpha - )) - coord.names <- c(coord.names, "mean") - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = if(box.notch == FALSE){X_BOX_INF}else{X_NOTCH_INF}, xend = if(box.notch == FALSE){X_BOX_SUP}else{X_NOTCH_SUP}, y = MEDIAN, yend = MEDIAN, group = categ[length(categ)]), color = "black", size = box.line.size * 2, alpha = box.alpha)) # - coord.names <- c(coord.names, "median") - } - # end boxplot display before dot display if box.fill = TRUE - - - - - - - # dot display - if( ! is.null(dot.color)){ - if(dot.tidy == FALSE){ - if(is.null(dot.categ)){ - if(dot.border.size == 0){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( - data = dot.coord.rd3, - mapping = ggplot2::aes_string(x = "dot.x", y = "y", group = categ[length(categ)]), - size = dot.size, - shape = 19, - color = dot.coord.rd3$dot.color, - alpha = dot.alpha - )) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic - }else{ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( - data = dot.coord.rd3, - mapping = ggplot2::aes_string(x = "dot.x", y = "y", group = categ[length(categ)]), - shape = 21, - stroke = dot.border.size, - color = if(is.null(dot.border.color)){dot.coord.rd3$dot.color}else{rep(dot.border.color, nrow(dot.coord.rd3))}, - size = dot.size, - fill = dot.coord.rd3$dot.color, - alpha = dot.alpha - )) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic - } - }else{ - if(dot.border.size == 0){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( - data = dot.coord.rd3, - mapping = ggplot2::aes_string(x = "dot.x", y = "y", alpha = dot.categ), - size = dot.size, - shape = 19, - color = dot.coord.rd3$dot.color - )) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic - }else{ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point( - data = dot.coord.rd3, - mapping = ggplot2::aes_string(x = "dot.x", y = "y", alpha = dot.categ), - size = dot.size, - shape = 21, - stroke = dot.border.size, - color = if(is.null(dot.border.color)){dot.coord.rd3$dot.color}else{rep(dot.border.color, nrow(dot.coord.rd3))}, - fill = dot.coord.rd3$dot.color - )) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", name = dot.legend.name, values = rep(dot.alpha, length(dot.categ.class.order)), guide = ggplot2::guide_legend(override.aes = list(fill = dot.color, color = if(is.null(dot.border.color)){dot.color}else{dot.border.color}, stroke = dot.border.size, alpha = dot.alpha)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor - } - coord.names <- c(coord.names, "dots") - }else if(dot.tidy == TRUE){ - # here plot using group -> no scale - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_dotplot( - data = dot.coord, - mapping = ggplot2::aes_string(x = categ[1], y = "y", group = "group"), # not dot.categ here because the classes of dot.categ create new separations - position = ggplot2::position_dodge(width = box.width), - binpositions = "all", - binaxis = "y", - stackdir = "center", - alpha = dot.alpha, - fill = dot.coord$dot.color, - stroke = dot.border.size, - color = if(is.null(dot.border.color)){dot.coord$dot.color}else{rep(dot.border.color, nrow(dot.coord))}, - show.legend = FALSE, # WARNING: do not use show.legend = TRUE because it uses the arguments outside aes() as aesthetics (here color and fill). Thus I must find a way using ggplot2::scale_discrete_manual() - binwidth = (y.lim[2] - y.lim[1]) / dot.tidy.bin.nb - )) # geom_dotplot ggplot2 v3.3.0: I had to remove rev() in fill and color # very weird behavior of geom_dotplot ggplot2 v3.2.1, (1) because with aes group = (to avoid legend), the dot plotting is not good in term of coordinates, and (2) because data1 seems reorderer according to x = categ[1] before plotting. Thus, I have to use fill = dot.coord[rev(order(dot.coord[, categ[1]], decreasing = TRUE)), "dot.color"] to have the good corresponding colors # show.legend option do not remove the legend, only the aesthetic of the legend (dot, line, etc.) - coord.names <- c(coord.names, "dots") - if( ! is.null(dot.categ)){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_dotplot( - data = dot.coord, - mapping = ggplot2::aes_string(x = categ[1], y = "y", alpha = dot.categ), # not dot.categ here because the classes of dot.categ create new separations - position = ggplot2::position_dodge(width = box.width), - binpositions = "all", - binaxis = "y", - stackdir = "center", - fill = NA, - stroke = NA, - color = NA, - # WARNING: do not use show.legend = TRUE because it uses the arguments outside aes() as aesthetics (here color and fill). Thus I must find a way using ggplot2::scale_discrete_manual() - binwidth = (y.lim[2] - y.lim[1]) / dot.tidy.bin.nb - )) # geom_dotplot ggplot2 v3.3.0: I had to remove rev() in fill and color # very weird behavior of geom_dotplot ggplot2 v3.2.1, (1) because with aes group = (to avoid legend), the dot plotting is not good in term of coordinates, and (2) because data1 seems reorderer according to x = categ[1] before plotting. Thus, I have to use fill = dot.coord[rev(order(dot.coord[, categ[1]], decreasing = TRUE)), "dot.color"] to have the good corresponding colors # show.legend option do not remove the legend, only the aesthetic of the legend (dot, line, etc.) - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "linetype", name = dot.legend.name, values = rep(1, length(categ.color)))) # values = rep("black", length(categ.color)) are the values of color (which is the border color of dots), and this modify the border color on the plot. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor - coord.names <- c(coord.names, "bad_remove") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", name = dot.legend.name, values = rep(dot.alpha, length(dot.categ.class.order)), labels = dot.categ.class.order, guide = ggplot2::guide_legend(title = if(ini.dot.categ == categ[length(categ)]){dot.categ}else{ini.dot.categ}, override.aes = list(fill = levels(dot.coord$dot.color), color = if(is.null(dot.border.color)){levels(dot.coord$dot.color)}else{dot.border.color}, stroke = dot.border.size, alpha = dot.alpha)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor - } - # coordinates of tidy dots - tempo.coord <- ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))$data # to have the tidy dot coordinates - if(length(which(sapply(X = tempo.coord, FUN = function(X){any(names(X) == "binwidth", na.rm = TRUE)}))) != 1){ # detect the compartment of tempo.coord which is the binned data frame - # if(length(which(sapply(tempo.coord, FUN = nrow) == nrow(data1))) > if(is.null(dot.categ)){1}else{2}){ # this does not work if only one dot per class, thus replaced by above # if(is.null(dot.categ)){1}else{2} because 1 dotplot if dot.categ is NULL and 2 dotplots if not, with the second being a blank dotplot with wrong coordinates. Thus take the first in that situation - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nEITHER MORE THAN 1 OR NO COMPARTMENT HAVING A DATA FRAME WITH binwidth AS COLUMN NAME IN THE tempo.coord LIST (FOR TIDY DOT COORDINATES). CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - # dot.coord.tidy1 <- tempo.coord[[which(sapply(tempo.coord, FUN = nrow) == nrow(data1))[1]]] # this does not work if only one dot per class, thus replaced by above # the second being a blank dotplot with wrong coordinates. Thus take the first whatever situation - dot.coord.tidy1 <- tempo.coord[[which(sapply(X = tempo.coord, FUN = function(X){any(names(X) == "binwidth", na.rm = TRUE)}))]] # detect the compartment of tempo.coord which is the binned data frame - dot.coord.tidy1$x <- as.numeric(dot.coord.tidy1$x) # because weird class - dot.coord.tidy1$PANEL <- as.numeric(dot.coord.tidy1$PANEL) # because numbers as levels. But may be a problem is facet are reordered ? - } - # tempo.box.coord <- merge(box.coord, unique(dot.coord[, c("PANEL", "group", categ)]), by = c("PANEL", "group"), sort = FALSE) # not required anymore because box.coord already contains categ do not add dot.categ and tidy_group_coord here because the coordinates are for stats. Add the categ in box.coord. WARNING: by = c("PANEL", "group") without fill column because PANEL & group columns are enough as only one value of x column per group number in box.coord. Thus, no need to consider fill column - # below inactivated because not true when dealing with dot.categ different from categ - # if(nrow(tempo.box.coord) != nrow(box.coord)){ - # tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT tempo.box.coord DATA FRAME. CODE HAS TO BE MODIFIED") - # stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - # } - dot.coord.tidy2 <- merge(dot.coord.tidy1, box.coord[c("fill", "PANEL", "group", "x", categ)], by = c("PANEL", "group"), sort = FALSE) # send the coord of the boxes into the coord data.frame of the dots (in the column x.y).WARNING: by = c("PANEL", "group") without fill column because PANEL & group columns are enough as only one value of x column per group number in tempo.box.coord. Thus, no need to consider fill colum # DANGER: from here the fill.y and x.y (from tempo.box.coord) are not good in dot.coord.tidy2. It is ok because Categ1 Categ2 from tempo.box.coord are ok with the group column from dot.coord.tidy1. This is due to the fact that dot.coord.tidy resulting from geom_dotplot does not make the same groups as the other functions - if(nrow(dot.coord.tidy2) != nrow(dot.coord)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.tidy2 DATA FRAME. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # From here, check for dot.coord.tidy3 which wil be important for stat over the plot. WARNING: dot.categ has nothing to do here for stat coordinates. Thus, not in tempo.data1 - if(length(categ)== 1L){ - tempo.data1 <- unique(data.frame(data1[categ[1]], group = as.integer(data1[, categ[1]]), stringsAsFactors = TRUE)) # categ[1] is factor - names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") - verif <- paste0(categ[1], ".check") - }else if(length(categ) == 2L){ - tempo.data1 <- unique( - data.frame( - data1[c(categ[1], categ[2])], - group = as.integer(factor(paste0( - formatC(as.integer(data1[, categ[2]]), width = nchar(max(as.integer(data1[, categ[2]]), na.rm = TRUE)), flag = "0"), # convert factor into numeric with leading zero for proper ranking - ".", - formatC(as.integer(data1[, categ[1]]), width = nchar(max(as.integer(data1[, categ[1]]), na.rm = TRUE)), flag = "0")# convert factor into numeric with leading zero for proper ranking - )), stringsAsFactors = TRUE) # merge the 2 formatC() to create a new factor. The convertion to integer should recreate the correct group number - ) - ) # categ[2] first if categ[2] is used to make the categories in ggplot and categ[1] is used to make the x-axis - names(tempo.data1)[names(tempo.data1) == categ[1]] <- paste0(categ[1], ".check") - names(tempo.data1)[names(tempo.data1) == categ[2]] <- paste0(categ[2], ".check") - verif <- c(paste0(categ[1], ".check"), paste0(categ[2], ".check")) - }else{ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 4") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - dot.coord.tidy3 <- merge(dot.coord.tidy2, tempo.data1, by = intersect("group", "group"), sort = FALSE) # send the factors of data1 into coord. WARNING: I have tested intersect("group", "group") instead of by = "group". May be come back to by = "group" in case of error. But I did this because of an error in dot.coord.rd3 above - if(nrow(dot.coord.tidy3) != nrow(dot.coord) | ( ! fun_comp_2d(dot.coord.tidy3[categ], dot.coord.tidy3[verif])$identical.content)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nTHE merge() FUNCTION DID NOT RETURN A CORRECT dot.coord.tidy3 DATA FRAME. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end coordinates of tidy dots - } - } - # end dot display - - - - # boxplot display (if box.fill = FALSE, otherwise, already plotted above) - if(box.fill == TRUE){ - # overcome "work only for the filling of boxes, not for the frame. See https://github.com/tidyverse/ggplot2/issues/252" - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[length(categ)]])))}else{categ.color}, guide = ggplot2::guide_legend(order = 1))) #, guide = ggplot2::guide_legend(override.aes = list(fill = levels(tempo.polygon$COLOR), color = "black")))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = rep(hsv(0, 0, 0, alpha = box.alpha), length(unique(data1[, categ[length(categ)]]))), guide = ggplot2::guide_legend(order = 1))) # , guide = ggplot2::guide_legend(override.aes = list(color = "black", alpha = box.alpha)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor # outline of the polygon in black but with alpha - }else{ - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_boxplot(data = data1, mapping = ggplot2::aes_string(x = categ[1], y = y, color = categ[length(categ)], fill = categ[length(categ)]), position = ggplot2::position_dodge(width = NULL), width = box.width, size = box.line.size, notch = box.notch, alpha = box.alpha, coef = if(box.whisker.kind == "no"){0}else if(box.whisker.kind == "std"){1.5}else if(box.whisker.kind == "max"){Inf}, outlier.shape = if( ! is.null(dot.color)){NA}else{21}, outlier.color = if( ! is.null(dot.color)){NA}else{if(dot.border.size == 0){NA}else{dot.border.color}}, outlier.fill = if( ! is.null(dot.color)){NA}else{NULL}, outlier.size = if( ! is.null(dot.color)){NA}else{dot.size}, outlier.stroke = if( ! is.null(dot.color)){NA}else{dot.border.size}, outlier.alpha = if( ! is.null(dot.color)){NA}else{dot.alpha})) # the color, size, etc. of the outliers are dealt here. outlier.color = NA to do not plot outliers when dots are already plotted - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_path( - data = tempo.polygon, - mapping = ggplot2::aes_string(x = "X", y = "Y", group = "BOX", color = categ[length(categ)]), - size = box.line.size, - alpha = box.alpha, - lineend = "round", - linejoin = "round" - )) - coord.names <- c(coord.names, "main.box") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = if(box.notch == FALSE){X_BOX_INF}else{X_NOTCH_INF}, xend = if(box.notch == FALSE){X_BOX_SUP}else{X_NOTCH_SUP}, y = MEDIAN, yend = MEDIAN, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size * 2, alpha = box.alpha)) # - coord.names <- c(coord.names, "median") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha)) # - coord.names <- c(coord.names, "sup.whisker") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X, xend = X, y = BOX_INF, yend = WHISK_INF, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha)) # - coord.names <- c(coord.names, "inf.whisker") - if(box.whisker.width > 0){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_SUP, yend = WHISK_SUP, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha, lineend = "round")) # - coord.names <- c(coord.names, "sup.whisker.edge") - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_segment(data = stat, mapping = ggplot2::aes(x = X_WHISK_INF, xend = X_WHISK_SUP, y = WHISK_INF, yend = WHISK_INF, group = categ[length(categ)]), color = stat$COLOR, size = box.line.size, alpha = box.alpha, lineend = "round")) # - coord.names <- c(coord.names, "inf.whisker.edge") - } - if(box.mean == TRUE){ - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_point(data = stat, mapping = ggplot2::aes_string(x = "X", y = "MEAN", group = categ[length(categ)]), shape = 23, stroke = box.line.size * 2, color = stat$COLOR, size = box.mean.size, fill = NA, alpha = box.alpha)) # group used in aesthetic to do not have it in the legend. Here ggplot2::scale_discrete_manual() cannot be used because of the group easthetic - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_path( - data = tempo.diamon.mean, - mapping = ggplot2::aes(x = X, y = Y, group = GROUP), - color = tempo.diamon.mean[, "COLOR"], - size = box.line.size, - alpha = box.alpha, - lineend = "round", - linejoin = "round" - )) - coord.names <- c(coord.names, "mean") - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "fill", name = box.legend.name, values = rep(NA, length(unique(data1[, categ[length(categ)]]))))) #, guide = ggplot2::guide_legend(override.aes = list(color = categ.color)))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "color", name = box.legend.name, values = if(length(categ.color)== 1L){rep(categ.color, length(unique(data1[, categ[length(categ)]])))}else{categ.color}, guide = ggplot2::guide_legend(override.aes = list(alpha = if(plot == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list()) == 0L & Sys.info()["sysname"] == "Windows"))){1}else{box.alpha})))) # , guide = ggplot2::guide_legend(override.aes = list(color = as.character(categ.color))))) # values are the values of color (which is the border color in geom_box. WARNING: values = categ.color takes the numbers to make the colors if categ.color is a factor - if(plot == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list()) == 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 - # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - if(box.alpha == 0){ # remove box legend because no boxes drawn - # add this after the scale_xxx_manual() for boxplots - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = FALSE, color = FALSE)) # inactivate the legend - } - # end boxplot display (if box.fill = FALSE, otherwise, already plotted above) - - - - - # stat display - # layer after dots but ok, behind dots on the plot - if( ! is.null(stat.pos)){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NUMBERS DISPLAYED ARE ", ifelse(stat.mean == FALSE, "MEDIANS", "MEANS")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - if(stat.pos == "top"){ - tempo.stat <- data.frame(stat, Y = y.lim[2], stringsAsFactors = TRUE) # I had to create a data frame for geom_tex() so that facet is taken into account, (ggplot2::annotate() does not deal with facet because no data and mapping arguments). Of note, facet.categ is in tempo.stat, via tempo.mean, via dot.coord - if(stat.mean == FALSE){tempo.stat$MEDIAN <- formatC(stat.nolog$MEDIAN, digit = 2, drop0trailing = TRUE, format = "f")}else{tempo.stat$MEAN <- formatC(stat.nolog$MEAN, digit = 2, drop0trailing = TRUE, format = "f")} - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text( - data = tempo.stat, - mapping = ggplot2::aes_string(x = "X", y = "Y", label = ifelse(stat.mean == FALSE, "MEDIAN", "MEAN")), - size = stat.size, - color = "black", - angle = stat.angle, - hjust = stat.just$hjust, - vjust = stat.just$vjust - )) # stat$X used here because identical to stat.nolog but has the X. WARNING: no need of order() for labels because box.coord$x set the order. For justification, see https://stackoverflow.com/questions/7263849/what-do-hjust-and-vjust-do-when-making-a-plot-using-ggplot - coord.names <- c(coord.names, "stat.pos") - }else if(stat.pos == "above"){ - # stat coordinates - if( ! is.null(dot.color)){ # for text just above max dot - if(dot.tidy == FALSE){ - tempo.stat.ini <- dot.coord.rd3 - }else if(dot.tidy == TRUE){ - tempo.stat.ini <- dot.coord.tidy3 - tempo.stat.ini$x.y <- tempo.stat.ini$x.x # this is just to be able to use tempo.stat.ini$x.y for untidy or tidy dots (remember that dot.coord.tidy3$x.y is not good, see above) - } - stat.coord1 <- aggregate(x = tempo.stat.ini["y"], by = {x.env <- if(length(categ)== 1L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]])}else if(length(categ) == 2L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]], tempo.stat.ini[, categ[2]])} ; names(x.env) <- if(length(categ)== 1L){c("group", "PANEL", "x.y", categ[1])}else if(length(categ) == 2L){c("group", "PANEL", "x.y", categ[1], categ[2])} ; x.env}, FUN = min, na.rm = TRUE) - names(stat.coord1)[names(stat.coord1) == "y"] <- "dot.min" - stat.coord2 <- aggregate(x = tempo.stat.ini["y"], by = {x.env <- if(length(categ)== 1L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]])}else if(length(categ) == 2L){list(tempo.stat.ini$group, tempo.stat.ini$PANEL, tempo.stat.ini$x.y, tempo.stat.ini[, categ[1]], tempo.stat.ini[, categ[2]])} ; names(x.env) <- if(length(categ)== 1L){c("group", "PANEL", "x.y", categ[1])}else if(length(categ) == 2L){c("group", "PANEL", "x.y", categ[1], categ[2])} ; x.env}, FUN = max, na.rm = TRUE) - names(stat.coord2) <- paste0(names(stat.coord2), "_from.dot.max") - names(stat.coord2)[names(stat.coord2) == "y_from.dot.max"] <- "dot.max" - stat.coord3 <- cbind(box.coord[order(box.coord$group, box.coord$PANEL), ], stat.coord1[order(stat.coord1$group, stat.coord1$x.y), ], stat.coord2[order(stat.coord2$group, stat.coord2$x.y), ], stringsAsFactors = TRUE) # - if( ! all(identical(round(stat.coord3$x, 9), round(as.numeric(stat.coord3$x.y), 9)), na.rm = TRUE)){ # as.numeric() because stat.coord3$x is class "mapped_discrete" "numeric" - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nFUSION OF box.coord, stat.coord1 AND stat.coord2 ACCORDING TO box.coord$x, stat.coord1$x.y AND stat.coord2$x.y IS NOT CORRECT. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - # text.coord <- stat.coord3[, c("x", "group", "dot.min", "dot.max")] - # names(text.coord)[names(text.coord) == "dot.min"] <- "text.min.pos" - #names(text.coord)[names(text.coord) == "dot.max"] <- "text.max.pos" - box.coord <- box.coord[order(box.coord$x, box.coord$group, box.coord$PANEL), ] - # text.coord <- text.coord[order(text.coord$x), ] # to be sure to have the two objects in the same order for x. WARNING: cannot add identical(as.integer(text.coord$group), as.integer(box.coord$group)) because with error, the correspondence between x and group is not the same - stat.coord3 <- stat.coord3[order(stat.coord3$x, stat.coord3$group, stat.coord3$PANEL), ] # to be sure to have the two objects in the same order for x. WARNING: cannot add identical(as.integer(text.coord$group), as.integer(box.coord$group)) because with error, the correspondence between x and group is not the same - if( ! (identical(box.coord$x, stat.coord3$x) & identical(box.coord$group, stat.coord3$group) & identical(box.coord$PANEL, stat.coord3$PANEL))){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\ntext.coord AND box.coord DO NOT HAVE THE SAME x, group AND PANEL COLUMN CONTENT") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - }else{ - stat.coord3 <- box.coord - } - stat.coord3 <- data.frame( - stat.coord3, - Y = stat.coord3[, ifelse( - is.null(dot.color), - ifelse(diff(y.lim) > 0, "ymax", "ymin"), - ifelse(diff(y.lim) > 0, "ymax_final", "ymin_final") - )], - stringsAsFactors = TRUE - ) # ymax is top whisker, ymax_final is top dot - # stat.coord3 <- data.frame(stat.coord3, Y = vector("numeric", length = nrow(stat.coord3)), stringsAsFactors = TRUE) - # check.Y <- as.logical(stat.coord3$Y) # convert everything in Y into FALSE (because Y is full of zero) - # end stat coordinates - # stat display - # performed twice: first for y values >=0, then y values < 0, because only a single value allowed for hjust anf vjust - if(stat.mean == FALSE){ - tempo.center.ref <- "middle" - }else{ - tempo.center.ref <- "MEAN" - } - # if(is.null(dot.color)){ - # tempo.low.ref <- "ymin" - # tempo.high.ref <- "ymax" - # }else{ - # tempo.low.ref <- "ymin_final" - # tempo.high.ref <- "ymax_final" - # } - # tempo.log.high <- if(diff(y.lim) > 0){stat.coord3[, tempo.center.ref] >= 0}else{stat.coord3[, tempo.center.ref] < 0} - # tempo.log.low <- if(diff(y.lim) > 0){stat.coord3[, tempo.center.ref] < 0}else{stat.coord3[, tempo.center.ref] >= 0} - # stat.coord3$Y[tempo.log.high] <- stat.coord3[tempo.log.high, tempo.high.ref] - # stat.coord3$Y[tempo.log.low] <- stat.coord3[tempo.log.low, tempo.low.ref] - # add distance - stat.coord3$Y <- stat.coord3$Y + diff(y.lim) * stat.dist / 100 - # end add distance - # correct median or mean text format - if(y.log != "no"){ - stat.coord3[, tempo.center.ref] <- ifelse(y.log == "log2", 2, 10)^(stat.coord3[, tempo.center.ref]) - } - stat.coord3[, tempo.center.ref] <- formatC(stat.coord3[, tempo.center.ref], digit = 2, drop0trailing = TRUE, format = "f") - # end correct median or mean text format - # if(any(tempo.log.high) == TRUE){ - # tempo.stat <- stat.coord3[tempo.log.high,] - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text( - data = stat.coord3, - mapping = ggplot2::aes_string(x = "x", y = "Y", label = tempo.center.ref), - size = stat.size, - color = "black", - angle = stat.angle, - hjust = stat.just$hjust, - vjust = stat.just$vjust - )) # WARNING: no need of order() for labels because box.coord$x set the order - coord.names <- c(coord.names, "stat.pos") - # } - # if(any(tempo.log.low) == TRUE){ - # tempo.stat <- stat.coord3[tempo.log.low,] - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text( - # data = tempo.stat, - # mapping = ggplot2::aes_string(x = "x", y = "Y", label = tempo.center.ref), - # size = stat.size, - # color = "black", - # hjust = ifelse(vertical == TRUE, 0.5, 0.5 + stat.dist), - # vjust = ifelse(vertical == TRUE, 0.5 + stat.dist, 0.5) - # )) # WARNING: no need of order() for labels because box.coord$x set the order - # coord.names <- c(coord.names, "stat.pos.negative") - # } - # end stat display - }else{ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 5") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - } - # end stat display - # legend management - if(legend.show == FALSE){ # must be here because must be before bef.final.plot <- - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = FALSE, color = FALSE, alpha = FALSE)) # inactivate the initial legend - } - # end legend management - - - - # y scale management (cannot be before dot plot management) - # the rescaling aspect is complicated and not intuitive. See: - # explaination: https://github.com/tidyverse/ggplot2/issues/3948 - # the oob argument of scale_y_continuous() https://ggplot2.tidyverse.org/reference/scale_continuous.html - # see also https://github.com/rstudio/cheatsheets/blob/master/data-visualization-2.1.pdf - # secondary ticks - bef.final.plot <- ggplot2::ggplot_build(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), ' + if(vertical == TRUE){ggplot2::scale_y_continuous(expand = c(0, 0), limits = sort(y.lim), oob = scales::rescale_none)}else{ggplot2::coord_flip(ylim = y.lim)}')))) # here I do not need the x-axis and y-axis orientation, I just need the number of main ticks and the legend. I DI NOT UNDERSTAND THE COMMENT HERE BECAUSE WE NEED COORD_FLiP - tempo.coord <- bef.final.plot$layout$panel_params[[1]] - # y.second.tick.positions: coordinates of secondary ticks (only if y.second.tick.nb argument is non NULL or if y.log argument is different from "no") - if(y.log != "no"){ # integer main ticks for log2 and log10 - tempo.scale <- (as.integer(min(y.lim, na.rm = TRUE)) - 1):(as.integer(max(y.lim, na.rm = TRUE)) + 1) - }else{ - tempo <- if(is.null(attributes(tempo.coord$y$breaks))){tempo.coord$y$breaks}else{unlist(attributes(tempo.coord$y$breaks))} - if(all(is.na(tempo))){# all() without na.rm -> ok because is.na() cannot be NA - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nONLY NA IN tempo.coord$y$breaks") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - } - tempo.scale <- fun_scale(lim = y.lim, n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) # in ggplot 3.3.0, tempo.coord$y.major_source replaced by tempo.coord$y$breaks. If fact: n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) replaced by n = ifelse(is.null(y.tick.nb), 4, y.tick.nb)) - } - y.second.tick.values <- NULL - y.second.tick.pos <- NULL - if(y.log != "no"){ - tempo <- fun_inter_ticks(lim = y.lim, log = y.log) - y.second.tick.values <- tempo$values - y.second.tick.pos <- tempo$coordinates - # if(vertical == TRUE){ # do not remove in case the bug is fixed - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", y = y.second.tick.pos, yend = y.second.tick.pos, x = tempo.coord$x.range[1], xend = tempo.coord$x.range[1] + diff(tempo.coord$x.range) / 80)) - # }else{ # not working because of the ggplot2 bug - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", x = y.second.tick.pos, xend = y.second.tick.pos, y = tempo.coord$y.range[1], yend = tempo.coord$y.range[1] + diff(tempo.coord$y.range) / 80)) - # } - coord.names <- c(coord.names, "y.second.tick.positions") - }else if(( ! is.null(y.second.tick.nb)) & y.log == "no"){ - # if(y.second.tick.nb > 0){ #inactivated because already checked before - if(length(tempo.scale) < 2){ - tempo.cat1 <- c("y.tick.nb", "y.second.tick.nb") - tempo.cat2 <- sapply(list(y.tick.nb, y.second.tick.nb), FUN = paste0, collapse = " ") - tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") - tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE NUMBER OF GENERATED TICKS FOR THE Y-AXIS IS NOT CORRECT: ", length(tempo.scale), "\nUSING THESE ARGUMENT SETTINGS (NO DISPLAY MEANS NULL VALUE):\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n"), "\nPLEASE, TEST OTHER VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - tempo <- fun_inter_ticks(lim = y.lim, log = y.log, breaks = tempo.scale, n = y.second.tick.nb) - } - y.second.tick.values <- tempo$values - y.second.tick.pos <- tempo$coordinates - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( - geom = "segment", - y = y.second.tick.pos, - yend = y.second.tick.pos, - x = if(vertical == TRUE){tempo.coord$x.range[1]}else{tempo.coord$y.range[1]}, - xend = if(vertical == TRUE){tempo.coord$x.range[1] + diff(tempo.coord$x.range) / 80}else{tempo.coord$y.range[1] + diff(tempo.coord$y.range) / 80} - )) - coord.names <- c(coord.names, "y.second.tick.positions") - } - # end y.second.tick.positions - # for the ggplot2 bug with y.log, this does not work: eval(parse(text = ifelse(vertical == FALSE & y.log == "log10", "ggplot2::scale_x_continuous", "ggplot2::scale_y_continuous"))) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_y_continuous( - breaks = tempo.scale, - minor_breaks = y.second.tick.pos, - labels = if(y.log == "log10"){scales::trans_format("identity", scales::math_format(10^.x))}else if(y.log == "log2"){scales::trans_format("identity", scales::math_format(2^.x))}else if(y.log == "no"){ggplot2::waiver()}else{tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 6") ; stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)}, # == in stop() to be able to add several messages between == - expand = c(0, 0), # remove space after after axis limits - limits = sort(y.lim), # NA indicate that limits must correspond to data limits but ylim() already used - oob = scales::rescale_none, - trans = ifelse(diff(y.lim) < 0, "reverse", "identity") # equivalent to ggplot2::scale_y_reverse() but create the problem of y-axis label disappearance with y.lim decreasing. Thus, do not use. Use ylim() below and after this - )) - if(vertical == TRUE){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_cartesian(ylim = y.lim)) # problem of ggplot2::ylim() is that it redraws new breaks # coord_cartesian(ylim = y.lim)) not used because bug -> y-axis label disappearance with y.lim decreasing I DO NOT UNDERSTAND THIS MESSAGE WHILE I USE COORD_CARTESIAN # clip = "off" to have secondary ticks outside plot region does not work - }else{ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_flip(ylim = y.lim)) # clip = "off" to have secondary ticks outside plot region does not work # create the problem of y-axis label disappearance with y.lim decreasing. IDEM ABOVE - - } - # end y scale management (cannot be before dot plot management) - - - # legend management - if( ! is.null(legend.width)){ - legend.final <- fun_gg_get_legend(ggplot_built = bef.final.plot, fun.name = function.name, lib.path = lib.path) # get legend - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = FALSE, color = FALSE, alpha = FALSE)) # inactivate the initial legend - if(is.null(legend.final) & plot == TRUE){ # even if any(unlist(legend.disp)) is TRUE - legend.final <- ggplot2::ggplot()+ggplot2::theme_void() # empty graph instead of legend - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") LEGEND REQUESTED (NON NULL categ ARGUMENT OR legend.show ARGUMENT SET TO TRUE)\nBUT IT SEEMS THAT THE PLOT HAS NO LEGEND -> EMPTY LEGEND SPACE CREATED BECAUSE OF THE NON NULL legend.width ARGUMENT\n") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end legend management - - - # drawing - fin.plot <- suppressMessages(suppressWarnings(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))) - grob.save <- NULL - if(plot == TRUE){ - # following lines inactivated because of problem in warn.recov and message.recov - # assign("env_fun_get_message", new.env()) - # assign("tempo.gg.name", tempo.gg.name, envir = env_fun_get_message) - # assign("tempo.gg.count", tempo.gg.count, envir = env_fun_get_message) - # assign("add", add, envir = env_fun_get_message) - # two next line: for the moment, I cannot prevent the warning printing - # warn.recov <- fun_get_message(paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add}), kind = "warning", header = FALSE, print.no = FALSE, env = env_fun_get_message) # for recovering warnings printed by ggplot() functions - # message.recov <- fun_get_message('print(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add}))))', kind = "message", header = FALSE, print.no = FALSE, env = env_fun_get_message) # for recovering messages printed by ggplot() functions - # if( ! (return == TRUE & return.ggplot == TRUE)){ # because return() plots when return.ggplot is TRUE # finally not used -> see return.ggplot description - if(is.null(legend.width)){ - grob.save <- suppressMessages(suppressWarnings(gridExtra::grid.arrange(fin.plot))) - }else{ - grob.save <-suppressMessages(suppressWarnings(gridExtra::grid.arrange(fin.plot, legend.final, ncol=2, widths=c(1, legend.width)))) - } - # } - # suppressMessages(suppressWarnings(print(eval(parse(text = paste(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), if(is.null(add)){NULL}else{add})))))) - }else{ - # following lines inactivated because of problem in warn.recov and message.recov - # message.recov <- NULL - # warn.recov <- NULL - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") PLOT NOT SHOWN AS REQUESTED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # end drawing - - - - # output - # following lines inactivated because of problem in warn.recov and message.recov - # if( ! (is.null(warn) & is.null(warn.recov) & is.null(message.recov))){ - # warn <- paste0(warn, "\n\n", if(length(warn.recov) > 0 | length(message.recov) > 0){paste0(paste0("MESSAGES FROM ggplot2 FUNCTIONS: ", ifelse( ! is.null(warn.recov), unique(message.recov), ""), ifelse( ! is.null(message.recov), unique(message.recov), ""), collapse = "\n\n"), "\n\n")}) - # }else if( ! (is.null(warn) & is.null(warn.recov)) & is.null(message.recov)){ - # warn <- paste0(warn, "\n\n", if(length(warn.recov) > 0){paste0(paste0("MESSAGES FROM ggplot2 FUNCTIONS: ", unique(warn.recov), collapse = "\n\n"), "\n\n")}) - # }else if( ! (is.null(warn) & is.null(message.recov)) & is.null(warn.recov)){ - # warn <- paste0(warn, "\n\n", if(length(message.recov) > 0){paste0(paste0("MESSAGES FROM ggplot2 FUNCTIONS: ", unique(message.recov), collapse = "\n\n"), "\n\n")}) - # } - if(warn.print == TRUE & ! is.null(warn)){ - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) - } - on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) - if(return == TRUE){ - tempo.output <- ggplot2::ggplot_build(fin.plot) - tempo.output$data <- tempo.output$data[-1] # remove the first data because corresponds to the initial empty boxplot - if(length(tempo.output$data) != length(coord.names)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nlength(tempo.output$data) AND length(coord.names) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - names(tempo.output$data) <- coord.names - tempo.output$data <- tempo.output$data[coord.names != "bad_remove"] - } - tempo <- tempo.output$layout$panel_params[[1]] - output <- list( - data = data1.ini, - stat = stat.nolog, - removed.row.nb = removed.row.nb, - removed.rows = removed.rows, - plot = c(tempo.output$data, y.second.tick.values = list(y.second.tick.values)), - panel = facet.categ, - axes = list( - x.range = tempo$x.range, - x.labels = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{tempo$x$scale$get_labels()}, # is.null(attributes(tempo$x$breaks)) test if it is number (TRUE) or character (FALSE) - x.positions = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{unlist(attributes(tempo$x$breaks))}, - y.range = tempo$y.range, - y.labels = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{tempo$y$scale$get_labels()}, - y.positions = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{unlist(attributes(tempo$y$breaks))} - ), - warn = paste0("\n", warn, "\n\n"), - ggplot = if(return.ggplot == TRUE){fin.plot}else{NULL}, # fin.plot plots the graph if return == TRUE - gtable = if(return.gtable == TRUE){grob.save}else{NULL} - ) - return(output) # this plots the graph if return.ggplot is TRUE and if no assignment - } - # end output - # end main code } +if(point.count== 3L){ +fin.lg.disp[[3]] <- legend.disp[[point.count + line.count]] +lg.order[[3]] <- point.count + line.count +lg.color[[3]] <- color[[i1]] # if color == NULL -> NULL +lg.dot.shape[[3]] <- dot.shape[[i1]] +lg.dot.size[[3]] <- dot.size[[i1]] +lg.dot.border.size[[3]] <- dot.border.size[[i1]] +lg.dot.border.color[[3]] <- dot.border.color[[i1]] # if dot.border.color == NULL -> NULL +if(plot == TRUE & fin.lg.disp[[3]] == TRUE & dot.shape[[3]] %in% 0:14 & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE DOTS (DOT LAYER NUMBER ", point.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +lg.alpha[[3]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf +}else{ +lg.alpha[[3]] <- alpha[[i1]] +} +class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) +for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same +tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = scatter.kind[[i1]]))(data = tempo.data.frame, mapping = ggplot2::aes_string(x = x[[i1]], y = y[[i1]], stroke = categ[[i1]]), shape = dot.shape[[i1]], size = dot.size[[i1]], fill = color[[i1]][i5], stroke = dot.border.size[[i1]], color = if(dot.shape[[i1]] %in% 21:24 & ! is.null(dot.border.color)){dot.border.color[[i1]]}else{color[[i1]][i5]}, alpha = alpha[[i1]], show.legend = FALSE)) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency +coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "stroke", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(dot.border.size[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of stroke, breaks reorder the classes according to class.categ in the legend. See guide_legend settings of scale_..._manual below +} +}else{ +line.count <- line.count + 1 +if(line.count== 1L){ +fin.lg.disp[[4]] <- legend.disp[[point.count + line.count]] +lg.order[[4]] <- point.count + line.count +lg.color[[4]] <- color[[i1]] # if color == NULL -> NULL +lg.line.size[[4]] <- line.size[[i1]] +lg.line.type[[4]] <- line.type[[i1]] +if(plot == TRUE & fin.lg.disp[[4]] == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES (LINE LAYER NUMBER ", line.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +lg.alpha[[4]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf +}else{ +lg.alpha[[4]] <- alpha[[i1]] +} +class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) +for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same +tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::", # no CR here te0("ggpl +ifelse(geom[[i1]] == 'geom_stick', 'geom_segment', geom[[i1]]), # geom_segment because geom_stick converted to geom_segment for plotting +"(data = tempo.data.frame, mapping = ggplot2::aes(x = ", +x[[i1]], +ifelse(geom[[i1]] == 'geom_stick', ", yend = ", ", y = "), +y[[i1]], +if(geom[[i1]] == 'geom_stick'){paste0(', xend = ', x[[i1]], ', y = ', ifelse(is.null(geom.stick.base), y.lim[1], geom.stick.base[[i1]]))}, +", linetype = ", +categ[[i1]], +"), color = \"", +color[[i1]][i5], +"\", size = ", +line.size[[i1]], +ifelse(geom[[i1]] == 'geom_path', ', lineend = \"round\"', ''), +ifelse(geom[[i1]] == 'geom_step', paste0(', direction = \"', geom.step.dir[[i1]], '\"'), ''), +", alpha = ", +alpha[[i1]], +", show.legend = ", +ifelse(i5== 1L, TRUE, FALSE), +")" +)))) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency +coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "linetype", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(line.type[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of linetype. 1 means solid. Regarding the alpha bug, I have tried different things without success: alpha in guide alone, in geom alone, in both, with different values, breaks reorder the classes according to class.categ in the legend +} +if(line.count== 2L){ +fin.lg.disp[[5]] <- legend.disp[[point.count + line.count]] +lg.order[[5]] <- point.count + line.count +lg.color[[5]] <- color[[i1]] # if color == NULL -> NULL +lg.line.size[[5]] <- line.size[[i1]] +lg.line.type[[5]] <- line.type[[i1]] +if(plot == TRUE & fin.lg.disp[[5]] == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES (LINE LAYER NUMBER ", line.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +lg.alpha[[5]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf +}else{ +lg.alpha[[5]] <- alpha[[i1]] +} +class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) +for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same +tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::", # no CR here te0("ggpl +ifelse(geom[[i1]] == 'geom_stick', 'geom_segment', geom[[i1]]), # geom_segment because geom_stick converted to geom_segment for plotting +"(data = tempo.data.frame, mapping = ggplot2::aes(x = ", +x[[i1]], +ifelse(geom[[i1]] == 'geom_stick', ", yend = ", ", y = "), +y[[i1]], +if(geom[[i1]] == 'geom_stick'){paste0(', xend = ', x[[i1]], ', y = ', ifelse(is.null(geom.stick.base), y.lim[1], geom.stick.base[[i1]]))}, +", alpha = ", +categ[[i1]], +"), color = \"", +color[[i1]][i5], +"\", size = ", +line.size[[i1]], +", linetype = ", +ifelse(is.numeric(line.type[[i1]]), "", "\""), +line.type[[i1]], +ifelse(is.numeric(line.type[[i1]]), "", "\""), +ifelse(geom[[i1]] == 'geom_path', ', lineend = \"round\"', ''), +ifelse(geom[[i1]] == 'geom_step', paste0(', direction = \"', geom.step.dir[[i1]], '\"'), ''), +", show.legend = FALSE)" +)))) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency +coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(alpha[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of linetype. 1 means solid. Regarding the alpha bug, I have tried different things without success: alpha in guide alone, in geom alone, in both, with different values, breaks reorder the classes according to class.categ in the legend +} +if(line.count== 3L){ +fin.lg.disp[[6]] <- legend.disp[[point.count + line.count]] +lg.order[[6]] <- point.count + line.count +lg.color[[6]] <- color[[i1]] # if color == NULL -> NULL +lg.line.size[[6]] <- line.size[[i1]] +lg.line.type[[6]] <- line.type[[i1]] +if(plot == TRUE & fin.lg.disp[[6]] == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES (LINE LAYER NUMBER ", line.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +lg.alpha[[6]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf +}else{ +lg.alpha[[6]] <- alpha[[i1]] +} +class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) +for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same +tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::", # no CR here te0("ggpl +ifelse(geom[[i1]] == 'geom_stick', 'geom_segment', geom[[i1]]), # geom_segment because geom_stick converted to geom_segment for plotting +"(data = tempo.data.frame, mapping = ggplot2::aes(x = ", +x[[i1]], +ifelse(geom[[i1]] == 'geom_stick', ", yend = ", ", y = "), +y[[i1]], +if(geom[[i1]] == 'geom_stick'){paste0(', xend = ', x[[i1]], ', y = ', ifelse(is.null(geom.stick.base), y.lim[1], geom.stick.base[[i1]]))}, +", size = ", +categ[[i1]], +"), color = \"", +color[[i1]][i5], +"\", linetype = ", +ifelse(is.numeric(line.type[[i1]]), "", "\""), +line.type[[i1]], +ifelse(is.numeric(line.type[[i1]]), "", "\""), +ifelse(geom[[i1]] == 'geom_path', ', lineend = \"round\"', ''), +ifelse(geom[[i1]] == 'geom_step', paste0(', direction = \"', geom.step.dir[[i1]], '\"'), ''), +", alpha = ", +alpha[[i1]], +", show.legend = FALSE)" +)))) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency +coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) +} +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "size", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(line.size[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of linetype. 1 means solid. Regarding the alpha bug, I have tried different things without success: alpha in guide alone, in geom alone, in both, breaks reorder the classes according to class.categ in the legend +} +} +} +# end loop part -# add density -# rasterise all kind: https://cran.r-project.org/web/packages/ggrastr/vignettes/Raster_geoms.html -fun_gg_scatter <- function( - data1, - x, - y, - categ = NULL, - categ.class.order = NULL, - color = NULL, - geom = "geom_point", - geom.step.dir = "hv", - geom.stick.base = NULL, - alpha = 0.5, - dot.size = 2, - dot.shape = 21, - dot.border.size = 0.5, - dot.border.color = NULL, - line.size = 0.5, - line.type = "solid", - x.lim = NULL, - x.lab = NULL, - x.log = "no", - x.tick.nb = NULL, - x.second.tick.nb = NULL, - x.include.zero = FALSE, - x.left.extra.margin = 0.05, - x.right.extra.margin = 0.05, - x.text.angle = 0, - y.lim = NULL, - y.lab = NULL, - y.log = "no", - y.tick.nb = NULL, - y.second.tick.nb = NULL, - y.include.zero = FALSE, - y.top.extra.margin = 0.05, - y.bottom.extra.margin = 0.05, - y.text.angle = 0, - raster = FALSE, - raster.ratio = 1, - raster.threshold = NULL, - text.size = 12, - title = "", - title.text.size = 12, - legend.show = TRUE, - legend.width = 0.5, - legend.name = NULL, - article = TRUE, - grid = FALSE, - add = NULL, - return = FALSE, - return.ggplot = FALSE, - return.gtable = TRUE, - plot = TRUE, - warn.print = FALSE, - lib.path = NULL -){ - # AIM - # Plot ggplot2 scatterplot with the possibility to overlay dots from up to 3 different data frames (-> three different legends) and lines from up to 3 different data frames (-> three different legends) -> up to 6 overlays totally - # For ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html - # WARNINGS - # Rows containing NA in data1[, c(x, y, categ)] will be removed before processing, with a warning (see below) - # Size arguments (dot.size, dot.border.size, line.size, text.size and title.text.size) are in mm. See Hadley comment in https://stackoverflow.com/questions/17311917/ggplot2-the-unit-of-size. See also http://sape.inf.usi.ch/quick-reference/ggplot2/size). Unit object are not accepted, but conversion can be used (e.g., grid::convertUnit(grid::unit(0.2, "inches"), "mm", valueOnly = TRUE)) - # ARGUMENTS - # data1: a dataframe compatible with ggplot2, or a list of data frames. Order matters for the order of the legend and for the layer staking (starting from below to top) - # x: single character string of the data1 column name for x-axis coordinates. If data1 is a list, then x must be a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Write NULL for each "geom_hline" in geom argument - # y: single character string of the data1 column name for y-axis coordinates. If data1 is a list, then y must be a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Write NULL for each "geom_vline" in geom argument - # categ: either NULL or a single character string or a list of single character strings, indicating the data1 column names to use for categories which creates legend display - # If categ == NULL, no categories -> no legend displayed - # If data1 is a data frame, categ must be a single character string of the data1 column name for categories - # If data1 is a list, then categ must be a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Some of the list compartments can be NULL (no legend display for these compartments), and other not - # categ.class.order: either (1) NULL or (2) a vector of character strings or (3) a list of these vectors, setting the order of the classes of categ in the legend display - # If categ.class.order is NULL, classes are represented according to the alphabetical order - # If data1 is a data frame, categ.class.order must be a vector of character strings specifying the different classes in the categ column name of data1 - # If data1 is a list, then categ.class.order must be a list of vector of character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Some of the list compartments can be NULL (alphabetical order for these compartments), and other not - # color: either (1) NULL, or (2) a vector of character strings or integers, or (3) a list of vectors of character strings or integers - # If color is NULL, default colors of ggplot2 - # If data1 is a data frame, color argument can be either: - # (1) a single color string. All the dots of the corresponding data1 will have this color, whatever the categ value (NULL or not) - # (2) if categ is non-null, a vector of string colors, one for each class of categ. Each color will be associated according to the categ.class.order argument if specified, or to the alphabetical order of categ classes otherwise - # (3) if categ is non-null, a vector or factor of string colors, like if it was one of the column of data1 data frame. WARNING: a single color per class of categ and a single class of categ per color must be respected - # Positive integers are also accepted instead of character strings, as long as above rules about length are respected. Integers will be processed by fun_gg_palette() using the max integer value among all the integers in color (see fun_gg_palette()) - # If data1 is a list, then color argument must be either: - # (1) a list of character strings or integers, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. - # (2) a single character string or a single integer - # With a list (first possibility), the rules described for when data1 is a data frame apply to each compartment of the list. Some of the compartments can be NULL. In that case, a different grey color will be used for each NULL compartment. With a single value (second possibility), the same color will be used for all the dots and lines, whatever the data1 list - # geom: single character string of the kind of plot, or a list of single character strings - # Either: - # "geom_point" (scatterplot) - # "geom_line" (coordinates plotted then line connection, from the lowest to highest x coordinates first and from the lowest to highest y coordinates thenafter) - # "geom_path" (coordinates plotted then line connection respecting the row order in data1) - # "geom_step" coordinates plotted then line connection respecting the row order in data1 but drawn in steps). See the geom.step.dir argument - # "geom_hline" (horizontal line, no x value provided) - # "geom_vline" (vertical line, no y value provided) - # "geom_stick" (dots as vertical bars) - # If data1 is a list, then geom must be either: - # (1) a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. - # (2) a single character string. In that case the same kind of plot will apply for the different compartments of the data1 list - # WARNING concerning "geom_hline" or "geom_vline": - # (1) x or y argument must be NULL, respectively - # (2) x.lim or y.lim argument must NOT be NULL, respectively, if only these kind of lines are drawn (if other geom present, then x.lim = NULL and y.lim = NULL will generate x.lim and y.lim defined by these other geom, which is not possible with "geom_hline" or "geom_vline" alone) - # (3) the function will draw n lines for n values in the x argument column name of the data1 data frame. If several colors required, the categ argument must be specified and the corresponding categ column name must exist in the data1 data frame with a different class name for each row - # geom.step.dir: single character string indicating the direction when using "geom_step" of the geom argument, or a list of single character strings - # Either: - # "vh" (vertical then horizontal) - # "hv" (horizontal then vertical) - # "mid" (step half-way between adjacent x-values) - # See https://ggplot2.tidyverse.org/reference/geom_path.html - # If data1 is a list, then geom.step.dir must be either: - # (1) a list of single character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. The value in compartments related to other geom values than "geom_step" will be ignored - # (2) a single character string, which will be used for all the "geom_step" values of the geom argument, whatever the data1 list - # geom.stick.base: either (1) NULL or (2) a single numeric value or (3) a list of single numeric values, setting the base of the sticks when using "geom_stick" of the geom argument - # If geom.stick.base is NULL, the bottom limit of the y-axis is taken as the base - # If data1 is a list, then geom.stick.base must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the values in compartments related to other geom values than "geom_stick" will be ignored. With a single value (latter possibility), the same base will be used for all the sticks, whatever the data1 list - # Warning: the y-axis limits are not modified by the value of geom.stick.base, meaning that this value can be outside of the range of y.lim. Add the value of geom.stick.base also in the y.lim argument if required - # Warning: if geom.stick.base is NULL, the bottom limit of the y-axis is taken as the base. Thus, be careful with inverted y-axis - # alpha: single numeric value (from 0 to 1) of transparency. If data1 is a list, then alpha must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. In that case the same transparency will apply for the different compartments of the data1 list - # dot.size: single numeric value of dot shape radius? in mm. If data1 is a list, then dot.size must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the value in compartments related to lines will be ignored. With a single value (latter possibility), the same dot.size will be used for all the dots, whatever the data1 list - # dot.shape: value indicating the shape of the dots (see https://ggplot2.tidyverse.org/articles/ggplot2-specs.html) If data1 is a list, then dot.shape must be either (1) a list of single shape values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single shape value. With a list (former possibility), the value in compartments related to lines will be ignored. With a single value (latter possibility), the same dot.shape will be used for all the dots, whatever the data1 list - # dot.border.size: single numeric value of border dot width in mm. Write zero for no dot border. If data1 is a list, then dot.border.size must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the value in compartments related to lines will be ignored. With a single value (latter possibility), the same dot.border.size will be used for all the dots, whatever the data1 list - # dot.border.color: single character color string defining the color of the dot border (same border color for all the dots, whatever their categories). If dot.border.color == NULL, the border color will be the same as the dot color. A single integer is also accepted instead of a character string, that will be processed by fun_gg_palette() - # line.size: single numeric value of line width in mm. If data1 is a list, then line.size must be either (1) a list of single numeric values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single numeric value. With a list (former possibility), the value in compartments related to dots will be ignored. With a single value (latter possibility), the same line.size will be used for all the lines, whatever the data1 list - # line.type: value indicating the kind of lines (see https://ggplot2.tidyverse.org/articles/ggplot2-specs.html) If data1 is a list, then line.type must be either (1) a list of single line kind values, of same size as data1, with compartment 1 related to compartment 1 of data1, etc., or (2) a single line kind value. With a list (former possibility), the value in compartments related to dots will be ignored. With a single value (latter possibility), the same line.type will be used for all the lines, whatever the data1 list - # x.lim: 2 numeric values setting the x-axis range. Order of the 2 values matters (for inverted axis). If NULL, the range of the x column name of data1 will be used - # x.lab: a character string or expression for x-axis label. If NULL, will use the first value of x (x column name of the first data frame in data1). Warning message if the elements in x are different between data frames in data1 - # x.log: either "no", "log2" (values in the x column name of the data1 data frame will be log2 transformed and x-axis will be log2 scaled) or "log10" (values in the x column name of the data1 data frame will be log10 transformed and x-axis will be log10 scaled) - # x.tick.nb: approximate number of desired values labeling the x-axis (i.e., main ticks, see the n argument of the the cute::fun_scale() function). If NULL and if x.log is "no", then the number of labeling values is set by ggplot2. If NULL and if x.log is "log2" or "log10", then the number of labeling values corresponds to all the exposant integers in the x.lim range (e.g., 10^1, 10^2 and 10^3, meaning 3 main ticks for x.lim = c(9, 1200)). WARNING: if non-NULL and if x.log is "log2" or "log10", labeling can be difficult to read (e.g., ..., 10^2, 10^2.5, 10^3, ...) - # x.second.tick.nb: number of desired secondary ticks between main ticks. Ignored if x.log is other than "no" (log scale plotted). Use argument return = TRUE and see $plot$x.second.tick.values to have the values associated to secondary ticks. IF NULL, no secondary ticks - # x.include.zero: logical. Does x.lim range include 0? Ignored if x.log is "log2" or "log10" - # x.left.extra.margin: single proportion (between 0 and 1) indicating if extra margins must be added to x.lim. If different from 0, add the range of the axis multiplied by x.left.extra.margin (e.g., abs(x.lim[2] - x.lim[1]) * x.left.extra.margin) to the left of x-axis - # x.right.extra.margin: idem as x.left.extra.margin but to the right of x-axis - # x.text.angle: integer value of the text angle for the x-axis labeling values, using the same rules as in ggplot2. Use positive value for clockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Use negative values for counterclockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. - # y.lim: 2 numeric values setting the y-axis range. Order of the 2 values matters (for inverted axis). If NULL, the range of the y column name of data1 will be used - # y.lab: a character string or expression for y-axis label. If NULL, will use the first value of y (y column name of the first data frame in data1). Warning message if the elements in y are different between data frames in data1 - # y.log: either "no", "log2" (values in the y column name of the data1 data frame will be log2 transformed and y-axis will be log2 scaled) or "log10" (values in the y column name of the data1 data frame will be log10 transformed and y-axis will be log10 scaled) - # y.tick.nb: approximate number of desired values labeling the y-axis (i.e., main ticks, see the n argument of the the cute::fun_scale() function). If NULL and if y.log is "no", then the number of labeling values is set by ggplot2. If NULL and if y.log is "log2" or "log10", then the number of labeling values corresponds to all the exposant integers in the y.lim range (e.g., 10^1, 10^2 and 10^3, meaning 3 main ticks for y.lim = c(9, 1200)). WARNING: if non-NULL and if y.log is "log2" or "log10", labeling can be difficult to read (e.g., ..., 10^2, 10^2.5, 10^3, ...) - # y.second.tick.nb: number of desired secondary ticks between main ticks. Ignored if y.log is other than "no" (log scale plotted). Use argument return = TRUE and see $plot$y.second.tick.values to have the values associated to secondary ticks. IF NULL, no secondary ticks - # y.include.zero: logical. Does y.lim range include 0? Ignored if y.log is "log2" or "log10" - # y.top.extra.margin: single proportion (between 0 and 1) indicating if extra margins must be added to y.lim. If different from 0, add the range of the axis multiplied by y.top.extra.margin (e.g., abs(y.lim[2] - y.lim[1]) * y.top.extra.margin) to the top of y-axis - # y.bottom.extra.margin: idem as y.top.extra.margin but to the bottom of y-axis - # y.text.angle: integer value of the text angle for the y-axis labeling values, using the same rules as in ggplot2. Use positive value for clockwise rotation: 0 for horizontal, 90 for vertical, 180 for upside down etc. Use negative values for counterclockwise rotation: 0 for horizontal, -90 for vertical, -180 for upside down etc. - # raster: logical. Dots in raster mode? If FALSE, dots from each "geom_point" from geom argument are plotted in vectorial mode (bigger pdf and long to display if lots of dots). If TRUE, dots from each "geom_point" from geom argument are plotted in matricial mode (smaller pdf and easy display if lots of dots, but it takes time to generate the layer). If TRUE, the raster.ratio argument is used to avoid an ellipsoid representation of the dots. If TRUE, solve the transparency problem with some GUI. Overriden by the non-NULL raster.threshold argument - # raster.ratio: single numeric value indicating the height / width ratio of the graphic device used (for instance provided by the $dim compartment in the output of the fun_open() function). The default value is 1 because by default R opens a square graphic device. But this argument has to be set when using other device dimensions. Ignored if raster == FALSE - # raster.threshold: positive integer value indicating the limit of the dot number above which "geom_point" layers from the geom argument switch from vectorial mode to matricial mode (see the raster argument). If any layer is matricial, then the raster.ratio argument is used to avoid an ellipsoid representation of the dots. If non-NULL, it overrides the raster argument - # text.size: numeric value of the font size of the (1) axis numbers and axis legends and (2) texts in the graphic legend (in mm) - # title: character string of the graph title - # title.text.size: numeric value of the title font size in mm - # legend.show: logical. Show legend? Not considered if categ argument is NULL, because this already generate no legend, excepted if legend.width argument is non-NULL. In that specific case (categ is NULL, legend.show is TRUE and legend.width is non-NULL), an empty legend space is created. This can be useful when desiring graphs of exactly the same width, whatever they have legends or not - # legend.width: single proportion (between 0 and 1) indicating the relative width of the legend sector (on the right of the plot) relative to the width of the plot. Value 1 means that the window device width is split in 2, half for the plot and half for the legend. Value 0 means no room for the legend, which will overlay the plot region. Write NULL to inactivate the legend sector. In such case, ggplot2 will manage the room required for the legend display, meaning that the width of the plotting region can vary between graphs, depending on the text in the legend - # legend.name: character string of the legend title. If legend.name is NULL and categ argument is not NULL, then legend.name <- categ. If data1 is a list, then legend.name must be a list of character strings, of same size as data1, with compartment 1 related to compartment 1 of data1, etc. Some of the list compartments can be NULL, and other not - # article: logical. If TRUE, use an article theme (article like). If FALSE, use a classic related ggplot theme. Use the add argument (e.g., add = "+ggplot2::theme_classic()" for the exact classic ggplot theme - # grid: logical. Draw lines in the background to better read the box values? Not considered if article == FALSE (grid systematically present) - # add: character string allowing to add more ggplot2 features (dots, lines, themes, facet, etc.). Ignored if NULL - # WARNING: (1) the string must start with "+", (2) the string must finish with ")" and (3) each function must be preceded by "ggplot2::". Example: "+ ggplot2::coord_flip() + ggplot2::theme_bw()" - # If the character string contains the "ggplot2::theme" string, then the article argument of fun_gg_scatter() (see above) is ignored with a warning. In addition, some arguments can be overwritten, like x.angle (check all the arguments) - # Handle the add argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions - # WARNING: the call of objects inside the quotes of add can lead to an error if the name of these objects are some of the fun_gg_scatter() arguments. Indeed, the function will use the internal argument instead of the global environment object. Example article <- "a" in the working environment and add = '+ ggplot2::ggtitle(article)'. The risk here is to have TRUE as title. To solve this, use add = '+ ggplot2::ggtitle(get("article", envir = .GlobalEnv))' - # return: logical. Return the graph parameters? - # return.ggplot: logical. Return the ggplot object in the output list? Ignored if return argument is FALSE. WARNING: always assign the fun_gg_scatter() function (e.g., a <- fun_gg_scatter()) if return.ggplot argument is TRUE, otherwise, double plotting is performed. See $ggplot in the RETURN section below for more details - # return.gtable: logical. Return the ggplot object as gtable of grobs in the output list? Ignored if plot argument is FALSE. Indeed, the graph must be plotted to get the grobs dispositions. See $gtable in the RETURN section below for more details - # plot: logical. Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting - # warn.print: logical. Print warnings at the end of the execution? ? If FALSE, warning messages are never printed, but can still be recovered in the returned list. Some of the warning messages (those delivered by the internal ggplot2 functions) are not apparent when using the argument plot = FALSE - # lib.path: character string indicating the absolute path of the required packages (see below). if NULL, the function will use the R library default folders - # RETURN - # a scatter plot if plot argument is TRUE - # a list of the graph info if return argument is TRUE: - # $data: the initial data with graphic information added. WARNING: if the x.log or y.log argument is not "no", x or y argument column of the data1 data frame are log2 or log10 converted in $data, respectively. Use 2^values or 10^$values to recover the initial values - # $removed.row.nb: a list of the removed rows numbers in data frames (because of NA). NULL if no row removed - # $removed.rows: a list of the removed rows in data frames (because of NA). NULL if no row removed - # $plot: the graphic box and dot coordinates - # $dots: dot coordinates - # y.second.tick.positions: coordinates of secondary ticks (only if y.second.tick.nb argument is non-null or if y.log argument is different from "no") - # y.second.tick.values: values of secondary ticks. NULL except if y.second.tick.nb argument is non-null or if y.log argument is different from "no") - # $panel: the variable names used for the panels (NULL if no panels). WARNING: NA can be present according to ggplot2 upgrade to v3.3.0 - # $axes: the x-axis and y-axis info - # $warn: the warning messages. Use cat() for proper display. NULL if no warning. WARNING: warning messages delivered by the internal ggplot2 functions are not apparent when using the argument plot = FALSE - # $ggplot: ggplot object that can be used for reprint (use print($ggplot) or update (use $ggplot + ggplot2::...). NULL if return.ggplot argument is FALSE. Of note, a non-null $ggplot in the output list is sometimes annoying as the manipulation of this list prints the plot - # $gtable: gtable object that can be used for reprint (use gridExtra::grid.arrange(...$ggplot) or with additionnal grobs (see the grob decomposition in the examples). NULL if return.ggplot argument is FALSE. Contrary to $ggplot, a non-NULL $gtable in the output list is not annoying as the manipulation of this list does not print the plot - # REQUIRED PACKAGES - # ggplot2 - # gridExtra - # lemon (in case of use in the add argument) - # scales - # if raster plots are drawn (see the raster and raster.threshold arguments): - # Cairo - # grid - # REQUIRED FUNCTIONS FROM THE cute PACKAGE - # fun_gg_empty_graph() - # fun_gg_palette() - # fun_gg_point_rast() - # fun_pack() - # fun_check() - # fun_round() - # fun_scale() - # fun_inter_ticks() - # EXAMPLES - # set.seed(1) ; obs1 <- data.frame(Km = c(2, 1, 6, 5, 4, 7), Time = c(2, 1, 6, 5, 4, 7)^2, Car = c("TUUT", "TUUT", "TUUT", "WIIM", "WIIM", "WIIM"), Color1 = rep(c("coral", "lightblue"), each = 3), stringsAsFactors = TRUE) ; fun_gg_scatter(data1 = obs1, x = "Km", y = "Time") - # DEBUGGING - # set.seed(1) ; obs1 <- data.frame(km = rnorm(1000, 10, 3), time = rnorm(1000, 10, 3), group1 = rep(c("A1", "A2"), 500), stringsAsFactors = TRUE) ; obs2 <-data.frame(km = rnorm(1000, 15, 3), time = rnorm(1000, 15, 3), group2 = rep(c("G1", "G2"), 500), stringsAsFactors = TRUE) ; set.seed(NULL) ; obs1$km[2:3] <- NA ; data1 = list(L1 = obs1, L2 = obs2) ; x = list(L1 = "km", L2 = "km") ; y = list(L1 = "time", L2 = "time") ; categ = list(L1 = "group1", L2 = "group2") ; categ = NULL ; categ.class.order = NULL ; color = NULL ; geom = "geom_point" ; geom.step.dir = "hv" ; geom.stick.base = NULL ; alpha = 0.5 ; dot.size = 2 ; dot.shape = 21 ; dot.border.size = 0.5 ; dot.border.color = NULL ; line.size = 0.5 ; line.type = "solid" ; x.lim = NULL ; x.lab = NULL ; x.log = "no" ; x.tick.nb = NULL ; x.second.tick.nb = NULL ; x.include.zero = FALSE ; x.left.extra.margin = 0.05 ; x.right.extra.margin = 0.05 ; x.text.angle = 0 ; y.lim = NULL ; y.lab = NULL ; y.log = "no" ; y.tick.nb = NULL ; y.second.tick.nb = NULL ; y.include.zero = FALSE ; y.top.extra.margin = 0.05 ; y.bottom.extra.margin = 0.05 ; y.text.angle = 0 ; raster = FALSE ; raster.ratio = 1 ; raster.threshold = NULL ; text.size = 12 ; title = "" ; title.text.size = 12 ; legend.show = TRUE ; legend.width = 0.5 ; legend.name = NULL ; article = TRUE ; grid = FALSE ; add = NULL ; return = FALSE ; return.ggplot = FALSE ; return.gtable = TRUE ; plot = TRUE ; warn.print = FALSE ; lib.path = NULL - # function name - function.name <- paste0(as.list(match.call(expand.dots=FALSE))[[1]], "()") - arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments - arg.user.setting <- as.list(match.call(expand.dots=FALSE))[-1] # list of the argument settings (excluding default values not provided by the user) - # end function name - # required function checking - req.function <- c( - "fun_check", - "fun_gg_just", - "fun_gg_empty_graph", - "fun_gg_palette", - "fun_gg_point_rast", - "fun_round", - "fun_pack", - "fun_scale", - "fun_inter_ticks" - ) - tempo <- NULL - for(i1 in req.function){ - if(length(find(i1, mode = "function"))== 0L){ - tempo <- c(tempo, i1) - } - } - if( ! is.null(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end required function checking - # reserved words to avoid bugs (used in this function) - reserved.words <- c("fake_x", "fake_y", "fake_categ") - # end reserved words to avoid bugs (used in this function) - # arg with no default values - mandat.args <- c( - "data1", - "x", - "y" - ) - tempo <- eval(parse(text = paste0("missing(", paste0(mandat.args, collapse = ") | missing("), ")"))) - if(any(tempo)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", "HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end arg with no default values - # argument primary checking - arg.check <- NULL # - text.check <- NULL # - checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools - ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name)) - tempo1 <- fun_check(data = data1, class = "data.frame", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = data1, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A DATA FRAME OR A LIST OF DATA FRAMES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(x)){ - tempo1 <- fun_check(data = x, class = "vector", mode = "character", na.contain = TRUE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = x, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = x, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(y)){ - tempo1 <- fun_check(data = y, class = "vector", mode = "character", na.contain = TRUE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = y, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(categ)){ - tempo1 <- fun_check(data = categ, class = "vector", mode = "character", length = 1, fun.name = function.name) - tempo2 <- fun_check(data = categ, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": categ ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = categ, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(categ.class.order)){ - if(is.null(categ)){ - tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT IS NOT NULL, BUT categ IS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo1 <- fun_check(data = categ.class.order, class = "vector", mode = "character", fun.name = function.name) - tempo2 <- fun_check(data = categ.class.order, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT MUST BE A VECTOR OF CHARACTER STRINGS OR A LIST OF VECTOR OF CHARACTER STRINGS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = categ.class.order, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(legend.name)){ - tempo1 <- fun_check(data = legend.name, class = "vector", mode = "character", na.contain = TRUE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = legend.name, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": legend.name ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = legend.name, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(color)){ - tempo1 <- fun_check(data = color, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = color, class = "factor", na.contain = TRUE, fun.name = function.name) - tempo3 <- fun_check(data = color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name) - tempo4 <- fun_check(data = color, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo4$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE & tempo3$problem == TRUE & tempo4$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": color ARGUMENT MUST BE A VECTOR (OF CHARACTER STRINGS OR INTEGERS) OR A FACTOR OR A LIST OF THESE POSSIBILITIES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = color, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo1 <- fun_check(data = geom, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = geom, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT MUST BE A SINGLE CHARACTER STRING OR A LIST OF CHARACTER STRINGS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo1 <- fun_check(data = geom.step.dir, options = c("vh", "hv", "mid"), na.contain = FALSE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = geom.step.dir, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom.step.dir ARGUMENT MUST BE A SINGLE CHARACTER STRING (\"vh\" OR \"hv\" OR \"mid\") OR A LIST OF THESE CHARACTER STRINGS") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(geom.stick.base)){ - tempo1 <- fun_check(data = geom.stick.base, class = "vector", mode = "numeric", na.contain = FALSE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = color, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom.stick.base ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = geom.stick.base, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo1 <- fun_check(data = alpha, prop = TRUE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = alpha, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": alpha ARGUMENT MUST BE A SINGLE NUMERIC VALUE BETWEEN 0 AND 1 OR A LIST OF SUCH VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo1 <- fun_check(data = dot.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) - tempo2 <- fun_check(data = dot.size, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.size ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo1 <- fun_check(data = dot.shape, class = "vector", length = 1, fun.name = function.name) - tempo2 <- fun_check(data = dot.shape, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.shape ARGUMENT MUST BE A SINGLE SHAPE VALUE OR A LIST OF SINGLE SHAPE VALUES (SEE https://ggplot2.tidyverse.org/articles/ggplot2-specs.html)") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo1 <- fun_check(data = dot.border.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) - tempo2 <- fun_check(data = dot.border.size, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.size ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(dot.border.color)){ - tempo1 <- fun_check(data = dot.border.color, class = "vector", mode = "character", length = 1, fun.name = function.name) - tempo2 <- fun_check(data = dot.border.color, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - # integer colors -> gg_palette - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.color MUST BE A SINGLE CHARACTER STRING OF COLOR OR A SINGLE INTEGER VALUE") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = dot.border.color, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo1 <- fun_check(data = line.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) - tempo2 <- fun_check(data = line.size, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo2$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": line.size ARGUMENT MUST BE A SINGLE NUMERIC VALUE OR A LIST OF SINGLE NUMERIC VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - tempo1 <- fun_check(data = line.type, class = "vector", typeof = "integer", double.as.integer.allowed = FALSE, length = 1, fun.name = function.name) - tempo2 <- fun_check(data = line.type, class = "vector", mode = "character", length = 1, fun.name = function.name) - tempo3 <- fun_check(data = line.type, class = "list", na.contain = TRUE, fun.name = function.name) - checked.arg.names <- c(checked.arg.names, tempo3$object.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE & tempo3$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": line.type ARGUMENT MUST BE A SINGLE LINE KIND VALUE OR A LIST OF SINGLE LINE KIND VALUES (SEE https://ggplot2.tidyverse.org/articles/ggplot2-specs.html)") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - if( ! is.null(x.lim)){ - tempo <- fun_check(data = x.lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & any(x.lim %in% c(Inf, -Inf))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x.lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = x.lim, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(x.lab)){ - if(all(class(x.lab) %in% "expression")){ # to deal with math symbols - tempo <- fun_check(data = x.lab, class = "expression", length = 1, fun.name = function.name) ; eval(ee) - }else{ - tempo <- fun_check(data = x.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = x.lab, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = x.log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(x.tick.nb)){ - tempo <- fun_check(data = x.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & x.tick.nb < 0){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = x.tick.nb, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(x.second.tick.nb)){ - tempo <- fun_check(data = x.second.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & x.second.tick.nb <= 0){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x.second.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = x.second.tick.nb, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = x.include.zero, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.left.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.right.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = x.text.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) - if( ! is.null(y.lim)){ - tempo <- fun_check(data = y.lim, class = "vector", mode = "numeric", length = 2, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & any(y.lim %in% c(Inf, -Inf))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y.lim ARGUMENT CANNOT CONTAIN -Inf OR Inf VALUES") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.lim, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(y.lab)){ - if(all(class(y.lab) %in% "expression")){ # to deal with math symbols - tempo <- fun_check(data = y.lab, class = "expression", length = 1, fun.name = function.name) ; eval(ee) - }else{ - tempo <- fun_check(data = y.lab, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.lab, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = y.log, options = c("no", "log2", "log10"), length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(y.tick.nb)){ - tempo <- fun_check(data = y.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & y.tick.nb < 0){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.tick.nb, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if( ! is.null(y.second.tick.nb)){ - tempo <- fun_check(data = y.second.tick.nb, class = "vector", typeof = "integer", length = 1, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE & y.second.tick.nb <= 0){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y.second.tick.nb ARGUMENT MUST BE A NON-NULL POSITIVE INTEGER") - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = y.second.tick.nb, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = y.include.zero, class = "vector", mode = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.top.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.bottom.extra.margin, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = y.text.angle, class = "vector", typeof = "integer", double.as.integer.allowed = TRUE, length = 1, neg.values = TRUE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = raster, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = raster.ratio, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - if( ! is.null(raster.threshold)){ - tempo <- fun_check(data = raster.threshold, class = "vector", typeof = "integer", neg.values = FALSE, double.as.integer.allowed = TRUE, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = raster.threshold, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = legend.show, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(legend.width)){ - tempo <- fun_check(data = legend.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = legend.width, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = article, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = grid, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(add)){ - tempo <- fun_check(data = add, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee) - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = add, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - tempo <- fun_check(data = return, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return.ggplot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = return.gtable, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = plot, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee) - if( ! is.null(lib.path)){ - tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) - if(tempo$problem == FALSE){ - if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA - tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n")) - text.check <- c(text.check, tempo.cat) - arg.check <- c(arg.check, TRUE) - } - } - }else{ - # no fun_check test here, it is just for checked.arg.names - tempo <- fun_check(data = lib.path, class = "vector") - checked.arg.names <- c(checked.arg.names, tempo$object.name) - } - if(any(arg.check) == TRUE){ - stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) # - } - # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check() - # end argument primary checking - - - # second round of checking and data preparation - # management of NA arguments - tempo.arg <- names(arg.user.setting) # values provided by the user - tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length)== 1L # no argument provided by the user can be just NA - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT JUST BE NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NA arguments - # management of NULL arguments - tempo.arg <-c( - "data1", - # "x", # inactivated because of hline or vline - # "y", # inactivated because of hline or vline - "geom", - "geom.step.dir", - # "geom.stick.base", # inactivated because can be null - "alpha", - "dot.size", - "dot.shape", - "dot.border.size", - "line.size", - "line.type", - "x.log", - "x.include.zero", - "x.left.extra.margin", - "x.right.extra.margin", - "x.text.angle", - "y.log", - "y.include.zero", - "y.top.extra.margin", - "y.bottom.extra.margin", - "y.text.angle", - "raster", - "raster.ratio", - "text.size", - "title", - "title.text.size", - "legend.show", - # "legend.width", # inactivated because can be null - "article", - "grid", - "return", - "return.ggplot", - "return.gtable", - "plot", - "warn.print" - ) - tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null) - if(any(tempo.log) == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between == - } - # end management of NULL arguments - # code that protects set.seed() in the global environment - # end code that protects set.seed() in the global environment - # warning initiation - ini.warning.length <- options()$warning.length - options(warning.length = 8170) - warn <- NULL - warn.count <- 0 - # end warning initiation - # other checkings - # check list lengths (and names of data1 compartments if present) - list.color <- NULL - list.geom <- NULL - list.geom.step.dir <- NULL - list.geom.stick.base <- NULL - list.alpha <- NULL - list.dot.size <- NULL - list.dot.shape <- NULL - list.dot.border.size <- NULL - list.dot.border.color <- NULL - list.line.size <- NULL - list.line.type <- NULL - if(all(class(data1) == "list")){ - if(length(data1) > 6){ - tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A LIST OF 6 DATA FRAMES MAXIMUM (6 OVERLAYS MAX)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if(is.null(names(data1))){ - names(data1) <- paste0("L", 1:length(data1)) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL NAME COMPARTMENT OF data1 LIST -> NAMES RESPECTIVELY ATTRIBUTED TO EACH COMPARTMENT:\n", paste(names(data1), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - if( ! is.null(x)){ - if( ! (all(class(x) == "list") & length(data1) == length(x))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - }else{ - x <- vector("list", length(data1)) - } - if( ! is.null(y)){ - if( ! (all(class(y) == "list") & length(data1) == length(y))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - }else{ - y <- vector("list", length(data1)) - } - if( ! is.null(categ)){ - if( ! (all(class(categ) == "list") & length(data1) == length(categ))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": categ ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if( ! is.null(categ.class.order)){ - if( ! (all(class(categ.class.order) == "list") & length(data1) == length(categ.class.order))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if( ! is.null(color)){ - if( ! ((all(class(color) == "list") & length(data1) == length(color)) | ((all(mode(color) == "character") | all(mode(color) == "numeric")) & length(color)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": color ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER STRING OR INTEGER") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if((all(mode(color) == "character") | all(mode(color) == "numeric")) & length(color)== 1L){ # convert the single value into a list of single value - list.color <- vector(mode = "list", length = length(data1)) - list.color[] <- color - } - } - if( ! ((all(class(geom) == "list") & length(data1) == length(geom)) | (all(mode(geom) == "character") & length(geom)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(geom) == "character") & length(geom)== 1L){ # convert the single value into a list of single value - list.geom <- vector(mode = "list", length = length(data1)) - list.geom[] <- geom - } - if( ! ((all(class(geom.step.dir) == "list") & length(data1) == length(geom.step.dir)) | (all(mode(geom.step.dir) == "character") & length(geom.step.dir)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": geom.step.dir ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(geom.step.dir) == "character") & length(geom.step.dir)== 1L){ # convert the single value into a list of single value - list.geom.step.dir <- vector(mode = "list", length = length(data1)) - list.geom.step.dir[] <- geom.step.dir - } - if( ! is.null(geom.stick.base)){ - if( ! ((all(class(geom.stick.base) == "list") & length(data1) == length(geom.stick.base)) | (all(mode(geom.stick.base) == "numeric") & length(geom.stick.base)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": geom.stick.base ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(geom.stick.base) == "numeric") & length(geom.stick.base)== 1L){ # convert the single value into a list of single value - list.geom.stick.base <- vector(mode = "list", length = length(data1)) - list.geom.stick.base[] <- geom.stick.base - } - } - if( ! ((all(class(alpha) == "list") & length(data1) == length(alpha)) | (all(mode(alpha) == "numeric") & length(alpha)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": alpha ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(alpha) == "numeric") & length(alpha)== 1L){ # convert the single value into a list of single value - list.alpha <- vector(mode = "list", length = length(data1)) - list.alpha[] <- alpha - } - if( ! ((all(class(dot.size) == "list") & length(data1) == length(dot.size)) | (all(mode(dot.size) == "numeric") & length(dot.size)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.size ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(dot.size) == "numeric") & length(dot.size)== 1L){ # convert the single value into a list of single value - list.dot.size <- vector(mode = "list", length = length(data1)) - list.dot.size[] <- dot.size - } - if( ! ((all(class(dot.shape) == "list") & length(data1) == length(dot.shape)) | (all(mode(dot.shape) != "list") & length(dot.shape)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.shape ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE SHAPE VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(dot.shape) != "list") & length(dot.shape)== 1L){ # convert the single value into a list of single value - list.dot.shape <- vector(mode = "list", length = length(data1)) - list.dot.shape[] <- dot.shape - } - if( ! ((all(class(dot.border.size) == "list") & length(data1) == length(dot.border.size)) | (all(mode(dot.border.size) == "numeric") & length(dot.border.size)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.size ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(dot.border.size) == "numeric") & length(dot.border.size)== 1L){ # convert the single value into a list of single value - list.dot.border.size <- vector(mode = "list", length = length(data1)) - list.dot.border.size[] <- dot.border.size - } - if( ! is.null(dot.border.color)){ - if( ! ((all(class(dot.border.color) == "list") & length(data1) == length(dot.border.color)) | ((all(mode(dot.border.color) == "character") | all(mode(dot.border.color) == "numeric")) & length(dot.border.color)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.color ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE CHARACTER STRING OR INTEGER") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if((all(mode(dot.border.color) == "character") | all(mode(dot.border.color) == "numeric")) & length(dot.border.color)== 1L){ # convert the single value into a list of single value - list.dot.border.color <- vector(mode = "list", length = length(data1)) - list.dot.border.color[] <- dot.border.color - } - } - if( ! ((all(class(line.size) == "list") & length(data1) == length(line.size)) | (all(mode(line.size) == "numeric") & length(line.size)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": line.size ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE NUMERIC VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(line.size) == "numeric") & length(line.size)== 1L){ # convert the single value into a list of single value - list.line.size <- vector(mode = "list", length = length(data1)) - list.line.size[] <- line.size - } - if( ! ((all(class(line.type) == "list") & length(data1) == length(line.type)) | (all(mode(line.type) != "list") & length(line.type)== 1L))){ # list of same length as data1 or single value - tempo.cat <- paste0("ERROR IN ", function.name, ": line.type ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST, OR A SINGLE LINE KIND VALUE") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(all(mode(line.type) != "list") & length(line.type)== 1L){ # convert the single value into a list of single value - list.line.type <- vector(mode = "list", length = length(data1)) - list.line.type[] <- line.type - } - if( ! is.null(legend.name)){ - if( ! (all(class(legend.name) == "list") & length(data1) == length(legend.name))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": legend.name ARGUMENT MUST BE A LIST OF SAME LENGTH AS data1 IF data1 IS A LIST") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - } - # end check list lengths (and names of data1 compartments if present) - # conversion into lists - if(all(is.data.frame(data1))){ - data1 <- list(L1 = data1) - if(all(class(x) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": x ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - x <- list(L1 = x) - } - if(all(class(y) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": y ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - y <- list(L1 = y) - } - if( ! is.null(categ)){ - if(all(class(categ) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": categ ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - categ <- list(L1 = categ) - } - } - if( ! is.null(categ.class.order)){ - if(all(class(categ.class.order) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": categ.class.order ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - categ.class.order <- list(L1 = categ.class.order) - } - } - if( ! is.null(color)){ - if(all(class(color) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": color ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - color <- list(L1 = color) - } - } - if(all(class(geom) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - geom <- list(L1 = geom) - } - if(all(class(geom.step.dir) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom.step.dir ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - geom.step.dir <- list(L1 = geom.step.dir) - } - if( ! is.null(geom.stick.base)){ - if(all(class(geom.stick.base) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom.stick.base ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - geom.stick.base <- list(L1 = geom.stick.base) - } - } - if(all(class(alpha) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": alpha ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - alpha <- list(L1 = alpha) - } - if(all(class(dot.size) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.size ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - dot.size <- list(L1 = dot.size) - } - if(all(class(dot.shape) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.shape ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - dot.shape <- list(L1 = dot.shape) - } - if(all(class(dot.border.size) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.size ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - dot.border.size <- list(L1 = dot.border.size) - } - if( ! is.null(dot.border.color)){ - if(all(class(dot.border.color) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": dot.border.color ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - dot.border.color <- list(L1 = dot.border.color) - } - } - if(all(class(line.size) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": line.size ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - line.size <- list(L1 = line.size) - } - if(all(class(line.type) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": line.type ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - line.type <- list(L1 = line.type) - } - if( ! is.null(legend.name)){ - if(all(class(legend.name) == "list")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": legend.name ARGUMENT CANNOT BE A LIST IF data1 IS A DATA FRAME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - legend.name <- list(L1 = legend.name) - } - } - }else if( ! all(sapply(data1, FUN = "class") == "data.frame")){ # if not a data frame, data1 can only be a list, as tested above - tempo.cat <- paste0("ERROR IN ", function.name, ": data1 ARGUMENT MUST BE A DATA FRAME OR A LIST OF DATA FRAMES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - # single value converted into list now reattributed to the argument name - if( ! is.null(color)){ - if( ! is.null(list.color)){ - color <- list.color - } - } - if( ! is.null(list.geom)){ - geom <- list.geom - } - if( ! is.null(list.geom.step.dir)){ - geom.step.dir <- list.geom.step.dir - } - if( ! is.null(geom.stick.base)){ - if( ! is.null(list.geom.stick.base)){ - geom.stick.base <- list.geom.stick.base - } - } - if( ! is.null(list.alpha)){ - alpha <- list.alpha - } - if( ! is.null(list.dot.size)){ - dot.size <- list.dot.size - } - if( ! is.null(list.dot.shape)){ - dot.shape <- list.dot.shape - } - if( ! is.null(list.dot.border.size)){ - dot.border.size <- list.dot.border.size - } - if( ! is.null(dot.border.color)){ - if( ! is.null(list.dot.border.color)){ - dot.border.color <- list.dot.border.color - } - } - if( ! is.null(list.line.size)){ - line.size <- list.line.size - } - if( ! is.null(list.line.type)){ - line.type <- list.line.type - } - # end single value converted into list now reattributed to the argument name - # data, x, y, geom, alpha, dot.size, shape, dot.border.size, line.size, line.type, legend.name are list now - # if non-null, categ, categ.class.order, legend.name, color, dot.border.color are list now - # end conversion into lists - # verif of add - if( ! is.null(add)){ - if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by + - tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - - }else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ # - tempo.cat <- paste0("ERROR IN ", function.name, ": FOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by ) - tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - # end verif of add - # management of add containing facet - facet.categ <- NULL - if( ! is.null(add)){ - facet.check <- TRUE - tempo <- unlist(strsplit(x = add, split = "\\s*\\+\\s*(ggplot2|lemon)\\s*::\\s*")) # - tempo <- sub(x = tempo, pattern = "^facet_wrap", replacement = "ggplot2::facet_wrap") - tempo <- sub(x = tempo, pattern = "^facet_grid", replacement = "ggplot2::facet_grid") - tempo <- sub(x = tempo, pattern = "^facet_rep", replacement = "lemon::facet_rep") - if(length(data1) > 1 & (any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")) | grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid"))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nfacet PANELS CANNOT BE USED IF MORE THAN ONE DATA FRAME IN THE data1 ARGUMENT\nPLEASE REWRITE THE add STRING AND RERUN") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - if(any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap"))){ - tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")]))) - facet.categ <- list(names(tempo1$params$facets)) # list of length 1 - tempo.text <- "facet_wrap OR facet_rep_wrap" - facet.check <- FALSE - }else if(grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")){ - tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")]))) - facet.categ <- list(c(names(tempo1$params$rows), names(tempo1$params$cols))) # list of length 1 - tempo.text <- "facet_grid OR facet_rep_grid" - facet.check <- FALSE - } - if(facet.check == FALSE & ! all(facet.categ %in% names(data1[[1]]))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL - tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1[[1]]), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - } - # if facet.categ is not NULL, it is a list of length 1 now - # end management of add containing facet - # legend name filling - if(is.null(legend.name) & ! is.null(categ)){ - legend.name <- categ - }else if(is.null(legend.name) & is.null(categ)){ - legend.name <- vector("list", length(data1)) # null list - } - # legend.name not NULL anymore (list) - # end legend name filling - # ini categ for legend display - fin.lg.disp <- vector("list", 6) # will be used at the end to display or not legends - fin.lg.disp[] <- FALSE - legend.disp <- vector("list", length(data1)) - if(is.null(categ) | legend.show == FALSE){ - legend.disp[] <- FALSE - }else{ - for(i2 in 1:length(data1)){ - if(is.null(categ[[i2]])){ - legend.disp[[i2]] <- FALSE - }else{ - legend.disp[[i2]] <- TRUE - } - } - } - # end ini categ for legend display - # integer colors into gg_palette - tempo.check.color <- NULL - for(i1 in 1:length(data1)){ - if(any(is.na(color[[i1]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), ": color ARGUMENT CANNOT CONTAIN NA") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - tempo.check.color <- c(tempo.check.color, fun_check(data = color[[i1]], data.name = ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, fun.name = function.name)$problem) - } - tempo.check.color <- ! tempo.check.color # invert TRUE and FALSE because if integer, then problem = FALSE - if(any(tempo.check.color == TRUE)){ # convert integers into colors - tempo.integer <- unlist(color[tempo.check.color]) - tempo.color <- fun_gg_palette(max(tempo.integer, na.rm = TRUE)) - for(i1 in 1:length(data1)){ - if(tempo.check.color[i1] == TRUE){ - color[[i1]] <-tempo.color[color[[i1]]] - } - } - } - # end integer colors into gg_palette - # loop (checking inside list compartment) - compart.null.color <- 0 # will be used to attribute a color when color is non-null but a compartment of color is NULL - data1.ini <- data1 # to report NA removal - removed.row.nb <- vector("list", length = length(data1)) # to report NA removal. Contains NULL - removed.rows <- vector("list", length = length(data1)) # to report NA removal. Contains NULL - for(i1 in 1:length(data1)){ - tempo <- fun_check(data = data1[[i1]], data.name = ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), class = "data.frame", na.contain = TRUE, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - # reserved word checking - if(any(names(data1[[i1]]) %in% reserved.words)){ # I do not use fun_name_change() because cannot control y before creating "fake_y". But ok because reserved are not that common - tempo.cat <- paste0("ERROR IN ", function.name, ": COLUMN NAMES OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " ARGUMENT CANNOT BE ONE OF THESE WORDS\n", paste(reserved.words, collapse = " "), "\nTHESE ARE RESERVED FOR THE ", function.name, " FUNCTION") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if( ! (is.null(add))){ - if(any(sapply(X = reserved.words, FUN = grepl, x = add))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF COLUMN NAMES OF data1 IN THE add ARGUMENT STRING, THAT CORRESPOND TO RESERVED STRINGS FOR ", function.name, "\nFOLLOWING COLUMN NAMES HAVE TO BE CHANGED:\n", paste(arg.names[sapply(X = reserved.words, FUN = grepl, x = add)], collapse = "\n"), "\nFOR INFORMATION, THE RESERVED WORDS ARE:\n", paste(reserved.words, collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(any(sapply(X = arg.names, FUN = grepl, x = add))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NAMES OF ", function.name, " ARGUMENTS DETECTED IN THE add STRING:\n", paste(arg.names[sapply(X = arg.names, FUN = grepl, x = add)], collapse = "\n"), "\nRISK OF WRONG OBJECT USAGE INSIDE ", function.name) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end reserved word checking - # check of geom now because required for y argument - tempo <- fun_check(data = geom[[i1]], data.name = ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), options = c("geom_point", "geom_line", "geom_path", "geom_step", "geom_hline", "geom_vline", "geom_stick"), length = 1, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if(geom[[i1]] == "geom_step" & is.null(geom.step.dir[[i1]])){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(geom.step.dir)== 1L, "geom.step.dir", paste0("ELEMENT ", i1, " OF geom.step.dir ARGUMENT")), ": geom.step.dir ARGUMENT CANNOT BE NULL IF ", ifelse(length(geom)== 1L, "geom", paste0("ELEMENT ", i1, " OF geom")), " ARGUMENT IS \"geom_step\"\nHERE geom.step.dir ARGUMENT IS: ", paste(geom.step.dir[[i1]], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(geom[[i1]] == "geom_step" & ! is.null(geom.step.dir[[i1]])){ - tempo <- fun_check(data = geom.step.dir[[i1]], data.name = ifelse(length(geom.step.dir)== 1L, "geom.step.dir", paste0("geom.step.dir NUMBER ", i1)), options = c("vh", "hv", "mid"), length = 1, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if( ! (is.null(geom.stick.base))){ - if(geom[[i1]] == "geom_stick" & ! is.null(geom.stick.base[[i1]])){ - tempo <- fun_check(data = geom.stick.base[[i1]], data.name = ifelse(length(geom.stick.base)== 1L, "geom.stick.base", paste0("geom.stick.base NUMBER ", i1)), mode = "numeric", length = 1, na.contain = FALSE, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - } - # end check of geom now because required for y argument - if(is.null(x[[i1]])){ - if(all(geom[[i1]] != "geom_hline")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": x ARGUMENT CANNOT BE NULL EXCEPT IF ", ifelse(length(geom)== 1L, "x", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_hline\"\nHERE geom ARGUMENT IS: ", paste(geom[[i1]], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - x[[i1]] <- "fake_x" - data1[[i1]] <- cbind(data1[[i1]], fake_x = NA, stringsAsFactors = TRUE) - data1[[i1]][, "fake_x"] <- as.numeric(data1[[i1]][, "fake_x"]) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x")), " ARGUMENT ASSOCIATED TO ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT ", geom[[i1]], " -> FAKE COLUMN ADDED TO DATA FRAME ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", NAMED \"fake_x\" FOR FINAL DRAWING") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(all(geom[[i1]] == "geom_hline")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": x ARGUMENT MUST BE NULL IF ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_hline\"") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - tempo <- fun_check(data = x[[i1]], data.name = ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), class = "vector", mode = "character", length = 1, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if(is.null(y[[i1]])){ - if(all(geom[[i1]] != "geom_vline")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": y ARGUMENT CANNOT BE NULL EXCEPT IF ", ifelse(length(geom)== 1L, "y", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_vline\"\nHERE geom ARGUMENT IS: ", paste(geom[[i1]], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - y[[i1]] <- "fake_y" - data1[[i1]] <- cbind(data1[[i1]], fake_y = NA, stringsAsFactors = TRUE) - data1[[i1]][, "fake_y"] <- as.numeric(data1[[i1]][, "fake_y"]) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y")), " ARGUMENT ASSOCIATED TO ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT ", geom[[i1]], " -> FAKE COLUMN ADDED TO DATA FRAME ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", NAMED \"fake_y\" FOR FINAL DRAWING") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else{ - if(all(geom[[i1]] == "geom_vline")){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ": y ARGUMENT MUST BE NULL IF ", ifelse(length(geom)== 1L, "geom", paste0("geom NUMBER ", i1)), " ARGUMENT IS \"geom_vline\"") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - tempo <- fun_check(data = y[[i1]], data.name = ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), class = "vector", mode = "character", length = 1, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - # x[[i1]] and y[[i1]] not NULL anymore - if( ! (x[[i1]] %in% names(data1[[i1]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x")), " ARGUMENT MUST BE A COLUMN NAME OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT\nHERE IT IS: ", paste(x[[i1]], collapse = " ")))) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if( ! (y[[i1]] %in% names(data1[[i1]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y")), " ARGUMENT MUST BE A COLUMN NAME OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT\nHERE IT IS: ", paste(y[[i1]], collapse = " ")))) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - tempo <- fun_check(data = data1[[i1]][, x[[i1]]], data.name = ifelse(length(x)== 1L, "x ARGUMENT (AS COLUMN NAME OF data1 DATA FRAME)", paste0("ELEMENT ", i1, " OF x ARGUMENT", " (AS COLUMN NAME OF data1 DATA FRAME NUMBER ", i1, ")")), class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - tempo <- fun_check(data = data1[[i1]][, y[[i1]]], data.name = ifelse(length(y)== 1L, "y ARGUMENT (AS COLUMN NAME OF data1 DATA FRAME)", paste0("ELEMENT ", i1, " OF y ARGUMENT", " (AS COLUMN NAME OF data1 DATA FRAME NUMBER ", i1, ")")), class = "vector", mode = "numeric", na.contain = TRUE, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if(x[[i1]] == "fake_x" & y[[i1]] == "fake_y"){ # because the code cannot accept to be both "fake_x" and "fake_y" at the same time - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 2\nTHE CODE CANNOT ACCEPT x AND y TO BE \"fake_x\" AND \"fake_y\" IN THE SAME DATA FRAME ", i1, " ") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - - if(( ! is.null(categ)) & ( ! is.null(categ[[i1]]))){ # is.null(categ[[i1]]) works even if categ is NULL # is.null(categ[[i1]]) works even if categ is NULL # if categ[[i1]] = NULL, fake_categ will be created later on - tempo <- fun_check(data = categ[[i1]], data.name = ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")),, class = "vector", mode = "character", length = 1, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if( ! (categ[[i1]] %in% names(data1[[i1]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ")), " ARGUMENT MUST BE A COLUMN NAME OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT\nHERE IT IS: ", paste(categ[[i1]], collapse = " ")))) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - tempo1 <- fun_check(data = data1[[i1]][, categ[[i1]]], data.name = ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) - tempo2 <- fun_check(data = data1[[i1]][, categ[[i1]]], data.name = ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), class = "factor", na.contain = TRUE, fun.name = function.name) - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " MUST BE A FACTOR OR CHARACTER VECTOR") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(tempo1$problem == FALSE){ - data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", THE CHARACTER COLUMN HAS BEEN CONVERTED TO FACTOR") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - - } - if(geom[[i1]] == "geom_vline" | geom[[i1]] == "geom_hline"){ - if(length(unique(data1[[i1]][, categ[[i1]]])) != nrow(data1[[i1]])){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(geom)== 1L, "geom OF data1 ARGUMENT", paste0("geom NUMBER ", i1, " OF DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " ARGUMENT IS ", geom[[i1]], ", MEANING THAT ", ifelse(length(categ)== 1L, "categ OF data1 ARGUMENT", paste0("ELEMENT ", i1, " OF categ ARGUMENT IN DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " MUST HAVE A DIFFERENT CLASS PER LINE OF data1 (ONE x VALUE PER CLASS)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - }else if(( ! is.null(categ)) & is.null(categ[[i1]])){ # is.null(categ[[i1]]) works even if categ is NULL # if categ[[i1]] = NULL, fake_categ will be created. WARNING: is.null(categ[[i1]]) means no legend display (see above), because categ has not been precised. This also means a single color for data1[[i1]] - if(length(color[[i1]]) > 1){ # 0 means is.null(color[[i1]]) or is.null(color) and 1 is ok -> single color for data1[[i1]] - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ")), " ARGUMENT BUT CORRESPONDING COLORS IN ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " HAS LENGTH OVER 1\n", paste(color[[i1]], collapse = " "), "\nWHICH IS NOT COMPATIBLE WITH NULL CATEG -> COLOR RESET TO A SINGLE COLOR") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - color[i1] <- list(NULL) # will provide a single color below # Warning color[[i1]] <- NULL removes the compartment - } - categ[[i1]] <- "fake_categ" - data1[[i1]] <- cbind(data1[[i1]], fake_categ = "", stringsAsFactors = TRUE) - # inactivated because give a different color to different "Line_" categ while a single color for all the data1[[i1]] required. Thus, put back after the color management - # if(geom[[i1]] == "geom_hline" | geom[[i1]] == "geom_vline"){ - # data1[[i1]][, "fake_categ"] <- paste0("Line_", 1:nrow(data1[[i1]])) - # }else{ - data1[[i1]][, "fake_categ"] <- data1[[i1]][, "fake_categ"] # as.numeric("") create a vector of NA but class numeric - # } - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ")), " ARGUMENT -> FOR DATA FRAME ", ifelse(length(data1)== 1L, "data1 ARGUMENT:", paste0("NUMBER ", i1, " OF data1 ARGUMENT:")), "\n- FAKE \"fake_categ\" COLUMN ADDED FILLED WITH \"\"(OR WITH \"Line_...\" FOR LINES)\n- SINGLE COLOR USED FOR PLOTTING\n- NO LEGEND DISPLAYED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # OK: if categ is not NULL, all the non-null categ columns of data1 are factors from here - - # management of log scale and Inf removal - if(x[[i1]] != "fake_x"){ - if(any(( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") PRESENCE OF -Inf OR Inf VALUES IN ", ifelse(length(categ)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - if(y[[i1]] != "fake_y"){ - if(any(( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[, y])) & ( ! is.na(data1[, y])) detects only Inf - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") PRESENCE OF -Inf OR Inf VALUES IN ", ifelse(length(categ)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # log conversion - if(x.log != "no"){ - tempo1 <- ! is.finite(data1[[i1]][, x[[i1]]]) # where are initial NA and Inf - data1[[i1]][, x[[i1]]] <- suppressWarnings(get(x.log)(data1[[i1]][, x[[i1]]]))# no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - if(any( ! (tempo1 | is.finite(data1[[i1]][, x[[i1]]])))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") LOG CONVERSION INTRODUCED -Inf OR Inf OR NaN VALUES IN ", ifelse(length(categ)== 1L, "x", paste0("ELEMENT ", i1, " OF x ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - if(y.log != "no"){ - tempo1 <- ! is.finite(data1[[i1]][, y[[i1]]]) # where are initial NA and Inf - data1[[i1]][, y[[i1]]] <- suppressWarnings(get(y.log)(data1[[i1]][, y[[i1]]]))# no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - if(any( ! (tempo1 | is.finite(data1[[i1]][, y[[i1]]])))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") LOG CONVERSION INTRODUCED -Inf OR Inf OR NaN VALUES IN ", ifelse(length(categ)== 1L, "y", paste0("ELEMENT ", i1, " OF y ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # Inf removal - # removed.row.nb[[i1]] <- NULL # already NULL and Warning this removes the compartment - removed.rows[[i1]] <- data.frame(stringsAsFactors = FALSE) - if(any(( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])) detects only Inf - removed.row.nb[[i1]] <- c(removed.row.nb[[i1]], which(( ! is.finite(data1[[i1]][, x[[i1]]])) & ( ! is.na(data1[[i1]][, x[[i1]]])))) - } - if(any(( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])))){ # is.finite also detects NA: ( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])) detects only Inf - removed.row.nb[[i1]] <- c(removed.row.nb[[i1]], which(( ! is.finite(data1[[i1]][, y[[i1]]])) & ( ! is.na(data1[[i1]][, y[[i1]]])))) - } - if( ! is.null(removed.row.nb[[i1]])){ - removed.row.nb[[i1]] <- unique(removed.row.nb[[i1]]) # to remove the duplicated positions (NA in both x and y) - removed.rows[[i1]] <- rbind(removed.rows[[i1]], data1.ini[[i1]][removed.row.nb[[i1]], ]) # here data1.ini used to have the y = O rows that will be removed because of Inf creation after log transformation - data1[[i1]] <- data1[[i1]][-removed.row.nb[[i1]], ] - data1.ini[[i1]] <- data1.ini[[i1]][-removed.row.nb[[i1]], ] # - } - # From here, data1 and data.ini have no more Inf - # end Inf removal - # x.lim and y.lim dealt later on, after the end f the loop - # end management of log scale and Inf removal - # na detection and removal - column.check <- unique(unlist(c( # unlist because creates a list - if(x[[i1]] == "fake_x"){NULL}else{x[[i1]]}, - if(y[[i1]] == "fake_y"){NULL}else{y[[i1]]}, - if( ! is.null(categ)){if(is.null(categ[[i1]])){NULL}else{categ[[i1]]}}, - if( ! is.null(facet.categ)){if(is.null(facet.categ[[i1]])){NULL}else{facet.categ[[i1]]}} - ))) # dot.categ because can be a 3rd column of data1 - if(any(is.na(data1[[i1]][, column.check]))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NA DETECTED IN COLUMNS ", paste(column.check, collapse = " "), " OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - for(i3 in 1:length(column.check)){ - if(any(is.na(data1[[i1]][, column.check[i3]]))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("NA REMOVAL DUE TO COLUMN ", column.check[i3], " OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT"))) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - tempo <- unique(unlist(lapply(lapply(c(data1[[i1]][column.check]), FUN = is.na), FUN = which))) - removed.row.nb[[i1]] <- c(removed.row.nb[[i1]], tempo) - removed.rows[[i1]] <- rbind(removed.rows[[i1]], data1.ini[[i1]][tempo, ]) # # tempo used because removed.row.nb is not empty. Here data1.ini used to have the non NA rows that will be removed because of NAN creation after log transformation (neg values for instance) - column.check <- column.check[ ! (column.check == x[[i1]] | column.check == y[[i1]])] # remove x and y to keep quali columns - if(length(tempo) != 0){ - data1[[i1]] <- data1[[i1]][-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers thant the former - data1.ini[[i1]] <- data1.ini[[i1]][-tempo, ] # WARNING tempo here and not removed.row.nb because the latter contain more numbers than the former - for(i4 in 1:length(column.check)){ - if(any( ! unique(removed.rows[[i1]][, column.check[i4]]) %in% unique(data1[[i1]][, column.check[i4]]))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN COLUMN ", column.check[i4], " OF ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", THE FOLLOWING CLASSES HAVE DISAPPEARED AFTER NA REMOVAL\n(IF COLUMN USED IN THE PLOT, THIS CLASS WILL NOT BE DISPLAYED):\n", paste(unique(removed.rows[[i1]][, column.check[i4]])[ ! unique(removed.rows[[i1]][, column.check[i4]]) %in% unique(data1[[i1]][, column.check[i4]])], collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - tempo.levels <- levels(data1[[i1]][, column.check[i4]])[levels(data1[[i1]][, column.check[i4]]) %in% unique(as.character(data1[[i1]][, column.check[i4]]))] - data1[[i1]][, column.check[i4]] <- factor(as.character(data1[[i1]][, column.check[i4]]), levels = tempo.levels) - if(column.check[i4] %in% categ[[i1]] & ! is.null(categ.class.order)){ - categ.class.order[[i1]] <- levels(data1[[i1]][, column.check[i4]])[levels(data1[[i1]][, column.check[i4]]) %in% unique(data1[[i1]][, column.check[i4]])] # remove the absent class in the categ.class.order vector - data1[[i1]][, column.check[i4]] <- factor(as.character(data1[[i1]][, column.check[i4]]), levels = unique(categ.class.order[[i1]])) - } - } - } - } - } - # end na detection and removal - # From here, data1 and data.ini have no more NA or NaN in x, y, categ (if categ != NULL) and facet.categ (if categ != NULL) - if( ! is.null(categ.class.order)){ - # the following check will be done several times but I prefer to keep it here, after the creation of categ - if(is.null(categ[[i1]]) & ! is.null(categ.class.order[[i1]])){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i1, " OF categ ARGUMENT CANNOT BE NULL IF COMPARTMENT ", i1, " OF categ.class.order ARGUMENT IS NOT NULL: ", paste(categ.class.order, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - if(is.null(categ.class.order[[i1]])){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE categ.class.order COMPARTMENT ", i1, " IS NULL. ALPHABETICAL ORDER WILL BE APPLIED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - data1[[i1]][, categ[[i1]]] <- factor(as.character(data1[[i1]][, categ[[i1]]])) # if already a factor, change nothing, if characters, levels according to alphabetical order - categ.class.order[[i1]] <- levels(data1[[i1]][, categ[[i1]]]) # character vector that will be used later - }else{ - tempo <- fun_check(data = categ.class.order[[i1]], data.name = paste0("COMPARTMENT ", i1 , " OF categ.class.order ARGUMENT"), class = "vector", mode = "character", length = length(levels(data1[[i1]][, categ[[i1]]])), fun.name = function.name) # length(data1[, categ[i1]) -> if data1[, categ[i1] was initially character vector, then conversion as factor after the NA removal, thus class number ok. If data1[, categ[i1] was initially factor, no modification after the NA removal, thus class number ok - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if(any(duplicated(categ.class.order[[i1]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i1, " OF categ.class.order ARGUMENT CANNOT HAVE DUPLICATED CLASSES: ", paste(categ.class.order[[i1]], collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if( ! (all(categ.class.order[[i1]] %in% unique(data1[[i1]][, categ[[i1]]])) & all(unique(data1[[i1]][, categ[[i1]]]) %in% categ.class.order[[i1]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nCOMPARTMENT ", i1, " OF categ.class.order ARGUMENT MUST BE CLASSES OF COMPARTMENT ", i1, " OF categ ARGUMENT\nHERE IT IS:\n", paste(categ.class.order[[i1]], collapse = " "), "\nFOR COMPARTMENT ", i1, " OF categ.class.order AND IT IS:\n", paste(unique(data1[[i1]][, categ[[i1]]]), collapse = " "), "\nFOR COLUMN ", categ[[i1]], " OF data1 NUMBER ", i1) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]], levels = categ.class.order[[i1]]) # reorder the factor - } - names(categ.class.order)[i1] <- categ[[i1]] - } - } - # OK: if categ.class.order is not NULL, all the NULL categ.class.order columns of data1 are character from here - - if( ! is.null(legend.name[[i1]])){ - tempo <- fun_check(data = legend.name[[i1]], data.name = ifelse(length(legend.name)== 1L, "legend.name", paste0("legend.name NUMBER ", i1)),, class = "vector", mode = "character", length = 1, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if( ! is.null(color)){ # if color is NULL, will be filled later on - # check the nature of color - if(is.null(color[[i1]])){ - compart.null.color <- compart.null.color + 1 - color[[i1]] <- grey(compart.null.color / 8) # cannot be more than 7 overlays. Thus 7 different greys. 8/8 is excluded because white dots - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL COLOR IN ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " ASSOCIATED TO ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", SINGLE COLOR ", paste(color[[i1]], collapse = " "), " HAS BEEN ATTRIBUTED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - tempo1 <- fun_check(data = color[[i1]], data.name = ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name) # na.contain = TRUE in case of colum of data1 - tempo2 <- fun_check(data = color[[i1]], data.name = ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), class = "factor", na.contain = TRUE, fun.name = function.name) # idem - if(tempo1$problem == TRUE & tempo2$problem == TRUE){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A FACTOR OR CHARACTER VECTOR OR INTEGER VECTOR") # integer possible because dealt above - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if( ! (all(color[[i1]] %in% colors() | grepl(pattern = "^#", color[[i1]])))){ # check that all strings of low.color start by # - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A HEXADECIMAL COLOR VECTOR STARTING BY # AND/OR COLOR NAMES GIVEN BY colors(): ", paste(unique(color[[i1]]), collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if(any(is.na(color[[i1]]))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), ", THE COLORS:\n", paste(unique(color[[i1]]), collapse = " "), "\nCONTAINS NA") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # end check the nature of color - # check the length of color - if(is.null(categ) & length(color[[i1]]) != 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A SINGLE COLOR IF categ IS NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if( ! is.null(categ)){ - # No problem of NA management by ggplot2 because already removed - if(categ[[i1]] == "fake_categ" & length(color[[i1]]) != 1){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE A SINGLE COLOR IF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IS NULL") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(length(color[[i1]]) == length(unique(data1[[i1]][, categ[[i1]]]))){ # here length(color) is equal to the different number of categ - data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", THE FOLLOWING COLORS:\n", paste(color[[i1]], collapse = " "), "\nHAVE BEEN ATTRIBUTED TO THESE CLASSES:\n", paste(levels(factor(data1[[i1]][, categ[[i1]]])), collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else if(length(color[[i1]]) == length(data1[[i1]][, categ[[i1]]])){# here length(color) is equal to nrow(data1[[i1]]) -> Modif to have length(color) equal to the different number of categ (length(color) == length(levels(data1[[i1]][, categ[[i1]]]))) - data1[[i1]] <- cbind(data1[[i1]], color = color[[i1]], stringsAsFactors = TRUE) - tempo.check <- unique(data1[[i1]][ , c(categ[[i1]], "color")]) - if( ! (nrow(data1[[i1]]) == length(color[[i1]]) & nrow(tempo.check) == length(unique(data1[[i1]][ , categ[[i1]]])))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color")), " ARGUMENT HAS THE LENGTH OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), "\nBUT IS INCORRECTLY ASSOCIATED TO EACH CLASS OF THIS categ:\n", paste(unique(mapply(FUN = "paste", data1[[i1]][ ,categ[[i1]]], data1[[i1]][ ,"color"])), collapse = "\n")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order - color[[i1]] <- unique(color[[i1]][order(data1[[i1]][, categ[[i1]]])]) # Modif to have length(color) equal to the different number of categ (length(color) == length(levels(data1[[i1]][, categ[[i1]]]))) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count, ") FROM FUNCTION ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " HAS THE LENGTH OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " COLUMN VALUES\nCOLORS HAVE BEEN RESPECTIVELY ASSOCIATED TO EACH CLASS OF categ AS:\n", paste(levels(factor(data1[[i1]][, categ[[i1]]])), collapse = " "), "\n", paste(color[[i1]], collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else if(length(color[[i1]])== 1L){ - data1[[i1]][, categ[[i1]]] <- factor(data1[[i1]][, categ[[i1]]]) # if already a factor, change nothing, if characters, levels according to alphabetical order - color[[i1]] <- rep(color[[i1]], length(levels(data1[[i1]][, categ[[i1]]]))) - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") IN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), ", COLOR HAS LENGTH 1 MEANING THAT ALL THE DIFFERENT CLASSES OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), "\n", paste(levels(factor(data1[[i1]][, categ[[i1]]])), collapse = " "), "\nWILL HAVE THE SAME COLOR\n", paste(color[[i1]], collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST BE\n(1) LENGTH 1\nOR (2) THE LENGTH OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), " COLUMN VALUES\nOR (3) THE LENGTH OF THE CLASSES IN THIS COLUMN\nHERE IT IS COLOR LENGTH ", length(color[[i1]]), " VERSUS CATEG LENGTH ", length(data1[[i1]][, categ[[i1]]]), " AND CATEG CLASS LENGTH ", length(unique(data1[[i1]][, categ[[i1]]])), "\nPRESENCE OF NA IN THE COLUMN x, y OR categ OF data1 COULD BE THE PROBLEME") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - } - if((geom[[i1]] == "geom_hline" | geom[[i1]] == "geom_vline") & ! is.null(categ[[i1]])){ # add here after the color management, to deal with the different lines to plot inside any data[[i1]] - if(categ[[i1]] == "fake_categ"){ - data1[[i1]][, "fake_categ"] <- factor(paste0("Line_", formatC(1:nrow(data1[[i2]]), width = nchar(nrow(data1[[i2]])), flag = "0"))) - } - } - tempo <- fun_check(data = alpha[[i1]], data.name = ifelse(length(alpha)== 1L, "alpha", paste0("alpha NUMBER ", i1)), prop = TRUE, length = 1, fun.name = function.name) - if(tempo$problem == TRUE){ - stop(paste0("\n\n================\n\n", tempo$text, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - # end loop (checking inside list compartment) - if(length(data1) > 1){ - if(length(unique(unlist(x)[ ! x == "fake_x"])) > 1){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE x ARGUMENT DOES NOT CONTAIN IDENTICAL COLUMN NAMES:\n", paste(unlist(x), collapse = " "), "\nX-AXIS OVERLAYING DIFFERENT VARIABLES?") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - if(length(data1) > 1){ - if(length(unique(unlist(y)[ ! y == "fake_y"])) > 1){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE y ARGUMENT DOES NOT CONTAIN IDENTICAL COLUMN NAMES:\n", paste(unlist(y), collapse = " "), "\nY-AXIS OVERLAYING DIFFERENT VARIABLES?") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - if(sum(geom %in% "geom_point") > 3){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT CANNOT HAVE MORE THAN THREE \"geom_point\" ELEMENTS") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(length(geom) - sum(geom %in% "geom_point") > 3){ - tempo.cat <- paste0("ERROR IN ", function.name, ": geom ARGUMENT CANNOT HAVE MORE THAN THREE LINE ELEMENTS") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - # x.lim management before transfo by x.log - if(x.log != "no" & ! is.null(x.lim)){ - if(any(x.lim <= 0)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE x.log ARGUMENT SET TO ", x.log, ":\n", paste(x.lim, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(any( ! is.finite(if(x.log == "log10"){log10(x.lim)}else{log2(x.lim)}))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lim ARGUMENT RETURNS INF/NA WITH THE x.log ARGUMENT SET TO ", x.log, "\nAS SCALE COMPUTATION IS ", ifelse(x.log == "log10", "log10", "log2"), ":\n", paste(if(x.log == "log10"){log10(x.lim)}else{log2(x.lim)}, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if(x.log != "no" & x.include.zero == TRUE){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") x.log ARGUMENT SET TO ", x.log, " AND x.include.zero ARGUMENT SET TO TRUE -> x.include.zero ARGUMENT RESET TO FALSE BECAUSE 0 VALUE CANNOT BE REPRESENTED IN LOG SCALE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - x.include.zero <- FALSE - } - # end x.lim management before transfo by x.log - # y.lim management before transfo by y.log - if(y.log != "no" & ! is.null(y.lim)){ - if(any(y.lim <= 0)){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(any( ! is.finite(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT RETURNS INF/NA WITH THE y.log ARGUMENT SET TO ", y.log, "\nAS SCALE COMPUTATION IS ", ifelse(y.log == "log10", "log10", "log2"), ":\n", paste(if(y.log == "log10"){log10(y.lim)}else{log2(y.lim)}, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if(y.log != "no" & y.include.zero == TRUE){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") y.log ARGUMENT SET TO ", y.log, " AND y.include.zero ARGUMENT SET TO TRUE -> y.include.zero ARGUMENT RESET TO FALSE BECAUSE 0 VALUE CANNOT BE REPRESENTED IN LOG SCALE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - y.include.zero <- FALSE - } - # end y.lim management before transfo by y.log - # end other checkings - # reserved word checking - #already done above - # end reserved word checking - # end second round of checking and data preparation - - - # package checking - fun_pack(req.package = c( - "gridExtra", - "ggplot2", - "lemon", - "scales" - ), lib.path = lib.path) - # packages Cairo and grid tested by fun_gg_point_rast() - # end package checking - - - - - # main code - # axes management - if(is.null(x.lim)){ - if(any(unlist(mapply(FUN = "[[", data1, x, SIMPLIFY = FALSE)) %in% c(Inf, -Inf))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE x COLUMN IN data1 CONTAINS -Inf OR Inf VALUES THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - x.lim <- suppressWarnings(range(unlist(mapply(FUN = "[[", data1, x, SIMPLIFY = FALSE)), na.rm = TRUE, finite = TRUE)) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only. y.lim added here. If NULL, ok if y argument has values - }else if(x.log != "no"){ - x.lim <- get(x.log)(x.lim) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - } - if(x.log != "no"){ - if(any( ! is.finite(x.lim))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nx.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE x.log ARGUMENT SET TO ", x.log, ":\n", paste(x.lim, collapse = " "), "\nPLEASE, CHECK DATA VALUES (PRESENCE OF ZERO OR INF VALUES)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if(suppressWarnings(all(x.lim %in% c(Inf, -Inf)))){ # happen when x is only NULL - if(all(unlist(geom) %in% c("geom_vline", "geom_stick"))){ - tempo.cat <- paste0("ERROR IN ", function.name, " NOT POSSIBLE TO DRAW geom_vline OR geom_stick KIND OF LINES ALONE IF x.lim ARGUMENT IS SET TO NULL, SINCE NO X-AXIS DEFINED (", ifelse(length(x)== 1L, "x", paste0("ELEMENT ", i1, " OF x")), " ARGUMENT MUST BE NULL FOR THESE KIND OF LINES)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, " x.lim ARGUMENT MADE OF NA, -Inf OR Inf ONLY: ", paste(x.lim, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - x.lim.order <- order(x.lim) # to deal with inverse axis - # print(x.lim.order) - x.lim <- sort(x.lim) - x.lim[1] <- x.lim[1] - abs(x.lim[2] - x.lim[1]) * ifelse(diff(x.lim.order) > 0, x.right.extra.margin, x.left.extra.margin) # diff(x.lim.order) > 0 means not inversed axis - x.lim[2] <- x.lim[2] + abs(x.lim[2] - x.lim[1]) * ifelse(diff(x.lim.order) > 0, x.left.extra.margin, x.right.extra.margin) # diff(x.lim.order) > 0 means not inversed axis - if(x.include.zero == TRUE){ # no need to check x.log != "no" because done before - x.lim <- range(c(x.lim, 0), na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - } - x.lim <- x.lim[x.lim.order] - if(any(is.na(x.lim))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 3") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if(is.null(y.lim)){ - if(any(unlist(mapply(FUN = "[[", data1, y, SIMPLIFY = FALSE)) %in% c(Inf, -Inf))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE y COLUMN IN data1 CONTAINS -Inf OR Inf VALUES THAT WILL NOT BE CONSIDERED IN THE PLOT RANGE") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - y.lim <- suppressWarnings(range(unlist(mapply(FUN = "[[", data1, y, SIMPLIFY = FALSE)), na.rm = TRUE, finite = TRUE)) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only. y.lim added here. If NULL, ok if y argument has values - }else if(y.log != "no"){ - y.lim <- get(y.log)(y.lim) # no env = sys.nframe(), inherit = FALSE in get() because look for function in the classical scope - } - if(y.log != "no"){ - if(any( ! is.finite(y.lim))){ - tempo.cat <- paste0("ERROR IN ", function.name, "\ny.lim ARGUMENT CANNOT HAVE ZERO OR NEGATIVE VALUES WITH THE y.log ARGUMENT SET TO ", y.log, ":\n", paste(y.lim, collapse = " "), "\nPLEASE, CHECK DATA VALUES (PRESENCE OF ZERO OR INF VALUES)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - if(suppressWarnings(all(y.lim %in% c(Inf, -Inf)))){ # happen when y is only NULL - if(all(unlist(geom) == "geom_vline")){ - tempo.cat <- paste0("ERROR IN ", function.name, " NOT POSSIBLE TO DRAW geom_vline KIND OF LINES ALONE IF y.lim ARGUMENT IS SET TO NULL, SINCE NO Y-AXIS DEFINED (", ifelse(length(y)== 1L, "y", paste0("ELEMENT ", i1, " OF y")), " ARGUMENT MUST BE NULL FOR THESE KIND OF LINES)") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, " y.lim ARGUMENT MADE OF NA, -Inf OR Inf ONLY: ", paste(y.lim, collapse = " ")) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - y.lim.order <- order(y.lim) # to deal with inverse axis - y.lim <- sort(y.lim) - y.lim[1] <- y.lim[1] - abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.bottom.extra.margin, y.top.extra.margin) # diff(y.lim.order) > 0 means not inversed axis - y.lim[2] <- y.lim[2] + abs(y.lim[2] - y.lim[1]) * ifelse(diff(y.lim.order) > 0, y.top.extra.margin, y.bottom.extra.margin) # diff(y.lim.order) > 0 means not inversed axis - if(y.include.zero == TRUE){ # no need to check y.log != "no" because done before - y.lim <- range(c(y.lim, 0), na.rm = TRUE, finite = TRUE) # finite = TRUE removes all the -Inf and Inf except if only this. In that case, whatever the -Inf and/or Inf present, output -Inf;Inf range. Idem with NA only - } - y.lim <- y.lim[y.lim.order] - if(any(is.na(y.lim))){ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 4") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - # end axes management - - - - - # create a fake categ if NULL to deal with legend display - if(is.null(categ)){ - categ <- vector("list", length(data1)) - categ[] <- "fake_categ" - for(i2 in 1:length(data1)){ - data1[[i2]] <- cbind(data1[[i2]], fake_categ = "", stringsAsFactors = TRUE) - if(geom[[i2]] == "geom_hline" | geom[[i2]] == "geom_vline"){ - data1[[i2]][, "fake_categ"] <- factor(paste0("Line_", 1:nrow(data1[[i2]]))) - } - } - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL categ ARGUMENT -> FAKE \"fake_categ\" COLUMN ADDED TO EACH DATA FRAME OF data1, AND FILLED WITH \"\"") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - # categ is not NULL anymore - if(is.null(categ.class.order)){ - categ.class.order <- vector("list", length = length(data1)) - tempo.categ.class.order <- NULL - for(i2 in 1:length(categ.class.order)){ - categ.class.order[[i2]] <- levels(data1[[i2]][, categ[[i2]]]) - names(categ.class.order)[i2] <- categ[[i2]] - tempo.categ.class.order <- c(tempo.categ.class.order, ifelse(i2 != 1, "\n", ""), categ.class.order[[i2]]) - } - if(any(unlist(legend.disp))){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") THE categ.class.order SETTING IS NULL. ALPHABETICAL ORDER WILL BE APPLIED FOR CLASS ORDERING:\n", paste(tempo.categ.class.order, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end create a fake categ if NULL to deal with legend display - # categ.class.order is not NULL anymore - - - # vector of color with length as in levels(categ) of data1 - if(is.null(color)){ - color <- vector("list", length(data1)) - length.categ.list <- lapply(lapply(mapply(FUN = "[[", data1, categ, SIMPLIFY = FALSE), FUN = unique), FUN = function(x){length(x[ ! is.na(x)])}) - length.categ.list[sapply(categ, FUN = "==", "fake_categ")] <- 1 # when is.null(color), a single color for all the dots or lines of data[[i1]] that contain "fake_categ" category - total.categ.length <- sum(unlist(length.categ.list), na.rm = TRUE) - tempo.color <- fun_gg_palette(total.categ.length) - tempo.count <- 0 - for(i2 in 1:length(data1)){ - color[[i2]] <- tempo.color[(1:length.categ.list[[i2]]) + tempo.count] - tempo.count <- tempo.count + length.categ.list[[i2]] - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") NULL color ARGUMENT -> COLORS RESPECTIVELY ATTRIBUTED TO EACH CLASS OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i2, " OF categ ARGUMENT")), " (", categ[[i2]], ") IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i2, " OF data1 ARGUMENT")), ":\n", paste(color[[i2]], collapse = " "), "\n", paste(if(all(levels(data1[[i2]][, categ[[i2]]]) == "")){'\"\"'}else{levels(data1[[i2]][, categ[[i2]]])}, collapse = " ")) - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end vector of color with length as in levels(categ) of data1 - # color is not NULL anymore - - - - - - # last check - for(i1 in 1:length(data1)){ - if(categ[[i1]] != "fake_categ" & length(color[[i1]]) != length(unique(data1[[i1]][, categ[[i1]]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, " LAST CHECK: ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST HAVE THE LENGTH OF LEVELS OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), "\nHERE IT IS COLOR LENGTH ", length(color[[i1]]), " VERSUS CATEG LEVELS LENGTH ", length(unique(data1[[i1]][, categ[[i1]]])), "\nREMINDER: A SINGLE COLOR PER CLASS OF CATEG AND A SINGLE CLASS OF CATEG PER COLOR MUST BE RESPECTED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else if(categ[[i1]] == "fake_categ" & length(color[[i1]]) != 1){ - tempo.cat <- paste0("ERROR IN ", function.name, " LAST CHECK: ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST HAVE LENGTH 1 WHEN ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IS NULL\nHERE IT IS COLOR LENGTH ", length(color[[i1]])) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - # end last check - - - - - - # conversion of geom_hline and geom_vline - for(i1 in 1:length(data1)){ - if(geom[[i1]] == "geom_hline" | geom[[i1]] == "geom_vline"){ - final.data.frame <- data.frame() - for(i3 in 1:nrow(data1[[i1]])){ - tempo.data.frame <- rbind(data1[[i1]][i3, ], data1[[i1]][i3, ], stringsAsFactors = TRUE) - if(geom[[i1]] == "geom_hline"){ - tempo.data.frame[, x[[i1]]] <- x.lim - }else if(geom[[i1]] == "geom_vline"){ - tempo.data.frame[, y[[i1]]] <- y.lim - }else{ - tempo.cat <- paste0("ERROR IN ", function.name, ": CODE INCONSISTENCY 5") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - # 3 lines below inactivated because I put that above - # if(is.null(categ[[i1]])){ - # data1[, "fake_categ"] <- paste0("Line_", i3) - # } - final.data.frame <- rbind(final.data.frame, tempo.data.frame, stringsAsFactors = TRUE) - } - data1[[i1]] <- final.data.frame - geom[[i1]] <- "geom_line" - if(length(color[[i1]])== 1L){ - color[[i1]] <- rep(color[[i1]], length(unique(data1[[i1]][ , categ[[i1]]]))) - }else if(length(color[[i1]]) != length(unique(data1[[i1]][ , categ[[i1]]]))){ - tempo.cat <- paste0("ERROR IN ", function.name, " geom_hline AND geom_vline CONVERSION TO FIT THE XLIM AND YLIM LIMITS OF THE DATA: ", ifelse(length(color)== 1L, "color", paste0("ELEMENT NUMBER ", i1, " OF color ARGUMENT")), " MUST HAVE THE LENGTH OF LEVELS OF ", ifelse(length(categ)== 1L, "categ", paste0("ELEMENT ", i1, " OF categ ARGUMENT")), " IN ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i1, " OF data1 ARGUMENT")), "\nHERE IT IS COLOR LENGTH ", length(color[[i1]]), " VERSUS CATEG LEVELS LENGTH ", length(unique(data1[[i1]][, categ[[i1]]]))) - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - } - } - # end conversion of geom_hline and geom_vline - - - - - # kind of geom_point (vectorial or raster) - scatter.kind <- vector("list", length = length(data1)) # list of same length as data1, that will be used to use either ggplot2::geom_point() (vectorial dot layer) or fun_gg_point_rast() (raster dot layer) - fix.ratio <- FALSE - if(is.null(raster.threshold)){ - if(raster == TRUE){ - scatter.kind[] <- "fun_gg_point_rast" # not important to fill everything: will be only used when geom == "geom_point" - fix.ratio <- TRUE - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") RASTER PLOT GENERATED -> ASPECT RATIO OF THE PLOT REGION SET BY THE raster.ratio ARGUMENT (", fun_round(raster.ratio, 2), ") TO AVOID A BUG OF ELLIPSOID DOT DRAWING") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - }else{ - scatter.kind[] <- "ggplot2::geom_point" - } - }else{ - for(i2 in 1:length(data1)){ - if(geom[[i2]] == "geom_point"){ - if(nrow(data1[[i2]]) <= raster.threshold){ - scatter.kind[[i2]] <- "ggplot2::geom_point" - }else{ - scatter.kind[[i2]] <- "fun_gg_point_rast" - fix.ratio <- TRUE - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") ", ifelse(length(data1)== 1L, "data1 ARGUMENT", paste0("DATA FRAME NUMBER ", i2, " OF data1 ARGUMENT")), " LAYER AS RASTER (NOT VECTORIAL)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - } - if(any(unlist(scatter.kind) == "fun_gg_point_rast")){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") RASTER PLOT GENERATED -> ASPECT RATIO OF THE PLOT REGION SET BY THE raster.ratio ARGUMENT (", fun_round(raster.ratio, 2), ") TO AVOID A BUG OF ELLIPSOID DOT DRAWING") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - } - # end kind of geom_point (vectorial or raster) - - - - - # no need loop part - coord.names <- NULL - tempo.gg.name <- "gg.indiv.plot." - tempo.gg.count <- 0 - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::xlab(if(is.null(x.lab)){x[[1]]}else{x.lab})) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ylab(if(is.null(y.lab)){y[[1]]}else{y.lab})) - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggtitle(title)) - # text angle management - x.tempo.just <- fun_gg_just(angle = x.text.angle, pos = "bottom", kind = "axis") - y.tempo.just <- fun_gg_just(angle = y.text.angle, pos = "left", kind = "axis") - # end text angle management - add.check <- TRUE - if( ! is.null(add)){ # if add is NULL, then = 0 - if(grepl(pattern = "ggplot2::theme", add) == TRUE){ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT\n-> INTERNAL GGPLOT2 THEME FUNCTIONS theme() AND theme_classic() HAVE BEEN INACTIVATED, TO BE USED BY THE USER\n-> article ARGUMENT WILL BE IGNORED\nIT IS RECOMMENDED TO USE \"+ theme(aspect.ratio = raster.ratio)\" IF RASTER MODE IS ACTIVATED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - add.check <- FALSE - } - } - if(add.check == TRUE & article == TRUE){ - # WARNING: not possible to add several times theme(). NO message but the last one overwrites the others - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_classic(base_size = text.size)) - if(grid == TRUE){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( - text = ggplot2::element_text(size = text.size), - plot.title = ggplot2::element_text(size = title.text.size), # stronger than text - legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend - line = ggplot2::element_line(size = 0.5), - axis.line.y.left = ggplot2::element_line(colour = "black"), # draw lines for the y axis - axis.line.x.bottom = ggplot2::element_line(colour = "black"), # draw lines for the x axis - panel.grid.major.x = ggplot2::element_line(colour = "grey85", size = 0.75), - panel.grid.minor.x = ggplot2::element_line(colour = "grey90", size = 0.25), - panel.grid.major.y = ggplot2::element_line(colour = "grey85", size = 0.75), - panel.grid.minor.y = ggplot2::element_line(colour = "grey90", size = 0.25), - axis.text.x = ggplot2::element_text(angle = x.tempo.just$angle, hjust = x.tempo.just$hjust, vjust = x.tempo.just$vjust), - axis.text.y = ggplot2::element_text(angle = y.tempo.just$angle, hjust = y.tempo.just$hjust, vjust = y.tempo.just$vjust), - aspect.ratio = if(fix.ratio == TRUE){raster.ratio}else{NULL} # for raster - )) - }else{ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( - text = ggplot2::element_text(size = text.size), - plot.title = ggplot2::element_text(size = title.text.size), # stronger than text - line = ggplot2::element_line(size = 0.5), - legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend - axis.line.y.left = ggplot2::element_line(colour = "black"), - axis.line.x.bottom = ggplot2::element_line(colour = "black"), - axis.text.x = ggplot2::element_text(angle = x.tempo.just$angle, hjust = x.tempo.just$hjust, vjust = x.tempo.just$vjust), - axis.text.y = ggplot2::element_text(angle = y.tempo.just$angle, hjust = y.tempo.just$hjust, vjust = y.tempo.just$vjust), - aspect.ratio = if(fix.ratio == TRUE){raster.ratio}else{NULL} # for raster - )) - } - }else if(add.check == TRUE & article == FALSE){ - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), m.gg <- ggplot2::theme( - text = ggplot2::element_text(size = text.size), - plot.title = ggplot2::element_text(size = title.text.size), # stronger than text - line = ggplot2::element_line(size = 0.5), - legend.key = ggplot2::element_rect(color = "white", size = 1.5), # size of the frame of the legend - panel.background = ggplot2::element_rect(fill = "grey95"), - axis.line.y.left = ggplot2::element_line(colour = "black"), - axis.line.x.bottom = ggplot2::element_line(colour = "black"), - panel.grid.major.x = ggplot2::element_line(colour = "grey85", size = 0.75), - panel.grid.minor.x = ggplot2::element_line(colour = "grey90", size = 0.25), - panel.grid.major.y = ggplot2::element_line(colour = "grey85", size = 0.75), - panel.grid.minor.y = ggplot2::element_line(colour = "grey90", size = 0.25), - strip.background = ggplot2::element_rect(fill = "white", colour = "black"), - axis.text.x = ggplot2::element_text(angle = x.tempo.just$angle, hjust = x.tempo.just$hjust, vjust = x.tempo.just$vjust), - axis.text.y = ggplot2::element_text(angle = y.tempo.just$angle, hjust = y.tempo.just$hjust, vjust = y.tempo.just$vjust), - aspect.ratio = if(fix.ratio == TRUE){raster.ratio}else{NULL} # for raster - # do not work -> legend.position = "none" # to remove the legend completely: https://www.datanovia.com/en/blog/how-to-remove-legend-from-a-ggplot/ - )) - } - # end no need loop part - - - # loop part - point.count <- 0 - line.count <- 0 - lg.order <- vector(mode = "list", length = 6) # order of the legend - lg.order <- lapply(lg.order, as.numeric) # order of the legend - lg.color <- vector(mode = "list", length = 6) # color of the legend - lg.dot.shape <- vector(mode = "list", length = 6) # etc. - lg.dot.size <- vector(mode = "list", length = 6) # etc. - lg.dot.size <- lapply(lg.dot.size, as.numeric) # etc. - lg.dot.border.size <- vector(mode = "list", length = 6) # etc. - lg.dot.border.size <- lapply(lg.dot.border.size, as.numeric) # etc. - lg.dot.border.color <- vector(mode = "list", length = 6) # etc. - lg.line.size <- vector(mode = "list", length = 6) # etc. - lg.line.size <- lapply(lg.line.size, as.numeric) # etc. - lg.line.type <- vector(mode = "list", length = 6) # etc. - lg.alpha <- vector(mode = "list", length = 6) # etc. - lg.alpha <- lapply(lg.alpha, as.numeric) # etc. - for(i1 in 1:length(data1)){ - if(geom[[i1]] == "geom_point"){ - point.count <- point.count + 1 - if(point.count== 1L){ - fin.lg.disp[[1]] <- legend.disp[[point.count + line.count]] - lg.order[[1]] <- point.count + line.count - lg.color[[1]] <- color[[i1]] # if color == NULL -> NULL - lg.dot.shape[[1]] <- dot.shape[[i1]] - lg.dot.size[[1]] <- dot.size[[i1]] - lg.dot.border.size[[1]] <- dot.border.size[[i1]] - lg.dot.border.color[[1]] <- dot.border.color[[i1]] # if dot.border.color == NULL -> NULL - if(plot == TRUE & fin.lg.disp[[1]] == TRUE & dot.shape[[1]] %in% 0:14 & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE DOTS (DOT LAYER NUMBER ", point.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - lg.alpha[[1]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf - }else{ - lg.alpha[[1]] <- alpha[[i1]] - } - class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) - for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same - tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = scatter.kind[[i1]]))(data = tempo.data.frame, mapping = ggplot2::aes_string(x = x[[i1]], y = y[[i1]], fill = categ[[i1]]), shape = dot.shape[[i1]], size = dot.size[[i1]], stroke = dot.border.size[[i1]], color = if(dot.shape[[i1]] %in% 21:24 & ! is.null(dot.border.color)){dot.border.color[[i1]]}else{color[[i1]][i5]}, alpha = alpha[[i1]], show.legend = if(i5== 1L){TRUE}else{FALSE})) # WARNING: a single color allowed for color argument outside aesthetic, but here a single color for border --> loop could be inactivated but kept for commodity # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency - coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_manual(name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = as.character(color[[i1]]), breaks = class.categ)) # values are the values of fill, breaks reorder the classes according to class.categ in the legend, order argument of guide_legend determines the order of the different aesthetics in the legend (not order of classes). See guide_legend settings of scale_..._manual below - } - if(point.count== 2L){ - fin.lg.disp[[2]] <- legend.disp[[point.count + line.count]] - lg.order[[2]] <- point.count + line.count - lg.color[[2]] <- color[[i1]] # if color == NULL -> NULL - lg.dot.shape[[2]] <- dot.shape[[i1]] - lg.dot.size[[2]] <- dot.size[[i1]] - lg.dot.border.size[[2]] <- dot.border.size[[i1]] - lg.dot.border.color[[2]] <- dot.border.color[[i1]] # if dot.border.color == NULL -> NULL - if(plot == TRUE & fin.lg.disp[[2]] == TRUE & dot.shape[[2]] %in% 0:14 & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE DOTS (DOT LAYER NUMBER ", point.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - lg.alpha[[2]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf - }else{ - lg.alpha[[2]] <- alpha[[i1]] - } - class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) - for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same - tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = scatter.kind[[i1]]))(data = tempo.data.frame, mapping = ggplot2::aes_string(x = x[[i1]], y = y[[i1]], shape = categ[[i1]]), size = dot.size[[i1]], stroke = dot.border.size[[i1]], fill = color[[i1]][i5], color = if(dot.shape[[i1]] %in% 21:24 & ! is.null(dot.border.color)){dot.border.color[[i1]]}else{color[[i1]][i5]}, alpha = alpha[[i1]], show.legend = FALSE)) # WARNING: a single color allowed for fill argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency - coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_shape_manual(name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(dot.shape[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of shape, breaks reorder the classes according to class.categ in the legend. See guide_legend settings of scale_..._manual below - - } - if(point.count== 3L){ - fin.lg.disp[[3]] <- legend.disp[[point.count + line.count]] - lg.order[[3]] <- point.count + line.count - lg.color[[3]] <- color[[i1]] # if color == NULL -> NULL - lg.dot.shape[[3]] <- dot.shape[[i1]] - lg.dot.size[[3]] <- dot.size[[i1]] - lg.dot.border.size[[3]] <- dot.border.size[[i1]] - lg.dot.border.color[[3]] <- dot.border.color[[i1]] # if dot.border.color == NULL -> NULL - if(plot == TRUE & fin.lg.disp[[3]] == TRUE & dot.shape[[3]] %in% 0:14 & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE DOTS (DOT LAYER NUMBER ", point.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - lg.alpha[[3]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf - }else{ - lg.alpha[[3]] <- alpha[[i1]] - } - class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) - for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same - tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = scatter.kind[[i1]]))(data = tempo.data.frame, mapping = ggplot2::aes_string(x = x[[i1]], y = y[[i1]], stroke = categ[[i1]]), shape = dot.shape[[i1]], size = dot.size[[i1]], fill = color[[i1]][i5], stroke = dot.border.size[[i1]], color = if(dot.shape[[i1]] %in% 21:24 & ! is.null(dot.border.color)){dot.border.color[[i1]]}else{color[[i1]][i5]}, alpha = alpha[[i1]], show.legend = FALSE)) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency - coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "stroke", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(dot.border.size[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of stroke, breaks reorder the classes according to class.categ in the legend. See guide_legend settings of scale_..._manual below - - } - }else{ - line.count <- line.count + 1 - if(line.count== 1L){ - fin.lg.disp[[4]] <- legend.disp[[point.count + line.count]] - lg.order[[4]] <- point.count + line.count - lg.color[[4]] <- color[[i1]] # if color == NULL -> NULL - lg.line.size[[4]] <- line.size[[i1]] - lg.line.type[[4]] <- line.type[[i1]] - if(plot == TRUE & fin.lg.disp[[4]] == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES (LINE LAYER NUMBER ", line.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - lg.alpha[[4]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf - }else{ - lg.alpha[[4]] <- alpha[[i1]] - } - class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) - for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same - tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::", # no CR here te0("ggpl - ifelse(geom[[i1]] == 'geom_stick', 'geom_segment', geom[[i1]]), # geom_segment because geom_stick converted to geom_segment for plotting - "(data = tempo.data.frame, mapping = ggplot2::aes(x = ", - x[[i1]], - ifelse(geom[[i1]] == 'geom_stick', ", yend = ", ", y = "), - y[[i1]], - if(geom[[i1]] == 'geom_stick'){paste0(', xend = ', x[[i1]], ', y = ', ifelse(is.null(geom.stick.base), y.lim[1], geom.stick.base[[i1]]))}, - ", linetype = ", - categ[[i1]], - "), color = \"", - color[[i1]][i5], - "\", size = ", - line.size[[i1]], - ifelse(geom[[i1]] == 'geom_path', ', lineend = \"round\"', ''), - ifelse(geom[[i1]] == 'geom_step', paste0(', direction = \"', geom.step.dir[[i1]], '\"'), ''), - ", alpha = ", - alpha[[i1]], - ", show.legend = ", - ifelse(i5== 1L, TRUE, FALSE), - ")" - )))) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency - coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "linetype", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(line.type[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of linetype. 1 means solid. Regarding the alpha bug, I have tried different things without success: alpha in guide alone, in geom alone, in both, with different values, breaks reorder the classes according to class.categ in the legend - } - if(line.count== 2L){ - fin.lg.disp[[5]] <- legend.disp[[point.count + line.count]] - lg.order[[5]] <- point.count + line.count - lg.color[[5]] <- color[[i1]] # if color == NULL -> NULL - lg.line.size[[5]] <- line.size[[i1]] - lg.line.type[[5]] <- line.type[[i1]] - if(plot == TRUE & fin.lg.disp[[5]] == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES (LINE LAYER NUMBER ", line.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - lg.alpha[[5]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf - }else{ - lg.alpha[[5]] <- alpha[[i1]] - } - class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) - for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same - tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::", # no CR here te0("ggpl - ifelse(geom[[i1]] == 'geom_stick', 'geom_segment', geom[[i1]]), # geom_segment because geom_stick converted to geom_segment for plotting - "(data = tempo.data.frame, mapping = ggplot2::aes(x = ", - x[[i1]], - ifelse(geom[[i1]] == 'geom_stick', ", yend = ", ", y = "), - y[[i1]], - if(geom[[i1]] == 'geom_stick'){paste0(', xend = ', x[[i1]], ', y = ', ifelse(is.null(geom.stick.base), y.lim[1], geom.stick.base[[i1]]))}, - ", alpha = ", - categ[[i1]], - "), color = \"", - color[[i1]][i5], - "\", size = ", - line.size[[i1]], - ", linetype = ", - ifelse(is.numeric(line.type[[i1]]), "", "\""), - line.type[[i1]], - ifelse(is.numeric(line.type[[i1]]), "", "\""), - ifelse(geom[[i1]] == 'geom_path', ', lineend = \"round\"', ''), - ifelse(geom[[i1]] == 'geom_step', paste0(', direction = \"', geom.step.dir[[i1]], '\"'), ''), - ", show.legend = FALSE)" - )))) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency - coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "alpha", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(alpha[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of linetype. 1 means solid. Regarding the alpha bug, I have tried different things without success: alpha in guide alone, in geom alone, in both, with different values, breaks reorder the classes according to class.categ in the legend - } - if(line.count== 3L){ - fin.lg.disp[[6]] <- legend.disp[[point.count + line.count]] - lg.order[[6]] <- point.count + line.count - lg.color[[6]] <- color[[i1]] # if color == NULL -> NULL - lg.line.size[[6]] <- line.size[[i1]] - lg.line.type[[6]] <- line.type[[i1]] - if(plot == TRUE & fin.lg.disp[[6]] == TRUE & ((length(dev.list()) > 0 & names(dev.cur()) == "windows") | (length(dev.list())== 0L & Sys.info()["sysname"] == "Windows"))){ # if any Graph device already open and this device is "windows", or if no Graph device opened yet and we are on windows system -> prevention of alpha legend bug on windows using value 1 - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") GRAPHIC DEVICE USED ON A WINDOWS SYSTEM ->\nTRANSPARENCY OF THE LINES (LINE LAYER NUMBER ", line.count, ") IS INACTIVATED IN THE LEGEND TO PREVENT A WINDOWS DEPENDENT BUG (SEE https://github.com/tidyverse/ggplot2/issues/2452)\nTO OVERCOME THIS ON WINDOWS, USE ANOTHER DEVICE (pdf() FOR INSTANCE)") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - lg.alpha[[6]] <- 1 # to avoid a bug on windows: if alpha argument is different from 1 for lines (transparency), then lines are not correctly displayed in the legend when using the R GUI (bug https://github.com/tidyverse/ggplot2/issues/2452). No bug when using a pdf - }else{ - lg.alpha[[6]] <- alpha[[i1]] - } - class.categ <- levels(factor(data1[[i1]][, categ[[i1]]])) - for(i5 in 1:length(color[[i1]])){ # or length(class.categ). It is the same because already checked that lengths are the same - tempo.data.frame <- data1[[i1]][data1[[i1]][, categ[[i1]]] == class.categ[i5], ] - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::", # no CR here te0("ggpl - ifelse(geom[[i1]] == 'geom_stick', 'geom_segment', geom[[i1]]), # geom_segment because geom_stick converted to geom_segment for plotting - "(data = tempo.data.frame, mapping = ggplot2::aes(x = ", - x[[i1]], - ifelse(geom[[i1]] == 'geom_stick', ", yend = ", ", y = "), - y[[i1]], - if(geom[[i1]] == 'geom_stick'){paste0(', xend = ', x[[i1]], ', y = ', ifelse(is.null(geom.stick.base), y.lim[1], geom.stick.base[[i1]]))}, - ", size = ", - categ[[i1]], - "), color = \"", - color[[i1]][i5], - "\", linetype = ", - ifelse(is.numeric(line.type[[i1]]), "", "\""), - line.type[[i1]], - ifelse(is.numeric(line.type[[i1]]), "", "\""), - ifelse(geom[[i1]] == 'geom_path', ', lineend = \"round\"', ''), - ifelse(geom[[i1]] == 'geom_step', paste0(', direction = \"', geom.step.dir[[i1]], '\"'), ''), - ", alpha = ", - alpha[[i1]], - ", show.legend = FALSE)" - )))) # WARNING: a single color allowed for color argument outside aesthetic, hence the loop # legend.show option do not remove the legend, only the aesthetic of the legend (dot, line, etc.). Used here to avoid multiple layers of legend which corrupt transparency - coord.names <- c(coord.names, paste0(geom[[i1]], ".", class.categ[i5])) - } - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_discrete_manual(aesthetics = "size", name = if(is.null(legend.name)){NULL}else{legend.name[[i1]]}, values = rep(line.size[[i1]], length(color[[i1]])), breaks = class.categ)) # values are the values of linetype. 1 means solid. Regarding the alpha bug, I have tried different things without success: alpha in guide alone, in geom alone, in both, breaks reorder the classes according to class.categ in the legend - } - } - } - # end loop part - - - - - # legend display - tempo.legend.final <- 'ggplot2::guides( +# legend display +tempo.legend.final <- 'ggplot2::guides( fill = if(fin.lg.disp[[1]] == TRUE){ ggplot2::guide_legend( order = lg.order[[1]], @@ -13411,7 +13417,7 @@ linetype = 0 ) ) }else{ -FALSE +"none" }, shape = if(fin.lg.disp[[2]] == TRUE){ ggplot2::guide_legend( @@ -13427,7 +13433,7 @@ linetype = 0 ) ) }else{ -FALSE +"none" }, stroke = if(fin.lg.disp[[3]] == TRUE){ ggplot2::guide_legend( @@ -13443,7 +13449,7 @@ linetype = 0 ) ) }else{ -FALSE +"none" }, linetype = if(fin.lg.disp[[4]] == TRUE){ ggplot2::guide_legend( @@ -13457,7 +13463,7 @@ shape = NA ) ) }else{ -FALSE +"none" }, alpha = if(fin.lg.disp[[5]] == TRUE){ ggplot2::guide_legend( @@ -13471,7 +13477,7 @@ shape = NA ) ) }else{ -FALSE +"none" }, size = if(fin.lg.disp[[6]] == TRUE){ ggplot2::guide_legend( @@ -13485,29 +13491,29 @@ shape = NA ) ) }else{ -FALSE +"none" } )' # clip = "off" to have secondary ticks outside plot region does not work if( ! is.null(legend.width)){ - if(any(unlist(legend.disp))){ # means some TRUE - tempo.graph.info <- suppressMessages(ggplot2::ggplot_build(eval(parse(text = paste0(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), ' + ', tempo.legend.final))))) # will be recovered later again, when ylim will be considered - legend.final <- fun_gg_get_legend(ggplot_built = tempo.graph.info, fun.name = function.name) # get legend - fin.lg.disp[] <- FALSE # remove all the legends. Must be done even if fin.lg.disp is not appearing in the code thenafter. Otherwise twice the legend - if(is.null(legend.final) & plot == TRUE){ # even if any(unlist(legend.disp)) is TRUE - legend.final <- fun_gg_empty_graph() # empty graph instead of legend - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") LEGEND REQUESTED (NON-NULL categ ARGUMENT OR legend.show ARGUMENT SET TO TRUE)\nBUT IT SEEMS THAT THE PLOT HAS NO LEGEND -> EMPTY LEGEND SPACE CREATED BECAUSE OF THE NON-NULL legend.width ARGUMENT\n") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } - }else if(plot == TRUE){ # means all FALSE - legend.final <- ggplot2::ggplot()+ggplot2::theme_void() # empty graph instead of legend - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") LEGEND REQUESTED (NON-NULL categ ARGUMENT OR legend.show ARGUMENT SET TO TRUE)\nBUT IT SEEMS THAT THE PLOT HAS NO LEGEND -> EMPTY LEGEND SPACE CREATED BECAUSE OF THE NON-NULL legend.width ARGUMENT\n") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) - } +if(any(unlist(legend.disp))){ # means some TRUE +tempo.graph.info <- suppressMessages(ggplot2::ggplot_build(eval(parse(text = paste0(paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "), ' + ', tempo.legend.final))))) # will be recovered later again, when ylim will be considered +legend.final <- fun_gg_get_legend(ggplot_built = tempo.graph.info, fun.name = function.name) # get legend +fin.lg.disp[] <- FALSE # remove all the legends. Must be done even if fin.lg.disp is not appearing in the code thenafter. Otherwise twice the legend +if(is.null(legend.final) & plot == TRUE){ # even if any(unlist(legend.disp)) is TRUE +legend.final <- fun_gg_empty_graph() # empty graph instead of legend +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") LEGEND REQUESTED (NON-NULL categ ARGUMENT OR legend.show ARGUMENT SET TO TRUE)\nBUT IT SEEMS THAT THE PLOT HAS NO LEGEND -> EMPTY LEGEND SPACE CREATED BECAUSE OF THE NON-NULL legend.width ARGUMENT\n") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} +}else if(plot == TRUE){ # means all FALSE +legend.final <- ggplot2::ggplot()+ggplot2::theme_void() # empty graph instead of legend +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") LEGEND REQUESTED (NON-NULL categ ARGUMENT OR legend.show ARGUMENT SET TO TRUE)\nBUT IT SEEMS THAT THE PLOT HAS NO LEGEND -> EMPTY LEGEND SPACE CREATED BECAUSE OF THE NON-NULL legend.width ARGUMENT\n") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +} } if( ! any(unlist(legend.disp))){ - fin.lg.disp[] <- FALSE # remove all the legends. Must be done even if fin.lg.disp is not appearing in the code thenafter. Otherwise twice the legend +fin.lg.disp[] <- FALSE # remove all the legends. Must be done even if fin.lg.disp is not appearing in the code thenafter. Otherwise twice the legend } assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = tempo.legend.final))) # end legend display @@ -13520,135 +13526,135 @@ assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(t tempo.coord <- suppressMessages(ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + ", ' + ggplot2::scale_x_continuous(expand = c(0, 0), limits = sort(x.lim), oob = scales::rescale_none) + ggplot2::scale_y_continuous(expand = c(0, 0), limits = sort(y.lim), oob = scales::rescale_none)'))))$layout$panel_params[[1]]) # here I do not need the x-axis and y-axis orientation, I just need the number of main ticks # x.second.tick.positions # coordinates of secondary ticks (only if x.second.tick.nb argument is non-null or if x.log argument is different from "no") if(x.log != "no"){ # integer main ticks for log2 and log10 - tempo.scale <- (as.integer(min(x.lim, na.rm = TRUE)) - 1):(as.integer(max(x.lim, na.rm = TRUE)) + 1) -}else{ - tempo <- if(is.null(attributes(tempo.coord$x$breaks))){tempo.coord$x$breaks}else{unlist(attributes(tempo.coord$x$breaks))} - if(all(is.na(tempo))){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nONLY NA IN tempo.coord$x$breaks") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if(length(unique(x.lim)) <= 1){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nIT SEEMS THAT X-AXIS VALUES HAVE A NULL RANGE: ", paste(x.lim, collapse = " "), "\nPLEASE, USE THE x.lim ARGUMENT WITH 2 DIFFERENT VALUES TO SOLVE THIS") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - tempo.scale <- fun_scale(lim = x.lim, n = ifelse(is.null(x.tick.nb), length(tempo[ ! is.na(tempo)]), x.tick.nb)) # in ggplot 3.3.0, tempo.coord$x.major_source replaced by tempo.coord$x$breaks. If fact: n = ifelse(is.null(x.tick.nb), length(tempo[ ! is.na(tempo)]), x.tick.nb)) replaced by n = ifelse(is.null(x.tick.nb), 4, x.tick.nb)) - } +tempo.scale <- (as.integer(min(x.lim, na.rm = TRUE)) - 1):(as.integer(max(x.lim, na.rm = TRUE)) + 1) +}else{ +tempo <- if(is.null(attributes(tempo.coord$x$breaks))){tempo.coord$x$breaks}else{unlist(attributes(tempo.coord$x$breaks))} +if(all(is.na(tempo))){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nONLY NA IN tempo.coord$x$breaks") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if(length(unique(x.lim)) <= 1){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nIT SEEMS THAT X-AXIS VALUES HAVE A NULL RANGE: ", paste(x.lim, collapse = " "), "\nPLEASE, USE THE x.lim ARGUMENT WITH 2 DIFFERENT VALUES TO SOLVE THIS") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +tempo.scale <- fun_scale(lim = x.lim, n = ifelse(is.null(x.tick.nb), length(tempo[ ! is.na(tempo)]), x.tick.nb)) # in ggplot 3.3.0, tempo.coord$x.major_source replaced by tempo.coord$x$breaks. If fact: n = ifelse(is.null(x.tick.nb), length(tempo[ ! is.na(tempo)]), x.tick.nb)) replaced by n = ifelse(is.null(x.tick.nb), 4, x.tick.nb)) +} } x.second.tick.values <- NULL x.second.tick.pos <- NULL if(x.log != "no"){ - tempo <- fun_inter_ticks(lim = x.lim, log = x.log) - x.second.tick.values <- tempo$values - x.second.tick.pos <- tempo$coordinates - # if(vertical == TRUE){ # do not remove in case the bug is fixed - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( - geom = "segment", x = x.second.tick.pos, - xend = x.second.tick.pos, - y = if(diff(y.lim) > 0){tempo.coord$y.range[1]}else{tempo.coord$y.range[2]}, - yend = if(diff(y.lim) > 0){tempo.coord$y.range[1] + abs(diff(tempo.coord$y.range)) / 80}else{tempo.coord$y.range[2] - abs(diff(tempo.coord$y.range)) / 80} - )) - # }else{ # not working because of the ggplot2 bug - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", y = x.second.tick.pos, yend = x.second.tick.pos, x = tempo.coord$x.range[1], xend = tempo.coord$x.range[1] + diff(tempo.coord$x.range) / 80)) - # } - coord.names <- c(coord.names, "x.second.tick.positions") +tempo <- fun_inter_ticks(lim = x.lim, log = x.log) +x.second.tick.values <- tempo$values +x.second.tick.pos <- tempo$coordinates +# if(vertical == TRUE){ # do not remove in case the bug is fixed +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( +geom = "segment", x = x.second.tick.pos, +xend = x.second.tick.pos, +y = if(diff(y.lim) > 0){tempo.coord$y.range[1]}else{tempo.coord$y.range[2]}, +yend = if(diff(y.lim) > 0){tempo.coord$y.range[1] + abs(diff(tempo.coord$y.range)) / 80}else{tempo.coord$y.range[2] - abs(diff(tempo.coord$y.range)) / 80} +)) +# }else{ # not working because of the ggplot2 bug +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", y = x.second.tick.pos, yend = x.second.tick.pos, x = tempo.coord$x.range[1], xend = tempo.coord$x.range[1] + diff(tempo.coord$x.range) / 80)) +# } +coord.names <- c(coord.names, "x.second.tick.positions") }else if(( ! is.null(x.second.tick.nb)) & x.log == "no"){ - # if(x.second.tick.nb > 0){ #inactivated because already checked before - if(length(tempo.scale) < 2){ - tempo.cat1 <- c("x.tick.nb", "x.second.tick.nb") - tempo.cat2 <- sapply(list(x.tick.nb, x.second.tick.nb), FUN = paste0, collapse = " ") - tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") - tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE NUMBER OF GENERATED TICKS FOR THE X-AXIS IS NOT CORRECT: ", length(tempo.scale), "\nUSING THESE ARGUMENT SETTINGS (NO DISPLAY MEANS NULL VALUE):\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n"), "\nPLEASE, TEST OTHER VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - tempo <- fun_inter_ticks(lim = x.lim, log = x.log, breaks = tempo.scale, n = x.second.tick.nb) - } - x.second.tick.values <- tempo$values - x.second.tick.pos <- tempo$coordinates - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( - geom = "segment", - x = x.second.tick.pos, - xend = x.second.tick.pos, - y = if(diff(y.lim) > 0){tempo.coord$y.range[1]}else{tempo.coord$y.range[2]}, - yend = if(diff(y.lim) > 0){tempo.coord$y.range[1] + abs(diff(tempo.coord$y.range)) / 80}else{tempo.coord$y.range[2] - abs(diff(tempo.coord$y.range)) / 80} - )) - coord.names <- c(coord.names, "x.second.tick.positions") +# if(x.second.tick.nb > 0){ #inactivated because already checked before +if(length(tempo.scale) < 2){ +tempo.cat1 <- c("x.tick.nb", "x.second.tick.nb") +tempo.cat2 <- sapply(list(x.tick.nb, x.second.tick.nb), FUN = paste0, collapse = " ") +tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") +tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE NUMBER OF GENERATED TICKS FOR THE X-AXIS IS NOT CORRECT: ", length(tempo.scale), "\nUSING THESE ARGUMENT SETTINGS (NO DISPLAY MEANS NULL VALUE):\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n"), "\nPLEASE, TEST OTHER VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +tempo <- fun_inter_ticks(lim = x.lim, log = x.log, breaks = tempo.scale, n = x.second.tick.nb) +} +x.second.tick.values <- tempo$values +x.second.tick.pos <- tempo$coordinates +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( +geom = "segment", +x = x.second.tick.pos, +xend = x.second.tick.pos, +y = if(diff(y.lim) > 0){tempo.coord$y.range[1]}else{tempo.coord$y.range[2]}, +yend = if(diff(y.lim) > 0){tempo.coord$y.range[1] + abs(diff(tempo.coord$y.range)) / 80}else{tempo.coord$y.range[2] - abs(diff(tempo.coord$y.range)) / 80} +)) +coord.names <- c(coord.names, "x.second.tick.positions") } # for the ggplot2 bug with x.log, this does not work: eval(parse(text = ifelse(vertical == FALSE & x.log == "log10", "ggplot2::scale_x_continuous", "ggplot2::scale_x_continuous"))) assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_x_continuous( - breaks = tempo.scale, - minor_breaks = x.second.tick.pos, - labels = if(x.log == "log10"){scales::trans_format("identity", scales::math_format(10^.x))}else if(x.log == "log2"){scales::trans_format("identity", scales::math_format(2^.x))}else if(x.log == "no"){ggplot2::waiver()}else{tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 10") ; stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)}, - expand = c(0, 0), # remove space after after axis limits - limits = sort(x.lim), # NA indicate that limits must correspond to data limits but xlim() already used - oob = scales::rescale_none, - trans = ifelse(diff(x.lim) < 0, "reverse", "identity") # equivalent to ggplot2::scale_x_reverse() but create the problem of x-axis label disappearance with x.lim decreasing. Thus, do not use. Use xlim() below and after this +breaks = tempo.scale, +minor_breaks = x.second.tick.pos, +labels = if(x.log == "log10"){scales::trans_format("identity", scales::math_format(10^.x))}else if(x.log == "log2"){scales::trans_format("identity", scales::math_format(2^.x))}else if(x.log == "no"){ggplot2::waiver()}else{tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 10") ; stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)}, +expand = c(0, 0), # remove space after after axis limits +limits = sort(x.lim), # NA indicate that limits must correspond to data limits but xlim() already used +oob = scales::rescale_none, +trans = ifelse(diff(x.lim) < 0, "reverse", "identity") # equivalent to ggplot2::scale_x_reverse() but create the problem of x-axis label disappearance with x.lim decreasing. Thus, do not use. Use xlim() below and after this )) # end x.second.tick.positions # y.second.tick.positions # coordinates of secondary ticks (only if y.second.tick.nb argument is non-null or if y.log argument is different from "no") if(y.log != "no"){ # integer main ticks for log2 and log10 - tempo.scale <- (as.integer(min(y.lim, na.rm = TRUE)) - 1):(as.integer(max(y.lim, na.rm = TRUE)) + 1) -}else{ - tempo <- if(is.null(attributes(tempo.coord$y$breaks))){tempo.coord$y$breaks}else{unlist(attributes(tempo.coord$y$breaks))} - if(all(is.na(tempo))){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nONLY NA IN tempo.coord$y$breaks") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - } - if(length(unique(y.lim)) <= 1){ - tempo.cat <- paste0("ERROR IN ", function.name, "\nIT SEEMS THAT Y-AXIS VALUES HAVE A NULL RANGE: ", paste(y.lim, collapse = " "), "\nPLEASE, USE THE y.lim ARGUMENT WITH 2 DIFFERENT VALUES TO SOLVE THIS") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - tempo.scale <- fun_scale(lim = y.lim, n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) # in ggplot 3.3.0, tempo.coord$y.major_source replaced by tempo.coord$y$breaks. If fact: n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) replaced by n = ifelse(is.null(y.tick.nb), 4, y.tick.nb)) - } +tempo.scale <- (as.integer(min(y.lim, na.rm = TRUE)) - 1):(as.integer(max(y.lim, na.rm = TRUE)) + 1) +}else{ +tempo <- if(is.null(attributes(tempo.coord$y$breaks))){tempo.coord$y$breaks}else{unlist(attributes(tempo.coord$y$breaks))} +if(all(is.na(tempo))){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nONLY NA IN tempo.coord$y$breaks") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +} +if(length(unique(y.lim)) <= 1){ +tempo.cat <- paste0("ERROR IN ", function.name, "\nIT SEEMS THAT Y-AXIS VALUES HAVE A NULL RANGE: ", paste(y.lim, collapse = " "), "\nPLEASE, USE THE y.lim ARGUMENT WITH 2 DIFFERENT VALUES TO SOLVE THIS") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +tempo.scale <- fun_scale(lim = y.lim, n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) # in ggplot 3.3.0, tempo.coord$y.major_source replaced by tempo.coord$y$breaks. If fact: n = ifelse(is.null(y.tick.nb), length(tempo[ ! is.na(tempo)]), y.tick.nb)) replaced by n = ifelse(is.null(y.tick.nb), 4, y.tick.nb)) +} } y.second.tick.values <- NULL y.second.tick.pos <- NULL if(y.log != "no"){ - tempo <- fun_inter_ticks(lim = y.lim, log = y.log) - y.second.tick.values <- tempo$values - y.second.tick.pos <- tempo$coordinates - # if(vertical == TRUE){ # do not remove in case the bug is fixed - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( - geom = "segment", - y = y.second.tick.pos, - yend = y.second.tick.pos, - x = if(diff(x.lim) > 0){tempo.coord$x.range[1]}else{tempo.coord$x.range[2]}, - xend = if(diff(x.lim) > 0){tempo.coord$x.range[1] + abs(diff(tempo.coord$x.range)) / 80}else{tempo.coord$x.range[2] - abs(diff(tempo.coord$x.range)) / 80} - )) - # }else{ # not working because of the ggplot2 bug - # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", x = y.second.tick.pos, xend = y.second.tick.pos, y = tempo.coord$y.range[1], yend = tempo.coord$y.range[1] + diff(tempo.coord$y.range) / 80)) - # } - coord.names <- c(coord.names, "y.second.tick.positions") +tempo <- fun_inter_ticks(lim = y.lim, log = y.log) +y.second.tick.values <- tempo$values +y.second.tick.pos <- tempo$coordinates +# if(vertical == TRUE){ # do not remove in case the bug is fixed +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( +geom = "segment", +y = y.second.tick.pos, +yend = y.second.tick.pos, +x = if(diff(x.lim) > 0){tempo.coord$x.range[1]}else{tempo.coord$x.range[2]}, +xend = if(diff(x.lim) > 0){tempo.coord$x.range[1] + abs(diff(tempo.coord$x.range)) / 80}else{tempo.coord$x.range[2] - abs(diff(tempo.coord$x.range)) / 80} +)) +# }else{ # not working because of the ggplot2 bug +# assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(geom = "segment", x = y.second.tick.pos, xend = y.second.tick.pos, y = tempo.coord$y.range[1], yend = tempo.coord$y.range[1] + diff(tempo.coord$y.range) / 80)) +# } +coord.names <- c(coord.names, "y.second.tick.positions") }else if(( ! is.null(y.second.tick.nb)) & y.log == "no"){ - # if(y.second.tick.nb > 0){ #inactivated because already checked before - if(length(tempo.scale) < 2){ - tempo.cat1 <- c("y.tick.nb", "y.second.tick.nb") - tempo.cat2 <- sapply(list(y.tick.nb, y.second.tick.nb), FUN = paste0, collapse = " ") - tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") - tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE NUMBER OF GENERATED TICKS FOR THE Y-AXIS IS NOT CORRECT: ", length(tempo.scale), "\nUSING THESE ARGUMENT SETTINGS (NO DISPLAY MEANS NULL VALUE):\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n"), "\nPLEASE, TEST OTHER VALUES") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == - }else{ - tempo <- fun_inter_ticks(lim = y.lim, log = y.log, breaks = tempo.scale, n = y.second.tick.nb) - } - y.second.tick.values <- tempo$values - y.second.tick.pos <- tempo$coordinates - assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( - geom = "segment", - y = y.second.tick.pos, - yend = y.second.tick.pos, - x = if(diff(x.lim) > 0){tempo.coord$x.range[1]}else{tempo.coord$x.range[2]}, - xend = if(diff(x.lim) > 0){tempo.coord$x.range[1] + abs(diff(tempo.coord$x.range)) / 80}else{tempo.coord$x.range[2] - abs(diff(tempo.coord$x.range)) / 80} - )) - coord.names <- c(coord.names, "y.second.tick.positions") +# if(y.second.tick.nb > 0){ #inactivated because already checked before +if(length(tempo.scale) < 2){ +tempo.cat1 <- c("y.tick.nb", "y.second.tick.nb") +tempo.cat2 <- sapply(list(y.tick.nb, y.second.tick.nb), FUN = paste0, collapse = " ") +tempo.sep <- sapply(mapply(" ", max(nchar(tempo.cat1)) - nchar(tempo.cat1) + 3, FUN = rep, SIMPLIFY = FALSE), FUN = paste0, collapse = "") +tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE NUMBER OF GENERATED TICKS FOR THE Y-AXIS IS NOT CORRECT: ", length(tempo.scale), "\nUSING THESE ARGUMENT SETTINGS (NO DISPLAY MEANS NULL VALUE):\n", paste0(tempo.cat1, tempo.sep, tempo.cat2, collapse = "\n"), "\nPLEASE, TEST OTHER VALUES") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between == +}else{ +tempo <- fun_inter_ticks(lim = y.lim, log = y.log, breaks = tempo.scale, n = y.second.tick.nb) +} +y.second.tick.values <- tempo$values +y.second.tick.pos <- tempo$coordinates +assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate( +geom = "segment", +y = y.second.tick.pos, +yend = y.second.tick.pos, +x = if(diff(x.lim) > 0){tempo.coord$x.range[1]}else{tempo.coord$x.range[2]}, +xend = if(diff(x.lim) > 0){tempo.coord$x.range[1] + abs(diff(tempo.coord$x.range)) / 80}else{tempo.coord$x.range[2] - abs(diff(tempo.coord$x.range)) / 80} +)) +coord.names <- c(coord.names, "y.second.tick.positions") } # for the ggplot2 bug with y.log, this does not work: eval(parse(text = ifelse(vertical == FALSE & y.log == "log10", "ggplot2::scale_x_continuous", "ggplot2::scale_y_continuous"))) assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_y_continuous( - breaks = tempo.scale, - minor_breaks = y.second.tick.pos, - labels = if(y.log == "log10"){scales::trans_format("identity", scales::math_format(10^.x))}else if(y.log == "log2"){scales::trans_format("identity", scales::math_format(2^.x))}else if(y.log == "no"){ggplot2::waiver()}else{tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 10") ; stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)}, - expand = c(0, 0), # remove space after axis limits - limits = sort(y.lim), # NA indicate that limits must correspond to data limits but ylim() already used - oob = scales::rescale_none, - trans = ifelse(diff(y.lim) < 0, "reverse", "identity") # equivalent to ggplot2::scale_y_reverse() but create the problem of y-axis label disappearance with y.lim decreasing. Thus, do not use. Use ylim() below and after this +breaks = tempo.scale, +minor_breaks = y.second.tick.pos, +labels = if(y.log == "log10"){scales::trans_format("identity", scales::math_format(10^.x))}else if(y.log == "log2"){scales::trans_format("identity", scales::math_format(2^.x))}else if(y.log == "no"){ggplot2::waiver()}else{tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, "\nCODE INCONSISTENCY 10") ; stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)}, +expand = c(0, 0), # remove space after axis limits +limits = sort(y.lim), # NA indicate that limits must correspond to data limits but ylim() already used +oob = scales::rescale_none, +trans = ifelse(diff(y.lim) < 0, "reverse", "identity") # equivalent to ggplot2::scale_y_reverse() but create the problem of y-axis label disappearance with y.lim decreasing. Thus, do not use. Use ylim() below and after this )) # end y.second.tick.positions assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_cartesian(xlim = x.lim, ylim = y.lim)) # clip = "off" to have secondary ticks outside plot region. The problem is that points out of bounds are also drawn outside the plot region. Thus, I cannot use it # at that stage, x.lim and y.lim not NULL anymore @@ -13661,15 +13667,15 @@ assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coo fin.plot <- eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))) grob.save <- NULL if(plot == TRUE){ - if( ! is.null(legend.width)){ # any(unlist(legend.disp)) == TRUE removed to have empty legend space # not & any(unlist(fin.lg.disp)) == TRUE here because converted to FALSE - grob.save <- suppressMessages(suppressWarnings(gridExtra::grid.arrange(fin.plot, legend.final, ncol=2, widths=c(1, legend.width)))) - }else{ - grob.save <- suppressMessages(suppressWarnings(print(fin.plot))) - } +if( ! is.null(legend.width)){ # any(unlist(legend.disp)) == TRUE removed to have empty legend space # not & any(unlist(fin.lg.disp)) == TRUE here because converted to FALSE +grob.save <- suppressMessages(suppressWarnings(gridExtra::grid.arrange(fin.plot, legend.final, ncol=2, widths=c(1, legend.width)))) +}else{ +grob.save <- suppressMessages(suppressWarnings(print(fin.plot))) +} }else{ - warn.count <- warn.count + 1 - tempo.warn <- paste0("(", warn.count,") PLOT NOT SHOWN AS REQUESTED") - warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) +warn.count <- warn.count + 1 +tempo.warn <- paste0("(", warn.count,") PLOT NOT SHOWN AS REQUESTED") +warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn))) } # end drawing @@ -13677,49 +13683,49 @@ if(plot == TRUE){ # output if(warn.print == TRUE & ! is.null(warn)){ - on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) +on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE)) } on.exit(exp = options(warning.length = ini.warning.length), add = TRUE) if(return == TRUE){ - output <- suppressMessages(ggplot2::ggplot_build(fin.plot)) - # output$data <- output$data[-1] # yes for boxplot but not for scatter # remove the first data because corresponds to the initial empty boxplot - if(length(output$data) != length(coord.names)){ - tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, ": length(output$data) AND length(coord.names) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") - stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) - }else{ - names(output$data) <- coord.names - } - if(is.null(unlist(removed.row.nb))){ - removed.row.nb <- NULL - removed.rows <- NULL - }else{ - for(i3 in 1:length(data1)){ - if( ! is.null(removed.row.nb[[i3]])){ - removed.row.nb[[i3]] <- sort(removed.row.nb[[i3]]) - removed.rows[[i3]] <- data1.ini[[i3]][removed.row.nb[[i3]], ] - } - } - } - tempo <- output$layout$panel_params[[1]] - output <- list( - data = data1, - removed.row.nb = removed.row.nb, - removed.rows = removed.rows, - plot = c(output$data, x.second.tick.values = list(x.second.tick.values), y.second.tick.values = list(y.second.tick.values)), - panel = facet.categ, - axes = list( - x.range = tempo$x.range, - x.labels = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{tempo$x$scale$get_labels()}, # is.null(attributes(tempo$x$breaks)) test if it is number (TRUE) or character (FALSE) - x.positions = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{unlist(attributes(tempo$x$breaks))}, - y.range = tempo$y.range, - y.labels = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{tempo$y$scale$get_labels()}, - y.positions = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{unlist(attributes(tempo$y$breaks))} - ), - warn = paste0("\n", warn, "\n\n"), - ggplot = if(return.ggplot == TRUE){fin.plot}else{NULL}, # fin.plot plots the graph if return == TRUE - gtable = if(return.gtable == TRUE){grob.save}else{NULL} # - ) - return(output) # this plots the graph if return.ggplot is TRUE and if no assignment +output <- suppressMessages(ggplot2::ggplot_build(fin.plot)) +# output$data <- output$data[-1] # yes for boxplot but not for scatter # remove the first data because corresponds to the initial empty boxplot +if(length(output$data) != length(coord.names)){ +tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, ": length(output$data) AND length(coord.names) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED") +stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) +}else{ +names(output$data) <- coord.names +} +if(is.null(unlist(removed.row.nb))){ +removed.row.nb <- NULL +removed.rows <- NULL +}else{ +for(i3 in 1:length(data1)){ +if( ! is.null(removed.row.nb[[i3]])){ +removed.row.nb[[i3]] <- sort(removed.row.nb[[i3]]) +removed.rows[[i3]] <- data1.ini[[i3]][removed.row.nb[[i3]], ] +} +} +} +tempo <- output$layout$panel_params[[1]] +output <- list( +data = data1, +removed.row.nb = removed.row.nb, +removed.rows = removed.rows, +plot = c(output$data, x.second.tick.values = list(x.second.tick.values), y.second.tick.values = list(y.second.tick.values)), +panel = facet.categ, +axes = list( +x.range = tempo$x.range, +x.labels = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{tempo$x$scale$get_labels()}, # is.null(attributes(tempo$x$breaks)) test if it is number (TRUE) or character (FALSE) +x.positions = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{unlist(attributes(tempo$x$breaks))}, +y.range = tempo$y.range, +y.labels = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{tempo$y$scale$get_labels()}, +y.positions = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{unlist(attributes(tempo$y$breaks))} +), +warn = paste0("\n", warn, "\n\n"), +ggplot = if(return.ggplot == TRUE){fin.plot}else{NULL}, # fin.plot plots the graph if return == TRUE +gtable = if(return.gtable == TRUE){grob.save}else{NULL} # +) +return(output) # this plots the graph if return.ggplot is TRUE and if no assignment } # end output # end main code @@ -13727,9 +13733,3 @@ if(return == TRUE){ - - - - - - diff --git a/cute_little_R_functions.docx b/cute_little_R_functions.docx index 1aee9a26f0040c8db000e41a41fbf4e2d39b2ca1..e0905868b371d07c5e89ecf67192f21afbdaf85e 100755 GIT binary patch delta 414678 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%0DyzeN4iIS?H`B2_XZ~; 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	K(NiC`LpHBZiEy97) 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 delta 414585 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�j|!UR6P(CB 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�ia{b6h!(DU`9?Ax8q7NAYiML@S)GD233K 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≧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�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%0D^)W}i`P;MY6@5i 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	g<(94z1Qj(wM6><g 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≺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 MT88J~2}AY&042LK3;+NC diff --git a/dev/internal.function.check.fun.R b/dev/internal.function.check.fun.R new file mode 100644 index 0000000..f047b42 --- /dev/null +++ b/dev/internal.function.check.fun.R @@ -0,0 +1,91 @@ +internal.function.check.fun <- function(expect.arg.values_fun, req.functions_fun, function.name_fun){ + # AIM + # check the arguments of the other local functions of this script + # WARNING + # use the object of the checked function + # ARGUMENTS + # expect.arg.values_fun: a list of character strings made of the authorized values of the arguments of the checked function + # req.functions_fun: a vector of character strings made of the names of the cute little toolbox required functions. Write NULL is no functions required + # function.name_fun: a character string of the name of the parental function + # REQUIRED PACKAGES + # none + # REQUIRED FUNCTIONS FROM CUTE_LITTLE_R_FUNCTION + # none + # RETURN + # error messages + # EXAMPLES + # internal.function.check.fun(expect.arg.values_fun = list("a"), req.functions_fun = "b") + # DEBUGGING + # expect.arg.values_fun = list("a") ; req.functions_fun = "b" + # function name + function.name_intern <- paste0(as.list(match.call(expand.dots=FALSE))[[1]], "()") + function.name_intern <- paste0(function.name_intern, " INTERNAL FUNCTION OF SLITHERINE") + # end function name + # argument checking + if( ! all(class(expect.arg.values_fun) == "list")){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_intern, ":\nARGUMENT expect.arg.values_fun MUST BE CLASS LIST AND NOT:\n", paste(class(expect.arg.values_fun) , collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + }else{ + for(i2 in 1:length(expect.arg.values_fun)){ + if( ! all(class(expect.arg.values_fun[[i2]]) == "character")){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_intern, ":\nCOMPARTMENT ", i2, " OF ARGUMENT expect.arg.values_fun MUST BE A VECTOR OF CLASS CHARACTER AND NOT:\n", paste(class(expect.arg.values_fun[[i2]]) , collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + } + } + } + if( ! is.null(req.functions_fun)){ + if( ! all(class(req.functions_fun) == "character")){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_intern, ":\nARGUMENT req.functions_fun MUST BE A VECTOR OF CLASS CHARACTER AND NOT:\n", paste(class(req.functions_fun) , collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + } + } + if( ! all(class(function.name_fun) == "character")){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_intern, ":\nARGUMENT function.name_fun MUST BE A VECTOR OF CLASS CHARACTER AND NOT:\n", paste(class(function.name_fun) , collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + } + # end argument checking + # main code using the object of the function evaluated + if( ! is.null(req.functions_fun)){ + for(i2 in 1:length(req.functions_fun)){ + if(length(find(req.functions_fun[i2], mode = "function")) == 0){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name, ": REQUIRED ", req.functions_fun[i2], " FUNCTION IS MISSING IN THE R ENVIRONMENTS\n\n================\n\n") + stop(tempo.cat) + } + } + } + arg.list <- { # this call the arguments of the parental function. To have the arguments of the current function: arg.list <- as.list(match.call(definition = sys.function(sys.parent(n = 2))))[-1] # recover the argument names with attributed values. -1 to remove the function name compartment. sys.function(sys.parent(n = 2)) to recover the agument beyond sys.function(). See also debugging_tools_for_r_dev for other kind of calling + cl <- sys.call(-1) ; + f <- get(x = as.character(cl[[1]]), mode = "function", envir = sys.frame(-2)) ; # no env = sys.nframe(), inherit = TRUE here + cl <- match.call(definition = f, call = cl) ; + as.list(cl)[-1] + } + arg.names <- names(arg.list) + arg.values <- unlist(as.character(arg.list)) + default.arg.list <- formals(fun = sys.function(sys.parent()), envir = parent.frame(n = 1)) # list of all the arguments of the parental function with their default values (not the values of the user !). Use formals(fun = sys.function(sys.parent(n = 2))) for arguments of the current function + arg.with.default.value <- ! sapply(default.arg.list, is.symbol) & sapply(sapply(default.arg.list, as.character), identical, "") # logical indicating which of the default.arg.list have default values + if(any(arg.with.default.value) == TRUE){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_fun, ":\nARGUMENTS WITH DEFAULT VALUE NOT ALLOWED IN INTERNAL FUNCTIONS (FOR CODE SAFETY):\n", paste(names(default.arg.list)[arg.with.default.value] , collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + } + expect.arg.names <- names(default.arg.list) + tempo.test <- expect.arg.names[expect.arg.names %in% unlist(mapply(FUN = ls, pos = 1:10))] + if(length(tempo.test) > 0){ # check that argument name are not in the R evironment + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_fun, ":\nARGUMENT NAME OF THIS INTERNAL FUNCTION ALSO PRESENT IN THE R ENVIRONMENT, WHICH IS NOT ALLOWED FOR CODE SAFETY:\n", paste(tempo.test, collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + } + if( ! (all(arg.names %in% expect.arg.names) & length(arg.names) == length(expect.arg.names))){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_fun, ": ARGUMENTS OF THIS FUNCTION MUST BE LENGTH:\n", length(expect.arg.names), "\nAND MUST HAVE THESE NAMES:\n", paste(expect.arg.names, collapse = "\n"), "\n\nHERE, IT IS LENGTH:\n", length(arg.names), "\nWITH NAMES:\n", paste(arg.names, collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + } + if(length(arg.values) != length(expect.arg.values_fun)){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_fun, ": ARGUMENTS OF THIS FUNCTION MUST BE LENGTH:\n", length(expect.arg.values_fun), "\n\nBUT HERE, IT IS LENGTH:\n", length(arg.values), "\n\n================\n\n") + stop(tempo.cat) + } + for(i1 in 1:length(expect.arg.values_fun)){ + if( ! all(arg.values[i1] %in% expect.arg.values_fun[[i1]])){ + tempo.cat <- paste0("\n\n================\n\nERROR IN ", function.name_fun, ": VALUE OF ARGUMENT ", arg.names[i1], " MUST BE:\n", paste(dQuote(expect.arg.values_fun[[i1]], q = FALSE), collapse = "\n"), "\n\nBUT HERE, IT IS VALUE:\n", paste(arg.values[i1], collapse = "\n"), "\n\n================\n\n") + stop(tempo.cat) + } + } +} + diff --git a/fun_gg_boxplot.docx b/fun_gg_boxplot.docx index 249ad42bac9df66e5ff94db11b077cbbbc2f667a..275bf32f0d8b4eac93608dc15dca16a2b8625644 100755 GIT binary patch delta 42030 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 Zx&tHx1l<2Ka2y1w3q*%n_vbl^@LxxBt!MxM delta 41916 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 diff --git a/fun_gg_scatter.docx b/fun_gg_scatter.docx index 5b50d5617889c6bfd2d4ea29d1495694decd9afa..f3605cccde00d915611f7fefbaadb229383748ad 100755 GIT binary patch delta 100859 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�AUuG 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 KX<h?LGXMaU8;P+1 delta 100822 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%0DLI*q0-uA7_;4N`iW27e8Lqpz&zwAdvw_09Gphvt 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 -- GitLab