R/CodeAndRoll2.R

Defines functions sort.mat as.named.vector.deprecated as.factor.numeric as.numeric.wNames.deprecated pretty_dput imovingSEM movingSEM movingAve2 movingAve mean_of_log geomean fano sem cv modus iround union.wNames intersect.wNames symdiff list.2.replicated.name.vec ls2categvec intermingle.cbind intermingle2vec splititsnames_byValues splitbyitsnames list2fullDF.presence list2fullDF.byNames reverse.list.hierarchy as.listalike intermingle2lists range.list reorder.list as.list.df.by.col as.list.df.by.row list.wNames unlapply setdiff.ls symdiff.ls union.ls intersect.ls any.duplicated.rownames.ls.of.df mdlapply2df arr.of.lists.2.df mdlapply copy.dimension.and.dimnames colNameMatrix rowNameMatrix df.remove.empty.rows.and.columns remove.na.cols remove.na.rows na.omit.mat rotate_matrix fix_tibble_lists get_col_types merge_ls_of_named_vec_as_df_cols merge_2_named_vec_as_df merge_numeric_df_by_rn merge_1col_dfs_by_rn merge_dfs_by_rn get.oddoreven getCols getRows select_rows_and_columns get_max_colname_per_row rowsplit colsplit rownames.trimws sort_matrix_rows sortEachColumn cbind_vectors_by_names colQuantile rowQuantile colIQR rowIQR colSD rowSD colSEM rowSEM colVariance rowVariance colCV rowCV colGeoMeans rowGeoMeans colMedians rowMedians colMax rowMax colMin rowMin mean_normalize median_normalize TPM_normalize row.Zscore rowMutliply rowDivide colMutliply colDivide rowSubtract colSubtract simplify_categories remove_outliers pc_in_total_of_match pc_overlap pc_TRUE zero.omit inf.omit na.omit.strip which_names_grep which_names checkMinOverlap sumBySameName cumsubtract MaxN numerate zigzagger split_vec_to_list_by_N bottomN.dfCol topN.dfCol col2named.vec.tbl as.logical.wNames clip.outliers.at.percentile clip.at.fixed.value which.NA which.duplicated any.duplicated sortbyitsnames flip_value2name fractions rescale translate as.character.wNames as.numeric.wNames.factor as.numeric.wNames.character unique.wNames as_tibble_from_namedVec tibble_summary_to_namedVec df.row.2.named.vector df.col.2.named.vector as.named.vector.2colDF as.named.vector.df sort.decreasing trail top_indices count_occurrence_each_element most_frequent_elements grepv nr.unique getCategories table_decreasing_hybrid table_decreasing table_fixed_categories printProgress printEveryN idimnames is.list2 idim what array.fromNames matrix.fromVector data.frame.fromNames matrix.fromNames list.from.template vec.from.template list.fromNames vec.fromNames pLength pSee savehistory_2 getProject getScriptName

Documented in any.duplicated any.duplicated.rownames.ls.of.df array.fromNames arr.of.lists.2.df as.character.wNames as.factor.numeric as.listalike as.list.df.by.col as.list.df.by.row as.logical.wNames as.named.vector.2colDF as.named.vector.deprecated as.named.vector.df as.numeric.wNames.character as.numeric.wNames.deprecated as.numeric.wNames.factor as_tibble_from_namedVec bottomN.dfCol cbind_vectors_by_names checkMinOverlap clip.at.fixed.value clip.outliers.at.percentile col2named.vec.tbl colCV colDivide colGeoMeans colIQR colMax colMedians colMin colMutliply colNameMatrix colQuantile colSD colSEM colsplit colSubtract colVariance copy.dimension.and.dimnames count_occurrence_each_element cumsubtract cv data.frame.fromNames df.col.2.named.vector df.remove.empty.rows.and.columns df.row.2.named.vector fano fix_tibble_lists flip_value2name fractions geomean getCategories getCols get_col_types get_max_colname_per_row get.oddoreven getProject getRows getScriptName grepv idim idimnames imovingSEM inf.omit intermingle2lists intermingle2vec intermingle.cbind intersect.ls intersect.wNames iround is.list2 list2fullDF.byNames list2fullDF.presence list.2.replicated.name.vec list.fromNames list.from.template list.wNames ls2categvec matrix.fromNames matrix.fromVector MaxN mdlapply mdlapply2df mean_normalize mean_of_log median_normalize merge_1col_dfs_by_rn merge_2_named_vec_as_df merge_dfs_by_rn merge_ls_of_named_vec_as_df_cols merge_numeric_df_by_rn modus most_frequent_elements movingAve movingAve2 movingSEM na.omit.mat na.omit.strip nr.unique numerate pc_in_total_of_match pc_overlap pc_TRUE pLength printEveryN printProgress pSee range.list remove.na.cols remove.na.rows remove_outliers reorder.list rescale reverse.list.hierarchy rotate_matrix rowCV rowDivide rowGeoMeans rowIQR rowMax rowMedians rowMin rowMutliply rowNameMatrix rownames.trimws rowQuantile rowSD rowSEM rowsplit rowSubtract rowVariance row.Zscore savehistory_2 select_rows_and_columns sem setdiff.ls simplify_categories sortbyitsnames sort.decreasing sortEachColumn sort.mat sort_matrix_rows splitbyitsnames splititsnames_byValues split_vec_to_list_by_N sumBySameName symdiff symdiff.ls table_decreasing table_decreasing_hybrid table_fixed_categories tibble_summary_to_namedVec top_indices topN.dfCol TPM_normalize trail translate union.ls union.wNames unique.wNames unlapply vec.fromNames vec.from.template what which.duplicated which.NA which_names which_names_grep zero.omit zigzagger

##################################################################### _
# CodeAndRoll2 - A collection of custom R functions ----
##################################################################### _
# source('~/GitHub/Packages/CodeAndRoll2/R/CodeAndRoll2.R')
# source('https://raw.githubusercontent.com/vertesy/CodeAndRoll2/master/CodeAndRoll2.R')
# source('~/.pack.R')

# devtools::check_man("~/GitHub/Packages/CodeAndRoll2")
# devtools::load_all("~/GitHub/Packages/CodeAndRoll2")
# devtools::document("~/GitHub/Packages/CodeAndRoll2")
# file.edit("~/GitHub/Packages/CodeAndRoll2/Development/Create_the_CodeAndRoll2_Package.R")



### CHAPTERS:

# _________________________________________________________________________________________________
## Rstudio based context retrieval ____________________________________________________________ ----

#' @title Get Current Script Name or Basename of Output Directory
#'
#' @description This function attempts to retrieve the name of the currently opened script in
#' the RStudio editor. If the script name cannot be obtained or if the `rstudioapi` package is
#' not available, it returns the basename of the directory specified by `OutDir`.
#'
#' @return A string containing the basename of the current script or the basename of `OutDir`
#' if the script name is unavailable.
#' @importFrom rstudioapi getSourceEditorContext
#'
#' @export
getScriptName <- function() {
  # Check if rstudioapi is available
  if (!requireNamespace("rstudioapi", quietly = TRUE)) {
    message("rstudioapi package is not available. Please install it using install.packages('rstudioapi').")
  } else {
    scriptName <- basename(rstudioapi::getSourceEditorContext()$path)
  }
  # If scriptName is empty, return basename of OutDir
  # Can happen at an unsaved file, etc.
  if (scriptName == "") scriptName <- basename(OutDir)

  return(scriptName)
}

# _________________________________________________________________________________________________
#' @title getProject
#'
#' @description Try to get the project name you are wokring on in Rstudio.
#' @returns The final subfolder of your project, or NULL, if you are not running one
#' @importFrom rstudioapi getActiveProject
#' @examples getProject()
#'
#' @export
getProject <- function() {
  if (!requireNamespace("rstudioapi", quietly = TRUE)) {
    warning("rstudioapi package is not available. Please install it using install.packages('rstudioapi').",
            immediate. = TRUE
    )
  } else {
    tryCatch(basename(rstudioapi::getActiveProject()), error = function(e) {})
  }
}

# _________________________________________________________________________________________________
#' @title Save Command History to "command_history.date.scriptname.txt"
#'
#' @description
#' This function saves the command history of the current R session to a text file. The file name
#' includes the current date and, if available, the name of the current R script (when running in
#' RStudio). The file is saved in the current working directory.
#'
#' @return Nothing is returned, but the file path is printed to the console.
#'
#' @importFrom rstudioapi getSourceEditorContext
#' @examples
#' \dontrun{
#' savehistory_2()
#' }
savehistory_2 <- function() {
  # Get the current working directory
  current_dir <- getwd()

  # Construct the file name using the current date and optionally the file name from RStudio
  script_name <- try(basename(rstudioapi::getSourceEditorContext()$path), silent = TRUE)
  if ("try-error" %in% is(script_name)) script_name <- ""

  file_name <- ppp(
    "command_history",
    format(Sys.time(), format = "%Y.%m.%d"),
    script_name, "txt"
  )

  # Save the command history
  savehistory(file = file_name)

  # Print and return the file path
  print(file.path(current_dir, file_name))
}


# _________________________________________________________________________________________________
## Pipetools ____________________________________________________________ ----

#' @title Print and Return an Object in a Pipe
#'
#' @description Prints the input object and returns it, enabling you to inspect values inside a pipe.
#'
#' @param x The object to print and return. Default: None.
#'
#' @return The input object `x`, unchanged.
#'
#' @examples
#' results <- c(1, 2, 3) %>% pSee() %>% sqrt() %>% tail(2) ; results
pSee <- function(x, max_elements = 100) {
  if(max_elements) y <- head(x)
  message(kppc(y))
  return(x)
}


#' @title Print Length and Return an Object in a Pipe
#'
#' @description Prints the length of the input object and returns it, allowing you to verify
#' length inside a pipe operation.
#'
#' @param x The object whose length to print and return. Default: None.
#'
#' @return The input object `x`, unchanged.
#'
#' @examples
#' results <- c(9:1) %>% tail(4) %>% pLength() %>% sqrt() ; results
pLength <- function(x) {
  stopifnot(!missing(x), is.vector(x) || is.list(x))
  message("length: ",length(x))
  return(x)
}


# _________________________________________________________________________________________________
## Create and check variables ____________________________________________________________ ----

#' @title vec.fromNames
#'
#' @description Create a vector from a vector of names.
#' @param name_vec A vector of names, Default: `LETTERS[1:5]`
#' @param fill The value to fill the new vector, Default: `NA`
#' @export
vec.fromNames <- function(name_vec = LETTERS[1:5], fill = NA) {
  v <- numeric(length(name_vec))
  if (length(fill) == 1) {
    v <- rep(fill, length(name_vec))
  } else if (length(fill == length(name_vec))) {
    v <- fill
  }
  names(v) <- name_vec
  return(v)
}


# _________________________________________________________________________________________________
#' @title Create a Named List from a Character Vector of Names, or a names of an object
#'
#' @description This function takes a character vector of names and creates a list where each
#' element is named according to the character vector and filled with a specified value.
#' @param x A character vector of names, with a default of the first five letters of the alphabet.
#' @param fill The value used to fill the elements of the list, with a default of `NaN`.
#' @param use.names Logical. If `TRUE`, the names of the input vector are used as names for the list.
#'
#' @return A named list with elements filled with the specified value.
#' @examples
#' list.fromNames() # Default behavior with `LETTERS[1:5]` and `NaN`
#' @export
list.fromNames <- function(x = LETTERS[1:5], fill = NaN, use.names = FALSE) {
  liszt <- as.list(rep(fill, length(x)))

  # names(liszt) <-
  #   if (is.character(x) | ) {
  #     x
  #   } else {
  #     if (!is.null(names(x))) {
  #       names(x)
  #     } else {
  #       stop("Input is nor a chacter vector, nor an object with names")
  #     }
  #   }

  names(liszt) <-
    if (!is.null(names(x)) & use.names) {
      names(x)
    } else {
      x
    }

  kollapse("List of", length(liszt), "| names:", head(names(liszt)), "...", collapseby = " ")
  return(liszt)
}


# _________________________________________________________________________________________________
#' @title vec.from.template
#'
#' @description Create a vector from a names of another vector / list / etc.
#' @param x A vector of names, Default: `LETTERS[1:5]`
#' @param fill The value to fill the new vector, Default: `NA`
#' @export
vec.from.template <- function(x = table(LETTERS[1:5]), fill = NA) {
  stopifnot(is.list(x) | is.vector(x) | is.table(x))
  v <- rep(fill, length(x))
  names(v) <- names(x)
  return(v)
}


# _________________________________________________________________________________________________
#' @title list.from.template
#'
#' @description Create an empty list from a template list, copying names and filling values with NA.
#' @param x A template list with names,.
#' @param fill The value to fill the new list, Default: `NA`
#'
#' @export
list.from.template <- function(x, fill = NA) {
  stopifnot(is(x)[1] == "list" | is.vector(x) | is.table(x))
  liszt <- as.list(rep(fill, length(x)))
  names(liszt) <- names(x)
  return(liszt)
}


# _________________________________________________________________________________________________
#' @title matrix.fromNames
#' @description Create a matrix from 2 vectors defining the row- and column names of the matrix. Default fill value: NA.
#' @param rowname_vec A vector of names for rows, Default: `1:10`
#' @param colname_vec A vector of names for columns, Default: `LETTERS[1:5]`
#' @param fill The value to fill the new vector, Default: `NA`
#' @export
matrix.fromNames <- function(rowname_vec = 1:10, colname_vec = LETTERS[1:5], fill = NA) {
  mx <- matrix(
    data = fill, nrow = length(rowname_vec), ncol = length(colname_vec),
    dimnames = list(rowname_vec, colname_vec)
  )
  iprint("Dimensions:", dim(mx))
  return(mx)
}

# _________________________________________________________________________________________________
#' @title data.frame.fromNames
#' @description Create a data frame from 2 vectors defining the row- and column names of the
#' data frame Default fill value: NA.
#' @param rowname_vec A vector of names for rows, Default: `1:10`
#' @param colname_vec A vector of names for columns, Default: `LETTERS[1:5]`
#' @param fill The value to fill the new vector, Default: `NA`
#' @export
data.frame.fromNames <- function(rowname_vec = 1:10, colname_vec = LETTERS[1:5], fill = NA) {
  df <- matrix(
    data = fill, nrow = length(rowname_vec), ncol = length(colname_vec),
    dimnames = list(rowname_vec, colname_vec)
  ) |> as.data.frame()
  iprint("Dimensions:", dim(df))
  return(df)
}



# _________________________________________________________________________________________________
#' @title matrix.fromVector
#' @description Create a matrix from values in a vector repeated for each column / each row.
#' Similar to rowNameMatrix and colNameMatrix.
#' @param vector Input vector for rows (number of rows = length), Default: `1:5`.
#' @param HowManyTimes Number of columns, Default: `3`.
#' @param IsItARow Transpose? Swap rows an columns. Default: `TRUE`
#' @export
matrix.fromVector <- function(vector = 1:5, HowManyTimes = 3, IsItARow = TRUE) {
  matt <- matrix(vector, nrow = length(vector), ncol = HowManyTimes)
  if (!IsItARow) {
    matt <- t(matt)
  }
  return(matt)
}



# _________________________________________________________________________________________________
#' @title array.fromNames
#' @description Create an N-dimensional array from N vectors defining the row-, column, etc names of the array.
#' @param rowname_vec A vector of names for rows, Default: `1:3`
#' @param colname_vec A vector of names for columns, Default: `letters[1:2]`
#' @param z_name_vec A vector of names for Z dimension, Default: `LETTERS[4:6]`
#' @param fill The value to fill the new vector, Default: NA
#' @export
array.fromNames <- function(
    rowname_vec = 1:3, colname_vec = letters[1:2],
    z_name_vec = LETTERS[4:6], fill = NA) {
  DimNames <- list(rowname_vec, colname_vec, z_name_vec)
  Dimensions_ <- lapply(DimNames, length)
  mx <- array(data = fill, dim = Dimensions_, dimnames = DimNames)
  iprint("Dimensions:", dim(mx))
  return(mx)
}



