R/FSA-internals.R

Defines functions iCheckConfLevel WARN STOP iTypeoflm is.wholenumber iPlotExists iMakeColor iListSpecies iLegendHelp iHndlMultWhat iHndlFormula iHndlCols2UseIgnore iGetVarFromFormula iGetDecimals iCILabel iCheckMultColor iCheckStartcatW iCheckStartcat iCheckW iCheckALK iAddLoessLine .onAttach

Documented in iAddLoessLine iCheckALK iCheckMultColor iCheckStartcatW iCILabel iGetDecimals iGetVarFromFormula iHndlCols2UseIgnore iHndlFormula iHndlMultWhat iLegendHelp iListSpecies iMakeColor iPlotExists is.wholenumber iTypeoflm .onAttach STOP WARN

#' @title Internal functions.
#'
#' @description Internal functions that are common to several functions in FSA.
#'
#' @rdname FSA-internals
#' @keywords internal
#' @aliases .onAttach iAddLoessLine iCheckALK iCheckMultColor iCheckStartcatW iCILabel iGetDecimals  iGetVarFromFormula iHndlCols2UseIgnore iHndlFormula iHndlMultWhat iLegendHelp iListSpecies iMakeColor iPlotExists is.wholenumber iTypeoflm STOP WARN


# Sends a start-up message to the console when the package is loaded.
.onAttach <- function(lib,pkg,...) {
  vers <- read.dcf(system.file("DESCRIPTION",package=pkg,lib.loc=lib),
                   fields="Version")
  msg <- paste0("## FSA v",vers,". See citation('FSA') if used in publication.\n")
  msg <- paste0(msg,"## Run fishR() for related website and fishR('IFAR') for related book.")    
  packageStartupMessage(msg)
}


iAddLoessLine <- function(r,fv,lty.loess,lwd.loess,col.loess,
                          trans.loess,span=0.75) {
  suppressWarnings(mdl <- stats::loess(r~fv,span=span))
  xseq <- seq(from=min(fv),to=max(fv),length=80)
  pred <- stats::predict(mdl,newdata=data.frame(fv=xseq),se=TRUE)
  graphics::polygon(c(xseq,rev(xseq)),
          c(pred$fit-pred$se.fit*stats::qt(0.975,pred$df),
            rev(pred$fit+pred$se.fit*stats::qt(0.975,pred$df))),
          col=col2rgbt(col.loess,trans.loess),border=NA,xpd=FALSE)
  graphics::lines(pred$fit~xseq,lwd=lwd.loess,lty=lty.loess,col=col.loess,
                  xpd=FALSE)
}  # end iAddLoessLine internal function


iCheckALK <- function(key,only1=FALSE,remove0rows=FALSE) {
  #### only1=TRUE ... only check if rows sum to 1, otherwise also check if sum to 0
  #### remove0rows=TRUE ... remove the rows that sum to 0.
  
  ## Check that row names and column names can be considered as numeric
  suppressWarnings(tmp <- as.numeric(rownames(key)))
  if (any(is.na(tmp))) STOP("The row names of 'key' must be numeric (and\n",
                            "contain the minimum value of the lenth intervals).")
  suppressWarnings(tmp <- as.numeric(colnames(key)))
  if (any(is.na(tmp))) STOP("The column names of 'key' must be numeric\n",
                            "(and contain age values).")  
  ## Check if key is proportions, if not change to proportions
  if (any(key>1,na.rm=TRUE)) {
    WARN("'key' contained values >1; to continue, assumed\n",
         "values were frequencies and converted to row proportions.")
    key <- prop.table(key,margin=1)
  }
  ## Remove rows that sum to 0 or NA (i.e., only keeps lens with data in key)
  key.rowSum <- rowSums(key,na.rm=TRUE)
  if (remove0rows & any(key.rowSum==0)) {
    WARN("'key' contained rows that sum to 0; as requested,\n",
         "these rows were removed from the table.")
    key <- key[!is.na(key.rowSum) & key.rowSum!=0,]
  }
  ## Check if rows sum to 1 (allows for some minimal rounding error and
  ##   does not consider zeroes )
  if (any(key.rowSum>0.01 & (key.rowSum<0.99 | key.rowSum>1.01)))
    WARN("Key contained a row that does not sum to 1.")
  ## Check if rows sum to 0 (allow for some minimal rounding error)
  if (!only1 & !remove0rows & any(key.rowSum==0))
    WARN("Key contained rows that sum to 0.")
  ## Return the potentially modified key
  key
}


