  # -------------------------------------------------------------------------------------------------------------------------  #
  #  -----------------------------------------------------------------------------------------------------------------------   #
  #                                                                                                                            #
  #           File Name              :  normalizeAndStack.r                                                                    #
  #           Last Updated Funclist  :  19 Feb 2015, 12:52 PM (Thursday)                                                       #
  #                                                                                                                            #
  #           Author Name            :  Rick Saporta                                                                           #
  #           Author Email           :  RickSaporta@gmail.com                                                                  #
  #           Author URL             :  www.github.com/rsaporta                                                                #
  #                                                                                                                            #
  #           Packages Called        :  NA                                                                                     #
  #           Packages Used via NS   :  NA                                                                                     #
  #                                                                                                                            #
  #  -----------------------------------------------------------------------------------------------------------------------   #
  #                                                                                                                            #
  #   normalizeAndStack  ( DT, byCols.aggregate=key(DT), byCols.scale=NULL, colGroups=list(), scaleFunc="scale"                #
  #                        , coerce.numeric=TRUE, variable.name="variable", justStack=FALSE )                                  #
  #                                                                                                                            #
  #                                                                                                                            #
  #                                                     <END FUNCS>                                                            #
  #  -----------------------------------------------------------------------------------------------------------------------   #
  # -------------------------------------------------------------------------------------------------------------------------  #

normalizeAndStack <- function(DT, byCols.aggregate=key(DT), byCols.scale=NULL, colGroups=list(), scaleFunc="scale", coerce.numeric=TRUE, variable.name="variable", justStack=FALSE) {
  # colGroups should be a list, where the name of each element will 

  scaleFunc <- match.fun(scaleFunc)

  ## ERROR CHECK
  # -------------------- #
  ## The names within each group should be identical to the next group
  if (!allEqual(lapply(colGroups, names))) stop ("The names of the vectors in colGroups are not identical")
  if (any(wh.missing <- unlist(colGroups) %ni% names(DT))) stop (warningCols("Some columns are not in DT: ", unlist(colGroups)[wh.missing]))
  # -------------------- #

  colsToScale <- unlist(colGroups, use.names=FALSE)
  varValues   <- names(colGroups[[1]])

  ## Scale the columns, first coercing if necessary.
  if (!justStack) {
    ## Deep copy
    DT <- copy(DT)

    if (coerce.numeric) {
      colsToCoerce <- nwhich(sapply(DT[, colsToScale, with=FALSE], function(x) !identical(is(x), "numeric")))
      DT[, (colsToCoerce) := lapply(.SD, as.numeric), .SDcols=colsToCoerce]
    }

    DT[, (colsToScale) := lapply(.SD, function(x) as.vector(scaleFunc(x))), .SDcols=colsToScale, by=byCols.scale]
  }
 
  ## stack them
  return((
      setkeyIfNot(
        setnames(
          DT[, {
              e <- environment(); .SD; 
              c(  list(.VARCHAR. = rep(varValues, each=.N))
                , lapply(colGroups, function(cols) unlist(gapply(cols, envir=e), use.names=FALSE) )
                )
            }, keyby=byCols.aggregate, .SDcols=unlist(colGroups)]
        , ".VARCHAR.", variable.name)
      , c(byCols.aggregate, variable.name) ## Keys
      , verbose=FALSE
      ))
  )
}


## ----------------------------------------------------------------------------------- ##

# ALT :  
# ALT :  ### ALTERNATE METHOD FOR TESTING RESULTS
# ALT :  normalizeAndStack_ALT <- function(DT, byCols.aggregate=key(DT), byCols.scale=NULL, colGroups=list(), variable.name="variable") {
# ALT :  
# ALT :    lib(reshape2, quiet=FALSE)
# ALT :    DT <- copy(DT)
# ALT :  
# ALT :  
# ALT :    ret <- lapply(seq(colGroups), function(i) {
# ALT :  
# ALT :      value.name <- names(colGroups) [[i]]
# ALT :      colsToScale <- colGroups[[i]]
# ALT :  
# ALT :      if (any(names(colsToScale) %in% names(DT)))
# ALT :        suppressWarnings(DT[, names(colsToScale) := NULL])
# ALT :      setNamesDict(DT, colsToScale, showWarnings=FALSE)
# ALT :      colsToScale <- invDict(colsToScale)
# ALT :  
# ALT :      ## Scale
# ALT :      DT[, (colsToScale) := lapply(.SD, function(x) as.vector(scale(as.numeric(x)))), .SDcols=colsToScale, by=byCols.scale]
# ALT :  
# ALT :      ## reshape
# ALT :      setkeyIfNot(
# ALT :          melt (DT, id.vars=byCols.aggregate, measure.vars=colsToScale, variable.name=variable.name, value.name=value.name)
# ALT :        , c(byCols.aggregate, variable.name)
# ALT :        , verbose=FALSE
# ALT :        )
# ALT :    })
# ALT :  
# ALT :    ## This only works if unique by row
# ALT :    # Reduce(function(x, y) data.table:::merge.data.table(x, y, all=TRUE, allow=TRUE), ret)
# ALT :    Reduce(function(x, y) cbind(x, y[, setdiff(names(y), names(x)), with=FALSE]), ret)
# ALT :  }
