R/shape.predictor.r

Defines functions shape.predictor

Documented in shape.predictor

#' Shape prediction from numeric predictors
#'
#' Function estimates one or more configurations based on one or more linear predictors, such as PC scores
#' allometric relationships, or any other least squares or partial least squares regression.  These configurations
#' can be used with \code{\link{plotRefToTarget}} to generate graphical representations of shape change, based on prediction criteria. 
#' 
#' @param A A 3D array (p x k x n) containing Procrustes shape variables either from GPA or fitted values from a previous
#' analytical procedure. 
#' @param x Linear (numeric) predictors.  Can be a vector or a matrix, or a list containing vectors or matrices.  Values must
#' be numeric.  If a factor is desired, one should use \code{\link[stats]{model.matrix}} to obtain a design matrix.  This will
#' impact how prediction criteria need to be provided (see below).
#' @param Intercept Logical value to indicate whether an intercept should be used in the linear equation for predictions.  Generally, 
#' this value will be FALSE for shape predictions made in ordination plots.  It should be TRUE in cases where the expected
#' shape at the point the predictor has a value of 0 is not the mean shape.
#' @param method A choice between least squares (LS) or partial least squares (PLS) regression for prediction.  The function defaults
#' to LS prediction.  PLS might be chosen in cases where correlation is preferred over linear regression.  If PLS is chosen, a two-block
#' PLS analysis using  \code{\link{two.b.pls}} should be performed first, as only the first singular vector for predictors will be used
#' for defining prediction criteria (see below).
#' @param ... Any number of prediction criteria.  Criteria should be presented as either a scalar (if one predictor is provided) or 
#' a vector (if more than one predictor or a prediction matrix is provided); e.g., pred1 = c(0.1, -0.5), pred2 = c(-0.2, -0.1) (which
#' would be the case if two predictors were provided).  It is essential that the number of elements in any prediction criterion matches
#' the number of predictors.  Caution should be used when providing a design matrix to ensure that correct dummy variables are used in
#' prediction criteria, and that either 1) an intercept is not included in the design and 2) is TRUE in the Intercept argument; or
#' or 1) an intercept is included in the design and 2) is FALSE in the Intercept argument; or 1) an intercept is not included in the design
#' and 2) is FALSE in the Intercept argument, if no intercept is desired.
#' @export
#' @author Michael Collyer
#' @return A list of predicted shapes matching the number of vectors of prediction criteria provides.  The predictions 
#' also have names matching those of the prediction criteria.
#' @examples
#' \dontrun{
#' 
#' # Examples using Plethodon data
#' 
#'  data("plethodon")
#'
#'  Y.gpa <- gpagen(plethodon$land)    #GPA-alignment    
#'  plot(gm.prcomp(Y.gpa$coords))
#'
#'  preds <- shape.predictor(Y.gpa$coords, x = NULL, Intercept = FALSE, 
#'  pred1 = -0.1, pred2 = 0.1) # PC 1 extremes, sort of
#'  M <- mshape(Y.gpa$coords)
#'  plotRefToTarget(M, preds$pred1)
#'  plotRefToTarget(M, preds[[1]]) # same result
#'  plotRefToTarget(M, preds$pred2)
#'
#'  PCA <- gm.prcomp(Y.gpa$coords)
#'  PC <- PCA$x[,1]
#'  preds <- shape.predictor(Y.gpa$coords, x = PC, Intercept = FALSE, 
#'  pred1 = min(PC), pred2 = max(PC)) # PC 1 extremes, more technically
#'  plotRefToTarget(M, preds$pred1)
#'  plotRefToTarget(M, preds$pred2)
#'
#'  PC <- PCA$x[,1:2]
#'# user-picked spots can be anything, but it in this case, apparent groups
#'  preds <- shape.predictor(Y.gpa$coords, x = PC, Intercept = FALSE, 
#'                           pred1 = c(0.045,-0.02), 
#'                           pred2 = c(-0.025,0.06), 
#'                           pred3 = c(-0.06,-0.04)) 
#'  plotRefToTarget(M, preds$pred1)
#'  plotRefToTarget(M, preds$pred2)
#'  plotRefToTarget(M, preds$pred3)
#'
#'# allometry example - straight-up allometry
#'
#'  preds <- shape.predictor(Y.gpa$coords, x = log(Y.gpa$Csize), 
#'                           Intercept = TRUE, 
#'                           predmin = min(log(Y.gpa$Csize)), 
#'                           predmax = max(log(Y.gpa$Csize))) 
#'
#'  plotRefToTarget(M, preds$predmin, mag = 3)
#'  plotRefToTarget(M, preds$predmax, mag = 3)
#'
#' # allometry example - using RegScore or PredLine via procD.lm
#'
#'  gdf <- geomorph.data.frame(Y.gpa)
#'  plethAllometry <- procD.lm(coords ~ log(Csize), data = gdf)
#'  allom.plot <- plot(plethAllometry, 
#'  type = "regression", 
#'  predictor = log(gdf$Csize),
#'  reg.type ="RegScore") # make sure to have a predictor 
#'
#'  preds <- shape.predictor(plethAllometry$GM$fitted, 
#'                           x = allom.plot$RegScore, Intercept = FALSE, 
#'                           predmin = min(allom.plot$RegScore), 
#'                           predmax = max(allom.plot$RegScore)) 
#'  plotRefToTarget(M, preds$predmin, mag = 3)
#'  plotRefToTarget(M, preds$predmax, mag = 3)
#'
#'  allom.plot <- plot(plethAllometry, 
#'  type = "regression", 
#'  predictor = log(gdf$Csize),
#'  reg.type ="PredLine")
#'  preds <- shape.predictor(plethAllometry$GM$fitted, 
#'                           x = allom.plot$PredLine, Intercept = FALSE, 
#'                           predmin = min(allom.plot$PredLine), 
#'                           predmax = max(allom.plot$PredLine)) 
#'  plotRefToTarget(M, preds$predmin, mag = 3)
#'  plotRefToTarget(M, preds$predmax, mag = 3)
#'
#'# using factors via PCA
#'
#'  gdf <- geomorph.data.frame(Y.gpa, species = plethodon$species, 
#'          site = plethodon$site)
#'  pleth <- procD.lm(coords ~ species * site, data=gdf)
#'  PCA <- prcomp(pleth$fitted)
#'  plot(PCA$x, asp = 1, pch = 19)
#'
#'  means <- unique(round(PCA$x, 3))
#'  means # note: suggests 3 PCs useful enough
#'
#'  preds <- shape.predictor(arrayspecs(pleth$fitted, 12,2), x = PCA$x[,1:3],
#'                           Intercept = FALSE,
#'                           pred1 = means[1,1:3], 
#'                           pred2 = means[2,1:3],
#'                           pred3 = means[3,1:3], 
#'                           pred4 = means[4,1:3])                   
#'  plotRefToTarget(M, preds$pred1, mag = 2)
#'  plotRefToTarget(M, preds$pred2, mag = 2)
#'  plotRefToTarget(M, preds$pred3, mag = 2)
#'  plotRefToTarget(M, preds$pred4, mag = 2)
#'
#' # Using a design matrix for factors
#'
#'  X <- pleth$X
#'  X # includes intercept; remove for better functioning 
#'  X <- X[,-1]
#'  symJord <- c(0,1,0) # design for P. Jordani in sympatry
#'  alloJord <- c(0,0,0) # design for P. Jordani in allopatry
#'  preds <- shape.predictor(arrayspecs(pleth$fitted, 12,2), x = X, 
#'                           Intercept = TRUE, 
#'                           symJord=symJord, alloJord=alloJord)
#'  plotRefToTarget(M, preds$symJord, mag = 2)
#'  plotRefToTarget(M, preds$alloJord, mag = 2)
#'
#' # PLS Example
#'
#'  data(plethShapeFood) 
#'  Y.gpa <- gpagen(plethShapeFood$land)    #GPA-alignment    
#'
#' # 2B-PLS between head shape and food use data
#'  PLS <- two.b.pls(A1 = plethShapeFood$food, A2 = Y.gpa$coords) 
#'  summary(PLS)
#'  plot(PLS)
#'
#'  preds <- shape.predictor(Y.gpa$coords, plethShapeFood$food, 
#'                           Intercept = FALSE,
#'                           method = "PLS",
#'                           pred1 = 2, pred2 = -4, pred3 = 2.5) 
#'                         # using PLS plot as a guide
#'  M <- mshape(Y.gpa$coords)
#'  plotRefToTarget(M, preds$pred1, mag = 2)
#'  plotRefToTarget(M, preds$pred2, mag = 2)
#'  plotRefToTarget(M, preds$pred3, mag = 2)
#' }
shape.predictor <- function(A, 
                            x = NULL, 
                            Intercept = FALSE, 
                            method = c("LS", "PLS"), ...){
  if(length(dim(A)) != 3) stop("Shape data must be in the form of a p x k x n array")
  dims <- dim(A); p <- dims[1]; k <- dims[2]; n <- dims[3]
  Y <- two.d.array(A)
  if(is.list(x)){
    if(any(sapply(x, is.factor)) == TRUE) stop("Predictors must be numeric.  Consider using model.matrix first.")
    if(any(sapply(x, is.character)) == TRUE) stop("Predictors must be numeric.  Consider using model.matrix first.")
    if(any(sapply(x, is.logical)) == TRUE) stop("Predictors must be numeric.  Consider using model.matrix first.")
    Ns <- sapply(x, NROW)
    if(length(unique(Ns)) > 1) stop("Predictors have different numbers of observations")
    N <- Ns[1]
    if(N != n) (stop("Predictors and Shape variables do not match in length"))
    X <- matrix(unlist(x),N)
  }
  if(!is.list(x)){
    if(is.matrix(x) || is.vector(x)) {
      if(is.factor(x)) stop("Predictors must be numeric.  Consider using model.matrix first.")
      if(is.character(x)) stop("Predictors must be numeric.  Consider using model.matrix first.")
      if(is.logical(x)) stop("Predictors must be numeric.  Consider using model.matrix first.")
      X <-x    
    }
  }
  if(is.null(x)) {
    pca <- prcomp(Y)
    X <- pca$x[,1]
  }
  X <- as.matrix(X)
  if(is.null(method)) method <- "LS" else
    method = match.arg(method, c("LS", "PLS"))
  if(method == "PLS"){
    plsRaw <- pls(X, Y, verbose=TRUE)
    XScores <- as.matrix(plsRaw$XScores); YScores <- as.matrix(plsRaw$YScores)
    X <- as.matrix(XScores[,1])
    Y <- predict(lm(Y~YScores+0))
  }
  colnames(X)<-paste("P",1:NCOL(X), sep="")
  form <- Y~X
  if(method == "PLS") Intercept <- FALSE
  if(!Intercept) form <- update(form, ~.+0)
  dat <- data.frame(Y=Y, X=X)
  fit <- lm(form, data=dat)
  B <- fit$coefficients
  dots <- list(...)
  if(is.null(dots)) stop("No prediction criteria given")
  np <- length(dots)
  if(length(unlist(dots)) != np*NCOL(X)) 
    stop("\nPrediction crtieria not consistent with the number of predictors.  \nCheck number of values input")
  dots <- lapply(dots, as.vector)
  if(Intercept){
    add.int <- function(x) c(1,x)
    dots <- lapply(dots, add.int)
  }
  pr.shape <- function(x) crossprod(x,B)
  preds <- lapply(dots, pr.shape)
  M <- mshape(A)
  if(is.null(rownames(M))) rownames(M) <- 1:nrow(M)
  if(is.null(colnames(M))) colnames(M) <- 1:ncol(M)
  M0 <- M; M0[M0!=0] <- 0
  if(!Intercept) reshape <- function(x) M + arrayspecs(x,p,k)[,,1] else
    reshape <- function(x) M0 + arrayspecs(x,p,k)[,,1] 
  preds <- lapply(preds, reshape)
  return(preds)
}

Try the geomorph package in your browser

Any scripts or data that you put into this service are public.

geomorph documentation built on June 24, 2024, 5:07 p.m.