iCheckW <- function(w) {
  if (!is.numeric(w)) STOP("'w' must be numeric.")
  if (length(w)>1) STOP("'w' must be a single value.")
  if (w<=0) STOP("'w' must be positive.")
  # returns decimals of w
  iGetDecimals(w)
}

iCheckStartcat <- function(startcat,w,d) {
  if (!is.numeric(startcat)) STOP("'startcat' must be numeric.")
  if (length(startcat)>1) STOP("'startcat' must be a single value.")
  if (startcat<0) STOP("'startcat' must be non-negative.")
  if (round(min(d,na.rm=TRUE),w) < round(startcat,w))
    STOP("'startcat' is larger than the minimum observation.")
  # return decimals of w
  iGetDecimals(startcat)
}

iCheckStartcatW <- function(startcat,w,d) {
  wdec <- iCheckW(w)
  scdec <- iCheckStartcat(startcat,w,d)
  # does w have more than (or equal) decimals as startcat 
  if (scdec>wdec) STOP("'startcat' should not have more decimals than 'w'.",
                       call.=FALSE)
  # return decimals
  list(scdec=scdec,wdec=wdec)
}


# Creates a vector of default multiple colors if col==NULL. If col is in 
# hcl.pals() then it will create default colors based on that palette,
# Otherwise checks if the provided vector of colors is the same length as n.
iCheckMultColor <- function(col,num) {
  # Function to check if all items are valid colors. This is largely from ...
  #   https://stackoverflow.com/questions/13289009/check-if-character-string-is-a-valid-color-representation/13290832#13290832
  iAreColors <- function(x) {
    out <- sapply(x,function(x) {
      tryCatch(is.matrix(grDevices::col2rgb(x)),error=function(e) FALSE)
    })
    if(any(is.na(names(out)))) out[is.na(names(out))] <-  FALSE
    out
  }
  
  ## Main function
  if (is.null(col)) col <- grDevices::hcl.colors(num)
  if (length(col)==1) {
    if (col %in% grDevices::hcl.pals())
      col <- grDevices::hcl.colors(num,palette=col)
  }
  colcheck <- iAreColors(col)
  if (any(!colcheck))
    STOP("Your col='",col[!colcheck],
         "' is not a valid color or palette in 'hcl.pals()'.")
  if (length(col) < num)
    WARN("Number of colors (",length(col),
         ") is less than number of ages (",num,
         "); colors will be recycled")
  else if (length(col) > num)
    WARN("Number of colors (",length(col),
         ") is more than number of ages (",num,
         "); some colors will not be used")
  col
}


iCILabel <- function(conf.level,digits=1)
  paste(paste0(round(100*conf.level,digits),"%"),c("LCI","UCI"))



# Returns number of decimal places in a number
# Completely from Peter Savicky in http://r.789695.n4.nabble.com/number-of-decimal-places-in-a-number-td4635697.html
iGetDecimals <- function(x) {
  if (!is.numeric(x)) STOP("'x' must be numeric.")
  if (length(x)>1) STOP("'x' must be a single value.")
  if (is.integer(x)) 0
  else {
    tmp <- format.info(x,digits=10)
    if (tmp[3]!=0)
      WARN("'x' will be presented in exponential notation.\nReturned decimals may not be what you expected.")
    tmp[2]
  }
}