# _________________________________________________________________________________________________
#' @title what
#' @description A better version of is(). It can print the first "printme" elements.
#' @param x An object to identify.
#' @param printme print the first "printme" elements, Default: 0
#' @export
what <- function(x, printme = 0) {
  iprint(is(x), "; nr. of elements:", length(x))
  if (is.numeric(x)) {
    iprint("min&max:", range(x))
  } else {
    print("Not numeric")
  }
  if (length(dim(x)) > 0) {
    iprint("Dim:", dim(x))
  }
  if (printme > 0) {
    iprint("Elements:", x[0:printme])
  }
  head(x)
}



# _________________________________________________________________________________________________
#' @title idim
#' @description A dim() function that can handle if you pass on a vector: then, it gives the length.
#' @param any_object An object to identify.
#' @export
idim <- function(any_object) {
  if (is.null(dim(any_object))) {
    if (is.list(any_object)) {
      print("list")
    } # if
    print(length(any_object))
  } else {
    print(dim(any_object))
  }
}


# _________________________________________________________________________________________________
#' @title Test if object is a list
#' @description The 'is.list()' function fails on tibbles: it returns TRUE, as if it were a list. This distiguishes. Thaat's why we need this function.
#' @param object Object to test.
#' @export
#' @examples is.list2(list())
#' is.list2(dplyr::tibble())
is.list2 <- function(object) {
  "list" %in% class(object)
}



# _________________________________________________________________________________________________
#' @title idimnames
#' @description A dimnames() function that can handle if you pass on a vector: it gives back the names.
#' @param any_object Any object with N dimensions (with or w/o names).
#' @param print_max Max number of names to print per dimension. Default 25.
#' @export
idimnames <- function(any_object, print_max = 25) {
  iprint("print max:", print_max, "names.")
  if (!is.null(dimnames(any_object))) {
    dimNamesShort <- lapply(dimnames(any_object), head, n = print_max)
    idim(any_object)
    print(dimNamesShort)
  } else if (!is.null(colnames(any_object))) {
    iprint("colnames:", colnames(any_object))
  } else if (!is.null(rownames(any_object))) {
    iprint("rownames:", rownames(any_object))
  } else if (!is.null(names(any_object))) {
    iprint("names:", names(any_object))
  }
}


# _________________________________________________________________________________________________
#' @title printEveryN
#'
#' @description Report iterator value at every e.g. 1000
#' @param i Current iteration of the loop. Default: i.
#' @param N  print Every N
#' @param prefix Optional prefix to display alongside the progress.
#'
#' @export
printEveryN <- function(i = i, N = 1000, prefix = NULL) {
  if ((i %% N) == 0) message(prefix, i)
}

# _________________________________________________________________________________________________
#' @title Print Loop Progress
#'
#' @description Prints the progress of a loop as a number and percentage.
#'
#' @param i Current iteration of the loop. Default: i.
#' @param total Total number of iterations in the loop.
#' @param digits Digits to display
#' @param message Optional message to display alongside the progress.
#'
#' @export
printProgress <- function(i = i, total, message = "Progress", digits = 0) {
  percentage <- formatC(100 * i / total, format = "f", digits = digits)
  cat(paste0(message, ": ", i, "/", total, " (", percentage, "%)\n"))
}


# _________________________________________________________________________________________________
#' @title table_fixed_categories
#'
#' @description Generate a table() with a fixed set of categories. It fills up the table with
#' missing categories, that are relevant when comparing to other vectors.
#'
#' @param vec Input vector to be counted.
#' @param categories_vec Fixed list of categories to be counted in your input vector.
#' @param strict Stop or warn if not all values are covered in the categories vector?
#' @param v Verbose. Default: TRUE.
#'
#' @return A table with the fixed set of categories.
#'
#' @export

table_fixed_categories <- function(vec, categories_vec, strict = TRUE,
                                   v = TRUE) {
  if (!is.vector(vec)) {
    iprint("vec is not a vector -  it is a:", is(vec)[1])
  }

  missing_from_category <- unique(vec) %!in% categories_vec
  if (any(missing_from_category)) {
    txt1 <- pc_TRUE(
      logical_vector = missing_from_category, NumberAndPC = TRUE,
      suffix = "values are NOT found in the categories vector!"
    )
    if (strict) stop(txt1) else warning(txt1)
  }

  txt2 <- pc_TRUE(
    logical_vector = categories_vec %in% vec, NumberAndPC = TRUE,
    suffix = "categories are found in the vector"
  )
  if (v) print(txt2)

  table(factor(unlist(vec), levels = categories_vec))
}



# _________________________________________________________________________________________________
#' @title Frequency Table with Sorting Option
#'
#' @description
#' This function generates a frequency table of the input vector `vec` and allows the option
#' to sort the table in decreasing or increasing order. It handles NA values.
#'
#' @param vec A vector. The vector for which the frequency table is to be generated.
#' @param decreasing Logical. Should the table be sorted in decreasing order? Default: `TRUE`.
#' @param useNA A string. Specifies how to handle NA values. Can be "ifany", "always", or "no".
#' Default: `"ifany"`.
#'
#' @return A frequency table sorted based on the `decreasing` argument.
#' @examples
#' vec <- c(1, 2, 2, NA, 3, 3, 3)
#' table_decreasing(vec)
#' table_decreasing(vec, decreasing = FALSE)
#'
#' @export
table_decreasing <- function(vec, decreasing = TRUE, useNA = "ifany") {
  stopifnot(useNA %in% c("ifany", "always", "no"))
  tbl <- table(vec, useNA = useNA)
  if (decreasing) sort.decreasing(tbl) else sort(tbl)
}


# _________________________________________________________________________________________________
#' @title Frequency Table with Hyrid Sorting: you can sort by frequency and by specified value
#'
#' @description
#' This function generates a frequency table of the input vector `vec` and displays the table
#' sorted by frequency and by a set of specified values. It handles NA values.
#'
#' @param vec A vector. The vector for which the frequency table is to be generated.
#' @param first_elements Logical. Should the table be sorted in decreasing order? Default: `TRUE`.
#' @param useNA A string. Specifies how to handle NA values. Can be "ifany", "always", or "no".
#' Default: `"ifany"`.
#'
#' @return A frequency table sorted.
#' @examples
#' vec <- c(1, 2, 2, NA, 3, 3, 3)
#' table_decreasing_hybrid(vec)
#' table_decreasing_hybrid(vec, first_elements = "1")
#'
#' @export
table_decreasing_hybrid <- function(vec, first_elements = FALSE, useNA = "ifany") {
  stopifnot(useNA %in% c("ifany", "always", "no"))
  tbl <- table(vec, useNA = useNA)

  tbl_decr <- sort.decreasing(tbl)
  if (isFALSE(first_elements)) {
    return(tbl_decr)
  } else {
    if (!all(first_elements %in% names(tbl_decr))) warning("Not all elements are found in the table!", immediate. = TRUE)
    first_elements <- intersect(first_elements, names(tbl_decr))
    tbl_decr <- c(tbl_decr[first_elements], tbl_decr[!names(tbl_decr) %in% first_elements])
    return(tbl_decr)
  }
}

# _________________________________________________________________________________________________
#' @title getCategories
#' @description Extract unique entries with a corresponding name.
#' @param named_categ_vec A vector of categories with names.
#' "Uniqueness" in the vector and its name should be the same!!!
#' @export
#' @examples getCategories(c("A" = 1, "B" = 1, "C" = 2, 3))
getCategories <- function(named_categ_vec) {
  named_categ_vec[names(unique(named_categ_vec))]
}



# _________________________________________________________________________________________________
## Vector operations ____________________________________________________________ ----


#' @title Count the number of unique values
#'
#' @description Count the number of unique values
#' @param x vector
#' @export
nr.unique <- function(x) {
  if (is.data.frame(x)) x <- x[[1]]
  length(unique(x))
}



#' @title grep that returns the value of the match.
#'
#' @description grep returning the value. A character string containing a regular expression (or character string for fixed = TRUE) to be matched in the given character vector. Coerced by as.character to a character string if possible. If a character vector of length 2 or more is supplied, the first element is used with a warning. Missing values are allowed except for regexpr, gregexpr and regexec.
#' @param pattern pattern to look for
#' @param x The haystack to search through. a character vector where matches are sought, or an object which can be coerced by as.character to a character vector. Long vectors are supported.
#' @param ignore.case Ignore letter case, Default: FALSE
#' @param perl logical. Should Perl-compatible regexps be used? Default: FALSE
#' @param value if FALSE, a vector containing the (integer) indices of the matches determined by grep is returned, and if TRUE, a vector containing the matching elements themselves is returned. Default: FALSE
#' @param fixed logical. If TRUE, pattern is a string to be matched as is. Overrides all conflicting arguments. Default: FALSE
#' @param useBytes logical. If TRUE the matching is done byte-by-byte rather than character-by-character. See ‘Details’., Default: FALSE
#' @param invert logical. If TRUE return indices or values for elements that do not match. Default: FALSE
#' @param ... Pass any other argument.
#' @export
grepv <- function(
    pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE,
    fixed = FALSE, useBytes = FALSE, invert = FALSE, ...) {
  grep(pattern, x,
       ignore.case = ignore.case, perl = perl, fixed = fixed,
       useBytes = useBytes, invert = invert, ..., value = TRUE
  )
}



# _________________________________________________________________________________________________
#' @title most_frequent_elements
#' @description Show the most frequent elements of a table.
#' @param vec input vector
#' @param topN How many pof the most frequent elements should be returned? Default: 10
#' @export
most_frequent_elements <- function(vec, topN = 10) {
  tail(sort(table(vec, useNA = "ifany")), topN)
}


# _________________________________________________________________________________________________
#' @title count_occurrence_each_element
#' @description Count the number of times each element occurs in the full vector, AND give it back as a vector, that is the same length as the input vector, each element corresponding one-by-one.
#' @param vec input vector
#' @export
count_occurrence_each_element <- function(vec) {
  tableX <- table(vec)
  rep(x = tableX, tableX)
}




# _________________________________________________________________________________________________
#' @title top_indices
#'
#' @description Returns the positions of the `n` highest values in `x`. For equal values, it maintains the original order.
#' @param x A vector.
#' @param n The number of highest values to return.
#' @param top Whether to return the `n` highest (or `n` lowest) values.
#' @return A vector of indices.
#' @examples top_indices(rnorm(10), n = 3)
#'
#' @export
top_indices <- function(x, n = 3, top = TRUE) {
  head(order(x, decreasing = top), n)
}



# _________________________________________________________________________________________________
#' @title trail
#'
#' @description A combination of `head()` and `tail()`: Returns a vector containing the first
#' `N` and last `N` elements of vector.
#'
#' @param vec A vector.
#' @param N The number of elements to include from each end.
#' @return A vector.
#' @examples trail(rnorm(10), N = 3)
#'
#' @export
trail <- function(vec, N = 10) unique(c(head(vec, n = N), tail(vec, n = N)))



# _________________________________________________________________________________________________
#' @title sort.decreasing
#'
#' @description Sorts `vec` in decreasing order.
#' @param vec A vector.
#' @return A sorted vector.
#' @examples sort.decreasing(rnorm(10))
#'
#' @export sort.decreasing
sort.decreasing <- function(vec) sort(vec, decreasing = TRUE) # Sort in decreasing order.


# _________________________________________________________________________________________________
#' @title as.named.vector.df
#'
#' @description Convert any column or row of a dataframe into a vector, keeping the
#' corresponding dimension name.
#' @param df A dataframe.
#' @param col.or.row.name.or.index Which column or row to extract (numeric index).
#' @param verbose Print the columnname or rowname that is being used
#' @param WhichDimNames Shall we extract rows (2) or columns (1, default)?, Default: 1
#' @param ... Additional arguments passed to `as.vector()`.
#'
#' @export
as.named.vector.df <- function(
    df, col.or.row.name.or.index = 1, verbose = TRUE,
    WhichDimNames = 1,
    ...) {
  .Deprecated(old = "as.named.vector.df", new = "df.col.2.named.vector or df.row.2.named.vector")

  if (verbose) message("input df dimensions: ", kppc(idim(df)))

  name.selection <- dimnames(df)[[(3 - WhichDimNames)]][col.or.row.name.or.index]
  if (verbose) iprint("Variable used:", name.selection)

  vecc <- if (WhichDimNames == 1) {
    as.vector(unlist(df[, col.or.row.name.or.index]), ...)
  } else if (WhichDimNames == 2) {
    as.vector(unlist(df[col.or.row.name.or.index, ]), ...)
  }

  names(vecc) <- dimnames(df)[[WhichDimNames]]
  return(vecc)
}



# # _________________________________________________________________________________________________
# #' @title as.named.vector.df
# #'
# #' @description Convert any column or row of a dataframe into a vector, keeping the
# #' corresponding dimension name.
# #'
# #' @param df A dataframe.
# #' @param col.or.row.name.or.index Which column or row to extract (numeric index).
# #' @param verbose Print the columnname or rowname that is being used
# #' @param WhichDimNames Shall we extract rows (1) or columns (2, default)?, Default: 1
# #' @param ... Additional arguments passed to `as.vector()`.
# #'
# #' @export
# as.named.vector.df <- function(
    #     df, col.or.row.name.or.index = 1, verbose = TRUE,
#     WhichDimNames = 2,
#     ...) {
#
#   # name.selection <- dimnames(df)[[(3 - WhichDimNames)]][col.or.row.name.or.index] # Original not working properlu
#
#   if(verbose) {
#     tag <- if(WhichDimNames == 1) "row" else "column"
#     message("input df dimensions: ", kppc(idim(df)))
#     if(is.numeric(col.or.row.name.or.index)) name.of.selection <- dimnames(df)[[WhichDimNames]][col.or.row.name.or.index]
#     message("Selecting: ", tag, " ", col.or.row.name.or.index, ", called: ", name.selection)
#   }
#
#   # Extract the column or row
#   vecc <- if (WhichDimNames == 1) {
#     as.vector(unlist(df[, col.or.row.name.or.index]), ...)
#   } else if (WhichDimNames == 2) {
#     as.vector(unlist(df[col.or.row.name.or.index, ]), ...)
#   }
#
#   names(vecc) <- dimnames(df)[[WhichDimNames]]
#   return(vecc)
# }

# _________________________________________________________________________________________________
#' @title as.named.vector.2colDF
#'
#' @description Convert a 2-column dataframe (value, name) into a named vector. Use for simple tibbles.
#' @param df data frame
#' @param values Index of column with values, Default: 1
#' @param names Index of column with names, Default: 2
#' @param make.names make.names, Default: F
#'
#' @export
as.named.vector.2colDF <- function(df, values = 1, names = 2, make.names = FALSE) {
  vec <- df[[values]]
  names(vec) <- df[[names]]
  if (make.names) names(vec) <- make.names(names(vec))
  return(vec)
}


# _________________________________________________________________________________________________
#' @title df.col.2.named.vector
#'
#' @description Convert a dataframe column into a vector, keeping the corresponding dimension name.
#' @param df data frame
#' @param col column index
#' @export
df.col.2.named.vector <- function(df, col) {
  vec <- df[, col]
  names(vec) <- rownames(df)
  return(vec)
}

# df.col.2.named.vector <- function(df_col) {
#   namez <- rownames(df_col)
#   vecc <- as.vector(unlist(df_col))
#   names(vecc) <- namez
#   return(vecc)
# }


# _________________________________________________________________________________________________
#' @title df.row.2.named.vector
#'
#' @description Convert a dataframe row into a vector, keeping the corresponding dimension name.
#' @param df_row data frame row
#' @export
df.row.2.named.vector <- function(df_row) {
  "Needs update see above"
  namez <- colnames(df_row)
  vecc <- as.vector(unlist(df_row))
  names(vecc) <- namez
  return(vecc)
}



