From 3b14459612d078f64872f480cd05f1eb92d70101 Mon Sep 17 00:00:00 2001 From: gmillot <gael.millot@pasteur.fr> Date: Fri, 3 Feb 2023 20:45:14 +0100 Subject: [PATCH] release v11.6: bug fixed in fun_comp_2d, since identical(data1, data2) returns FALSE if both are identical but data1 is typeof double and data2 is typeof integer --- README.md | 5 + check/TODO.txt | 3 +- cute_little_R_functions.R | 26643 +++++++++++++++++---------------- cute_little_R_functions.docx | Bin 499127 -> 499498 bytes fun_gg_boxplot.docx | Bin 115871 -> 116015 bytes fun_gg_scatter.docx | Bin 121152 -> 121237 bytes 6 files changed, 13334 insertions(+), 13317 deletions(-) diff --git a/README.md b/README.md index ee7618e..f934bd2 100755 --- a/README.md +++ b/README.md @@ -170,6 +170,11 @@ Gitlab developers ## WHAT'S NEW IN +### v11.6.0 + +1) bug fixed in fun_comp_2d, since identical(data1, data2) returns FALSE if both are identical but data1 is typeof double and data2 is typeof integer + + ### 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" diff --git a/check/TODO.txt b/check/TODO.txt index ce76e92..68e93d7 100644 --- a/check/TODO.txt +++ b/check/TODO.txt @@ -84,7 +84,8 @@ Argument and return : leave it to me For packages : See mails with cuteathon - +For speed, use rbenchmark : +https://towardsdatascience.com/r-is-slow-and-its-your-fault-2fcedacc7abb diff --git a/cute_little_R_functions.R b/cute_little_R_functions.R index 35485dd..0ca1133 100755 --- a/cute_little_R_functions.R +++ b/cute_little_R_functions.R @@ -20,7 +20,7 @@ # https://stackoverflow.com/questions/17552917/merging-existing-pdf-files-using-r # https://www.r-bloggers.com/2019/04/join-split-and-compress-pdf-files-with-pdftools/ # https://rdrr.io/cran/staplr/man/staple_pdf.html - +# For speed, use rbenchmark : https://towardsdatascience.com/r-is-slow-and-its-your-fault-2fcedacc7abb @@ -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 @@ return(output) # -> 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 @@ return(output) 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 @@ return(data1[row, col]) 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,12371 +1038,12377 @@ return(data1[row, col]) 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 == + # 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) } + + +######## 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 + # } + # code above inactivated because integer dataset are sometimes imported into R as integer or as double, which is different for identical(data1, data2) + if(all(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) } -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_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) } + + +######## 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) } -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) +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))) } -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 +if( ! is.null(tempo.try.error)){ +kind <- c(kind, "ERROR") +problem <- c(problem, TRUE) +res <- c(res, tempo.try.error) }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) +if( ! is.null(tempo.try.warning)){ +kind <- c(kind, "WARNING") +problem <- c(problem, FALSE) +res <- c(res, tempo.try.warning) }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) +kind <- c(kind, "OK") +problem <- c(problem, FALSE) +res <- c(res, "") } -if( ! (is.null(names(data1)) & is.null(names(data2)))){ -if(identical(names(data1), names(data2))){ -same.names <- TRUE -name <- names(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)) }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])) +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 == } } -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) +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")) } -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) +', +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")) } -######## fun_comp_2d() #### comparison of two 2D datasets (row & col names, dimensions, etc.) +################ Object modification -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_name_change() #### check a vector of character strings and modify any string if present in another vector -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_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_comp_list() #### comparison of two lists +######## fun_df_remod() #### remodeling a data frame to have column name as a qualitative values and vice-versa -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)) +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(any(names(data2) %in% names(data1))){ -any.id.name <- TRUE -same.names.pos2 <- which(names(data2) %in% names(data1)) + + + + +######## 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) } -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) + + +######## 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(data2 %in% data1)){ -any.id.compartment <- TRUE -same.compartment.pos2 <- which(data2 %in% data1) + + +######## 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) } -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 + + +######## 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)) + } } -}else{ -identical.content <- FALSE + + +######## 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) } -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) + + +######## 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_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_codon2aa() #### convert codon to amino acid using standard genetic code -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" + +fun_codon2aa <- function( + data, + display = FALSE ){ -# 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]])) + # 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) } -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))) +######## fun_open() #### open a GUI or pdf graphic window -} -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) +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) + } } -# end axes management +######## fun_prior_plot() #### set graph param before plotting (erase axes for instance) -# 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))) -} +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) + } } -# end vector of color with length as in levels(categ) of data1 -# color is not NULL anymore + + +######## fun_scale() #### select nice label numbers when setting number of ticks on an axis -# 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) +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) } -# end last check +######## fun_post_plot() #### set graph param after plotting (axes redesign for instance) -# 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) -} -} + + +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) } -# end conversion of geom_hline and geom_vline +######## fun_close() #### close specific graphic windows -# 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])) +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) + } } -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 + +################ 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) } -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])) + + +################ 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] } -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 + +######## 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) } -}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]] + + +######## 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) } -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])) + + +######## 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 } -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 + + +######## 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)) + } } -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]] + + +######## 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 = " + "))))) } -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])) + + +################ 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) + } } -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 + + +######## 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) } -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]] + + +################ 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)))) + } + } } -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])) + + +######## 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 + # } + } } -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 + + +################ 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 +# time for excecution : microbenchmark package. See also in RStudio time per line of code + +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() OR A COLUMN NAME OF THE data1 PARAMETER: ", 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 } -# end loop part +# add density +# rasterise all kind: https://cran.r-project.org/web/packages/ggrastr/vignettes/Raster_geoms.html -# legend display -tempo.legend.final <- 'ggplot2::guides( + +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() OR A COLUMN NAME OF THE data1 PARAMETER: ", 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( fill = if(fin.lg.disp[[1]] == TRUE){ ggplot2::guide_legend( order = lg.order[[1]], @@ -13495,25 +13501,25 @@ shape = NA } )' # 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 @@ -13526,135 +13532,136 @@ 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 @@ -13667,15 +13674,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 @@ -13683,49 +13690,49 @@ warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn # 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 @@ -13733,3 +13740,7 @@ return(output) # this plots the graph if return.ggplot is TRUE and if no assignm + + + + diff --git a/cute_little_R_functions.docx b/cute_little_R_functions.docx index e0905868b371d07c5e89ecf67192f21afbdaf85e..a42532236df2c47feaf0ecd05f13141efb27063b 100755 GIT binary patch delta 426086 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ℓcgU3+p)K0`m_ru@I(UnCw4`Eb$Q$o2aC-3D-)zo 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 Oi_d}}sDSeVy!#(|sLmJw delta 425738 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�kAj$HWj~fP?bEkSTfZv5fIB;i^oZ0&T;9Y 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 R=^Y-F2|<8yN&h}}{{uoZmRtY; diff --git a/fun_gg_boxplot.docx b/fun_gg_boxplot.docx index 275bf32f0d8b4eac93608dc15dca16a2b8625644..3cab248e103f7784c258232ae7f8ca5dca6167cc 100755 GIT binary patch delta 102999 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 I!2tjO03*+(NB{r; delta 102881 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 H!{PZiukqC5 diff --git a/fun_gg_scatter.docx b/fun_gg_scatter.docx index f3605cccde00d915611f7fefbaadb229383748ad..5e322b4aceb287f5acf07e405e8826e32125aa73 100755 GIT binary patch delta 49155 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�?K!GM8lWG5`4>p4A10V$9Z@kj)+d^W3V;K$h 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 delta 49121 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 -- GitLab