iGetVarFromFormula <- function(formula,data,expNumVars=NULL) {
  varNms <- names(stats::model.frame(formula,data=data))
  # don't "error" check the number of variables
  if (is.null(expNumVars)) varNms
  else if (length(varNms)!=expNumVars)
    STOP("Function only works with formulas with ",expNumVars," variable",
         ifelse(expNumVars==1,".","s."))
  else varNms
}


iHndlCols2UseIgnore <- function(df,cols2use=NULL,cols2ignore=NULL) {
  iHndlCols2Use <- function(df,cols2use) {
    if (!inherits(cols2use,c("integer","numeric","character")))
      STOP("'cols2use' must be a numeric index or column name.")
    if (is.character(cols2use)) {
      ## Convert character column names to numeric
      cols2use <- which(names(df) %in% cols2use)
      if (length(cols2use)==0)
        STOP("None of columns in 'cols2use' exist in 'df'.")
    } else { ## numeric column choices
      if (any(cols2use<=0) & any(cols2use>=0))
        STOP("'cols2use' must be all positive or all negative.")
      if (any(abs(cols2use)>ncol(df)))
        STOP("Some 'cols2use' do not exist in 'df'.")
    }
    cols2use
  }
  iHndlCols2Ignore <- function(df,col2ignore) {
    if (!inherits(cols2ignore,c("integer","numeric","character")))
      STOP("'cols2ignore' must be a numeric index or column name.")
    if (is.character(cols2ignore)) {
      ## Convert character column names to numeric
      cols2ignore <- which(names(df) %in% cols2ignore)
      if (length(cols2ignore)==0)
        STOP("None of columns in 'cols2ignore' exist in 'df'.")
    } else {
      if (any(cols2ignore<=0) & any(cols2ignore>=0))
        STOP("'cols2ignore' must be all positive or all negative.")
      cols2ignore <- abs(cols2ignore)
      if (any(cols2ignore>ncol(df)))
        STOP("Some 'cols2ignore' do not exist in 'df'.")
    }    
    -cols2ignore
  }

  ## if both cols2use and cols2ignore are NULL, return the original df
  if (is.null(cols2use) & is.null(cols2ignore)) ind <- seq_len(ncol(df))
  else if (!is.null(cols2use) & !is.null(cols2ignore))
    STOP("Cannot use both 'cols2use' and 'cols2ignore'.")
  else if (!is.null(cols2use)) ind <- iHndlCols2Use(df,cols2use)
  else ind <- iHndlCols2Ignore(df,cols2ignore)
  ## Return data.frame of only columns asked for
  res <- df[,ind,drop=FALSE]
  if (ncol(res)==0) WARN("Resultant data.frame contains no columns.")
  res
}