# _________________________________________________________________________________________________
#' @title tibble_summary_to_namedVec
#' @description Convert a key-value tibble into a named vector (as opposed to using rownames).
#' @param tbl A tibble, Default: dplyr::tibble(key = sample(x = 1:5, size = 20, replace = TRUE), value = rnorm(20))
#' @param idx PARAM_DESCRIPTION, Default: c(key = 1, value = 2)
#' @seealso
#'  \code{\link[dplyr]{reexports}}
#' @examples tibble_summary_to_namedVec()
#' @importFrom dplyr tibble
#'
#' @export
tibble_summary_to_namedVec <- function(
    tbl = dplyr::tibble("key" = sample(x = 1:5, size = 20, replace = TRUE), "value" = rnorm(20)),
    idx = c(key = 1, value = 2)) {
  iprint("The following name and value columns are taken:", colnames(tbl[idx]), "; with indices:", idx)
  tbl_2_col <- tbl[, idx]
  named.vec <- tbl_2_col[[2]]
  names(named.vec) <- tbl_2_col[[1]]
  return(named.vec)
}



# _________________________________________________________________________________________________
#' @title as_tibble_from_namedVec
#' @description Convert a vector with names into a tibble, keeping the names as rownames.
#' @param vec.w.names A vector with names, Default: c(a = 1, b = 2)
#' @param transpose Transpose? Default: T
#' @examples as_tibble_from_namedVec()
#' @importFrom dplyr bind_rows
#'
#' @export
as_tibble_from_namedVec <- function(vec.w.names = c("a" = 1, "b" = 2), transpose = TRUE) {
  stopifnot(!is.null(names(vec.w.names)))
  tbl <- dplyr::bind_rows(vec.w.names)
  if (transpose) t(tbl) else tbl
}


# _________________________________________________________________________________________________
#' @title Unique elements
#' @description Get the unique elements of a vector, keep their names
#' @param x A vector with names
#' @export unique.wNames
unique.wNames <- function(x) {
  x[!duplicated(x)]
}




# _________________________________________________________________________________________________
#' @title as.numeric.wNames.character
#'
#' @description Converts (1) a 'character' v. into a numeric v., or
#' a 'factor' v. as as.numeric(as.character(vec)) and preserves the original names.
#' The old 'as.numeric.wNames()' is deprecated as it was not clearly documented that it converts via facotr in any case. Code saved at the end.
#' @param vec input vector
#' @param verbose Print troubleshooting messages
#' @param factor.to.character convert Input vector to first to 'character', then numeric.
#' @param ... Pass any other argument to as.numeric()
#' @examples vec <- as.character(c(1, 2, 8, 9))
#' names(vec) <- LETTERS[1:4]
#' vec
#' as.numeric.wNames.character(vec)
#' vec2 <- as.factor(c(1, 2, 8, 9))
#' names(vec2) <- LETTERS[1:4]
#' vec2
#' as.numeric.wNames.character(vec2, factor.to.character = FALSE)
#'
#' @export as.numeric.wNames.character
as.numeric.wNames.character <- function(
    vec, verbose = TRUE,
    factor.to.character = TRUE, ...) {
  if (is.character(vec) | is.logical(vec)) {
    numerified_vec <- as.numeric(vec, ...)
  } else {
    if (verbose) print("Input vector is not 'character' or 'logical'.")
    if (is.factor(vec)) {
      if (verbose) print("Input vector is factor.")
      if (factor.to.character) {
        if (verbose) {
          print("Input vector converted to 'character', then numeric.")
        }
        numerified_vec <- as.numeric(as.character(vec), ...)
      } else {
        warning("Input is factor, now converted as is")
        numerified_vec <- as.numeric(vec, ...)
      } # else / factor.to.character
    } else {
      warning("Input vector is not character/logical/factor. Simple conversion attempted.")
      numerified_vec <- as.numeric(vec, ...)
    } # else / is.factor
  } # else / is.character or is.logical

  if (is.null(names(vec))) {
    if (verbose) warning("Input vector has no names!")
  } else {
    names(numerified_vec) <- names(vec)
  } # else: has names

  return(numerified_vec)
}



# _________________________________________________________________________________________________
#' @title as.numeric.wNames.factor
#'
#' @description  Turn any vector into numeric categories as.numeric(as.factor(vec))
#' Forerly as.factor.numeric
#' @param vec vector of factors, strings, (or even logical)
#' @param ... Pass any other argument to as.factor()
#' @examples as.numeric.wNames.factor(LETTERS[1:4])
#'
#' @export as.numeric.wNames.factor

as.numeric.wNames.factor <- function(vec, ...) {
  if (is.character(vec)) warning("Input is character, now converted via as.factor()")
  if (is.logical(vec)) warning("Input is logical, now converted via as.factor()")

  numerified_vec <- as.numeric(as.factor(vec, ...))

  if (is.null(names(vec))) {
    warning("Input vector has no names!")
  } else {
    names(numerified_vec) <- names(vec)
  } # else: has names

  return(numerified_vec)
}



# _________________________________________________________________________________________________
#' @title as.character.wNames
#' @description Converts your input vector into a character vector, and puts the original
#' character values into the names of the new vector, unless it already has names.
#' @param vec input vector
#'
#' @export as.character.wNames
as.character.wNames <- function(vec) {
  char_vec <- as.character(vec)
  if (!is.null(names(vec))) {
    names(char_vec) <- names(vec)
  }
  return(char_vec)
}


# _________________________________________________________________________________________________
#' @title Translate a set of values to a new set using a dictionary
#'
#' @description Replaces a set of values in a vector with another set of values, so
#' it translates your vector. Oldvalues and newvalues have to be 1-to-1
#' corresponding vectors.  'chartr("a-cX", "D-Fw", x) does the same as above
#' in theory, but it did not seem very robust regarding your input...'
#'
#' @param vec set of values where you want to replace
#' @param old oldvalues, a vector of values that occur in `vec` to be replaced.
#' @param new newvalues, a vector of equal length, to be copied from, corresponding 1-by-1 to old.
#' @examples A <- 1:3
#' translate(vec = A, old = 2:3, new = letters[1:2])
#'
#' @export
translate <- function(vec, old, new) {
  stopifnot(length(old) == length(new) | length(new) == 1)
  # "PROVIDE ONE NEW VALUE, OR THE SAME NUMBER OF NEW VALUES AS OLD VALUES!"

  if (length(old) > length(new) & length(new) == 1) {
    new <- rep(new, length(old))
  }

  vec_replaced <- vec
  for (i in 1:length(old)) {
    oldval <- old[i]
    vec_replaced[vec == oldval] <- new[i]
    printEveryN(i = i, N = 1000)
  }
  return(vec_replaced)
}


# _________________________________________________________________________________________________
#' @title rescale
#' @description Linear transformation to a given range of values.
#' @param vec input vector
#' @param from min, Default: 0
#' @param upto max, Default: 100
#' @export
rescale <- function(vec, from = 0, upto = 100) {
  vec <- vec - min(vec, na.rm = TRUE)
  vec <- vec * ((upto - from) / max(vec, na.rm = TRUE))
  vec <- vec + from
  return(vec)
} # fun

# _________________________________________________________________________________________________
#' @title fractions
#' @description x/sum(x)
#' @param vec input vector
#' @param na_rm remove NAs
#' @export
fractions <- function(vec, na_rm = TRUE) vec / sum(vec, na.rm = na_rm)


# _________________________________________________________________________________________________

#' @title flip_value2name
#' @description Flip the values and the names of a vector with names.
#' @param namedVector named vector
#' @param NumericNames PARAM_DESCRIPTION, Default: FALSE
#' @param silent Suppress printing info? Default: FALSE
#' @export
flip_value2name <- function(namedVector, NumericNames = FALSE, silent = FALSE) {
  if (!is.null(names(namedVector))) {
    newvec <- names(namedVector)
    if (NumericNames) {
      newvec <- as.numeric(names(namedVector))
    }
    names(newvec) <- namedVector
  } else {
    iprint("Vector without names!", head(namedVector))
  }
  if (!silent) {
    if (any(duplicated(namedVector))) {
      iprint("New names contain duplicated elements", head(namedVector[which(duplicated(namedVector))]))
    }
    if (any(duplicated(newvec))) {
      iprint("Old names contained duplicated elements", head(newvec[which(duplicated(newvec))]))
    }
  }
  return(newvec)
}


# _________________________________________________________________________________________________
#' @title sortbyitsnames
#'
#' @description Sort a vector or list by the alphanumeric order of its names (instead of its values).
#' @param vec_or_list A vector or list.
#' @param decreasing Logical. Whether to sort in decreasing order.
#' @param ... Additional arguments passed to `mixedsort()`.
#' @return A sorted vector or list.
#' @importFrom gtools mixedsort
#' @export
sortbyitsnames <- function(vec_or_list, decreasing = FALSE, ...) {
  xx <- names(vec_or_list)
  names(xx) <- 1:length(vec_or_list)
  order <- as.numeric(names(gtools::mixedsort(xx, decreasing = decreasing, ...)))
  vec_or_list[order]
}



# _________________________________________________________________________________________________
#' @title any.duplicated
#' @description How many entries are duplicated?.
#' @param vec input vector
#' @param summarize Print summary? Default: TRUE
#' @param max.shown Print first X elements. Default: 25
#' @export any.duplicated
any.duplicated <- function(vec, summarize = TRUE, max.shown = 25) {
  y <- sum(duplicated(vec))
  if (summarize & y) {
    x <- table(vec)
    x <- sort.decreasing(x[x > 1])

    print(paste("The following", y, "elements have  > 1 extra copies: (max", max.shown, "shown.)"))
    print(head(x, n = max.shown))
  }
  invisible(y > 0)
}



# _________________________________________________________________________________________________
#' @title which.duplicated
#' @description Which values are duplicated?.
#' @param vec input vector
#' @param verbose print statistics to terminal. Default: TRUE
#' @export
which.duplicated <- function(vec, verbose = TRUE) {
  DPL <- vec[which(duplicated(vec))]
  if (verbose) iprint(length(DPL), "Duplicated entries (1-5): ", head(DPL), "...")
  return(DPL)
}



# _________________________________________________________________________________________________
#' @title which.NA
#' @description Which values are NA?.
#' @param vec input vector. Default: TRUE
#' @param verbose print statistics to terminal. Default: TRUE
#' @export
which.NA <- function(vec, verbose = TRUE) {
  NANs <- vec[which(is.na(vec))]
  if (verbose) iprint(length(NANs), "NaN entries: ", NANs)
  NAs <- vec[which(is.na(vec))]
  if (verbose) iprint(length(NAs), "NA entries: ", NAs, "(only NA-s are returned)")
  return(NAs)
}



# _________________________________________________________________________________________________
#' @title clip.at.fixed.value
#' @description Signal clipping. Cut values in a distribution, above or below a threshold.
#' @param x A vector of numeric values (distribution).
#' @param high Clip above threshold? Default: TRUE
#' @param thr threshold values, Default: 3
#'
#' @export
clip.at.fixed.value <- function(x, high = TRUE, thr = 3) {
  if (high) {
    x[x > thr] <- thr
  } else {
    x[x < thr] <- thr
  }
  x
}


# _________________________________________________________________________________________________
#' @title clip.outliers.at.percentile
#' @description Signal clipping based on the input data's distribution. It clips values
#' in a distribution above or below the extreme N% of the distribution.
#'
#' @param x A vector of numeric values.
#' @param high Clip above threshold? Default: TRUE
#' @param percentiles At which percentiles to cut off?, Default: c(0.01, 0.99)
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @param showhist PARAM_DESCRIPTION, Default: FALSE
#' @param ... Pass any other argument.
#' @export
# #' @importFrom MarkdownReports whist

clip.outliers.at.percentile <- function(x, high = TRUE,
                                        percentiles = c(.01, .99),
                                        na.rm = TRUE, showhist = FALSE,
                                        ...) {
  qnt <- quantile(x, probs = percentiles, na.rm = na.rm)
  if (showhist) {
    hist(unlist(x),
         breaks = 50, main = "Distribution and cutoffs histogram",
         sub = paste("Percentile cutoffs at: ", paste(percentiles, collapse = " and ")),
         xlab = "Values"
    )
    abline(v = qnt, col = 2)
  }
  # if (showhist) { MarkdownReports::whist(unlist(x), breaks = 50 ,vline = qnt, filtercol = -1)} #if
  y <- x
  y[x < qnt[1]] <- qnt[1]
  y[x > qnt[2]] <- qnt[2]
  y
}



# _________________________________________________________________________________________________
#' @title as.logical.wNames
#' @description Converts your input vector into a logical vector, and puts the original character values into the names of the new vector, unless it already has names.
#' @param x input vector
#' @param ... Pass any other argument.
#' @export as.logical.wNames
as.logical.wNames <- function(x, ...) {
  numerified_vec <- as.logical(x, ...)
  if (!is.null(names(x))) {
    names(numerified_vec) <- names(x)
  }
  return(numerified_vec)
}


# _________________________________________________________________________________________________
#' @title col2named.vec.tbl
#' @description Convert a 2-column table(data frame) into a named vector. 1st column will be used as names.
#' @param tbl.2col 2-column tibble
#' @export
col2named.vec.tbl <- function(tbl.2col) {
  nvec <- tbl.2col[[2]]
  names(nvec) <- tbl.2col[[1]]
  nvec
}



# _________________________________________________________________________________________________
#' @title topN.dfCol
#' @description Find the n highest values in a named vector.
#' @param df_col data frame column, Default: `as.named.vector(df[, 1, drop = FALSE])`
#' @param n top N values, Default: 5
#' @export
topN.dfCol <- function(df_col = as.named.vector(df[, 1, drop = FALSE]), n = 5) {
  head(sort(df_col, decreasing = TRUE), n = n)
} # Find the n highest values in a named vector


# _________________________________________________________________________________________________
#' @title bottomN.dfCol
#' @description Find the n lowest values in a named vector.
#' @param df_col data frame column, Default: `as.named.vector(df[, 1, drop = FALSE])`
#' @param n lowest N values, Default: 5
#' @export
bottomN.dfCol <- function(df_col = as.named.vector(df[, 1, drop = FALSE]), n = 5) {
  head(sort(df_col, decreasing = FALSE), n = n)
} # Find the n lowest values in a named vector



# _________________________________________________________________________________________________
#' @title Split a Vector into a List by Every N-th Element
#' @description This function divides a given vector into chunks of size `by` (default is 9).
#' The resulting list contains vectors of the specified chunk size or smaller.
#' @param vec A numeric or character vector to be split.
#' @param by Integer value specifying the chunk size. Default is 9.
#' @return A list where each element is a vector containing up to `by` elements from `vec`.
#' @export
split_vec_to_list_by_N <- function(vec = 1:27, by = 9) {
  n_groups <- ceiling(length(vec) / by)
  assignment <- gl(n_groups, by, length = length(vec))
  lsX <- split(x = vec, f = assignment)
  names(lsX) <- paste0("v", 1:length(lsX))
  lsX
}
# FORMERLY / aka: iterBy.over()
# lsX = split(vec, sort(rank(vec) %% steps))

# _________________________________________________________________________________________________
#' @title zigzagger
#' @description Mix entries so that they differ.
#' @param vec input vector, Default: 1:9
#' @export
zigzagger <- function(vec = 1:9) {
  intermingle2vec(vec, rev(vec))[1:length(vec)]
}


# _________________________________________________________________________________________________
#' @title Formats a Sequence of Numbers with Zero Padding
#'
#' @description This function generates a sequence of numbers between two specified values,
#' optionally padding them with leading zeros to a specified length. It is useful
#' for creating numeric sequences with consistent character lengths.
#' @param x The starting number of the sequence. Default: 1.
#' @param y The ending number of the sequence. Default: 100.
#' @param zeropadding Logical; whether to pad numbers with zeros. Default: TRUE.
#' @param pad_length The length of padding for the numbers. Default: Calculated as
#' floor(log10(max(abs(x), abs(y)))) + 1.
#' @return A vector of formatted numbers, with or without zero padding.
#' @seealso \code{\link[stringr]{str_pad}}
#' @export
#' @examples numerate(1, 122)
#' @importFrom stringr str_pad
numerate <- function(x = 1, y = 100, zeropadding = TRUE,
                     pad_length = floor(log10(max(abs(x), abs(y)))) + 1) {
  # Input argument assertions
  stopifnot(
    is.numeric(x), is.numeric(y),
    x <= y,
    is.logical(zeropadding), is.numeric(pad_length),
    pad_length > 0
  )

  # Create the sequence of numbers
  z <- x:y

  # Pad the numbers with zeros if required
  if (zeropadding) {
    z <- stringr::str_pad(z, pad = "0", width = pad_length)
  }

  return(z)
}




