Isometry test and size correction

isometry.test=function(response, reference, specimen.id, expected.slope, alpha=0.05){ #1 Creates function "isometry.test", with arguments "response", "reference", "specimen.id", "expected.slope" and "alpha".
  
  ## VERIFYING THE ARGUMENTS
  
  if ((missing(response)) || (is.na(suppressWarnings(as.numeric((response)[[1]])))) || is.factor(data.frame(response)[[1]])) #2 Verifies if "response" was inserted and if its coercible to numeric.
  { 
    stop("response should be specified as a numeric vector containing the response measurement(s) for isometry test") #3 If not, interrupts the function and gives a message to the user.
  }
  
  response=as.data.frame(response) #4 Transforms "response" in a data frame, so its length can be read as number of variables.
  
  if ((missing(reference)) || (length(reference) != max(c(length(response),length(as.data.frame(t(response)))))) || (is.na(suppressWarnings(as.numeric(reference[[1]])))) || is.factor(data.frame(reference)[[1]])) #5 Verifies if "reference" was inserted, if it has the same length as response, and is coercible to numeric.
  {
    stop("reference should be specified as a numeric vector containing the reference measurements for isometry test.") #6 If not, interrupts the function and gives a message to the user.
  }
  
  if ((missing(specimen.id)) || (class(specimen.id) != "character") || (length(specimen.id) != length(reference))) #7 Verifies if "specimen.id" was inserted, if it is a character and if it has the same length as "reference".
  {
    stop("specimen.id should be specified as a character vector containing the names of specimens in data table.") #8 If not, interrupts the function and gives a message to the user.
  }
  
  if ((missing(alpha)) || (class(alpha) != "numeric") || (alpha<=0) || (alpha>=1)) #9 Verifies if "alpha" was inserted, if it is numeric and >0 and <1.
  {
    message("\talpha should be numeric and >0 or <1; herein default set to 0.05.\n") #10 If not, gives a message to the user and uses the default value set above with the argument.
  }
  
  if ((colnames(response)==as.character(rep(1:length(colnames(response)),1))) || (match(colnames(response), specimen.id, nomatch = 0) != rep(0,length(response)))) #11 Verifies if the input data frame "response" has the specimens in rows.
  {
    response=as.data.frame(t(response)) #12 If not, transposes so the specimens are in rows. Will have further implications in calculating number of response variables.
  }
  
  if ((missing(expected.slope)) || (class(expected.slope) != "numeric") || (length(expected.slope) != length(colnames(response)))) #13 Verifies if "expected.slope" was inserted, if it is numeric and has the same length as "response".
  {
    message("\texpected.slope should be a concatenation of the ratios: power of response variable/power of the reference variable; herein default set to 1 for all.\n") #14 #10 If not, gives a message to the user.
    expected.slope=rep(1,length(colnames(response))) #15 Uses the default value (set here).
  }
  
  ## FUNCTION FOR ISOMETRY TEST AND SIZE CORRECTION
  
  # Setting and configuring specific parameters
  
  ref.name=readline("\tPlease tell me the name of your reference variable: ") #16 Asks for name of reference variable, that will be used in the output.
  result=matrix(data=NA, nrow=1, ncol=length(colnames(response))) #17 Creates the empty "result" matrix, with 1 row and "response" columns.
  result=as.data.frame(result) #18 Transforms "result" into a data frame, so it will later accept characters.
  result2=matrix(data=NA, nrow=length(rownames(response)), ncol=length(colnames(response))+1) #19 Creates the empty "result2" matrix with as muchs rows as specimens, and as much columns as reference+response variables.
  reference=as.numeric(reference) #20 Coerces "reference" to numeric, in case it was read before as characters.
  result2[,1]=reference #21 Sets reference variable to the first column of "result2".
  
  if (length(response)==1) #22 Verifies if there is only one response variable.
  {
    res.name=readline("\tPlease tell me the name of your response variable: ") #23 If yes, asks the user for the name of the response variable.
    colnames(response)=res.name #24 Attributes the name of the response variable to its specific column, which will be used to construct the output matrices.
  }
  
  response.all=as.matrix(response) #25 Creates the matrix "response.all" with "response", as "response" will enter the for loop that follows.
  
  # Loop to compare each response variable to the reference variable, perform the isometry test and the specific size-correction.
  
  for (k in 1:length(colnames(response))) #26 Loop using counter k from 1 to the length of "response"
  {
    response=response.all[,k] #27 Creates object "response" with k column of "response.all"
    response=as.numeric(response) #28 Coerces "response" to numeric, in case it was read before as character.
    response=log(response) #29 Log transformation of response variable attributed to "response"
    reference2=log(reference) #30 Log transformation of reference variable attributed to "reference2", so that it overwrites "reference", similar to what was done with "response.all" and "response".
    response.m=lm(response~reference2) #31 Creates the object "response.m" with the linear model of "response" as a function of "reference2".
    f=(summary(response.m))$fstatistic #32 Creates the object "f" with the F-statistic values of the linear model.
    p=pf(f[1],f[2],f[3],lower.tail=F) #33 Creates the object "p" with the p-value from the F-statistic values above.
    
    if (p > alpha) #34 Verifies if "p" is bigger than "alpha".
    {
      result[k]="lm not significant" #35 If yes, attribute "lm not significant" to element k in "result".
    }
    else #36 If not, linear model is significant, so proceeds with calculations.
    {
      slope=(summary(response.m))$coefficients[2] #37 Creates the object "slope" and stores the slope coefficient from "response.m".
      slope.se=(summary(response.m))$coefficients[4] #38 Creates the object "slope.se" and stores the slope's standard error coefficient from "response.m".
      tvalue=abs((slope-expected.slope[k])/slope.se) #39 Creates the object "tvalue", with the t test value from the comparison between slope and expected slope.
      pvalue=(1-pt(tvalue,length(response)-2))*2 #40 Creates the object "pvalue", with the p-value corresponding to the t test value above (degrees of freedom = n-2).
      
      if (pvalue > alpha) #41 Verifies if "pvalue" is bigger than "alpha".
      {
        result[k]="Isometry" #42 If yes, the slope from response.m is not significantly different from the expected slope, so it attributes "Isometry" to element k in "result".
        result2[,k+1]=as.numeric(response.all[,k])*((mean(result2[,1])/result2[,1])^expected.slope[k]) #43 Makes isometric transformation based on expected slope, following the formula in Lleonart et al. (2000).
      }
      else #44 Verifies if "pvalue" is not bigger than alpha.
      { 
        result[k]="Allometry" #45 If it is not bigger, the slope from response.m is significantly different from the expected.slope, so it attributes "Allometry" to element k in "result".
        result2[,k+1]=as.numeric(response.all[,k])*((mean(result2[,1])/result2[,1])^slope) #46 Makes allometric transformation based on observed slope, following the formula in Lleonart et al. (2000).
      }
      
    }
    
    colnames(result)[k]=colnames(response.all)[k] #47 Establishes column names of "result" as the column names in response.all.
  }
  
  ## SETTING AND RETURNING THE OUTPUT
  
  result2[,1]=mean(result2[,1]) #48 Makes the size correction of the reference variable, by changing it in the first column of "result2" for its mean.
  colnames(result2)=c(ref.name,colnames(response.all)) #49 Establishes the column names of "result2" as the reference name given by the user, and the column names of "response.all". 
  rownames(result2)=specimen.id #50 Establishes the row names of "result2" as the specimen names given by the user.
  all=array(list(result, result2)) #51 Creates the output object "all", as array with a list that includes "result" and "result2".
  return(all) #52 Returns to the user the output array "all" with the results of the isometry test and size correction.
}