iHndlFormula <- function(formula,data,expNumR=NULL,
                         expNumE=NULL,expNumENums=NULL,expNumEFacts=NULL) {
  mf <- stats::model.frame(formula,data=data,na.action=NULL)
  if (ncol(mf)==1) {
    # One variable. Return only model.frame, name of variable, and it's
    # class; but handle an odd case where the item is an array by
    # returning the mode
    return(list(mf=mf,vnum=1,vname=names(mf),
                vclass=ifelse(is.array(mf[,1]),mode(mf[,1]),class(mf[,1]))))
  } else {
    # More than one variable in the formula.
    # Must identify if there is a LHS.
    ifelse(attr(stats::terms(formula),"response")==0,
           LHS <- FALSE,LHS <- TRUE)
    # See if more than one variable on LHS
    if (LHS) {
      fcLHS <- as.character(formula)[2]
      ifelse(any(c("*","+") %in% substring(fcLHS,seq_len(nchar(fcLHS)),
                                           seq_len(nchar(fcLHS)))),
             LHSgt1 <- TRUE, LHSgt1 <- FALSE)
      # STOP if there is more than one variable on LHS
      if (LHSgt1)
        STOP("Function does not work with more than one variable on the LHS.")
      else {
        # There is a LHS and it has only one variable.
        Rpos <- Rnum <- 1
        Rname <- names(mf)[Rpos]
        Rmf <- mf[,Rpos]
        Rclass <- class(Rmf)
        Epos <- 2:ncol(mf)
        Enames <- names(mf)[Epos]
        Enum <- length(Enames)
        Emf <- mf[,Epos]
      }
    } else {
      # There is not a LHS
      Rnum <- 0
      Rpos <- Rname <- Rclass <- NULL
      Rmf <- NULL
      Emf <- mf
      Enames <- names(Emf)
      Enum <- length(Enames)
      Epos <- seq_len(Enum)      
    }
    # find the class of each response and explanatory variable on the RHS
    if (Enum>0) ifelse(Enum==1,Eclass <- class(Emf),
                       Eclass <- unlist(lapply(Emf,class)))
    # get positions of numeric and factor explanatory vars on RHS
    ENumPos <- which(Eclass %in% c("numeric","integer","AsIs"))
    EFactPos <- which(Eclass %in% c("factor","character"))
    # add one to positions if Rnum==1
    if (Rnum==1) {
      ENumPos <- ENumPos + 1
      EFactPos <- EFactPos + 1
    }
    # get number of numeric and number of factor explanatory vars on RHS
    ENumNum <- length(ENumPos)
    EFactNum <- length(EFactPos)
  }
  # Identify the type of data at hand
  Etype <- "mixed"
  if (ENumNum==0) Etype <- "factor"
  if (EFactNum==0) Etype <- "numeric"
  # Recreate the model frame data frame
  if (Rnum==0) df <- Emf
  else df <- data.frame(Rmf,Emf)
  names(df) <- c(Rname,Enames)
  # Check if the expected number of each type of variable was met
  metExpNumR <- metExpNumE <- metExpNumENums <- metExpNumEFacts <- NULL
  if (!is.null(expNumR)) ifelse(Rnum==expNumR,
                                metExpNumR <- TRUE,metExpNumR <- FALSE)
  if (!is.null(expNumE)) ifelse(Enum==expNumE,
                                metExpNumE <- TRUE,metExpNumE <- FALSE)
  if (!is.null(expNumENums)) ifelse(ENumNum==expNumENums,
                                    metExpNumENums <- TRUE,
                                    metExpNumENums <- FALSE)
  if (!is.null(expNumEFacts)) ifelse(EFactNum==expNumEFacts,
                                     metExpNumEFacts <- TRUE,
                                     metExpNumEFacts <- FALSE)
  # put it all together to return
  list(formula=formula,mf=df,vnum=Rnum+Enum,
       Rnum=Rnum,Rname=Rname,Rclass=Rclass,Rpos=Rpos,
       Etype=Etype,Enames=Enames,Eclass=Eclass,Enum=Enum,
       ENumNum=ENumNum,ENumPos=ENumPos,
       EFactNum=EFactNum,EFactPos=EFactPos,
       metExpNumR=metExpNumR,metExpNumE=metExpNumE,
       metExpNumENums=metExpNumENums,metExpNumEFacts=metExpNumEFacts)
}


# Internal functions used to handle multiple what= arguments. If more than one
# item then print a line return and return the what vector without item in it.
# This allows an easy way to put space between multiple items without an extra
# space after the last one.
iHndlMultWhat <- function(what,item,type=c("message","cat")) {
  type <- match.arg(type)
  if (length(what)>1) {
    if (type=="message") message("\n")
    else cat("\n")
    what[-pmatch(item, what)]
  }
}


iLegendHelp <- function(legend) {
  do.legend <- FALSE
  x <- y <- NULL
  if (inherits(legend,"logical")) {
    if(legend) { # nocov start
      do.legend <- TRUE
      x <- graphics::locator(1)
    } # nocov end
  } else if (!is.null(legend)) {
    do.legend <- TRUE
    if (inherits(legend,"character")) {
      if (!(legend %in% c("bottomright","bottom","bottomleft","left",
                          "topleft","top","topright","right","center")))
        STOP("Must use proper keyword for 'legend'.")
      x <- legend
    } else {
      x <- legend[1]
      y <- legend[2]
    }
  }
  list(do.legend=do.legend,x=x,y=y)
}