# _________________________________________________________________________________________________
#' @title MaxN
#' @description Find second (third…) highest/lowest value in vector.
#' @param vec input vector, Default: rpois(4, lambda = 3)
#' @param topN PARAM_DESCRIPTION, Default: 2
#' @export
MaxN <- function(vec = rpois(4, lambda = 3), topN = 2) {
  topN <- topN - 1
  n <- length(vec)
  sort(vec, partial = n - topN)[n - topN]
}
# https://stackoverflow.com/questions/2453326/fastest-way-to-find-second-third-highest-lowest-value-in-vector-or-column



# _________________________________________________________________________________________________
#' @title cumsubtract
#' @description Cumulative subtraction, opposite of cumsum().
#' @param numericVec PARAM_DESCRIPTION, Default: blanks
#' @export
cumsubtract <- function(numericVec = blanks) {
  DiffZ <- numericVec[-1] - numericVec[-length(numericVec)]
  print(table(DiffZ))
  DiffZ
}



# _________________________________________________________________________________________________
#' @title sumBySameName
#' @description Sum up vector elements with the same name.
#' @param namedVec PARAM_DESCRIPTION
#' @export
sumBySameName <- function(namedVec) {
  # unlapply(splitbyitsnames(namedVec), sum)
  tapply(X = namedVec, INDEX = names(namedVec), sum)
}


# _________________________________________________________________________________________________
#' @title Check Minimum Overlap Between Two Vectors
#'
#' @description Checks if the overlap between two character vectors is at least a specified
#' percentage of the shorter vector. Stops execution with an error if the condition is not met.
#'
#' @param x First character vector.
#' @param y Second character vector.
#' @param min_overlap Minimum required overlap as a fraction of the shorter vector's length.
#' @param stop_it Use stop(), else warning()
#' @param verbose verbose
#' @return Invisible TRUE if the overlap is sufficient, otherwise stops with an error.
#' @export
checkMinOverlap <- function(x, y, min_overlap = 0.2, stop_it = TRUE, verbose = TRUE) {
  stopifnot(is.character(x), is.character(y), is.numeric(min_overlap), min_overlap >= 0)

  namez <- c(substitute(x), substitute(y))
  lengths <- c(length(x), length(y))

  min_len <- min(lengths)
  max_len <- max(lengths)

  overlap_len <- length(intersect(x, y))

  required_overlap <- min_len * min_overlap

  if (verbose) {
    iprint("Overlap is", overlap_len)
    iprint(percentage_formatter(overlap_len / min_len), "or", min_len, "of", namez[which.min(lengths)])
    iprint(percentage_formatter(overlap_len / max_len), "or", max_len, "of", namez[which.max(lengths)])
  }

  pass <- overlap_len > required_overlap
  if (!pass) {
    iprint(substitute(x), "-", head(x))
    iprint(substitute(y), "-", head(y))

    msg <- "Minimum overlap condition not met."
    if (stop_it) stop(msg) else warning(msg, immediate. = TRUE)
  }
  invisible(pass)
}



# _________________________________________________________________________________________________
### Vector filtering ____________________________________________________________ ----

#' @title which_names
#' @description Return the names where the input vector is TRUE. The input vector is converted to logical.
#' @param namedVec PARAM_DESCRIPTION
#' @export
which_names <- function(namedVec) {
  return(names(which(as.logical.wNames(namedVec))))
}



# _________________________________________________________________________________________________
#' @title which_names_grep
#'
#' @description Return the vector elements whose names partially match a pattern.
#' @param namedVec A vector of named elements.
#' @param pattern A regular expression pattern.
#' @param ... Pass any other argument to grepv()
#' @export
which_names_grep <- function(namedVec, pattern, ...) {
  idx <- grepv(x = names(namedVec), pattern = pattern, ...)
  return(namedVec[idx])
}



# _________________________________________________________________________________________________
#' @title na.omit.strip
#'
#' @description Calls na.omit() and returns a clean vector.
#' Omit NA values from a vector and return a clean vector without any spam.
#' @param object Values to filter for NA
#' @param silent Silence the data structure coversion warning: anything ->vector
#' @param ... Pass any other argument to na.omit()
#' @importFrom stats na.omit
#'
#' @export na.omit.strip
#'
#' @examples # CodeAndRoll2::na.omit.strip(c(1, 2, 3, NA, NaN, 2))
na.omit.strip <- function(object, silent = FALSE, ...) {
  if (is.data.frame(object)) {
    if (min(dim(object)) > 1 & silent == FALSE) {
      iprint(dim(object), "dimensional array is converted to a vector.")
    }
    object <- unlist(object)
  }
  clean <- stats::na.omit(object, ...)
  attributes(clean)$na.action <- NULL
  return(clean)
}



# _________________________________________________________________________________________________
#' @title inf.omit
#' @description Omit infinite values from a vector.
#' @param vec input vector
#'
#' @export
inf.omit <- function(vec) {
  if (is.data.frame(vec)) {
    if (min(dim(vec)) > 1) {
      iprint(dim(vec), "dimensional array is converted to a vector.")
    }
    vec <- unlist(vec)
  }
  clean <- vec[is.finite(vec)]
  # attributes(clean)$na.action <- NULL
  return(clean)
}



# _________________________________________________________________________________________________
#' @title zero.omit
#' @description Omit zero values from a vector.
#' @param vec input vector
#' @param verbose Whether to print the range of the cleaned vector.
#' @export
zero.omit <- function(vec, verbose = TRUE) {
  v2 <- vec[vec != 0]
  if (verbose) iprint("range: ", range(v2))
  if (!is.null(names(vec))) {
    names(v2) <- names(vec)[vec != 0]
  }
  return(v2)
}


# _________________________________________________________________________________________________
#' @title pc_TRUE
#'
#' @description  Calculates the percentage of true values in a logical vector, parsed as text.
#' @param logical_vector A logical vector.
#' @param percentify Whether to return the percentage as a formatted string (default: TRUE).
#' @param NumberAndPC Whether to return the percentage and the number of true values (default: FALSE).
#' @param NArm Whether to ignore NA values (default: TRUE).
#' @param prefix A prefix to add to the output string (default: NULL).
#' @param suffix A suffix to add to the output string (default: NULL).
#' @param digitz The number of decimal places to use when formatting the percentage (default: 3).
#' @param ... Additional arguments to pass to `percentage_formatter()`.
#'
#' @return A string representing the percentage of true values in the logical vector.
#'
#' @export
pc_TRUE <- function(
    logical_vector, percentify = TRUE, NumberAndPC = FALSE,
    NArm = TRUE, prefix = NULL, suffix = NULL, digitz = 3, ...) {
  # Calculate the percentage of true values
  SUM <- sum(logical_vector, na.rm = NArm)
  LEN <- length(logical_vector)
  out <- SUM / LEN

  # Format the percentage as a string
  if (percentify) out <- percentage_formatter(out, digitz = digitz, ...)

  # Add the number of true values if requested
  if (NumberAndPC) out <- paste0(out, " or ", SUM, " of ", LEN)

  # Add the prefix and suffix
  if (!is.null(prefix)) out <- paste(prefix, out)
  if (!is.null(suffix)) out <- paste(out, suffix)

  return(out)
}



# _________________________________________________________________________________________________
#' @title Calculate Percentage Overlap Between Two Vectors
#'
#' @description Computes the percentage of overlap between two vectors based on the specified basis of calculation.
#'
#' @param x The first vector for overlap calculation.
#' @param y The second vector for overlap calculation.
#' @param basis A character string specifying the basis for calculating the percentage overlap.
#' Can be "x" for the length of `x`, "y" for the length of `y`, or "sum" for the sum
#' of lengths of both `x` and `y`. Default is "x".
#' @param prefix A prefix to add to the output string (default: NULL).
#' @param suffix A suffix to add to the output string (default: NULL).
#' @param ... Additional arguments to pass to `percentage_formatter()`.
#'
#' @return The percentage of overlap between `x` and `y` based on the specified basis.
#' @examples x <- 1:5
#' y <- 3:8
#' pc_overlap(x, y, basis = "x")
#'
#' @export

pc_overlap <- function(x, y, basis = "x", prefix = NULL, suffix = NULL, ...) {
  # Assertions to ensure input validity
  stopifnot(
    is.character(basis),
    basis %in% c("x", "y", "sum"),
    is.vector(x),
    is.vector(y)
  )

  # Calculate intersection
  overlap <- length(intersect(x, y))

  # Calculate denominator based on the basis argument
  denominator <- switch(basis,
                        "x" = length(x),
                        "y" = length(y),
                        "sum" = length(x) + length(y)
  )

  # Calculate and return percent overlap
  percent_overlap <- percentage_formatter(x = overlap / denominator, ...)
  if (is.null(suffix)) suffix <- kppws("of", substitute(basis), "is found in both vectors.")
  text <- kppws(prefix, percent_overlap, suffix)
  message(text)

  return(overlap / denominator)
}




# _________________________________________________________________________________________________
#' @title pc_in_total_of_match
#'
#' @description Calculates the percentage of a certain value within a vector or table.
#' @param vec_or_table A vector or table.
#' @param category The value to calculate the percentage for.
#' @param NA_omit Logical. Whether to omit missing values from the calculation.
#' @return The percentage of `category` in `vec_or_table`.
#'
#' @export
#' @importFrom stats na.omit
pc_in_total_of_match <- function(vec_or_table, category, NA_omit = TRUE) {
  if (is.table(vec_or_table)) {
    vec_or_table[category] / sum(vec_or_table, na.rm = NA_omit)
  } else {
    # Check if `NA_omit` is TRUE and there are missing values
    if (NA_omit) {
      if (sum(is.na(vec_or_table))) {
        vec_or_table <- stats::na.omit(vec_or_table)
        iprint(sum(is.na(vec_or_table)), "NA are omitted from the vec_or_table of:", length(vec_or_table))
      }
      "Not working complelety : if NaN is stored as string, it does not detect it"
    }

    # Calculate the percentage
    sum(vec_or_table == category) / length(vec_or_table)
  } # else: is vector
} # fun


# _________________________________________________________________________________________________


# _________________________________________________________________________________________________
#' @title remove_outliers
#'
#' @description Remove values that fall outside the trailing `probs` percentiles of the distribution.
#' @param x A numeric vector.
#' @param na.rm Remove NA values for calculation? Default: TRUE.
#' @param probs A vector of two probabilities, specifying the trailing percentiles to use.
#' @param ... Additional arguments passed to `quantile()`.
#' @return A vector with the outliers removed.
#'
#' @export
remove_outliers <- function(x, na.rm = TRUE, probs = c(.05, .95), ...) {
  print("Deprecated. Use clip.outliers.at.percentile()")
  qnt <- quantile(x, probs = probs, na.rm = na.rm, ...)
  y <- x
  y[x < qnt[1]] <- NA ## Add IQR dependence
  y[x > qnt[2]] <- NA
  y
}


# _________________________________________________________________________________________________
#' @title simplify_categories
#'
#' @description Replace all occurrences of `replaceit` in `category_vec` with `to`.
#' @param category_vec A vector of categories.
#' @param replaceit The value to replace.
#' @param to The replacement value.
#' @return A vector with the simplified categories.
#'
#' @export
simplify_categories <- function(category_vec, replaceit, to) {
  matches <- which(category_vec %in% replaceit)
  iprint(length(matches), "instances of", replaceit, "are replaced by", to)
  category_vec[matches] <- to
  return(category_vec)
}


# _________________________________________________________________________________________________
## Matrix operations ____________________________________________________________ ----


# _________________________________________________________________________________________________
### Matrix calculations ____________________________________________________________ ----



# _________________________________________________________________________________________________
#' @title colSubtract
#' @description Subtract a vector (length = nr. columns) column by column from each value of the matrix.
#' @param mat Numeric input matrix.
#' @param vec Vector to subtract. Length = nr. columns.
#' @export
colSubtract <- function(mat = xx, vec = 5:1) {
  stopifnot(NCOL(mat) == length(vec))
  t(apply(mat, 1, function(x) x - vec))
}


# _________________________________________________________________________________________________
#' @title rowSubtract
#' @description Subtract a vector (length = nr. rows) row by row from each value of the matrix
#' @param mat Numeric input matrix.
#' @param vec Vector to subtract. Length = nr. rows.
#' @export
rowSubtract <- function(mat = yy, vec = 5:1) {
  stopifnot(NROW(mat) == length(vec))
  apply(mat, 2, function(x) x - vec)
}



# _________________________________________________________________________________________________
#' @title Row-wise division of a matrix by a column vector
#'
#' @description Each element of the matrix is divided by the corresponding element of the vector
#' that matches the column of the matrix element. This is typically used to normalize data,
#' for example, to scale values in each row by certain factors like totals or means. Soruce
#' \url{https://stackoverflow.com/questions/20596433/how-to-divide-each-row-of-a-matrix-by-elements-of-a-vector-in-r}.
#'
#' @param mat A numeric matrix where each row represents a distribution to be divided.
#' @param vec A numeric vector whose elements are the divisors for each column of the matrix.
#' The length of the vector must match the number of columns in the matrix. If not supplied,
#' the default is to use the column sums of the matrix as divisors.
#'
#' @return A matrix with the same dimensions as the input where each element in the original matrix
#' has been divided by the corresponding element in the vector.
#'
#' @export
colDivide <- function(mat, vec = colSums(mat)) {
  stopifnot(ncol(mat) == length(vec), is.numeric(vec))
  mat / vec[col(mat)] # This operation divides each column element-wise by the vector
}


# _________________________________________________________________________________________________
#' @title colMutliply
#' @description Multiply by column. See more: https://stackoverflow.com/questions/20596433/how-to-divide-each-row-of-a-matrix-by-elements-of-a-vector-in-r.
#' @param mat Numeric input matrix with the distribution.
#' @param vec Vector to multiply by.
#' @export
colMutliply <- function(mat, vec) {
  stopifnot(NCOL(mat) == length(vec))
  mat * vec[col(mat)] # fastest
}



# _________________________________________________________________________________________________
#' @title rowDivide
#' @description Divide by row.
#' @param mat Numeric input matrix with the distribution.
#' @param vec Vector to divide by.
#' @export
rowDivide <- function(mat, vec) {
  stopifnot(NROW(mat) == length(vec))
  mat / vec[row(mat)] # fastest
}



# _________________________________________________________________________________________________
#' @title rowMutliply
#' @description Mutliply by row.
#' @param mat Numeric input matrix with the distribution.
#' @param vec Vector to multiply by.
#' @export
rowMutliply <- function(mat, vec) {
  stopifnot(NROW(mat) == length(vec))
  mat * vec[row(mat)] # fastest
}



# _________________________________________________________________________________________________
#' @title row.Zscore
#' @description Calculate Z-score over rows of data frame.
#' @param x Numeric input matrix with the distribution.
#' @export
row.Zscore <- function(x) t(scale(t(x)))


# _________________________________________________________________________________________________
#' @title TPM_normalize
#' @description Normalize each column to 1 million.
#' @param mat Numeric input matrix with the distribution.
#' @param SUM PARAM_DESCRIPTION, Default: 1e+06
#' @export
TPM_normalize <- function(mat, SUM = 1e6) {
  cs <- colSums(mat, na.rm = TRUE)
  norm_mat <- (t(t(mat) / cs)) * SUM
  return(norm_mat)
}



# _________________________________________________________________________________________________
#' @title median_normalize
#' @description Normalize each column to the median of all the column-sums.
#' @param mat Numeric input matrix with the distribution.
#' @export
median_normalize <- function(mat) {
  cs <- colSums(mat, na.rm = TRUE)
  norm_mat <- (t(t(mat) / cs)) * median(cs)
  iprint("colMedians: ", head(signif(colMedians(norm_mat), digits = 3)))
  return(norm_mat)
}



