2018-02-21 11:52:31 +01:00
# ==================================================================== #
# TITLE #
2021-02-02 23:57:35 +01:00
# Antimicrobial Resistance (AMR) Data Analysis for R #
2018-02-21 11:52:31 +01:00
# #
2019-01-02 23:24:07 +01:00
# SOURCE #
2020-07-08 14:48:06 +02:00
# https://github.com/msberends/AMR #
2018-02-21 11:52:31 +01:00
# #
# LICENCE #
2020-12-27 00:30:28 +01:00
# (c) 2018-2021 Berends MS, Luz CF et al. #
2020-10-08 11:16:03 +02:00
# Developed at the University of Groningen, the Netherlands, in #
# collaboration with non-profit organisations Certe Medical #
2020-10-26 12:23:03 +01:00
# Diagnostics & Advice, and University Medical Center Groningen. #
2018-02-21 11:52:31 +01:00
# #
2019-01-02 23:24:07 +01:00
# This R package is free software; you can freely use and distribute #
# it for both personal and commercial purposes under the terms of the #
# GNU General Public License version 2.0 (GNU GPL-2), as published by #
# the Free Software Foundation. #
2020-01-05 17:22:09 +01:00
# We created this package for both routine data analysis and academic #
# research and it was publicly released in the hope that it will be #
# useful, but it comes WITHOUT ANY WARRANTY OR LIABILITY. #
2020-10-08 11:16:03 +02:00
# #
# Visit our website for the full manual and a complete tutorial about #
2021-02-02 23:57:35 +01:00
# how to conduct AMR data analysis: https://msberends.github.io/AMR/ #
2018-02-21 11:52:31 +01:00
# ==================================================================== #
2020-09-03 12:31:48 +02:00
# faster implementation of left_join than using merge() by poorman - we use match():
2020-09-18 16:05:53 +02:00
pm_left_join <- function ( x , y , by = NULL , suffix = c ( " .x" , " .y" ) ) {
2020-08-14 13:36:10 +02:00
if ( is.null ( by ) ) {
by <- intersect ( names ( x ) , names ( y ) ) [1L ]
if ( is.na ( by ) ) {
2020-09-18 16:05:53 +02:00
stop_ ( " no common column found for pm_left_join()" )
2020-08-14 13:36:10 +02:00
}
2020-09-19 11:54:01 +02:00
pm_join_message ( by )
2020-08-14 13:36:10 +02:00
} else if ( ! is.null ( names ( by ) ) ) {
by <- unname ( c ( names ( by ) , by ) )
}
if ( length ( by ) == 1 ) {
by <- rep ( by , 2 )
}
2020-10-26 12:23:03 +01:00
2020-08-15 12:54:47 +02:00
int_x <- colnames ( x ) %in% colnames ( y ) & colnames ( x ) != by [1 ]
int_y <- colnames ( y ) %in% colnames ( x ) & colnames ( y ) != by [2 ]
colnames ( x ) [int_x ] <- paste0 ( colnames ( x ) [int_x ] , suffix [1L ] )
colnames ( y ) [int_y ] <- paste0 ( colnames ( y ) [int_y ] , suffix [2L ] )
2020-10-26 12:23:03 +01:00
2020-08-14 13:36:10 +02:00
merged <- cbind ( x ,
y [match ( x [ , by [1 ] , drop = TRUE ] ,
y [ , by [2 ] , drop = TRUE ] ) ,
colnames ( y ) [ ! colnames ( y ) %in% colnames ( x ) & ! colnames ( y ) == by [2 ] ] ,
drop = FALSE ] )
2020-10-26 12:23:03 +01:00
2020-08-14 13:36:10 +02:00
rownames ( merged ) <- NULL
merged
}
2020-09-18 16:05:53 +02:00
quick_case_when <- function ( ... ) {
vectors <- list ( ... )
split <- lapply ( vectors , function ( x ) unlist ( strsplit ( paste ( deparse ( x ) , collapse = " " ) , " ~" , fixed = TRUE ) ) )
for ( i in seq_len ( length ( vectors ) ) ) {
if ( eval ( parse ( text = split [ [i ] ] [1 ] ) , envir = parent.frame ( ) ) ) {
return ( eval ( parse ( text = split [ [i ] ] [2 ] ) , envir = parent.frame ( ) ) )
}
2020-05-16 13:05:47 +02:00
}
2020-09-18 16:05:53 +02:00
return ( NA )
2020-05-16 13:05:47 +02:00
}
2018-07-04 17:20:03 +02:00
# No export, no Rd
addin_insert_in <- function ( ) {
2020-06-17 15:14:37 +02:00
import_fn ( " insertText" , " rstudioapi" ) ( " %in% " )
2018-07-04 17:20:03 +02:00
}
# No export, no Rd
addin_insert_like <- function ( ) {
2021-04-23 09:59:36 +02:00
# we want Shift + Ctrl/Cmd + L to iterate over %like%, %unlike%, %like_case%, and %unlike_case%
getActiveDocumentContext <- import_fn ( " getActiveDocumentContext" , " rstudioapi" )
insertText <- import_fn ( " insertText" , " rstudioapi" )
modifyRange <- import_fn ( " modifyRange" , " rstudioapi" )
document_range <- import_fn ( " document_range" , " rstudioapi" )
document_position <- import_fn ( " document_position" , " rstudioapi" )
context <- getActiveDocumentContext ( )
current_row <- context $ selection [ [1 ] ] $ range $ end [1 ]
current_col <- context $ selection [ [1 ] ] $ range $ end [2 ]
current_row_txt <- context $ contents [current_row ]
if ( is.null ( current_row ) || current_row_txt %unlike% " %(un)?like" ) {
insertText ( " %like% " )
return ( invisible ( ) )
}
pos_preceded_by <- function ( txt ) {
if ( tryCatch ( substr ( current_row_txt , current_col - nchar ( trimws ( txt , which = " right" ) ) , current_col ) == trimws ( txt , which = " right" ) ,
error = function ( e ) FALSE ) ) {
return ( TRUE )
}
tryCatch ( substr ( current_row_txt , current_col - nchar ( txt ) , current_col ) %like% paste0 ( " ^" , txt ) ,
error = function ( e ) FALSE )
}
replace_pos <- function ( old , with ) {
modifyRange ( document_range ( document_position ( current_row , current_col - nchar ( old ) ) ,
document_position ( current_row , current_col ) ) ,
text = with ,
id = context $ id )
}
if ( pos_preceded_by ( " %like% " ) ) {
replace_pos ( " %like% " , with = " %unlike% " )
} else if ( pos_preceded_by ( " %unlike% " ) ) {
replace_pos ( " %unlike% " , with = " %like_case% " )
} else if ( pos_preceded_by ( " %like_case% " ) ) {
replace_pos ( " %like_case% " , with = " %unlike_case% " )
} else if ( pos_preceded_by ( " %unlike_case% " ) ) {
replace_pos ( " %unlike_case% " , with = " %like% " )
} else {
insertText ( " %like% " )
}
2018-07-04 17:20:03 +02:00
}
2020-02-14 19:54:13 +01:00
check_dataset_integrity <- function ( ) {
2020-07-28 18:39:57 +02:00
# check if user overwrote our data sets in their global environment
data_in_pkg <- data ( package = " AMR" , envir = asNamespace ( " AMR" ) ) $ results [ , " Item" , drop = TRUE ]
data_in_globalenv <- ls ( envir = globalenv ( ) )
overwritten <- data_in_pkg [data_in_pkg %in% data_in_globalenv ]
2020-09-24 00:30:11 +02:00
# exception for example_isolates
overwritten <- overwritten [overwritten != " example_isolates" ]
2021-01-12 22:08:04 +01:00
if ( length ( overwritten ) > 0 ) {
2021-02-17 10:58:13 +01:00
if ( length ( overwritten ) > 1 ) {
plural <- c ( " s are" , " " , " s" )
} else {
plural <- c ( " is" , " s" , " " )
}
warning_ ( " The following data set" , plural [1 ] ,
" overwritten by your global environment and prevent" , plural [2 ] ,
" the AMR package from working correctly: " ,
2021-02-04 16:48:16 +01:00
vector_and ( overwritten , quotes = " '" ) ,
2021-02-17 10:58:13 +01:00
" .\nPlease rename your object" , plural [3 ] , " ." , call = FALSE )
2021-01-12 22:08:04 +01:00
}
2020-07-28 18:39:57 +02:00
# check if other packages did not overwrite our data sets
2021-02-17 10:58:13 +01:00
valid_microorganisms <- TRUE
valid_antibiotics <- TRUE
2020-02-16 22:43:56 +01:00
tryCatch ( {
2021-02-17 10:58:13 +01:00
valid_microorganisms <- all ( c ( " mo" , " fullname" , " kingdom" , " phylum" ,
2020-10-26 12:23:03 +01:00
" class" , " order" , " family" , " genus" ,
2020-02-16 22:43:56 +01:00
" species" , " subspecies" , " rank" ,
2020-05-27 16:37:49 +02:00
" species_id" , " source" , " ref" , " prevalence" ) %in% colnames ( microorganisms ) ,
2020-07-08 14:48:06 +02:00
na.rm = TRUE )
2021-02-17 10:58:13 +01:00
valid_antibiotics <- all ( c ( " ab" , " atc" , " cid" , " name" , " group" ,
2020-02-16 22:43:56 +01:00
" atc_group1" , " atc_group2" , " abbreviations" ,
2020-10-26 12:23:03 +01:00
" synonyms" , " oral_ddd" , " oral_units" ,
2020-02-16 22:43:56 +01:00
" iv_ddd" , " iv_units" , " loinc" ) %in% colnames ( antibiotics ) ,
na.rm = TRUE )
2020-08-14 13:36:10 +02:00
} , error = function ( e ) {
# package not yet loaded
require ( " AMR" )
} )
2021-02-17 10:58:13 +01:00
stop_if ( ! valid_microorganisms | ! valid_antibiotics ,
2021-02-21 22:56:35 +01:00
" the data set `microorganisms` or `antibiotics` was overwritten in your environment because another package with the same object name(s) was loaded _after_ the AMR package, preventing the AMR package from working correctly. Please load the AMR package last." )
2020-02-14 19:54:13 +01:00
invisible ( TRUE )
2019-11-23 12:39:57 +01:00
}
2020-09-24 00:30:11 +02:00
search_type_in_df <- function ( x , type , info = TRUE ) {
2020-11-17 16:57:41 +01:00
meet_criteria ( x , allow_class = " data.frame" )
meet_criteria ( type , allow_class = " character" , has_length = 1 )
2019-01-15 12:45:24 +01:00
# try to find columns based on type
found <- NULL
2020-10-26 12:23:03 +01:00
2020-11-17 16:57:41 +01:00
# remove attributes from other packages
2020-05-16 13:05:47 +02:00
x <- as.data.frame ( x , stringsAsFactors = FALSE )
2019-05-23 16:58:59 +02:00
colnames ( x ) <- trimws ( colnames ( x ) )
2020-10-26 12:23:03 +01:00
2019-01-15 12:45:24 +01:00
# -- mo
if ( type == " mo" ) {
2020-12-28 22:24:33 +01:00
if ( any ( vapply ( FUN.VALUE = logical ( 1 ) , x , is.mo ) ) ) {
found <- sort ( colnames ( x ) [vapply ( FUN.VALUE = logical ( 1 ) , x , is.mo ) ] ) [1 ]
2019-11-04 12:08:08 +01:00
} else if ( " mo" %in% colnames ( x ) &
suppressWarnings (
all ( x $ mo %in% c ( NA ,
microorganisms $ mo ,
microorganisms.translation $ mo_old ) ) ) ) {
found <- " mo"
2020-06-11 20:03:13 +02:00
} else if ( any ( colnames ( x ) %like% " ^(mo|microorganism|organism|bacteria|ba[ck]terie)s?$" ) ) {
found <- sort ( colnames ( x ) [colnames ( x ) %like% " ^(mo|microorganism|organism|bacteria|ba[ck]terie)s?$" ] ) [1 ]
} else if ( any ( colnames ( x ) %like% " ^(microorganism|organism|bacteria|ba[ck]terie)" ) ) {
found <- sort ( colnames ( x ) [colnames ( x ) %like% " ^(microorganism|organism|bacteria|ba[ck]terie)" ] ) [1 ]
2019-05-23 16:58:59 +02:00
} else if ( any ( colnames ( x ) %like% " species" ) ) {
2020-06-11 20:03:13 +02:00
found <- sort ( colnames ( x ) [colnames ( x ) %like% " species" ] ) [1 ]
2019-01-15 12:45:24 +01:00
}
2020-10-26 12:23:03 +01:00
2019-01-15 12:45:24 +01:00
}
# -- key antibiotics
if ( type == " keyantibiotics" ) {
2021-04-26 23:57:37 +02:00
if ( any ( colnames ( x ) %like% " ^key.*(ab|antibiotics|antimicrobials)" ) ) {
found <- sort ( colnames ( x ) [colnames ( x ) %like% " ^key.*(ab|antibiotics|antimicrobials)" ] ) [1 ]
2019-01-15 12:45:24 +01:00
}
}
# -- date
if ( type == " date" ) {
2019-05-23 16:58:59 +02:00
if ( any ( colnames ( x ) %like% " ^(specimen date|specimen_date|spec_date)" ) ) {
2019-03-15 13:57:25 +01:00
# WHONET support
2020-06-11 20:03:13 +02:00
found <- sort ( colnames ( x ) [colnames ( x ) %like% " ^(specimen date|specimen_date|spec_date)" ] ) [1 ]
2020-09-18 16:05:53 +02:00
if ( ! any ( class ( pm_pull ( x , found ) ) %in% c ( " Date" , " POSIXct" ) ) ) {
2020-12-03 16:59:04 +01:00
stop ( font_red ( paste0 ( " Found column '" , font_bold ( found ) , " ' to be used as input for `col_" , type ,
2020-06-11 20:03:13 +02:00
" `, but this column contains no valid dates. Transform its values to valid dates first." ) ) ,
2019-03-15 13:57:25 +01:00
call. = FALSE )
}
2020-12-28 22:24:33 +01:00
} else if ( any ( vapply ( FUN.VALUE = logical ( 1 ) , x , function ( x ) inherits ( x , c ( " Date" , " POSIXct" ) ) ) ) ) {
found <- sort ( colnames ( x ) [vapply ( FUN.VALUE = logical ( 1 ) , x , function ( x ) inherits ( x , c ( " Date" , " POSIXct" ) ) ) ] ) [1 ]
2019-01-15 12:45:24 +01:00
}
}
# -- patient id
if ( type == " patient_id" ) {
2019-05-23 16:58:59 +02:00
if ( any ( colnames ( x ) %like% " ^(identification |patient|patid)" ) ) {
2020-06-11 20:03:13 +02:00
found <- sort ( colnames ( x ) [colnames ( x ) %like% " ^(identification |patient|patid)" ] ) [1 ]
2019-01-29 00:06:50 +01:00
}
}
# -- specimen
if ( type == " specimen" ) {
2019-05-23 16:58:59 +02:00
if ( any ( colnames ( x ) %like% " (specimen type|spec_type)" ) ) {
2020-06-11 20:03:13 +02:00
found <- sort ( colnames ( x ) [colnames ( x ) %like% " (specimen type|spec_type)" ] ) [1 ]
2019-05-23 16:58:59 +02:00
} else if ( any ( colnames ( x ) %like% " ^(specimen)" ) ) {
2020-06-11 20:03:13 +02:00
found <- sort ( colnames ( x ) [colnames ( x ) %like% " ^(specimen)" ] ) [1 ]
2019-01-15 12:45:24 +01:00
}
}
2020-02-20 13:19:23 +01:00
# -- UTI (urinary tract infection)
if ( type == " uti" ) {
if ( any ( colnames ( x ) == " uti" ) ) {
found <- colnames ( x ) [colnames ( x ) == " uti" ] [1 ]
} else if ( any ( colnames ( x ) %like% " (urine|urinary)" ) ) {
2020-06-11 20:03:13 +02:00
found <- sort ( colnames ( x ) [colnames ( x ) %like% " (urine|urinary)" ] ) [1 ]
2020-02-20 13:19:23 +01:00
}
if ( ! is.null ( found ) ) {
2020-02-21 21:13:38 +01:00
# this column should contain logicals
2020-02-20 13:19:23 +01:00
if ( ! is.logical ( x [ , found , drop = TRUE ] ) ) {
2020-12-03 16:59:04 +01:00
message_ ( " Column '" , font_bold ( found ) , " ' found as input for `col_" , type ,
2020-10-27 15:56:51 +01:00
" `, but this column does not contain 'logical' values (TRUE/FALSE) and was ignored." ,
add_fn = font_red )
2020-02-20 13:19:23 +01:00
found <- NULL
}
}
}
2020-12-24 23:29:10 +01:00
2020-09-24 00:30:11 +02:00
if ( ! is.null ( found ) & info == TRUE ) {
2020-12-24 23:29:10 +01:00
if ( message_not_thrown_before ( fn = paste0 ( " search_" , type ) ) ) {
msg <- paste0 ( " Using column '" , font_bold ( found ) , " ' as input for `col_" , type , " `." )
if ( type %in% c ( " keyantibiotics" , " specimen" ) ) {
msg <- paste ( msg , " Use" , font_bold ( paste0 ( " col_" , type ) , " = FALSE" ) , " to prevent this." )
}
message_ ( msg )
remember_thrown_message ( fn = paste0 ( " search_" , type ) )
2019-01-15 12:45:24 +01:00
}
}
found
}
2019-03-26 14:24:03 +01:00
2021-04-16 11:41:05 +02:00
is_valid_regex <- function ( x ) {
regex_at_all <- tryCatch ( vapply ( FUN.VALUE = logical ( 1 ) ,
X = strsplit ( x , " " ) ,
FUN = function ( y ) any ( y %in% c ( " $" , " (" , " )" , " *" , " +" , " -" ,
" ." , " ?" , " [" , " ]" , " ^" , " {" ,
" |" , " }" , " \\" ) ,
2021-04-16 12:02:57 +02:00
na.rm = TRUE ) ,
USE.NAMES = FALSE ) ,
2021-04-16 11:41:05 +02:00
error = function ( e ) rep ( TRUE , length ( x ) ) )
regex_valid <- vapply ( FUN.VALUE = logical ( 1 ) ,
2021-04-16 12:02:57 +02:00
X = x ,
2021-04-16 13:24:59 +02:00
FUN = function ( y ) ! " try-error" %in% class ( try ( grepl ( y , " " , perl = TRUE ) ,
2021-04-16 11:41:05 +02:00
silent = TRUE ) ) ,
USE.NAMES = FALSE )
regex_at_all & regex_valid
2020-09-24 00:30:11 +02:00
}
2020-06-22 11:18:40 +02:00
stop_ifnot_installed <- function ( package ) {
2019-06-01 20:40:49 +02:00
# no "utils::installed.packages()" since it requires non-staged install since R 3.6.0
# https://developer.r-project.org/Blog/public/2019/02/14/staged-install/index.html
2020-12-28 22:24:33 +01:00
vapply ( FUN.VALUE = character ( 1 ) , package , function ( pkg )
2020-06-17 16:29:10 +02:00
tryCatch ( get ( " .packageName" , envir = asNamespace ( pkg ) ) ,
2020-07-02 21:12:52 +02:00
error = function ( e ) {
2021-04-23 09:59:36 +02:00
if ( pkg == " rstudioapi" ) {
stop ( " This function only works in RStudio when using R >= 3.2." , call. = FALSE )
2020-06-17 16:29:10 +02:00
} else if ( pkg != " base" ) {
2020-08-26 16:13:40 +02:00
stop ( " This requires the '" , pkg , " ' package." ,
2020-06-17 16:29:10 +02:00
" \nTry to install it with: install.packages(\"" , pkg , " \")" ,
2020-06-17 15:14:37 +02:00
call. = FALSE )
}
} ) )
2019-06-01 20:40:49 +02:00
return ( invisible ( ) )
2019-03-26 14:24:03 +01:00
}
2019-05-10 16:44:59 +02:00
2020-08-10 11:44:58 +02:00
import_fn <- function ( name , pkg , error_on_fail = TRUE ) {
if ( isTRUE ( error_on_fail ) ) {
stop_ifnot_installed ( pkg )
}
2020-07-08 14:48:06 +02:00
tryCatch (
2020-12-17 16:22:25 +01:00
# don't use get() to avoid fetching non-API functions
getExportedValue ( name = name , ns = asNamespace ( pkg ) ) ,
2020-08-10 11:44:58 +02:00
error = function ( e ) {
if ( isTRUE ( error_on_fail ) ) {
2020-12-17 16:22:25 +01:00
stop_ ( " function " , name , " () is not an exported object from package '" , pkg ,
2020-10-26 12:23:03 +01:00
" '. Please create an issue at https://github.com/msberends/AMR/issues. Many thanks!" ,
2020-08-26 16:13:40 +02:00
call = FALSE )
2020-08-10 11:44:58 +02:00
} else {
return ( NULL )
}
} )
2020-06-17 15:14:37 +02:00
}
2020-11-10 16:35:56 +01:00
# this alternative wrapper to the message(), warning() and stop() functions:
2020-10-26 12:23:03 +01:00
# - wraps text to never break lines within words
# - ignores formatted text while wrapping
2020-12-17 16:22:25 +01:00
# - adds indentation dependent on the type of message (such as NOTE)
2020-11-10 16:35:56 +01:00
# - can add additional formatting functions like blue or bold text
word_wrap <- function ( ... ,
add_fn = list ( ) ,
as_note = FALSE ,
width = 0.95 * getOption ( " width" ) ,
extra_indent = 0 ) {
2020-10-26 12:23:03 +01:00
msg <- paste0 ( c ( ... ) , collapse = " " )
2020-10-26 15:53:31 +01:00
if ( isTRUE ( as_note ) ) {
2021-04-26 23:57:37 +02:00
# \u2139 is a symbol officially named 'information source'
# \ufe0f can add the blue square around it: \u2139\ufe0f
msg <- paste0 ( " \u2139 " , gsub ( " ^note:? ?" , " " , msg , ignore.case = TRUE ) )
2020-10-26 15:53:31 +01:00
}
2020-11-10 16:35:56 +01:00
2020-12-22 00:51:17 +01:00
if ( msg %like% " \n" ) {
# run word_wraps() over every line here, bind them and return again
2020-12-28 22:24:33 +01:00
return ( paste0 ( vapply ( FUN.VALUE = character ( 1 ) ,
trimws ( unlist ( strsplit ( msg , " \n" ) ) , which = " right" ) ,
2020-12-22 00:51:17 +01:00
word_wrap ,
add_fn = add_fn ,
as_note = FALSE ,
width = width ,
extra_indent = extra_indent ) ,
collapse = " \n" ) )
}
2020-10-26 12:23:03 +01:00
# we need to correct for already applied style, that adds text like "\033[31m\"
msg_stripped <- font_stripstyle ( msg )
# where are the spaces now?
msg_stripped_wrapped <- paste0 ( strwrap ( msg_stripped ,
simplify = TRUE ,
2020-11-10 16:35:56 +01:00
width = width ) ,
collapse = " \n" )
msg_stripped_wrapped <- paste0 ( unlist ( strsplit ( msg_stripped_wrapped , " (\n|\\*\\|\\*)" ) ) ,
2020-10-26 12:23:03 +01:00
collapse = " \n" )
msg_stripped_spaces <- which ( unlist ( strsplit ( msg_stripped , " " ) ) == " " )
2020-11-10 16:35:56 +01:00
msg_stripped_wrapped_spaces <- which ( unlist ( strsplit ( msg_stripped_wrapped , " " ) ) != " \n" )
2020-10-26 12:23:03 +01:00
# so these are the indices of spaces that need to be replaced
replace_spaces <- which ( ! msg_stripped_spaces %in% msg_stripped_wrapped_spaces )
# put it together
msg <- unlist ( strsplit ( msg , " " ) )
msg [replace_spaces ] <- paste0 ( msg [replace_spaces ] , " \n" )
msg <- paste0 ( msg , collapse = " " )
msg <- gsub ( " \n " , " \n" , msg , fixed = TRUE )
2020-11-10 16:35:56 +01:00
2021-04-26 23:57:37 +02:00
if ( msg_stripped %like% " \u2139 " ) {
indentation <- 2 + extra_indent
2020-10-27 15:56:51 +01:00
} else if ( msg_stripped %like% " ^=> " ) {
2020-11-10 16:35:56 +01:00
indentation <- 3 + extra_indent
2020-10-26 12:23:03 +01:00
} else {
2020-11-10 16:35:56 +01:00
indentation <- 0 + extra_indent
2020-10-26 12:23:03 +01:00
}
msg <- gsub ( " \n" , paste0 ( " \n" , strrep ( " " , indentation ) ) , msg , fixed = TRUE )
2020-11-10 19:59:14 +01:00
# remove trailing empty characters
msg <- gsub ( " (\n| )+$" , " " , msg )
2020-10-26 12:23:03 +01:00
if ( length ( add_fn ) > 0 ) {
if ( ! is.list ( add_fn ) ) {
add_fn <- list ( add_fn )
}
for ( i in seq_len ( length ( add_fn ) ) ) {
msg <- add_fn [ [i ] ] ( msg )
}
}
2020-11-10 16:35:56 +01:00
2020-12-01 16:59:57 +01:00
# format backticks
msg <- gsub ( " (`.+?`)" , font_grey_bg ( " \\1" ) , msg )
2020-11-10 16:35:56 +01:00
msg
}
message_ <- function ( ... ,
appendLF = TRUE ,
add_fn = list ( font_blue ) ,
as_note = TRUE ) {
message ( word_wrap ( ... ,
add_fn = add_fn ,
as_note = as_note ) ,
appendLF = appendLF )
}
warning_ <- function ( ... ,
add_fn = list ( ) ,
immediate = FALSE ,
call = TRUE ) {
warning ( word_wrap ( ... ,
add_fn = add_fn ,
as_note = FALSE ) ,
immediate. = immediate ,
call. = call )
2020-10-26 12:23:03 +01:00
}
# this alternative to the stop() function:
# - adds the function name where the error was thrown
# - wraps text to never break lines within words
2020-07-01 11:07:01 +02:00
stop_ <- function ( ... , call = TRUE ) {
2020-11-28 22:15:44 +01:00
msg <- paste0 ( c ( ... ) , collapse = " " )
2020-07-01 11:07:01 +02:00
if ( ! isFALSE ( call ) ) {
if ( isTRUE ( call ) ) {
call <- as.character ( sys.call ( -1 ) [1 ] )
} else {
# so you can go back more than 1 call, as used in rsi_calc(), that now throws a reference to e.g. n_rsi()
call <- as.character ( sys.call ( call ) [1 ] )
}
msg <- paste0 ( " in " , call , " (): " , msg )
}
2020-11-28 22:15:44 +01:00
msg <- word_wrap ( msg , add_fn = list ( ) , as_note = FALSE )
2020-07-01 11:07:01 +02:00
stop ( msg , call. = FALSE )
}
2020-06-22 11:18:40 +02:00
stop_if <- function ( expr , ... , call = TRUE ) {
if ( isTRUE ( expr ) ) {
2020-07-01 11:07:01 +02:00
if ( isTRUE ( call ) ) {
call <- -1
}
2020-06-26 10:21:22 +02:00
if ( ! isFALSE ( call ) ) {
2020-07-01 11:07:01 +02:00
# since we're calling stop_(), which is another call
call <- call - 1
2020-06-26 10:21:22 +02:00
}
2020-07-01 11:07:01 +02:00
stop_ ( ... , call = call )
2020-03-08 11:18:59 +01:00
}
}
2020-06-22 11:18:40 +02:00
stop_ifnot <- function ( expr , ... , call = TRUE ) {
2020-09-24 00:30:11 +02:00
if ( isFALSE ( expr ) ) {
2020-07-01 11:07:01 +02:00
if ( isTRUE ( call ) ) {
call <- -1
}
2020-06-26 10:21:22 +02:00
if ( ! isFALSE ( call ) ) {
2020-07-01 11:07:01 +02:00
# since we're calling stop_(), which is another call
call <- call - 1
2020-06-26 10:21:22 +02:00
}
2020-07-01 11:07:01 +02:00
stop_ ( ... , call = call )
2020-06-22 11:18:40 +02:00
}
}
2020-03-08 11:18:59 +01:00
2019-05-20 19:12:41 +02:00
" %or%" <- function ( x , y ) {
2019-06-16 21:42:40 +02:00
if ( is.null ( x ) | is.null ( y ) ) {
if ( is.null ( x ) ) {
return ( y )
} else {
return ( x )
}
}
ifelse ( ! is.na ( x ) ,
x ,
ifelse ( ! is.na ( y ) , y , NA ) )
2019-05-20 19:12:41 +02:00
}
2019-08-26 16:02:03 +02:00
class_integrity_check <- function ( value , type , check_vector ) {
if ( ! all ( value [ ! is.na ( value ) ] %in% check_vector ) ) {
2020-11-10 16:35:56 +01:00
warning_ ( paste0 ( " invalid " , type , " , NA generated" ) , call = FALSE )
2019-08-26 16:02:03 +02:00
value [ ! value %in% check_vector ] <- NA
}
value
}
2020-01-27 19:14:23 +01:00
# transforms data set to data.frame with only ASCII values, to comply with CRAN policies
dataset_UTF8_to_ASCII <- function ( df ) {
trans <- function ( vect ) {
iconv ( vect , from = " UTF-8" , to = " ASCII//TRANSLIT" )
}
df <- as.data.frame ( df , stringsAsFactors = FALSE )
for ( i in seq_len ( NCOL ( df ) ) ) {
col <- df [ , i ]
if ( is.list ( col ) ) {
col <- lapply ( col , function ( j ) trans ( j ) )
df [ , i ] <- list ( col )
} else {
if ( is.factor ( col ) ) {
levels ( col ) <- trans ( levels ( col ) )
} else if ( is.character ( col ) ) {
col <- trans ( col )
} else {
col
}
df [ , i ] <- col
}
}
df
}
2020-05-16 13:05:47 +02:00
2020-11-28 22:15:44 +01:00
# for eucast_rules() and mdro(), creates markdown output with URLs and names
2020-09-24 00:30:11 +02:00
create_ab_documentation <- function ( ab ) {
ab_names <- ab_name ( ab , language = NULL , tolower = TRUE )
ab <- ab [order ( ab_names ) ]
ab_names <- ab_names [order ( ab_names ) ]
atcs <- ab_atc ( ab )
atcs [ ! is.na ( atcs ) ] <- paste0 ( " [" , atcs [ ! is.na ( atcs ) ] , " ](" , ab_url ( ab [ ! is.na ( atcs ) ] ) , " )" )
atcs [is.na ( atcs ) ] <- " no ATC code"
out <- paste0 ( ab_names , " (`" , ab , " `, " , atcs , " )" , collapse = " , " )
substr ( out , 1 , 1 ) <- toupper ( substr ( out , 1 , 1 ) )
out
}
2021-02-04 16:48:16 +01:00
vector_or <- function ( v , quotes = TRUE , reverse = FALSE , sort = TRUE , last_sep = " or " ) {
2021-01-12 22:08:04 +01:00
# makes unique and sorts, and this also removed NAs
2021-02-04 16:48:16 +01:00
v <- unique ( v )
if ( isTRUE ( sort ) ) {
v <- sort ( v )
2021-01-12 22:08:04 +01:00
}
2021-02-04 16:48:16 +01:00
if ( isTRUE ( reverse ) ) {
2021-01-12 22:08:04 +01:00
v <- rev ( v )
}
2021-01-25 21:58:00 +01:00
if ( isTRUE ( quotes ) ) {
quotes <- ' "'
} else if ( isFALSE ( quotes ) ) {
quotes <- " "
2021-02-04 16:48:16 +01:00
} else {
quotes <- quotes [1L ]
}
if ( length ( v ) == 1 ) {
return ( paste0 ( quotes , v , quotes ) )
}
if ( identical ( v , c ( " I" , " R" , " S" ) ) ) {
# class <rsi> should be sorted like this
v <- c ( " R" , " S" , " I" )
2021-01-25 21:58:00 +01:00
}
2021-01-12 22:08:04 +01:00
# all commas except for last item, so will become '"val1", "val2", "val3" or "val4"'
2021-01-25 21:58:00 +01:00
paste0 ( paste0 ( quotes , v [seq_len ( length ( v ) - 1 ) ] , quotes , collapse = " , " ) ,
last_sep , paste0 ( quotes , v [length ( v ) ] , quotes ) )
2021-01-12 22:08:04 +01:00
}
2021-02-04 16:48:16 +01:00
vector_and <- function ( v , quotes = TRUE , reverse = FALSE , sort = TRUE ) {
vector_or ( v = v , quotes = quotes , reverse = reverse , sort = sort , last_sep = " and " )
}
2021-01-18 16:57:56 +01:00
format_class <- function ( class , plural ) {
class.bak <- class
2021-01-24 14:48:56 +01:00
class [class == " numeric" ] <- " number"
2021-01-18 16:57:56 +01:00
class [class == " integer" ] <- " whole number"
2021-01-24 14:48:56 +01:00
if ( all ( c ( " numeric" , " integer" ) %in% class.bak , na.rm = TRUE ) ) {
2021-01-18 16:57:56 +01:00
class [class %in% c ( " number" , " whole number" ) ] <- " (whole) number"
}
class [class == " character" ] <- " text string"
class [class %in% c ( " Date" , " POSIXt" ) ] <- " date"
class [class != class.bak ] <- paste0 ( ifelse ( plural , " " , " a " ) ,
class [class != class.bak ] ,
ifelse ( plural , " s" , " " ) )
# exceptions
class [class == " logical" ] <- ifelse ( plural , " a vector of `TRUE`/`FALSE`" , " `TRUE` or `FALSE`" )
if ( " data.frame" %in% class ) {
class <- " a data set"
}
if ( " list" %in% class ) {
class <- " a list"
}
if ( " matrix" %in% class ) {
class <- " a matrix"
}
2021-04-07 08:37:42 +02:00
if ( " custom_eucast_rules" %in% class ) {
class <- " input created with `custom_eucast_rules()`"
2021-01-22 10:55:07 +01:00
}
2021-03-08 09:44:17 +01:00
if ( any ( c ( " mo" , " ab" , " rsi" ) %in% class ) ) {
2021-01-22 10:55:07 +01:00
class <- paste0 ( " of class <" , class [1L ] , " >" )
2021-01-18 16:57:56 +01:00
}
2021-01-22 10:55:07 +01:00
class [class == class.bak ] <- paste0 ( " of class <" , class [class == class.bak ] , " >" )
2021-01-18 16:57:56 +01:00
# output
2021-03-08 09:44:17 +01:00
vector_or ( class , quotes = FALSE , sort = FALSE )
2021-01-18 16:57:56 +01:00
}
2020-10-19 17:09:19 +02:00
# a check for every single argument in all functions
meet_criteria <- function ( object ,
allow_class = NULL ,
has_length = NULL ,
looks_like = NULL ,
is_in = NULL ,
2021-01-24 14:48:56 +01:00
is_positive = NULL ,
2021-04-07 08:37:42 +02:00
is_positive_or_zero = NULL ,
2021-01-24 14:48:56 +01:00
is_finite = NULL ,
2020-10-19 17:09:19 +02:00
contains_column_class = NULL ,
allow_NULL = FALSE ,
allow_NA = FALSE ,
ignore.case = FALSE ,
.call_depth = 0 ) { # depth in calling
2020-10-26 12:23:03 +01:00
2020-10-19 17:09:19 +02:00
obj_name <- deparse ( substitute ( object ) )
call_depth <- -2 - abs ( .call_depth )
2021-01-24 14:48:56 +01:00
# if object is missing, or another error:
tryCatch ( invisible ( object ) ,
error = function ( e ) pkg_env $ meet_criteria_error_txt <- e $ message )
if ( ! is.null ( pkg_env $ meet_criteria_error_txt ) ) {
error_txt <- pkg_env $ meet_criteria_error_txt
pkg_env $ meet_criteria_error_txt <- NULL
2021-02-21 22:56:35 +01:00
stop ( error_txt , call. = FALSE ) # don't use stop_() here, pkg may not be loaded yet
2021-01-24 14:48:56 +01:00
}
pkg_env $ meet_criteria_error_txt <- NULL
2020-10-26 12:23:03 +01:00
2020-10-19 17:09:19 +02:00
if ( is.null ( object ) ) {
stop_if ( allow_NULL == FALSE , " argument `" , obj_name , " ` must not be NULL" , call = call_depth )
return ( invisible ( ) )
}
2021-01-04 12:29:25 +01:00
if ( is.null ( dim ( object ) ) && length ( object ) == 1 && suppressWarnings ( is.na ( object ) ) ) { # suppressWarnings for functions
2020-10-19 17:09:19 +02:00
stop_if ( allow_NA == FALSE , " argument `" , obj_name , " ` must not be NA" , call = call_depth )
return ( invisible ( ) )
}
2020-10-26 12:23:03 +01:00
2020-10-19 17:09:19 +02:00
if ( ! is.null ( allow_class ) ) {
2020-10-26 12:23:03 +01:00
stop_ifnot ( inherits ( object , allow_class ) , " argument `" , obj_name ,
2021-01-18 16:57:56 +01:00
" ` must be " , format_class ( allow_class , plural = isTRUE ( has_length > 1 ) ) ,
2021-01-22 10:55:07 +01:00
" , i.e. not be " , format_class ( class ( object ) , plural = isTRUE ( has_length > 1 ) ) ,
2020-10-19 17:09:19 +02:00
call = call_depth )
# check data.frames for data
if ( inherits ( object , " data.frame" ) ) {
2020-10-26 12:23:03 +01:00
stop_if ( any ( dim ( object ) == 0 ) ,
2020-10-19 17:09:19 +02:00
" the data provided in argument `" , obj_name ,
2020-10-26 12:23:03 +01:00
" ` must contain rows and columns (current dimensions: " ,
2020-12-01 16:59:57 +01:00
paste ( dim ( object ) , collapse = " x" ) , " )" ,
2020-10-19 17:09:19 +02:00
call = call_depth )
}
}
if ( ! is.null ( has_length ) ) {
2020-10-26 12:23:03 +01:00
stop_ifnot ( length ( object ) %in% has_length , " argument `" , obj_name ,
" ` must " , # ifelse(allow_NULL, "be NULL or must ", ""),
2020-10-19 17:09:19 +02:00
" be of length " , vector_or ( has_length , quotes = FALSE ) ,
" , not " , length ( object ) ,
call = call_depth )
}
if ( ! is.null ( looks_like ) ) {
2020-10-26 12:23:03 +01:00
stop_ifnot ( object %like% looks_like , " argument `" , obj_name ,
" ` must " , # ifelse(allow_NULL, "be NULL or must ", ""),
2020-10-19 17:09:19 +02:00
" resemble the regular expression \"" , looks_like , " \"" ,
call = call_depth )
}
if ( ! is.null ( is_in ) ) {
if ( ignore.case == TRUE ) {
object <- tolower ( object )
is_in <- tolower ( is_in )
}
2020-10-26 12:23:03 +01:00
stop_ifnot ( all ( object %in% is_in , na.rm = TRUE ) , " argument `" , obj_name ,
2020-10-19 17:09:19 +02:00
" ` must be " ,
2021-01-14 14:41:44 +01:00
ifelse ( ! is.null ( has_length ) && length ( has_length ) == 1 && has_length == 1 , " either " , " " ) ,
vector_or ( is_in , quotes = ! isTRUE ( any ( c ( " double" , " numeric" , " integer" ) %in% allow_class ) ) ) ,
2021-01-25 21:58:00 +01:00
ifelse ( allow_NA == TRUE , " , or NA" , " " ) ,
2020-10-19 17:09:19 +02:00
call = call_depth )
}
2021-04-12 12:35:13 +02:00
if ( isTRUE ( is_positive ) ) {
2021-01-24 14:48:56 +01:00
stop_if ( is.numeric ( object ) && ! all ( object > 0 , na.rm = TRUE ) , " argument `" , obj_name ,
" ` must " ,
ifelse ( ! is.null ( has_length ) && length ( has_length ) == 1 && has_length == 1 ,
2021-04-07 08:37:42 +02:00
" be a number higher than zero" ,
" all be numbers higher than zero" ) ,
call = call_depth )
}
2021-04-12 12:35:13 +02:00
if ( isTRUE ( is_positive_or_zero ) ) {
2021-04-07 08:37:42 +02:00
stop_if ( is.numeric ( object ) && ! all ( object >= 0 , na.rm = TRUE ) , " argument `" , obj_name ,
" ` must " ,
ifelse ( ! is.null ( has_length ) && length ( has_length ) == 1 && has_length == 1 ,
" be zero or a positive number" ,
" all be zero or numbers higher than zero" ) ,
2021-01-24 14:48:56 +01:00
call = call_depth )
}
2021-04-12 12:35:13 +02:00
if ( isTRUE ( is_finite ) ) {
2021-01-24 14:48:56 +01:00
stop_if ( is.numeric ( object ) && ! all ( is.finite ( object [ ! is.na ( object ) ] ) , na.rm = TRUE ) , " argument `" , obj_name ,
" ` must " ,
ifelse ( ! is.null ( has_length ) && length ( has_length ) == 1 && has_length == 1 ,
" be a finite number" ,
" all be finite numbers" ) ,
" (i.e., not be infinite)" ,
call = call_depth )
}
2020-10-19 17:09:19 +02:00
if ( ! is.null ( contains_column_class ) ) {
2020-12-28 22:24:33 +01:00
stop_ifnot ( any ( vapply ( FUN.VALUE = logical ( 1 ) ,
object ,
function ( col , columns_class = contains_column_class ) {
inherits ( col , columns_class )
} ) , na.rm = TRUE ) ,
2020-10-26 12:23:03 +01:00
" the data provided in argument `" , obj_name ,
2020-10-19 17:09:19 +02:00
" ` must contain at least one column of class <" , contains_column_class , " >. " ,
" See ?as." , contains_column_class , " ." ,
call = call_depth )
}
return ( invisible ( ) )
}
2020-12-07 16:06:42 +01:00
get_current_data <- function ( arg_name , call ) {
2021-01-12 22:08:04 +01:00
# try dplyr::cur_data_all() first to support dplyr groups
# only useful for e.g. dplyr::filter(), dplyr::mutate() and dplyr::summarise()
# not useful (throws error) with e.g. dplyr::select() - but that will be caught later in this function
cur_data_all <- import_fn ( " cur_data_all" , " dplyr" , error_on_fail = FALSE )
if ( ! is.null ( cur_data_all ) ) {
out <- tryCatch ( cur_data_all ( ) , error = function ( e ) NULL )
if ( is.data.frame ( out ) ) {
return ( out )
}
}
2021-04-23 09:59:36 +02:00
2021-01-04 13:39:06 +01:00
if ( as.double ( R.Version ( ) $ major ) + ( as.double ( R.Version ( ) $ minor ) / 10 ) < 3.2 ) {
2021-01-12 22:08:04 +01:00
# R-3.0 and R-3.1 do not have an `x` element in the call stack, rendering this function useless
2021-01-04 12:29:25 +01:00
if ( is.na ( arg_name ) ) {
2021-01-12 22:08:04 +01:00
# like in carbapenems() etc.
2021-01-04 12:29:25 +01:00
warning_ ( " this function can only be used in R >= 3.2" , call = call )
return ( data.frame ( ) )
} else {
2021-04-23 09:59:36 +02:00
# mimic a default R error, e.g. for example_isolates[which(mo_name() %like% "^ent"), ]
2021-01-04 12:29:25 +01:00
stop_ ( " argument `" , arg_name , " ` is missing with no default" , call = call )
}
}
2021-01-22 10:20:41 +01:00
2021-01-03 23:40:05 +01:00
# try a (base R) method, by going over the complete system call stack with sys.frames()
not_set <- TRUE
frms <- lapply ( sys.frames ( ) , function ( el ) {
2021-01-12 22:08:04 +01:00
if ( not_set == TRUE && " .Generic" %in% names ( el ) ) {
if ( tryCatch ( " .data" %in% names ( el ) && is.data.frame ( el $ `.data` ) , error = function ( e ) FALSE ) ) {
2021-04-23 09:59:36 +02:00
# - - - -
# dplyr
# - - - -
# an element `.data` will be in the system call stack when using dplyr::select()
# [but not when using dplyr::filter(), dplyr::mutate() or dplyr::summarise()]
2021-01-03 23:40:05 +01:00
not_set <<- FALSE
2021-01-04 12:29:25 +01:00
el $ `.data`
2021-01-12 22:08:04 +01:00
} else if ( tryCatch ( any ( c ( " x" , " xx" ) %in% names ( el ) ) , error = function ( e ) FALSE ) ) {
2021-04-23 09:59:36 +02:00
# - - - -
# base R
# - - - -
2021-01-04 12:29:25 +01:00
# an element `x` will be in this environment for only cols, e.g. `example_isolates[, carbapenems()]`
# an element `xx` will be in this environment for rows + cols, e.g. `example_isolates[c(1:3), carbapenems()]`
if ( tryCatch ( is.data.frame ( el $ xx ) , error = function ( e ) FALSE ) ) {
not_set <<- FALSE
el $ xx
} else if ( tryCatch ( is.data.frame ( el $ x ) ) ) {
not_set <<- FALSE
el $ x
} else {
NULL
}
2021-01-03 23:40:05 +01:00
} else {
NULL
}
} else {
NULL
}
} )
2021-01-22 10:20:41 +01:00
2021-04-23 09:59:36 +02:00
# lookup the matched frame and return its value: a data.frame
2021-01-03 23:40:05 +01:00
vars_df <- tryCatch ( frms [ [which ( ! vapply ( FUN.VALUE = logical ( 1 ) , frms , is.null ) ) ] ] , error = function ( e ) NULL )
if ( is.data.frame ( vars_df ) ) {
return ( vars_df )
}
# nothing worked, so:
if ( is.na ( arg_name ) ) {
2021-02-08 14:18:42 +01:00
if ( isTRUE ( is.numeric ( call ) ) ) {
fn <- as.character ( sys.call ( call + 1 ) [1 ] )
examples <- paste0 ( " , e.g.:\n" ,
" your_data %>% select(" , fn , " ())\n" ,
" your_data %>% select(column_a, column_b, " , fn , " ())\n" ,
" your_data[, " , fn , " ()]\n" ,
' your_data[, c("column_a", "column_b", ' , fn , " ())]" )
} else {
examples <- " "
}
stop_ ( " this function must be used inside valid dplyr selection verbs or inside a data.frame call" ,
examples ,
2021-01-03 23:40:05 +01:00
call = call )
} else {
2021-01-12 22:08:04 +01:00
stop_ ( " argument `" , arg_name , " ` is missing with no default" , call = call )
2021-01-03 23:40:05 +01:00
}
2020-12-07 16:06:42 +01:00
}
2021-02-02 23:57:35 +01:00
get_current_column <- function ( ) {
# try dplyr::cur_columns() first
cur_column <- import_fn ( " cur_column" , " dplyr" , error_on_fail = FALSE )
if ( ! is.null ( cur_column ) ) {
out <- tryCatch ( cur_column ( ) , error = function ( e ) NULL )
if ( ! is.null ( out ) ) {
return ( out )
}
}
# cur_column() doesn't always work (only allowed for conditions set by dplyr), but it's probably still possible:
frms <- lapply ( sys.frames ( ) , function ( el ) {
if ( " i" %in% names ( el ) ) {
if ( " tibble_vars" %in% names ( el ) ) {
# for mutate_if()
el $ tibble_vars [el $ i ]
} else {
# for mutate(across())
df <- tryCatch ( get_current_data ( NA , 0 ) , error = function ( e ) NULL )
if ( is.data.frame ( df ) ) {
colnames ( df ) [el $ i ]
} else {
el $ i
}
}
} else {
NULL
}
} )
vars <- unlist ( frms )
if ( length ( vars ) > 0 ) {
vars [length ( vars ) ]
} else {
# not found, so:
NULL
}
}
2021-02-08 14:18:42 +01:00
is_null_or_grouped_tbl <- function ( x ) {
# attribute "grouped_df" might change at one point, so only set in one place; here.
2021-02-08 21:09:36 +01:00
is.null ( x ) || inherits ( x , " grouped_df" )
2021-02-08 14:18:42 +01:00
}
2021-01-03 23:40:05 +01:00
unique_call_id <- function ( entire_session = FALSE ) {
if ( entire_session == TRUE ) {
c ( envir = " session" ,
call = " session" )
} else {
# combination of environment ID (like "0x7fed4ee8c848")
# and highest system call
c ( envir = gsub ( " <environment: (.*)>" , " \\1" , utils :: capture.output ( sys.frames ( ) [ [1 ] ] ) ) ,
call = paste0 ( deparse ( sys.calls ( ) [ [1 ] ] ) , collapse = " " ) )
}
2020-12-24 23:29:10 +01:00
}
2021-01-03 23:40:05 +01:00
remember_thrown_message <- function ( fn , entire_session = FALSE ) {
2020-12-27 00:07:00 +01:00
# this is to prevent that messages/notes will be printed for every dplyr group
# e.g. this would show a msg 4 times: example_isolates %>% group_by(hospital_id) %>% filter(mo_is_gram_negative())
2021-01-12 22:08:04 +01:00
assign ( x = paste0 ( " thrown_msg." , fn ) ,
2021-01-03 23:40:05 +01:00
value = unique_call_id ( entire_session = entire_session ) ,
2020-12-27 00:07:00 +01:00
envir = pkg_env )
2020-12-24 23:29:10 +01:00
}
2021-01-03 23:40:05 +01:00
message_not_thrown_before <- function ( fn , entire_session = FALSE ) {
2021-01-12 22:08:04 +01:00
is.null ( pkg_env [ [paste0 ( " thrown_msg." , fn ) ] ] ) || ! identical ( pkg_env [ [paste0 ( " thrown_msg." , fn ) ] ] , unique_call_id ( entire_session ) )
2020-12-27 00:07:00 +01:00
}
reset_all_thrown_messages <- function ( ) {
# for unit tests, where the environment and highest system call do not change
pkg_env_contents <- ls ( envir = pkg_env )
2021-01-12 22:08:04 +01:00
rm ( list = pkg_env_contents [pkg_env_contents %like% " ^thrown_msg." ] ,
2020-12-27 00:07:00 +01:00
envir = pkg_env )
2020-12-24 23:29:10 +01:00
}
2020-06-17 15:14:37 +02:00
has_colour <- function ( ) {
2020-12-27 00:07:00 +01:00
# this is a base R version of crayon::has_color, but disables colours on emacs
if ( Sys.getenv ( " EMACS" ) != " " || Sys.getenv ( " INSIDE_EMACS" ) != " " ) {
2021-01-03 23:40:05 +01:00
# disable on emacs, which only supports 8 colours
2020-12-27 00:07:00 +01:00
return ( FALSE )
}
2020-06-05 13:56:05 +02:00
enabled <- getOption ( " crayon.enabled" )
if ( ! is.null ( enabled ) ) {
return ( isTRUE ( enabled ) )
}
rstudio_with_ansi_support <- function ( x ) {
if ( Sys.getenv ( " RSTUDIO" , " " ) == " " ) {
return ( FALSE )
}
if ( ( cols <- Sys.getenv ( " RSTUDIO_CONSOLE_COLOR" , " " ) ) != " " && ! is.na ( as.numeric ( cols ) ) ) {
return ( TRUE )
}
tryCatch ( get ( " isAvailable" , envir = asNamespace ( " rstudioapi" ) ) ( ) , error = function ( e ) return ( FALSE ) ) &&
tryCatch ( get ( " hasFun" , envir = asNamespace ( " rstudioapi" ) ) ( " getConsoleHasColor" ) , error = function ( e ) return ( FALSE ) )
}
if ( rstudio_with_ansi_support ( ) && sink.number ( ) == 0 ) {
return ( TRUE )
}
if ( ! isatty ( stdout ( ) ) ) {
2020-05-16 13:05:47 +02:00
return ( FALSE )
}
if ( tolower ( Sys.info ( ) [ " sysname" ] ) == " windows" ) {
2020-06-05 13:56:05 +02:00
if ( Sys.getenv ( " ConEmuANSI" ) == " ON" ) {
2020-05-16 13:05:47 +02:00
return ( TRUE )
}
2020-06-05 13:56:05 +02:00
if ( Sys.getenv ( " CMDER_ROOT" ) != " " ) {
return ( TRUE )
}
return ( FALSE )
}
if ( " COLORTERM" %in% names ( Sys.getenv ( ) ) ) {
return ( TRUE )
}
if ( Sys.getenv ( " TERM" ) == " dumb" ) {
return ( FALSE )
}
2020-10-26 12:23:03 +01:00
grepl ( pattern = " ^screen|^xterm|^vt100|color|ansi|cygwin|linux" ,
2020-06-05 13:56:05 +02:00
x = Sys.getenv ( " TERM" ) ,
ignore.case = TRUE ,
perl = TRUE )
2020-05-16 13:05:47 +02:00
}
2020-12-07 16:06:42 +01:00
# set colours if console has_colour()
2020-05-16 13:05:47 +02:00
try_colour <- function ( ... , before , after , collapse = " " ) {
txt <- paste0 ( unlist ( list ( ... ) ) , collapse = collapse )
if ( isTRUE ( has_colour ( ) ) ) {
if ( is.null ( collapse ) ) {
paste0 ( before , txt , after , collapse = NULL )
} else {
paste0 ( before , txt , after , collapse = " " )
}
} else {
txt
}
}
font_black <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[38;5;232m" , after = " \033[39m" , collapse = collapse )
}
font_blue <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[34m" , after = " \033[39m" , collapse = collapse )
}
font_green <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[32m" , after = " \033[39m" , collapse = collapse )
}
font_magenta <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[35m" , after = " \033[39m" , collapse = collapse )
}
font_red <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[31m" , after = " \033[39m" , collapse = collapse )
}
font_silver <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[90m" , after = " \033[39m" , collapse = collapse )
}
font_white <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[37m" , after = " \033[39m" , collapse = collapse )
}
font_yellow <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[33m" , after = " \033[39m" , collapse = collapse )
}
font_subtle <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[38;5;246m" , after = " \033[39m" , collapse = collapse )
}
font_grey <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[38;5;249m" , after = " \033[39m" , collapse = collapse )
}
2020-12-01 16:59:57 +01:00
font_grey_bg <- function ( ... , collapse = " " ) {
2021-02-21 22:56:35 +01:00
if ( tryCatch ( rstudioapi :: getThemeInfo ( ) $ dark == TRUE , error = function ( e ) FALSE ) ) {
# similar to HTML #444444
try_colour ( ... , before = " \033[48;5;238m" , after = " \033[49m" , collapse = collapse )
} else {
# similar to HTML #eeeeee
try_colour ( ... , before = " \033[48;5;254m" , after = " \033[49m" , collapse = collapse )
}
2020-12-01 16:59:57 +01:00
}
2020-05-16 13:05:47 +02:00
font_green_bg <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[42m" , after = " \033[49m" , collapse = collapse )
}
2020-12-27 00:07:00 +01:00
font_rsi_R_bg <- function ( ... , collapse = " " ) {
2021-02-25 10:33:08 +01:00
#ED553B
try_colour ( ... , before = " \033[48;5;203m" , after = " \033[49m" , collapse = collapse )
2020-12-27 00:07:00 +01:00
}
font_rsi_S_bg <- function ( ... , collapse = " " ) {
2021-02-25 10:33:08 +01:00
#3CAEA3
try_colour ( ... , before = " \033[48;5;79m" , after = " \033[49m" , collapse = collapse )
2020-12-27 00:07:00 +01:00
}
font_rsi_I_bg <- function ( ... , collapse = " " ) {
2021-02-25 10:33:08 +01:00
#F6D55C
try_colour ( ... , before = " \033[48;5;222m" , after = " \033[49m" , collapse = collapse )
2020-12-27 00:07:00 +01:00
}
2020-05-16 13:05:47 +02:00
font_red_bg <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[41m" , after = " \033[49m" , collapse = collapse )
}
font_yellow_bg <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[43m" , after = " \033[49m" , collapse = collapse )
}
2020-08-28 21:55:47 +02:00
font_na <- function ( ... , collapse = " " ) {
font_red ( ... , collapse = collapse )
}
2020-05-16 13:05:47 +02:00
font_bold <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[1m" , after = " \033[22m" , collapse = collapse )
}
font_italic <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[3m" , after = " \033[23m" , collapse = collapse )
}
font_underline <- function ( ... , collapse = " " ) {
try_colour ( ... , before = " \033[4m" , after = " \033[24m" , collapse = collapse )
}
font_stripstyle <- function ( x ) {
# from crayon:::ansi_regex
gsub ( " (?:(?:\\x{001b}\\[)|\\x{009b})(?:(?:[0-9]{1,3})?(?:(?:;[0-9]{0,3})*)?[A-M|f-m])|\\x{001b}[A-M]" , " " , x , perl = TRUE )
}
2021-04-20 10:46:17 +02:00
progress_ticker <- function ( n = 1 , n_min = 0 , print = TRUE , ... ) {
if ( print == FALSE || n < n_min ) {
2020-05-16 13:05:47 +02:00
pb <- list ( )
pb $ tick <- function ( ) {
invisible ( )
}
pb $ kill <- function ( ) {
invisible ( )
}
2020-11-16 16:57:55 +01:00
set_clean_class ( pb , new_class = " txtProgressBar" )
2020-07-29 10:33:47 +02:00
} else if ( n >= n_min ) {
pb <- utils :: txtProgressBar ( max = n , style = 3 )
pb $ tick <- function ( ) {
pb $ up ( pb $ getVal ( ) + 1 )
}
pb
2020-05-16 13:05:47 +02:00
}
}
2020-11-16 16:57:55 +01:00
set_clean_class <- function ( x , new_class ) {
2020-12-07 16:06:42 +01:00
# return the object with only the new class and no additional attributes where possible
2020-11-16 16:57:55 +01:00
if ( is.null ( x ) ) {
x <- NA_character_
}
if ( is.factor ( x ) ) {
2020-12-07 16:06:42 +01:00
# keep only levels and remove all other attributes
2020-11-16 16:57:55 +01:00
lvls <- levels ( x )
attributes ( x ) <- NULL
levels ( x ) <- lvls
2020-11-16 20:02:20 +01:00
} else if ( ! is.list ( x ) && ! is.function ( x ) ) {
2020-11-16 16:57:55 +01:00
attributes ( x ) <- NULL
}
class ( x ) <- new_class
x
}
2020-12-17 16:22:25 +01:00
formatted_filesize <- function ( ... ) {
size_kb <- file.size ( ... ) / 1024
if ( size_kb < 1 ) {
paste ( round ( size_kb , 1 ) , " kB" )
} else if ( size_kb < 100 ) {
paste ( round ( size_kb , 0 ) , " kB" )
} else {
paste ( round ( size_kb / 1024 , 1 ) , " MB" )
}
}
2020-08-28 21:55:47 +02:00
create_pillar_column <- function ( x , ... ) {
2021-03-07 13:52:39 +01:00
new_pillar_shaft_simple <- import_fn ( " new_pillar_shaft_simple" , " pillar" )
new_pillar_shaft_simple ( x , ... )
2020-08-28 21:55:47 +02:00
}
2020-09-12 08:49:01 +02:00
# copied from vctrs::s3_register by their permission:
# https://github.com/r-lib/vctrs/blob/05968ce8e669f73213e3e894b5f4424af4f46316/R/register-s3.R
2020-08-28 21:55:47 +02:00
s3_register <- function ( generic , class , method = NULL ) {
stopifnot ( is.character ( generic ) , length ( generic ) == 1 )
stopifnot ( is.character ( class ) , length ( class ) == 1 )
pieces <- strsplit ( generic , " ::" ) [ [1 ] ]
stopifnot ( length ( pieces ) == 2 )
package <- pieces [ [1 ] ]
generic <- pieces [ [2 ] ]
caller <- parent.frame ( )
get_method_env <- function ( ) {
top <- topenv ( caller )
if ( isNamespace ( top ) ) {
asNamespace ( environmentName ( top ) )
}
else {
caller
}
}
get_method <- function ( method , env ) {
if ( is.null ( method ) ) {
get ( paste0 ( generic , " ." , class ) , envir = get_method_env ( ) )
}
else {
method
}
}
method_fn <- get_method ( method )
stopifnot ( is.function ( method_fn ) )
setHook ( packageEvent ( package , " onLoad" ) , function ( ... ) {
ns <- asNamespace ( package )
method_fn <- get_method ( method )
registerS3method ( generic , class , method_fn , envir = ns )
} )
if ( ! isNamespaceLoaded ( package ) ) {
return ( invisible ( ) )
}
envir <- asNamespace ( package )
if ( exists ( generic , envir ) ) {
registerS3method ( generic , class , method_fn , envir = envir )
}
invisible ( )
}
2020-06-22 11:18:40 +02:00
# works exactly like round(), but rounds `round2(44.55, 1)` to 44.6 instead of 44.5
2020-05-16 13:05:47 +02:00
# and adds decimal zeroes until `digits` is reached when force_zero = TRUE
round2 <- function ( x , digits = 0 , force_zero = TRUE ) {
x <- as.double ( x )
# https://stackoverflow.com/a/12688836/4575331
val <- ( trunc ( ( abs ( x ) * 10 ^ digits ) + 0.5 ) / 10 ^ digits ) * sign ( x )
if ( digits > 0 & force_zero == TRUE ) {
values_trans <- val [val != as.integer ( val ) & ! is.na ( val ) ]
val [val != as.integer ( val ) & ! is.na ( val ) ] <- paste0 ( values_trans ,
2020-10-26 12:23:03 +01:00
strrep ( " 0" ,
max ( 0 ,
2020-05-16 13:05:47 +02:00
digits - nchar (
format (
as.double (
2020-10-26 12:23:03 +01:00
gsub ( " .*[.](.*)$" ,
2020-05-16 13:05:47 +02:00
" \\1" ,
values_trans ) ) ,
scientific = FALSE ) ) ) ) )
}
as.double ( val )
}
# percentage from our other package: 'cleaner'
percentage <- function ( x , digits = NULL , ... ) {
2020-10-26 12:23:03 +01:00
2020-05-16 13:05:47 +02:00
# getdecimalplaces() function
getdecimalplaces <- function ( x , minimum = 0 , maximum = 3 ) {
if ( maximum < minimum ) {
maximum <- minimum
}
if ( minimum > maximum ) {
minimum <- maximum
}
2020-10-26 12:23:03 +01:00
max_places <- max ( unlist ( lapply ( strsplit ( sub ( " 0+$" , " " ,
2020-05-16 13:05:47 +02:00
as.character ( x * 100 ) ) , " ." , fixed = TRUE ) ,
function ( y ) ifelse ( length ( y ) == 2 , nchar ( y [2 ] ) , 0 ) ) ) , na.rm = TRUE )
max ( min ( max_places ,
maximum , na.rm = TRUE ) ,
minimum , na.rm = TRUE )
}
2020-10-26 12:23:03 +01:00
2020-05-16 13:05:47 +02:00
# format_percentage() function
format_percentage <- function ( x , digits = NULL , ... ) {
if ( is.null ( digits ) ) {
digits <- getdecimalplaces ( x )
}
2020-10-26 12:23:03 +01:00
2020-05-16 13:05:47 +02:00
# round right: percentage(0.4455) and format(as.percentage(0.4455), 1) should return "44.6%", not "44.5%"
x_formatted <- format ( round2 ( as.double ( x ) , digits = digits + 2 ) * 100 ,
scientific = FALSE ,
digits = digits ,
nsmall = digits ,
... )
x_formatted <- paste0 ( x_formatted , " %" )
x_formatted [ ! grepl ( pattern = " ^[0-9.,e-]+$" , x = x ) ] <- NA_character_
x_formatted
}
2020-10-26 12:23:03 +01:00
2020-05-16 13:05:47 +02:00
# the actual working part
x <- as.double ( x )
if ( is.null ( digits ) ) {
# max one digit if undefined
digits <- getdecimalplaces ( x , minimum = 0 , maximum = 1 )
}
format_percentage ( structure ( .Data = as.double ( x ) ,
class = c ( " percentage" , " numeric" ) ) ,
digits = digits , ... )
}
2020-05-18 13:59:34 +02:00
2021-02-21 20:15:09 +01:00
time_start_tracking <- function ( ) {
pkg_env $ time_start <- round ( as.numeric ( Sys.time ( ) ) * 1000 )
}
time_track <- function ( name = NULL ) {
paste ( " (until now:" , trimws ( round ( as.numeric ( Sys.time ( ) ) * 1000 ) - pkg_env $ time_start ) , " ms)" )
}
2021-04-12 14:24:40 +02:00
# prevent dependency on package 'backports' ----
# these functions were not available in previous versions of R (last checked: R 4.0.5)
2020-05-19 12:08:49 +02:00
# see here for the full list: https://github.com/r-lib/backports
2020-05-19 13:18:01 +02:00
strrep <- function ( x , times ) {
x <- as.character ( x )
2020-10-26 12:23:03 +01:00
if ( length ( x ) == 0L )
2020-05-18 13:59:34 +02:00
return ( x )
unlist ( .mapply ( function ( x , times ) {
2020-10-26 12:23:03 +01:00
if ( is.na ( x ) || is.na ( times ) )
2020-05-18 13:59:34 +02:00
return ( NA_character_ )
2020-10-26 12:23:03 +01:00
if ( times <= 0L )
2020-05-18 13:59:34 +02:00
return ( " " )
paste0 ( replicate ( times , x ) , collapse = " " )
} , list ( x = x , times = times ) , MoreArgs = list ( ) ) , use.names = FALSE )
}
2020-05-19 13:18:01 +02:00
trimws <- function ( x , which = c ( " both" , " left" , " right" ) ) {
which <- match.arg ( which )
mysub <- function ( re , x ) sub ( re , " " , x , perl = TRUE )
2020-10-26 12:23:03 +01:00
if ( which == " left" )
2020-05-18 13:59:34 +02:00
return ( mysub ( " ^[ \t\r\n]+" , x ) )
2020-10-26 12:23:03 +01:00
if ( which == " right" )
2020-05-18 13:59:34 +02:00
return ( mysub ( " [ \t\r\n]+$" , x ) )
mysub ( " [ \t\r\n]+$" , mysub ( " ^[ \t\r\n]+" , x ) )
}
2020-05-19 13:18:01 +02:00
isFALSE <- function ( x ) {
2020-05-18 13:59:34 +02:00
is.logical ( x ) && length ( x ) == 1L && ! is.na ( x ) && ! x
}
2020-05-19 13:18:01 +02:00
deparse1 <- function ( expr , collapse = " " , width.cutoff = 500L , ... ) {
2020-05-19 12:08:49 +02:00
paste ( deparse ( expr , width.cutoff , ... ) , collapse = collapse )
}
2020-08-26 15:34:12 +02:00
file.size <- function ( ... ) {
2020-09-03 12:31:48 +02:00
file.info ( ... ) $ size
2020-08-16 21:38:42 +02:00
}
2020-08-26 15:34:12 +02:00
file.mtime <- function ( ... ) {
2020-09-03 12:31:48 +02:00
file.info ( ... ) $ mtime
}
str2lang <- function ( s ) {
stopifnot ( length ( s ) == 1L )
2020-09-14 12:21:23 +02:00
ex <- parse ( text = s , keep.source = FALSE )
2020-09-03 12:31:48 +02:00
stopifnot ( length ( ex ) == 1L )
ex [ [1L ] ]
2020-08-16 21:38:42 +02:00
}
2020-10-08 11:16:03 +02:00
isNamespaceLoaded <- function ( pkg ) {
pkg %in% loadedNamespaces ( )
}
2020-12-22 00:51:17 +01:00
lengths <- function ( x , use.names = TRUE ) {
2020-12-17 16:22:25 +01:00
vapply ( x , length , FUN.VALUE = NA_integer_ , USE.NAMES = use.names )
}
2021-04-12 14:24:40 +02:00
if ( as.double ( R.Version ( ) $ major ) + ( as.double ( R.Version ( ) $ minor ) / 10 ) < 3.1 ) {
# R-3.0 does not contain these functions, set them here to prevent installation failure
2021-04-23 09:59:36 +02:00
# (required for extension of the <mic> class)
2021-04-12 14:24:40 +02:00
cospi <- function ( ... ) 1
sinpi <- function ( ... ) 1
tanpi <- function ( ... ) 1
}