iListSpecies <- function(d) {
  message("\nSpecies name must be one of following. Be careful of spelling and capitalization.")
  tmp <- unique(d$species)
  print(tmp[order(tmp)])
  return(invisible())
} # end internal function    


iMakeColor <- function(col,transp) { 
  ## Takes a color string and will make it transparent based on
  ##  the value of transp. The transp value must be greater
  ##  than 0. If transp is greater than 1 than it is interpreted
  ##  as the number of points plotted on top of each other before the
  ##  transparency is lost and is, thus, transformed to 1/transp.
  ## The return value is an rgb() color.
  if (transp <= 0) STOP("'transp' must be greater than 0.")
  if (transp > 1) transp <- 1/transp
  colprts <- grDevices::col2rgb(col)/255
  grDevices::rgb(colprts[1,1],colprts[2,1],colprts[3,1],transp)
}

# Checks if a plot exists ... i.e., was there a plot.new
iPlotExists <- function() {
  # set options so that warnings are errors
  withr::local_options(list(warn=2))
  # if plot does not exist that par(new=TRUE) will error and then
  #   try will return a class of "try-error"
  res <- try(graphics::par(new=TRUE),silent=TRUE)
  # if errored then say FALSE (i.e., plot does not exist)
  res <- ifelse(class(res)=="try-error",FALSE,TRUE)
  # return
  res
}


# Checks if a value is a whole number
is.wholenumber <- function(x,tol=.Machine$double.eps^0.5) {
  abs(x - round(x)) < tol 
}


iTypeoflm <- function(mdl) {
  if (any(class(mdl)!="lm")) STOP("'iTypeoflm' only works with objects from 'lm()'.")
  tmp <- iHndlFormula(stats::formula(mdl),stats::model.frame(mdl))
  if (tmp$Enum==0)
    STOP("Object must have one response and at least one explanatory variable")
  if (!tmp$Rclass %in% c("numeric","integer"))
    STOP("Response variable must be numeric")
  if (any(tmp$Eclass=="character"))
    WARN("An explanatory variable is a 'character' class. If behavior is different\n than you expected you may want to change this to a 'factor' class.")
  if (tmp$Etype=="factor") { #ANOVA
    if (tmp$EFactNum>2) STOP("Function only works for one- or two-way ANOVA.")
    if (tmp$EFactNum==2) lmtype <- "TWOWAY"
    else lmtype <- "ONEWAY"
  } else { # not an anova
    if (tmp$Enum==1) lmtype <- "SLR"
    else if (tmp$Etype=="mixed") lmtype <- "IVR"
    else if (all(grepl(tmp$Enames[1],tmp$Enames[-1]))) lmtype <- "POLY"
    else lmtype <- "MLR"
  }
  tmp <- c(list(type=lmtype,mdl=mdl),tmp)
  class(tmp) <- c(lmtype,"list")
  tmp
}

# same as stop() and warning() but with call.=FALSE as default
STOP <- function(...,call.=FALSE,domain=NULL) stop(...,call.=call.,domain=domain)
WARN <- function(...,call.=FALSE,immediate.=FALSE,noBreaks.=FALSE,domain=NULL) {
  warning(...,call.=call.,immediate.=immediate.,noBreaks.=noBreaks.,domain=domain)
}


#Checks if specified confidence interval is numeric.
iCheckConfLevel <- function(conf.level) {
  if (!is.numeric(conf.level)) STOP("'conf.level' must be numeric.")
  if (conf.level<=0 | conf.level>=1) STOP("'conf.level' must be between 0 and 1")
}
  
droglenc/FSA documentation built on Aug. 30, 2023, 12:51 a.m.