# _________________________________________________________________________________________________
#' @title mean_normalize
#' @description Normalize each column to the median of the columns.
#' @param mat Numeric input matrix.
#' @export
mean_normalize <- function(mat) {
  cs <- colSums(mat, na.rm = TRUE)
  norm_mat <- (t(t(mat) / cs)) * mean(cs)
  iprint("colMeans: ", head(signif(colMeans(norm_mat))))
  return(norm_mat)
}



# _________________________________________________________________________________________________
### Matrix stats basic ____________________________________________________________ ----


# _________________________________________________________________________________________________
#' @title rowMin
#' @description Calculates the minimum of each row of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowMin <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, min, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colMin
#' @description Calculates the minimum of each column of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colMin <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, min, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title rowMax
#' @description Calculates the maximum of each row of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowMax <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, max, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colMax
#' @description Calculates the maximum of each column of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colMax <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, max, na.rm = na.rm)
}



# _________________________________________________________________________________________________
### Matrix stats ____________________________________________________________ ----


#' @title rowMedians
#' @description Calculates the median of each row of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowMedians <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, median, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colMedians
#' @description Calculates the median of each column of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colMedians <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, median, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title rowGeoMeans
#' @description Calculates the median of each row of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowGeoMeans <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, geomean, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colGeoMeans
#' @description Calculates the median of each column of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colGeoMeans <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, geomean, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title rowCV
#' @description Calculates the CV of each ROW of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowCV <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, cv, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colCV
#' @description Calculates the CV of each column of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colCV <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, cv, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title rowVariance
#' @description Calculates the CV of each ROW of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowVariance <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, var, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colVariance
#' @description Calculates the CV of each column of a numeric matrix / data frame.
#' @param x Input matrix, or all-numeric dataframe.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colVariance <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, var, na.rm = na.rm)
}




# _________________________________________________________________________________________________
#' @title rowSEM
#' @description Calculates the SEM of each row of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowSEM <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, sem, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colSEM
#' @description Calculates the SEM of each column of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colSEM <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, sem, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title rowSD
#' @description Calculates the SEM of each row of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowSD <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, sd, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colSD
#' @description Calculates the SD of each column of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colSD <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, sd, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title rowIQR
#' @description Calculates the IQR of each row of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
rowIQR <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 1, IQR, na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colIQR
#' @description Calculates the IQR of each column of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @export
colIQR <- function(x, na.rm = TRUE) {
  apply(data.matrix(x), 2, IQR, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title rowQuantile
#' @description Calculates the quantile of each row of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @param ... Pass any other argument.
#' @export
rowQuantile <- function(x, na.rm = TRUE, ...) {
  apply(data.matrix(x), 1, quantile, ..., na.rm = na.rm)
}


# _________________________________________________________________________________________________
#' @title colQuantile
#' @description Calculates the quantile of each column of a numeric matrix / data frame.
#' @param x Numeric input matrix with the distribution.
#' @param na.rm Remove NA values for calculation? Default: TRUE
#' @param ... Pass any other argument.
#' @export
colQuantile <- function(x, na.rm = TRUE, ...) {
  apply(data.matrix(x), 2, quantile, ..., na.rm = na.rm)
}

# _________________________________________________________________________________________________
#' @title Bind two named vectors by matching names
#' @description Combines two named vectors into a data frame by matching their names.
#' Missing values are filled with NA.
#' @param vec1 First named vector.
#' @param vec2 Second named vector.
#' @return A data frame with columns for both vectors, matched by name.
#' @export

cbind_vectors_by_names <- function(vec1, vec2) {
  all_names <- union(names(vec1), names(vec2))
  df <- data.frame(vec1 = vec1[all_names], vec2 = vec2[all_names])
  colnames(df) <- c(substitute(vec1), substitute(vec2))
  df
}


# _________________________________________________________________________________________________
## Matrix manipulations ____________________________________________________________ ----



# _________________________________________________________________________________________________
#' @title sortEachColumn
#' @description Sort each column of a numeric matrix / data frame.
#' @param data Numeric input matrix.
#' @param ... Pass any other argument.
#' @export
sortEachColumn <- function(data, ...) sapply(data, sort, ...) # Sort each column of a numeric matrix / data frame.



# _________________________________________________________________________________________________
#' @title Sort matrix or data frame by a column or row names
#'
#' @description
#' Sorts a numeric matrix or data frame by a specified column or by row names. The function can only
#' handle sorting by a single column. It offers options to sort in increasing or decreasing order,
#' and to control the placement of `NA` values.
#'
#' @param df A numeric matrix or data frame to be sorted. Default: none.
#' @param column A column name or index by which to sort. The function can handle only single-column
#'   sorting. Default: none.
#' @param rownames Logical. If `TRUE`, row names will be used for sorting instead of a column.
#'   Default: `FALSE`.
#' @param decrease Logical. If `TRUE`, the data will be sorted in decreasing order. Default: `FALSE`.
#' @param na_last Logical. If `TRUE`, `NA` values will be placed at the end. Default: `TRUE`.
#'
#' @examples
#' df <- data.frame(AA = c(1, 2, 3), BB = c(3, 2, 1), row.names = letters[7:5])
#' sort_matrix_rows(df, "AA")
#' sort_matrix_rows(df, "AA", decrease = TRUE)
#' sort_matrix_rows(df, "A", rownames = TRUE)
#'
#' @return A sorted version of the input matrix or data frame.
#'
#' @export
sort_matrix_rows <- function(df, column = NULL, rownames = FALSE, decrease = FALSE, na_last = TRUE) {
  stopifnot(
    is.data.frame(df) | is.matrix(df),
    is.character(column) | is.numeric(column) | if (rownames) is.null(column),
    "cannot handle multi column sort" = length(column) == 1 | if (rownames) is.null(column),
    is.logical(rownames), is.logical(decrease), is.logical(na_last),
    (if (isFALSE(rownames) & is.character(column)) column %in% colnames(df) else TRUE)
  )

  message("Sorting by ", if (rownames) "rownames" else paste(column, "column"), " in ", if (decrease) "Decreasing" else "Increasing", " order.")

  ordering_vakues <- if (rownames) rownames(df) else df[, column]
  sorted_order <- order(rownames(df), decreasing = decrease, na.last = na_last)

  df[sorted_order, ]
}





# _________________________________________________________________________________________________
#' @title rownames.trimws
#' @description Trim whitespaces from the rownames.
#' @param matrix1 Input matrix.
#' @export
rownames.trimws <- function(matrix1) {
  rownames(matrix1) <- trimws(rownames(matrix1))
  return(matrix1)
}



# _________________________________________________________________________________________________
#' @title colsplit
#' @description Split a data frame by a factor corresponding to columns.
#' @param df Input data frame.
#' @param f Splitting factor, Default: colnames(df)
#' @export
colsplit <- function(df, f = colnames(df)) {
  ListOfDFs <- NULL
  levelz <- unique(f)
  for (i in 1:length(levelz)) {
    ListOfDFs[[i]] <- df[, which(f == levelz[i])]
  }
  names(ListOfDFs) <- levelz
  return(ListOfDFs)
}



# _________________________________________________________________________________________________
#' @title rowsplit
#' @description Split a data frame by a factor corresponding to columns.
#' @param df Input data frame.
#' @param f Splitting factor, Default: rownames(df)
#' @export
rowsplit <- function(df, f = rownames(df)) {
  ListOfDFs <- NULL
  levelz <- unique(f)
  for (i in 1:length(levelz)) {
    ListOfDFs[[i]] <- df[which(f == levelz[i]), ]
  }
  names(ListOfDFs) <- levelz
  return(ListOfDFs)
}

# _________________________________________________________________________________________________
#' @title Get the Column Name corresponding to the Maximum Value in each Row (handles ambiguous matches)
#'
#' @description
#' This function takes a numeric matrix as input and returns a named vector where each element
#' corresponds to a row of the matrix. The names of the vector are the row names of the matrix,
#' and the values are the column names where the maximum value of each row is found. If there are
#' multiple columns with the maximum value in a row, the value for that row will be set to
#' `multi_max_str`. If `na.remove` is set to `TRUE`, NA values will be removed before finding the
#' maximum value.
#'
#' @param mat A numeric matrix
#' @param na.remove Logical. Should NA values be removed before finding the maximum value?
#' Default: TRUE
#' @param collapse Character. The character to use to collapse multiple column names into a single
#' string. Default: "-"
#' @param verbose Logical. Should messages be printed to the console? Default: TRUE
#' @param multi_max_str Character. The string to use when multiple columns have the maximum value.
#' Default: "multiple.maxima"
#' @param suffix Character. The suffix to add to the `multi_max_str` string. Default: "rows have
#'
#'
#' @examples
#' mat <- matrix(data = c(1, 2, 3, 9, 5, 6), nrow = 2, ncol = 3, byrow = TRUE)
#' colnames(mat) <- c("UVI1", "UVI2", "UVI3")
#' rownames(mat) <- c("Cell1", "Cell2")
#' mat
#' get_max_colname_per_row(mat)
#' mat[5] <- NA
#' mat[2] <- NaN
#' mat[1] <- 2
#' mat
#' get_max_colname_per_row(mat)
#'
#' @export

get_max_colname_per_row <- function(
    mat, na.remove = TRUE, collapse = "-", verbose = TRUE,
    multi_max_str = "multiple.maxima",
    suffix = "rows have multiple maxima.") {
  # Remove NA values if specified
  if (na.remove) mat[is.na(mat)] <- -Inf

  # Function to find the maximum indices (1 or more ) of values in a vector
  which.max.multi <- function(x) which(x == max(x, na.rm = TRUE))

  # Apply function to find the maximum indices to each row and return appropriate result
  max_colname_per_row <- apply(mat, 1, function(row) {
    # One or more maximum values
    max_indices <- which.max.multi(row)

    # If there are multiple maximum values, return the "multi_max_str"
    if (length(max_indices) > 1) {
      return(multi_max_str)
    }

    return(colnames(mat)[max_indices])
  })

  # Name the result with row names (cell names)
  names(max_colname_per_row) <- rownames(mat)

  # stats
  if (verbose) {
    message(paste(sum(max_colname_per_row == multi_max_str), "of", length(max_colname_per_row), suffix))
  }

  return(max_colname_per_row)
}




# _________________________________________________________________________________________________
#' @title select_rows_and_columns
#' @description Subset rows and columns. It checks if the selected dimension names exist and reports if any of those they aren't found.
#' @param df Input data frame.
#' @param RowIDs RowIDs, Default: NULL
#' @param ColIDs ColIDs, Default: NULL
#' @export
select_rows_and_columns <- function(df, RowIDs = NULL, ColIDs = NULL) {
  if (length(RowIDs)) {
    true_rownames <- intersect(rownames(df), RowIDs)
    NotFound <- setdiff(RowIDs, rownames(df))
    if (length(NotFound)) {
      iprint(length(NotFound), "Row IDs Not Found:", head(NotFound), "...     Rows found:", length(true_rownames))
    } else {
      iprint("All row IDs found")
    } # if
    df <- df[true_rownames, ]
  } # if
  if (length(ColIDs)) {
    true_colnames <- intersect(colnames(df), ColIDs)
    NotFound <- setdiff(ColIDs, colnames(df))
    if (length(NotFound)) {
      iprint(length(NotFound), "Column IDs Not Found:", head(NotFound), "...     Rows found:", length(true_colnames))
    } else {
      iprint("All column IDs found")
    }
    df <- df[, true_colnames]
  } # if
  iprint(dim(df))
  return(df)
}



# _________________________________________________________________________________________________
#' @title getRows
#'
#' @description Returns a subset of rows based on their names and optionally removes rows with only NA or zero values. Reports the number of missing rows.
#' @param mat Input matrix.
#' @param rownamez Vector of row names to search for in the matrix.
#' @param silent Logical indicating whether to suppress printing of missing rows. Default: FALSE
#' @param removeNAonly Logical indicating whether to remove rows with only NA values. Default: FALSE
#' @param remove0only Logical indicating whether to remove rows with only zero values. Default: FALSE
#' @return A matrix that is a subset of the input matrix.
#' @export
getRows <- function(mat, rownamez, silent = FALSE, removeNAonly = FALSE, remove0only = FALSE) {
  idx <- intersect(rownamez, row.names(mat))
  if (removeNAonly) {
    idx <- which_names(rowSums(!is.na(mat[idx, ]), na.rm = TRUE) > 0)
  }
  if (remove0only) {
    idx <- which_names(rowSums(mx != 0, na.rm = TRUE) > 0)
  }
  if (!silent) {
    iprint(length(idx), "/", length(rownamez), "are found. Missing: ", length(setdiff(row.names(mat), rownamez)))
  }
  mat[idx, ]
}



# _________________________________________________________________________________________________
#' @title getCols
#'
#' @description Returns a subset of columns based on their names and optionally removes columns with only NA or zero values. Reports the number of missing columns.
#' @param mat Input matrix.
#' @param colnamez Vector of column names to search for in the matrix.
#' @param silent Logical indicating whether to suppress printing of missing columns. Default: FALSE
#' @param removeNAonly Logical indicating whether to remove columns with only NA values. Default: FALSE
#' @param remove0only Logical indicating whether to remove columns with only zero values. Default: FALSE
#' @return A matrix that is a subset of the input matrix.
#' @export
getCols <- function(mat, colnamez, silent = FALSE, removeNAonly = FALSE, remove0only = FALSE) {
  idx <- intersect(colnamez, colnames(mat))
  print(symdiff(colnamez, colnames(mat)))
  if (removeNAonly) {
    idx <- which_names(colSums(!is.na(mat[, idx]), na.rm = TRUE) > 0)
  }
  if (remove0only) {
    idx <- which_names(colSums(mx != 0, na.rm = TRUE) > 0)
  }
  if (!silent) {
    iprint(length(idx), "/", length(colnamez), "are found. Missing: ", length(setdiff(colnames(mat), colnamez)))
  }
  mat[, idx]
}



# _________________________________________________________________________________________________
#' @title get.oddoreven
#'
#' @description Returns either odd or even indexed rows or columns from a data frame.
#' @param df_ A data frame. Default: NULL
#' @param rows Logical indicating whether to return rows (if TRUE) or columns (if FALSE). Default: FALSE
#' @param odd Logical indicating whether to return odd indexed rows/columns (if TRUE) or even indexed rows/columns (if FALSE). Default: TRUE
#' @return A subset of the input data frame.
#' @export
get.oddoreven <- function(df_ = NULL, rows = FALSE, odd = TRUE) {
  counter <- if (rows) NROW(df_) else NCOL(df_)
  IDX <- if (odd) seq(1, to = counter, by = 2) else seq(2, to = counter, by = 2)
  df_out <- if (rows) df_[IDX, ] else df_[, IDX]
  return(df_out)
}



# _________________________________________________________________________________________________
#' @title merge_dfs_by_rn
#' @description Merge any data frames by rownames. Required plyr package.
#' @param list_of_dfs PARAM_DESCRIPTION
#' @seealso
#'  \code{\link[plyr]{join_all}}
#' @export
#' @importFrom plyr join_all
merge_dfs_by_rn <- function(list_of_dfs) {
  if (length(names(list_of_dfs)) != length(list_of_dfs)) {
    names(list_of_dfs) <- 1:length(list_of_dfs)
  }

  for (i in names(list_of_dfs)) {
    colnames(list_of_dfs[[i]]) <- paste0(i, ".", colnames(list_of_dfs[[i]]))
  } # make unique column names
  for (i in names(list_of_dfs)) {
    list_of_dfs[[i]]$rn <- rownames(list_of_dfs[[i]])
  } # for
  COMBINED <- plyr::join_all(list_of_dfs, by = "rn", type = "full")
  idim(COMBINED)
  rownames(COMBINED) <- COMBINED$rn
  COMBINED$rn <- NULL
  return(COMBINED)
}

# _________________________________________________________________________________________________
#' @title merge_1col_dfs_by_rn
#' @description A function that merges single-column data frames based on rownames.
#' @param list_of_dfs  list of 1col dfs
#' @param FILLwith 0 by def
#' @param columnUSE column index in both. 1 by default.
#' @export
#' @examples merge_1col_dfs_by_rn()
merge_1col_dfs_by_rn <- function(list_of_dfs, FILLwith = 0, columnUSE = 1) {
  all.rn <- sort(union.ls(lapply(list_of_dfs, rownames)))
  iprint("n rownames:", length(all.rn))
  df_new <- data.frame(matrix(data = FILLwith, nrow = length(all.rn), ncol = length(list_of_dfs)), row.names = all.rn)
  colnames(df_new) <- names(list_of_dfs)
  for (i in 1:length(list_of_dfs)) {
    print(i)
    indf <- list_of_dfs[[i]]
    df_new[rownames(indf), i] <- indf[, columnUSE]
  }
  df_new
}



# _________________________________________________________________________________________________
#' @title merge_numeric_df_by_rn
#' @description Merge 2 numeric data frames by rownames.
#' @param x Input matrix, or all-numeric dataframe.
#' @param y Input matrix, or all-numeric dataframe.
#' @export
merge_numeric_df_by_rn <- function(x, y) {
  rn1 <- rownames(x)
  rn2 <- rownames(y)
  diffz <- symdiff(rn1, rn2)
  merged <- merge(x, y, by = "row.names", all = TRUE) # merge by row names(by = 0 or by = "row.names")
  rownames(merged) <- merged$Row.names
  merged <- merged[, -1] # remove row names
  merged[is.na(merged)] <- 0

  print("Uniq Rows (top 10 by sum)")
  x1 <- rowSums(x[diffz[[1]], ])
  x2 <- rowSums(y[diffz[[2]], ])
  print("")
  iprint("Values specific to 1: ", round(sum(x1)), "or", percentage_formatter(sum(x1) / sum(merged)))
  print(tail(sort(x1), n = 10))
  print("")
  iprint("Values specific to 2: ", round(sum(x2)), "or", percentage_formatter(sum(x2) / sum(merged)))
  print(tail(sort(x2), n = 10))
  iprint("Dimensions of merged DF:", dim(merged))

  return(merged)
}


# _________________________________________________________________________________________________
#' @title merge_2_named_vec_as_df
#' @description Merge two named vectors by names, into a dataframe with 2 columns.
#' @param x A vector with named elements.
#' @param y Another vector with named elements.
#' @examples # a <- 1:5; names(a) <- letters[a]; b <- 9:3; names(b) <- letters[b]; merge_2_named_vec_as_df(a,b)
#' @export

merge_2_named_vec_as_df <- function(x, y) {
  COMBINED <-
    full_join(x = stack(x), y = stack(y), by = "ind")[, c(2, 1, 3)] %>%
    FirstCol2RowNames.as.df()

  colnames(COMBINED) <- c(substitute(x), substitute(y))
  return(COMBINED)
}


# _________________________________________________________________________________________________
#' @title merge_ls_of_named_vec_as_df_cols
#' @description Merge any number of named vectors (presented as a list) by names, into a dataframe
#' @param named_list A named list of named vectors.
#' @param missing_values How to fill missing values
#' @examples # merge_ls_of_named_vec_as_df_cols()
#' @export

merge_ls_of_named_vec_as_df_cols <- function(
    named_list = list(
      vec1 = c(A = 1, B = 2, C = 3),
      vec2 = c(B = 4, D = 5),
      vec3 = c(A = 6, C = 7, D = 8),
      vec4 = c(B = 9, C = 10, D = 11, E = 12)
    ),
    missing_values = NaN) {
  stopifnot(length(names(named_list)) == length(named_list)) # stop if names are missing
  stopifnot(all(unlapply(lapply(named_list, names), length) > 0)) # stop if there are empty vectors

  # Merge any data frames by rownames. Required plyr package
  ls.indexed.dfs <- lapply(named_list, stack)
  suppressWarnings(COMBINED <- Reduce(function(x, y) merge(x, y, by = "ind", all = TRUE, ), ls.indexed.dfs))

  colnames(COMBINED)[-1] <- names(named_list)
  COMBINED[is.na(COMBINED)] <- missing_values

  return(FirstCol2RowNames(COMBINED))
}


# _________________________________________________________________________________________________
#' @title Extract and Display Column Types of a Data Frame or Tibble
#'
#' @description This function returns the primary class/type of each column in a data frame or tibble.
#' Additionally, it can print a summary of the column types.
#'
#' @param df A data frame or tibble whose column types are to be extracted.
#' @param print_it Logical; if `TRUE` (default), prints a table of column types and a summary.
#' @return A named character vector where names are column names and the values are their respective primary types.
#' @examples
#' df <- data.frame(a = 1:3, b = c("A", "B", "C"), c = factor(c("X", "Y", "X")))
#' get_col_types(df)
#'
#' @importFrom purrr map_chr
#' @export

get_col_types <- function(df, print_it = TRUE) {
  x <- purrr::map_chr(df, ~ class(.x)[1])
  if (print_it) {
    typetable <- t(t(x))
    colnames(typetable) <- "Type"
    print(typetable)
  }
  print("Summary")
  print(table(x))
  return(x)
}





# _________________________________________________________________________________________________
#' @title Convert List Columns of a Tibble to String Vectors
#'
#' @description Converts columns of type `list` in a tibble or data frame to string vectors.
#' It combines list elements into a single string per cell, using a specified separator.
#' @param df A tibble or data frame where list columns are to be converted.
#'           Default: None, must be supplied by the user.
#' @param verbose Logical; whether to print progress messages. Default: TRUE.
#' @param print_full Logical; whether to print full details. Default: FALSE.
#' @param collapse_by The character used to collapse list elements. Default: ",".
#' @return A tibble or data frame with list columns converted to string vectors.
#'
#' @examples
#' df <- tibble::tibble(a = list(1:2, 3:4, 5:6), b = c("A", "B", "C"))
#' fix_tibble_lists(df)
#'
#' @importFrom purrr map_chr
#' @importFrom tibble as_tibble
#'
#' @export
fix_tibble_lists <- function(df, verbose = TRUE, print_full = FALSE, collapse_by = ",") {
  stopifnot(
    is.data.frame(df), is.logical(verbose),
    is.logical(print_full), is.character(collapse_by)
  )

  if (verbose) {
    cat("Before conversion:\n")
    coltypes <- get_col_types(df, print_it = print_full)
  }

  list_cols <- which(coltypes %in% "list") # Identify list columns

  # Convert list columns to string vectors
  df[, list_cols] <- purrr::map(
    df[, list_cols],
    ~ sapply(.x, paste, collapse = collapse_by)
  )

  if (verbose) {
    cat("\nAfter conversion:\n")
    get_col_types(df, print_it = print_full)
  }

  # Output assertion
  stopifnot(is.data.frame(df))

  return(df)
}

# _________________________________________________________________________________________________
#' @title Rotate a Matrix by 90 Degrees
#'
#' @description Rotates a given numeric matrix 90 degrees in a specified direction. The rotation
#' can be either clockwise or counterclockwise, determined by the `clockwise` parameter.
#'
#' @param x A numeric matrix that is to be rotated.
#' @param clockwise Logical; if TRUE (default), rotates the matrix 90 degrees clockwise.
#'
#' @return A numeric matrix rotated 90 degrees in the specified direction.
#'
#' @examples
#' # Define a 3x3 matrix
#' matrix_original <- matrix(1:9, nrow = 3)
#'
#' # Rotate the matrix clockwise
#' rotated_clockwise <- rotate(matrix_original, TRUE)
#'
#' # Rotate the matrix counterclockwise
#' rotated_counterclockwise <- rotate(matrix_original, FALSE)
#'
#' @export
rotate_matrix <- function(x, clockwise = TRUE) {
  if (clockwise) {
    t(apply(x, 2, rev)) # first reverse, then transpose, it's the same as rotate 90 degrees
  } else {
    apply(t(x), 2, rev) # first transpose, then reverse, it's the same as rotate -90 degrees
  }
}


# _________________________________________________________________________________________________
## Matrix filtering ____________________________________________________________ ----


#' @title Omit Rows with NA Values from a Matrix
#'
#' @description Removes rows from a matrix based on the presence of NA values. Can remove rows with any NA values or only those completely filled with NAs.
#'
#' @param mat Input matrix from which rows with NAs are to be omitted.
#' @param any Logical; if TRUE (default), removes rows containing any NA values.
#' If FALSE, removes only rows completely filled with NA values.
#'
#' @return A matrix with rows containing NA values omitted according to the specified criteria.
#'
#' @examples
#' mat <- matrix(c(1, NA, 3, 4, 5, NA, NA, NA, 9), ncol = 3)
#' na.omit.mat(mat) # Default, any = TRUE
#' na.omit.mat(mat, any = FALSE)
#'
#' @export
na.omit.mat <- function(mat, any = TRUE) {
  mat <- as.matrix(mat)
  stopifnot(length(dim(mat)) == 2)
  if (any) {
    outMat <- mat[!is.na(rowSums(mat)), ]
  } else {
    outMat <- mat[(rowSums(is.na(mat)) < ncol(mat)), ]
  }
  outMat
}

# _________________________________________________________________________________________________
#' @title remove.na.rows
#' @description Cols have to be a vector of numbers corresponding to columns.
#' @param mat In put matrix.
#' @param cols PARAM_DESCRIPTION, Default: 1:NCOL(mat)
#' @export
remove.na.rows <- function(mat, cols = 1:NCOL(mat)) {
  mat2 <- mat[, cols]
  idxOK <- which(rowSums(!apply(mat2, 2, is.na)) == NCOL(mat))
  mat[idxOK, ]
}



# _________________________________________________________________________________________________
#' @title remove.na.cols
#' @description Cols have to be a vector of numbers corresponding to columns.
#' @param mat In put matrix.
#' @export
remove.na.cols <- function(mat) {
  idxOK <- !is.na(colSums(mat))
  return(mat[, idxOK])
}




# _________________________________________________________________________________________________
#' @title Remove empty rows and columns from a data frame.
#'
#' @description This function takes a data frame and a threshold value, and removes all rows and columns that contain only zeros or the threshold value.
#'
#' @param df A data frame.
#' @param suffix A suffix to add to the plot titles.
#' @param rows The name of the variable that will store the fraction of rows that were removed.
#' @param cols The name of the variable that will store the fraction of columns that were removed.
#' @param thr.cell.empty The threshold value below a cell is considered "empty".
#' @param plot_stats Whether to plot the fraction of rows and columns that were removed.
#' @param ... Additional arguments to pass to `qbarplot`.
#'
#' @return A data frame with the empty rows and columns removed.
#' @export

df.remove.empty.rows.and.columns <- function(
    df = UVI.assignment.filtered.3.HF,
    suffix =  substitute_deparse(df),
    rows = "rows",
    cols = "cols",
    thr.cell.empty = 0,
    plot_stats = TRUE,
    ...) {
  # Create a boolean vector that indicates whether each cell is non-empty
  df.boolean <- (df != thr.cell.empty)
  # view.head(df.boolean)

  # Calculate the number of non-empty rows and columns
  rsx <- rowSums(df.boolean)
  csx <- colSums(df.boolean)

  # Calculate the fraction of rows and columns that were removed
  s1 <- pc_TRUE(csx == 0, suffix = paste0(cols, " are empty/removed."), NumberAndPC = TRUE)
  s2 <- pc_TRUE(rsx == 0, suffix = paste0(rows, " are empty/removed."), NumberAndPC = TRUE)
  print(s1)
  print(s2)

  # Plot the fraction of rows and columns that were removed, if requested
  if (plot_stats) {
    Removal.Dimensions <- c(
      "rows" = pc_TRUE(rsx == 0, percentify = FALSE),
      "cols" = pc_TRUE(csx == 0, percentify = FALSE)
    )
    names(Removal.Dimensions) <- c(rows, cols)
    qbarplot(Removal.Dimensions,
             label = percentage_formatter(Removal.Dimensions),
             suffix = suffix,
             xlab.angle = 45, xlab = "",
             ylim = 0:1, ylab = "Fractions removed",
             ...
    )
  }

  # Remove the empty rows and columns
  df.filt <- df[rsx > 0, csx > 0, drop = FALSE]
  idim(df.filt)
  return(df.filt)
}



# _________________________________________________________________________________________________
## Create Special Matrices ____________________________________________________________ ----

# _________________________________________________________________________________________________
#' @title rowNameMatrix
#' @description Create a copy of your matrix, where every entry is replaced by the corresponding
#' row name. Useful if you want to color by row name in a plot (where you have different number of
#'  NA-values in each row).
#' @param mat_w_dimnames A named matrix to copy from.
#' @export
rowNameMatrix <- function(mat_w_dimnames) {
  matrix(rep(rownames(mat_w_dimnames), ncol(mat_w_dimnames)), nrow = nrow(mat_w_dimnames), ncol = ncol(mat_w_dimnames))
}



# _________________________________________________________________________________________________
#' @title colNameMatrix
#' @description Create a copy of your matrix, where every entry is replaced by the corresponding
#' column name. Useful if you want to color by column name in a plot (where you have different
#' number of NA-values in each column).
#' @param mat_w_dimnames A named matrix to copy from.
#' @export
colNameMatrix <- function(mat_w_dimnames) {
  x <- rep(colnames(mat_w_dimnames), nrow(mat_w_dimnames))
  t(matrix(x, nrow = ncol(mat_w_dimnames), ncol = nrow(mat_w_dimnames)))
}




# _________________________________________________________________________________________________
# Multi-dimensional lists ____________________________________________________________ ----


#' @title copy.dimension.and.dimnames
#'
#' @description Copy the dimension and dimnames of a 1D vector to a 2D array.
#' @param list.1D A 1D vector.
#' @param obj.2D A 2D array.
#' @return A 2D array with the same dimension and dimnames as `obj.2D`.
#' @export
copy.dimension.and.dimnames <- function(list.1D, obj.2D) {
  dim(list.1D) <- dim(obj.2D)
  dimnames(list.1D) <- dimnames(obj.2D)
  list.1D
}


# _________________________________________________________________________________________________
#' @title mdlapply
#'
#' @description A wrapper for `lapply()` that works on multidimensional arrays.
#' @param list_2D A multidimensional array.
#' @param ... Function and arguments to pass to `lapply()`.
#' @return A multidimensional array with the same dimensions as `list_2D`.
#' @export

mdlapply <- function(list_2D, ...) {
  x <- lapply(list_2D, ...)
  copy.dimension.and.dimnames(x, list_2D)
}


# _________________________________________________________________________________________________
#' @title arr.of.lists.2.df
#' @description Simplify 2D-list-array to a DF.
#' @param two.dim.arr.of.lists PARAM_DESCRIPTION
#' @export
arr.of.lists.2.df <- function(two.dim.arr.of.lists) {
  list.1D <- unlist(two.dim.arr.of.lists)
  dim(list.1D) <- dim(two.dim.arr.of.lists)
  dimnames(list.1D) <- dimnames(two.dim.arr.of.lists)
  list.1D
}



# _________________________________________________________________________________________________
#' @title mdlapply2df
#' @description Multi dimensional lapply + arr.of.lists.2.df (simplify 2D-list-array to a DF).
#' @param list_2D PARAM_DESCRIPTION
#' @param ... Pass any other argument.
#' @export
mdlapply2df <- function(list_2D, ...) {
  x <- lapply(list_2D, ...)
  z <- copy.dimension.and.dimnames(x, list_2D)
  arr.of.lists.2.df(z)
}



# _________________________________________________________________________________________________
# List operations ____________________________________________________________ ----


#' @title any.duplicated.rownames.ls.of.df
#' @description Check if there are any duplocated rownames in a list of dataframes.
#' @param ls List of 2 or more vectors (sets) with categorical variables.
#'
#' @export any.duplicated.rownames.ls.of.df
any.duplicated.rownames.ls.of.df <- function(ls) any.duplicated(rownames(ls)) # Check if there are any duplocated rownames in a list of dataframes.



# _________________________________________________________________________________________________
#' @title intersect.ls
#' @description Intersect any number of lists.
#' @param ls List of 2 or more vectors (sets) with categorical variables.
#' @param ... Pass any other argument.
#'
#' @export
intersect.ls <- function(ls, ...) {
  Reduce(intersect, ls)
} # Intersect any number of lists.



# _________________________________________________________________________________________________
#' @title union.ls
#' @description Intersect any number of list elements. Faster than reduce.
#' @param ls List of 2 or more vectors (sets) with categorical variables.
#' @param ... Pass any other argument.
#' @export
union.ls <- function(ls, ...) {
  sort(unique(do.call(c, ls)))
} # Intersect any number of list elements. Faster than reduce.

# _________________________________________________________________________________________________
#' @title symdiff.ls
#' @description Calculate symmetric difference on a list (of 2 vectors).
#' @param ls List of 2 or more vectors (sets) with categorical variables.
#' @param ... Pass any other argument.
#' @export
symdiff.ls <- function(ls, ...) {
  res <- Reduce(symdiff, ls)
  if (length(names(ls)) == length(ls)) {
    names(res) <- names(ls)
  } else {
    message("No names in list / some names missing. Numeric names will be used.")
    names(res) <- 1:length(res)
  }

  return(res)
} # Intersect any number of list elements. Faster than reduce.

# _________________________________________________________________________________________________
#' @title setdiff.ls
#' @description Calculate set difference on a list (of 2 vectors).
#' @param ls List of 2 or more vectors (sets) with categorical variables.
#' @param ... Pass any other argument.
#' @export
setdiff.ls <- function(ls, ...) {
  print("Difference to first element:")
  Reduce(setdiff, ls)
} # Intersect any number of list elements. Faster than reduce.


# _________________________________________________________________________________________________
#' @title Like sapply, but with names preserved. Help in some other cases too.
#'
#' @description Do an `lapply()`, then `unlist()`, with preserving the list element names.
#' @param list A list to apply the function to.
#' @param FUN PARAM_DESCRIPTION
#' @param ... Pass any other argument.
#'
#' @export
unlapply <- function(list, FUN, ...) {
  x <- unlist(lapply(X = list, FUN = FUN, ...))
  # names(x) <- names(list) # not needed
  return(x)
}



# _________________________________________________________________________________________________
#' @title list.wNames
#' @description Create a list with names from ALL variables you pass on to the function.
#' @param ... Pass any other argument.
#' @export
list.wNames <- function(...) {
  lst <- list(...)
  names(lst) <- as.character(match.call()[-1])
  return(lst)
}



# _________________________________________________________________________________________________
#' @title as.list.df.by.row
#'
#' @description Split a dataframe into a list by its columns.
#' @param dtf A dataframe.
#' @param na.omit Whether to omit rows with missing values.
#' @param zero.omit Whether to omit rows with all-zero values.
#' @param omit.empty Whether to omit rows with zero length.
#' @param verbose Whether to print messages.
#' @return A list.
#' @examples
#' dtf <- data.frame(x = c(1, 2, NA), y = c(3, 4, 0), z = c(5, 6, 7))
#' as.list.df.by.row(dtf)
#'
#' @export as.list.df.by.row
as.list.df.by.row <- function(dtf, na.omit = TRUE, zero.omit = FALSE, omit.empty = FALSE,
                              verbose = TRUE) {
  outList <- as.list(as.data.frame(t(dtf)))

  # Omit rows with missing values, all-zero values, or zero length
  if (na.omit) {
    outList <- lapply(outList, na.omit.strip)
  }
  if (zero.omit) {
    outList <- lapply(outList, zero.omit)
  }
  if (omit.empty) {
    outList <- outList[(lapply(outList, length)) > 0]
  }
  if (verbose) print(str(outList, vec.len = 2))
  return(outList)
}



# _________________________________________________________________________________________________
#' @title as.list.df.by.col
#'
#' @description Split a dataframe into a list by its rows.
#' @param dtf A dataframe.
#' @param na.omit Whether to omit rows with missing values.
#' @param zero.omit Whether to omit rows with all-zero values.
#' @param omit.empty Whether to omit rows with zero length.
#' @return A list.
#' @examples
#' dtf <- data.frame(x = c(1, 2, NA), y = c(3, 4, 0), z = c(5, 6, 7))
#' as.list.df.by.col(dtf)
#'
#' @export as.list.df.by.col
as.list.df.by.col <- function(dtf, na.omit = TRUE, zero.omit = FALSE, omit.empty = FALSE) {
  outList <- as.list(dtf)
  if (na.omit) {
    outList <- lapply(outList, na.omit.strip)
  }
  if (zero.omit) {
    outList <- lapply(outList, zero.omit)
  }
  if (omit.empty) {
    outList <- outList[(lapply(outList, length)) > 0]
  }
  print(str(outList, vec.len = 2))
  return(outList)
}



# _________________________________________________________________________________________________
#' @title reorder.list
#'
#' @description Reorder elements of lists in your custom order of names / indices.
#' @param L A list.
#' @param namesOrdered A vector of names or indices in the desired order.
#' @return A list.
#' @examples
#' L <- list(a = 1, b = 2, c = 3)
#' namesOrdered <- c("c", "a", "b")
#' reorder.list(L, namesOrdered)
#' @export reorder.list
#' @seealso
#'  \code{\link[gtools]{mixedsort}}
#' @importFrom gtools mixedsort
reorder.list <- function(L, namesOrdered = gtools::mixedsort(names(L))) {
  Lout <- list(NA)
  for (x in 1:length(namesOrdered)) {
    Lout[[x]] <- L[[namesOrdered[x]]]
  }
  if (length(names(L))) {
    names(Lout) <- namesOrdered
  }
  return(Lout)
}



# _________________________________________________________________________________________________
#' @title range.list
#'
#' @description Calculates the range of values in the entire a list.
#' @param L A list.
#' @return A vector of length 2, containing the minimum and maximum values in `L`.
#'
#' @export range.list
range.list <- function(L) {
  return(range(unlist(L), na.rm = TRUE))
}



# _________________________________________________________________________________________________
#' @title intermingle2lists
#'
#' @description Combine 2 lists (of the same length) so that form every odd and every even element of a unified list. Useful for side-by-side comparisons, e.g. in wstripchart_list().
#' @param L1 A list.
#' @param L2 A list.
#' @return A list.
#' @export
intermingle2lists <- function(L1, L2) {
  stopifnot(length(L1) == length(L2))
  Lout <- list(NA)

  # Create a new list with the combined elements of `L1` and `L2`
  for (x in 1:(2 * length(L1))) {
    if (x %% 2) {
      Lout[[x]] <- L1[[((x + 1) / 2)]]
      names(Lout)[x] <- names(L1)[((x + 1) / 2)]
    } else {
      Lout[[x]] <- L2[[x / 2]]
      names(Lout)[x] <- names(L2)[x / 2]
    }
  } # for
  return(Lout)
}



# _________________________________________________________________________________________________
#' @title as.listalike
#'
#' @description Converts a vector to a list with the same dimensions as a given list.
#' @param vec A vector.
#' @param list_wannabe A list with the desired dimensions.
#' @return A list with the same dimensions as `list_wannabe`, and the same elements as `vec` in the same order.
#'
#' @examples
#' vec <- 1:10
#' list_wannabe <- list(a = 1:3, b = 4:6, c = 7:10)
#' as.listalike(vec, list_wannabe)
#'
#' @export
as.listalike <- function(vec, list_wannabe) {
  stopifnot(length(vec) == length(unlist(list_wannabe)))
  list_return <- list_wannabe
  past <- 0

  # Iterate over the list, and fill in the elements with the corresponding elements from the vectorfor (v in 1:length(list_wannabe)) {
  for (v in 1:length(list_wannabe)) {
    lv <- length(list_wannabe[[v]])
    list_return[[v]] <- vec[(past + 1):(past + lv)]
    past <- past + lv
  } # for
  return(list_return)
}



# _________________________________________________________________________________________________
#' @title Reverse the Hierarchy of a List
#'
#' @description This function reverses the hierarchy of a given, 2 level, nested list. The
#' function will ensure that all lists at the same level have the same names,
#' and then transpose the structure, creating a new list for each unique name.
#' Any missing elements in the original lists are not included in the final
#' output. The result is a list where the top-level names are derived from
#' the unique names found at the lower levels of the input list.
#'
#' @param list_of_lists A list where some or all elements are themselves lists.
#' The hierarchy of this list will be reversed.
#'
#' @return A list with the elements of the original list in reversed order.
#' @source https://stackoverflow.com/a/15263737
#' @examples
#' list_of_lists <- list("z1" = list(a = 1, b = 2), "z2" = list(b = 4, a = 1, c = 0))
#' reverse.list.hierarchy(list_of_lists)
#'
#' @export
reverse.list.hierarchy <- function(list_of_lists) {
  # Find unique names in all sublists
  names_level2 <- unique(unlist(lapply(list_of_lists, function(X) names(X))))
  iprint("Level-1 names:", names(list_of_lists))
  iprint("Level-2 names:", names_level2)

  # Ensure all lists have the same names, in the same order
  list_of_lists <- lapply(list_of_lists, function(X) setNames(X[names_level2], names_level2))

  # Transpose the structure, creating a new list for each unique name
  list_of_lists <- apply(do.call(rbind, list_of_lists), 2, as.list)

  # Remove null entries
  lapply(list_of_lists, function(X) X[!sapply(X, is.null)])
}




# _________________________________________________________________________________________________
#' @title list2fullDF.byNames
#'
#' @description Converts a list to a full matrix, with rows and columns named by the elements of the list.
#' @param your.list A list.
#' @param as.df Logical. Whether to return a data frame (default) or a matrix.
#' @param byRow Logical. Whether the resulting matrix should be arranged by row (default) or by column.
#' @param FILL A value to fill in missing entries.
#' @return A matrix with the same elements as `your.list`, but with rows and columns named by the elements of the list.
#' @examples
#' your.list <- list(set.1 = LETTERS[1:5], set.2 = LETTERS[3:9])
#' list2fullDF.byNames(your.list)
#'
#' @export
list2fullDF.byNames <- function(your.list = list(
  "set.1" = vec.fromNames(LETTERS[1:5], fill = 1), # Convert a list to a full matrix. Rows = names(union.ls(your_list)) or all names of within list elements, columns = names(your_list).
  "set.2" = vec.fromNames(LETTERS[3:9], fill = 2)
),
as.df = TRUE,
byRow = TRUE,
FILL = NA) {
  # Get the lengths of the list elements
  length.list <- length(your.list)
  list.names <- names(your.list)
  list.element.names <- sort(unique(unlist(lapply(your.list, names))))

  # Create a matrix with the correct dimensions
  mat <- matrix.fromNames(rowname_vec = list.element.names, colname_vec = list.names, fill = FILL)

  # Fill in the matrix with the elements of the list
  for (i in 1:length.list) {
    element <- list.names[i]
    mat[names(your.list[[element]]), element] <- your.list[[element]]
  }
  if (!byRow) {
    mat <- t(mat)
  }
  if (as.df) mat <- as.data.frame(mat)
  return(mat)
}



# _________________________________________________________________________________________________
#' @title list2fullDF.presence
#'
#' @description Converts a list to a full matrix, with rows and columns named by the elements of the list.
#' The matrix will contain a 1 in each cell where the corresponding element of the list is present, and a 0 otherwise.
#' @param your.list A list.
#' @param byRow Logical. Whether the resulting matrix should be arranged by row (default) or by column.
#' @param FILL A value to fill in missing entries.
#' @return A matrix with the same elements as `your.list`, but with rows and columns named by the elements of the list.
#' @examples
#' your.list <- list("set.1" = LETTERS[1:5], "set.2" = LETTERS[3:9])
#' list2fullDF.presence(your.list)
#'
#' @export
list2fullDF.presence <- function(your.list = list(
  "set.1" = LETTERS[1:5] # Convert a list to a full matrix.  Designed for occurence counting, think tof table(). Rows = all ENTRIES of within your list, columns = names(your_list).
  , "set.2" = LETTERS[3:9]
), byRow = TRUE, FILL = 0) {
  length.list <- length(your.list)
  list.names <- names(your.list)
  list.elements <- sort(Reduce(f = union, your.list))

  # Create a matrix with the correct dimensions
  mat <- matrix.fromNames(rowname_vec = list.elements, colname_vec = list.names, fill = FILL)

  # Fill in the matrix with the elements of the list
  for (i in 1:length.list) {
    element <- list.names[i]
    mat[your.list[[element]], element] <- 1
  }
  if (!byRow) {
    mat <- t(mat)
  }
  return(mat)
}



# _________________________________________________________________________________________________
#' @title splitbyitsnames
#' @description Split a list by its names.
#' @param namedVec Vector with names
#' @export
splitbyitsnames <- function(namedVec) {
  stopifnot(!is.null(names(namedVec)))
  split(namedVec, f = names(namedVec))
}



# _________________________________________________________________________________________________
#' @title Split the names of list by its values.
#' @description Split the names of a list by its its values.
#' @param namedVec A vector with names.
#'
#' @return A list of vectors, each of which contains the elements of `namedVec` that have the corresponding value.
#'
#' @examples
#' namedVec <- c("A", "B", "C", "A", "B", "D")
#' splititsnames_byValues(namedVec)
#'
#' @export
splititsnames_byValues <- function(namedVec) {
  stopifnot(!is.null(names(namedVec)))
  split(names(namedVec), f = namedVec)
}



# _________________________________________________________________________________________________
#' @title intermingle2vec
#'
#' @description Combine 2 vectors (of the same length) so that form every odd and every even element of a unified vector.
#' @param V1 A vector.
#' @param V2 A vector.
#' @param wNames Logical. Whether to include the names of the vectors in the output vector.
#' @param name_prefix A character vector of length 2. If provided, the names of the vectors will
#' be prefixed with the corresponding element of `name_prefix`.
#' @return A vector that combines `V1` and `V2`, with the elements of `V1` alternating with the elements of `V2`.
#' @examples
#' V1 <- c(1, 3, 5)
#' V2 <- c(2, 4, 6)
#' intermingle2vec(V1, V2)
#'
#' @export
intermingle2vec <- function(V1, V2, wNames = TRUE, name_prefix = NULL) {
  stopifnot(
    length(V1) == length(V2),
    is.null(name_prefix) | length(name_prefix) == 2
  )

  if (!is.null(name_prefix)) {
    names(V1) <- paste0(names(V1), name_prefix[1])
    names(V2) <- paste0(names(V2), name_prefix[2])
  }

  Vout <- c(rbind(V1, V2))

  if (wNames) {
    names(Vout) <- c(rbind(names(V1), names(V2)))
  }
  return(Vout)
}



# _________________________________________________________________________________________________
#' @title intermingle.cbind
#'
#' @description Combine 2 data frames (of the same length) so that form every odd and every even
#' element of a unified list. Useful for side-by-side comparisons, e.g. in wstripchart_list().
#' @param df1 A data frame.
#' @param df2 A data frame.
#' @return A data frame that combines `df1` and `df2`, with the columns of `df1` alternating with the columns of `
#'
#' @export

intermingle.cbind <- function(df1, df2) {
  stopifnot(ncol(df1) == ncol(df2))
  if (nrow(df1) != nrow(df2)) { # not equal rows: subset
    print(symdiff(rownames(df2), rownames(df1)))
    CommonGenes <- intersect(rownames(df2), rownames(df1))
    print(length(CommonGenes))
    df1 <- df1[CommonGenes, ]
    df2 <- df2[CommonGenes, ]
  } else {
    CommonGenes <- rownames(df1)
  }

  # Create New column names
  if (length(colnames(df1)) == ncol(df1) & length(colnames(df2)) == ncol(df2)) {
    NewColNames <- intermingle2vec(paste0("df1.", colnames(df1)), paste0("df2.", colnames(df2)))
  } else {
    NewColNames <- intermingle2vec(paste0("df1.", 1:ncol(df1)), paste0("df2.", 1:ncol(df2)))
  }
  NewMatr <- matrix.fromNames(rowname_vec = CommonGenes, colname_vec = NewColNames)
  for (x in 1:(2 * length(df1))) {
    if (x %% 2) {
      NewMatr[, x] <- df1[, (x + 1) / 2]
    } else {
      NewMatr[, x] <- df2[, (x) / 2]
    }
  } # for
  print(idim(NewMatr))
  return(NewMatr)
}



# _________________________________________________________________________________________________
#' @title ls2categvec
#'
#' @description Converts a list to a vector repeating list-element names, while vector names are the list elements.
#' @param your_list A list.
#' @return A vector with the same elements as `your_list`, but with the names of the list elements repeated as many times as the number of elements in each list element.
#' @examples ls2categvec(list(a = 1, b = 2, c = 3))
#' @export
ls2categvec <- function(your_list) {
  VEC <- rep(names(your_list), unlapply(your_list, length))
  names(VEC) <- unlist(your_list, use.names = TRUE)
  return(VEC)
}



# _________________________________________________________________________________________________
#' @title list.2.replicated.name.vec
#'
#' @description Converts a list to a vector, with list elements names replicated as many times as many elements each element had.
#' @param ListWithNames A list.
#' @return A vector with the same elements as `ListWithNames`, but with the names of the list elements repeated as many times as the number of elements in each list element.
#' @examples
#' ListWithNames <- list(a = 1, b = 2, c = 3)
#' list.2.replicated.name.vec(ListWithNames)
#' @export
list.2.replicated.name.vec <- function(ListWithNames = Sections.ls.Final) {
  NZ <- names(ListWithNames)
  LZ <- unlapply(ListWithNames, length)
  replicated.name.vec <- rep(NZ, LZ)
  names(replicated.name.vec) <- unlist(ListWithNames)
  return(replicated.name.vec)
}

# _________________________________________________________________________________________________
## Set operations ____________________________________________________________ ----


#' @title Symmetric difference
#'
#' @description Quasi symmetric difference of any number of vectors.
#' @param x A vector.
#' @param y A vector.
#' @param z A vector.
#' @return A list of vectors, each of which contains the elements that are only present in that
#' vector and not in any of the other vectors.
#'
#' @examples
#' x <- c(1, 2, 3, 4, 5)
#' y <- c(2, 4, 6, 8, 10)
#' z <- c(3, 5, 7, 9, 11)
#' symdiff(x, y, z)
#'
#' @export
symdiff <- function(x, y, z = NULL) {
  big.vec <- c(unique(x), unique(y), unique(z))
  # ls <- list(x, y, z)
  ls <- Filter(function(l) !is.null(l), list(x, y, z))
  if (length(ls) > 2) {
    print("# Not Mathematically correct for more than 2 vectors,
          but logical: https://en.wikipedia.org/wiki/Symmetric_difference#Properties")
  }
  names(ls) <- paste("Only in", as.character(match.call()[-1]))
  duplicates <- big.vec[duplicated(big.vec)]
  lapply(ls, function(x) setdiff(x, duplicates))
}


#' @title Intersect with Name Preservation
#'
#' @description Intersects two character vectors while preserving names from the specified vector.
#'
#' @param x A character vector.
#' @param y A character vector.
#' @param names Character. Specifies which vector's names to preserve in the output.
#'   "x" preserves `x`'s names, "y" preserves `y`'s names. Default: "x".
#'
#' @return A character vector with names preserved from the specified vector (`x` or `y`).
#'
#' @examples
#' intersect.wNames(c(a = "gene1", b = "gene2"), c("gene2", "gene3"), names = "x")
#'
#' @export intersect.wNames
intersect.wNames <- function(x, y, names = "x") {
  # browser()
  stopifnot(
    is.vector(x), is.vector(y), names %in% c("x", "y")
  )
  warnif(
    "x argument has no names!" =  (names == "x" & !Stringendo::HasNames(x) )
    , "y argument has no names!" = (names == "y" & !Stringendo::HasNames(y) )
  )


  # Perform intersection with name preservation based on `names` argument
  result <-
    if (names == "x") {
      x[x %in% intersect(x, y)]
    } else {
      y[y %in% intersect(x, y)]
    }

  return(result)
}


#' @title Union with Name Preservation
#'
#' @description Unites two character vectors while preserving names from the specified vector.
#'   Gives a warning if there are conflicts in names between `x` and `y`.
#'
#' @param x A character vector.
#' @param y A character vector.
#' @param names Character. Specifies which vector's names to preserve in the output.
#'   "x" preserves `x`'s names, "y" preserves `y`'s names. Default: "x".
#'
#' @return A character vector with names preserved from the specified vector (`x` or `y`).
#'
#' @examples
#' union.wNames(x =c(a = "gene1", b = "gene2", c = "gene3")
#' , y =c( c = "gene3", dada = "gene2", "gene4")
#' , names = "x")
#'
#' @export union.wNames
union.wNames <- function(x, y, names = "x") {
  stopifnot(
    is.vector(x) || is.null(x),
    is.vector(y) || is.null(y),
    is.vector(x) || is.vector(y),
    names %in% c("x", "y")
  )
  warnifnot(HasNames(x), HasNames(y) )

  if(is.null(x)) {
    message("x is NULL, returning y.")
    return(y)
  }

  if(is.null(y)) {
    message("y is NULL, returning x.")
    return(x)
  }

  # Perform union
  all_elements <- union(x, y)
  common_elements <- intersect(x, y)

  # Check if names agree
  names_x <- names(sort(x[x %in% common_elements]))
  names_y <- names(sort(y[y %in% common_elements]))

  # Check for name conflicts: if names of common elements are different, issue a warning.
  if ( !identical(names_x, names_y) ) {
    warning("Names of intersecting elements is not the same in x & y!", immediate. = T)
    iprint("names_x: ", head(names_x))
    iprint("names_y: ", head(names_y))

    message("Names, for intersecting elements, inherited from: ", names)
  }


  result <-
    if (names == "x") {
      c(x, setdiff(y, x))
    } else if (names == "y") {
      c(y, setdiff(x, y))
    }

    message("Beware that: union(x, y) != union(y, x) - only if you sort the values.")
    return(result)
}



# _________________________________________________________________________________________________
## Math & stats ____________________________________________________________ ----



#' @title iround
#' @description Rounds a value to the significant amount of digits. Its a wrapper for signif().
#' @param x Unrounded number.
#' @param digitz Number of digits to keep. 3 by default.
#' @examples iround(x = 2.3232, digitz = 3)
#'
#' @export
iround <- function(x, digitz = 3) {
  signif(x, digits = digitz)
}



# _________________________________________________________________________________________________
#' @title modus
#'
#' @description Calculates the mode (modus) of a numeric vector (it excludes NA-s by default). https://en.wikipedia.org/wiki/Mode_(statistics)
#' @param x A numeric vector
#' @import stats
#' @examples modus(c(1, 1, 2, 3, 3, 3, 4, 5))
#' modus(1:4)
#'
#' @export
modus <- function(x) {
  x <- unlist(na.exclude(x))
  ux <- unique(x)
  tab <- tabulate(match(x, ux))
  ux[tab == max(tab)]
}



# _________________________________________________________________________________________________
#' @title cv
#'
#' @description Calculates the coefficient of variation (CV) for a numeric vector (it excludes NA-s by default).
#' @param x A numeric vector.
#' @param na.rm Should NA values be removed before calculation? Defaults to TRUE.
#' @return The coefficient of variation of the input vector.
#' @examples cv(1:5)
#'
#' @export
cv <- function(x, na.rm = TRUE) {
  sd(x, na.rm = na.rm) / mean(x, na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title sem
#'
#' @description Calculates the standard error of the mean (SEM) for a numeric vector (it excludes NA-s by default).
#' @param x A numeric vector.
#' @param na.rm Should NA values be removed before calculation? Defaults to TRUE.
#' @return The standard error of the mean of the input vector.
#' @examples sem(1:5)
#'
#' @export
sem <- function(x, na.rm = TRUE) {
  sd(unlist(x), na.rm = na.rm) / sqrt(length(na.omit.strip(as.numeric(x)))) # Calculates the standard error of the mean (SEM) for a numeric vector (it excludes NA-s by default)
}




# _________________________________________________________________________________________________
#' @title fano
#'
#' @description Calculates the fano factor on a numeric vector (it excludes NA-s by default).
#' @param x A numeric vector.
#' @param na.rm Should NA values be removed before calculation? Defaults to TRUE.
#' @param USE The method used to calculate the variance. Defaults to `"na.or.complete"`.
#' @return The fano factor of the input vector.
#' @examples fano(1:5)
#'
#' @export
fano <- function(x, na.rm = TRUE, USE = "na.or.complete") {
  var(x, na.rm = na.rm, use = USE) / mean(x, na.rm = na.rm) # Calculates the fano factor on a numeric vector (it excludes NA-s by default)
}

# _________________________________________________________________________________________________
#' @title geomean
#'
#' @description Calculates the geometric mean of a numeric vector (it excludes NA-s by default).
#' @param x A numeric vector.
#' @param na.rm Should NA values be removed before calculation? Defaults to TRUE.
#' @return The geometric mean of the input vector.
#' @examples geomean(1:5)
#'
#' @export
geomean <- function(x, na.rm = TRUE) {
  exp(sum(log(x[x > 0]), na.rm = na.rm) / length(x))
}



# _________________________________________________________________________________________________
#' @title mean_of_log
#'
#' @description Calculates the mean of the log_k of a numeric vector (it excludes NA-s by default).
#' @param x A numeric vector.
#' @param k The base of the logarithm. Defaults to 2.
#' @param na.rm Should NA values be removed before calculation? Defaults to TRUE.
#' @return The mean of the log_k of the input vector.
#' @examples mean_of_log(1:5)
#'
#' @export
mean_of_log <- function(x, k = 2, na.rm = TRUE) {
  stopifnot(is.numeric(x), length(k) == 1, is.finite(k), is.logical(na.rm))

  negs <- sum(x < 0)
  zeros <- sum(x == 0)
  if (negs | zeros) {
    iprint("The input vector has", negs, "negative values and", zeros, "zeros.")
  }
  mean(log(x, base = k), na.rm = na.rm)
}



# _________________________________________________________________________________________________
#' @title Moving / rolling average
#'
#' @description Calculates the moving / rolling average of a numeric vector.
#' @param x A numeric vector.
#' @param oneSide The size of the moving window. Defaults to 5.
#' @return A vector of the moving averages.
#' @examples movingAve(1:5)
#'
#' @export
movingAve <- function(x, oneSide = 5) {
  y <- NULL
  for (i in oneSide:length(x)) {
    y[i] <- mean(x[(i - oneSide):(i + oneSide)])
  }
  return(y)
}



# _________________________________________________________________________________________________
#' @title Moving / rolling average (v2, filter)
#' @description Calculates the moving / rolling average of a numeric vector, using `filter()`.
#' @param x A numeric vector.
#' @param n The size of the moving window. Defaults to 5.
#' @return A vector of the moving averages.
#' @examples movingAve2(1:5)
#'
#' @export
movingAve2 <- function(x, n = 5) {
  filter(x, rep(1 / n, n), sides = 2)
} # Calculates the moving / rolling average of a numeric vector, using filter().



# _________________________________________________________________________________________________
#' @title movingSEM
#' @description Calculates the moving / rolling standard error of the mean (SEM) on a numeric vector.
#' @param x A numeric vector.
#' @param oneSide The size of the moving window, in terms of the number of elements on either side of the current element.
#' @return A vector of the same length as `x`, containing the SEMs for each element.
#' @export
movingSEM <- function(x, oneSide = 5) {
  # Calculates the moving / rolling standard error of the mean (SEM) on a numeric vector.
  y <- NULL
  for (i in oneSide:length(x)) {
    y[i] <- sem(x[(i - oneSide):(i + oneSide)])
  }
  return(y)
}



# _________________________________________________________________________________________________
#' @title imovingSEM
#'
#' @description Calculates the moving / rolling standard error of the mean (SEM). It calculates it to the edge of the vector with incrementally smaller window-size.
#' @param x A numeric vector.
#' @param oneSide The size of the moving window, in terms of the number of elements on either side of the current element.
#' @return A vector of the same length as `x`, containing the SEMs for each element.
#' @export
imovingSEM <- function(x, oneSide = 5) {
  # Calculates the moving / rolling standard error of the mean (SEM). It calculates it to the edge of the vector with incrementally smaller window-size.
  y <- NULL
  for (i in 1:length(x)) {
    oneSideDynamic <- min(i - 1, oneSide, length(x) - i)
    oneSideDynamic
    indexx <- (i - oneSideDynamic):(i + oneSideDynamic)
    y[i] <- sem(x[indexx])
  }
  return(y)
}


################################################################################################
# NOT YET ORGANIZED


# _________________________________________________________________________________________________
#' @title Pretty Printing of R Objects (pretty_dput)
#'
#' @description This function modifies the output of the traditional dput() function
#' so that each key-value pair in a vector appears on a new line. It's useful for
#' creating more readable output.
#' @param vec A named vector to be printed. The vector should be in the form `c('name' = 'value')`.
#' @return No return value. Outputs the vector with each element on a new line.
#' @examples
#' vec <- c(`0` = "ACyte", `1` = "Misp.1.DCN")
#' pretty_dput(vec)
#'
#' @export
dput_pretty <- pretty_dput <- function(vec) {
  if (is.null(names(vec))) names(vec) <- 1:length(vec)
  cat("c(", sep = "")
  for (i in 1:length(vec)) {
    cat("\n`", vec[i], "` = \"", names(vec)[i], "\"",
        ifelse(i != length(vec), ",", ""),
        sep = ""
    )
  }
  cat("\n)\n")
}




################################################################################################

# DON'T DELETE: FOR BACKTRACKING

# _________________________________________________________________________________________________
#' @title as.numeric.wNames.deprecated
#'
#' @description Converts any vector into a numeric vector, and puts the original character values into the names of the new vector, unless it already has names. Useful for coloring a plot by categories, name-tags, etc.
#' @param vec input vector
#'
#' @export as.numeric.wNames.deprecated
as.numeric.wNames.deprecated <- function(vec) {
  numerified_vec <- as.numeric(as.factor(vec)) - 1 # as factor gives numbers [1:n] instead [0:n]
  if (!is.null(names(vec))) {
    names(numerified_vec) <- names(vec)
  }
  return(numerified_vec)
}

# _________________________________________________________________________________________________
#' @title as.factor.numeric.deprecated
#'
#' @description  Turn any vector into numeric categories as.numeric(as.factor(vec))
#' @param vec vector of factors or strings
#' @param rename Rename the vector?
#' @param ... Pass any other argument. to as.factor()
#' @examples as.factor.numeric(LETTERS[1:4])
#'
#' @export as.factor.numeric

as.factor.numeric <- function(vec, rename = FALSE, ...) {
  .Deprecated("as.numeric.wNames.factor")

  vec2 <- as.numeric(as.factor(vec, ...))
  names(vec2) <- if (!rename & !is.null(names(vec))) {
    names(vec)
  } else {
    vec
  }
  return(vec2)
}


# _________________________________________________________________________________________________
#' @title as.named.vector.deprecated
#'
#' @description Convert a dataframe column or row into a vector, keeping the corresponding dimension name.
#' @param df_col data frame column
#' @param WhichDimNames Shall we extract rows (2) or columns (1, default)?, Default: 1
#'
#' @export as.named.vector.deprecated
as.named.vector.deprecated <- function(df_col, WhichDimNames = 1) {
  namez <- dimnames(df_col)[[WhichDimNames]]
  # use RowNames: WhichDimNames = 1 , 2: use ColNames
  # !!! might require drop = FALSE in subsetting!!! eg: df_col[, 3, drop = FALSE]
  # df_col[which(unlist(lapply(df_col, is.null)))] = "NULL" # replace NULLs - they would fall out of vectors - DOES not work yet
  if (is.list(df_col) & !is.data.frame(df_col)) {
    namez <- names(df_col)
  }
  vecc <- as.vector(unlist(df_col))
  names(vecc) <- namez
  return(vecc)
}


# _________________________________________________________________________________________________
# Deprecated ----
# _________________________________________________________________________________________________
#' @title sort.mat
#' @export sort.mat
sort.mat <- function() .Deprecated("sort_matrix_rows()")



#  ______________________________________________________________________________________
vertesy/CodeAndRoll2 documentation built on Nov. 20, 2024, 5:23 a.m.