R/plot.R

Defines functions legend.m plot.VCA buildList varPlot plotRandVar

Documented in buildList legend.m plotRandVar plot.VCA varPlot

# TODO: R-source file for graphical methods of the R-package VCA.
# 
# Author: schueta6
###############################################################################



#' Plot Random Variates of a Mixed Model ('VCA' Object).
#' 
#' Plots, possibly transformed, random variates of a linear mixed model (random effects, contitional or marginal
#' residuals).
#' 
#' Function plots either random effects of a 'VCA' object or residuals. Parameter 'term' is used to specify either
#' one. If 'term' is one of c("conditional", "marginal") corresponding residuals will be plotted 
#' (see \code{\link{resid}} for details). If 'term' is either the name of a random term in the formula of the 'VCA'
#' object or an integer specifying the i-th random term, corresponding random effects will be plotted. Both types
#' of random variates (random effects, residuals) can be plotted untransformed ("raw"), "studentized" or "standardized".
#' In case of residuals, one can also use the "Pearson"-type transformation.
#' 
#' @param obj			(VCA) object
#' @param term			(character, integer) specifying a type of residuals if one of c("conditional",
#' 						"marginal"), or, the name of a random term (one of obj$re.assign$terms). If 'term'
#' 						is a integer, it is interpreted as the i-th random term in 'obj$re.assign$terms'. 
#' @param mode			(character) string specifying a possible transformation of random effects or 
#'                      residuals (see \code{\link{residuals.VCA}} and \code{\link{ranef.VCA}}for details)
#' @param main			(character) string used as main title of the plot, if NULL, it will be automatically
#'                      generated
#' @param Xlabels		(list) passed to function \code{\link{text}} adding labels to the bottom margin at
#'                      x-coordinates 1:N, where N is the number of random variates. Useful for customization.
#' @param Points		(list) passed to function \code{\link{points}} for customization of plotting symbols
#' @param Vlines		(list) passed to function (abline) adding vertical lines, separating random variates
#'                      for better visual separation, set to NULL for omitting vertical lines.
#' @param pick			(logical) TRUE = lets the user identify single points using the mouse, useful, when many,
#'                      points were drawn where the X-labels are not readable.
#' @param ...			additional arguments to be passed to methods, such as graphical parameters (see \code{\link{par}})
#' 
#' @author Andre Schuetzenmeister \email{andre.schuetzenmeister@@roche.com}
#' 
#' @examples 
#' 
#'  \dontrun{
#' data(dataEP05A2_1)
#' fit <- anovaVCA(y~day/run, dataEP05A2_1)
#' # solve mixed model equations including random effects
#' fit <- solveMME(fit)
#' plotRandVar(fit, "cond", "stand")	
#' plotRandVar(fit, 1, "stud")						# 1st random term 'day'
#' plotRandVar(fit, "day", "stud")					# equivalent to the above
#' 
#' # for larger datasets residuals can hardly be identified
#' # pick out interesting points with the mouse
#'
#' plotRandVar(fit, "marg", "stud", pick=TRUE)
#' 
#' # customize the appearance
#' plotRandVar( fit, 1, "stud", Vlines=list(col=c("red", "darkgreen")), 
#' 	Xlabels=list(offset=.5, srt=60, cex=1, col="blue"),
#' 	Points=list(col=c("black", "red", rep("black", 18)),
#'	pch=c(3,17,rep(3,18)), cex=c(1,2,rep(1,18))))	
#' } 

plotRandVar <- function(obj, term=NULL, mode=c("raw", "student", "standard", "pearson"), main=NULL, 
						Xlabels=list(), Points=list(), Vlines=list(), pick=FALSE, ...)
{
	Call <- match.call()
	
	stopifnot(class(obj) == "VCA")
	stopifnot(!is.null(term))
	
	if(length(mode) > 1)
		mode <- mode[1]
	
	ObjNam  <- as.character(as.list(Call)$obj)	
	
	if(is.null(obj$RandomEffects))					# compute required matrices
	{
		obj  <- solveMME(obj)
		
		if(!grepl("\\(", ObjNam))
		{
			expr <- paste(ObjNam, "<<- obj")		# update object missing MME results
			eval(parse(text=expr))
		}
		else
			warning("Some required information missing! Usually solving mixed model equations has to be done as a prerequisite!")
	}
	
	if(grepl(mode, "student") && is.null(obj$Matrices$Q))		# might be required when solveMME has been called yet
	{
		mats <- obj$Matrices
		X  <- mats$X
		T  <- mats$T
		Vi <- mats$Vi
		mats$H <- H  <- X %*% T
		mats$Q <- Q  <- Vi %*% (diag(nrow(H))-H)
		obj$Matrices <- mats
		
		if(!grepl("\\(", ObjNam))
		{
			expr <- paste(ObjNam, "<<- obj")		# update object missing MME results
			eval(parse(text=expr))
		}
		else
			warning("Some required information missing! Usually solving mixed model equations has to be done as a prerequisite!")
	}
	
	if(!is.character(term))
	{
		term <- obj$re.assign$terms[as.integer(term)]
		RE <- TRUE
		mode.opts <- c("raw", "student", "standard")		
	}
	else
	{
		term <- match.arg(term, choices=c("conditional", "marginal", obj$re.assign$terms))
		
		if(term %in% c("conditional", "marginal"))
		{
			RE <- FALSE
			mode.opts <- c("raw", "student", "standard", "pearson")
		}
		else
		{
			RE <- TRUE
			mode.opts <- c("raw", "student", "standard")			
		}
	}
	
	mode <- match.arg(mode, choices=mode.opts)
	m.names <- c(raw="raw", student="studentized", standard="standardized", pearson="Pearson-type")
	
	if(is.null(main))
		main <- paste(m.names[mode], if(RE)
							paste(" Random Effects: ",  "'", term, "'", sep="")
						else
							paste(term, "Residuals") )
	
	if(RE)
	{
		vec  <- ranef(obj, term=term, mode=mode)
		Ylab <- paste(m.names[attr(vec, "mode")], "Random Effects")
		nam  <- rownames(vec)
		vec  <- vec[,1]
		names(vec) <- nam
	}
	else
	{
		vec  <- resid(obj, type=term, mode=mode)
		Ylab <- paste(attr(vec, "mode"), "Residuals")
	}
	Nvec <- length(vec)
	
	nam <- names(vec)
	
	plot(1:Nvec, vec, main=main, axes=FALSE, xlab=NA, ylab=Ylab, type="n", ...)
	
	axis(2, las=1)
	axis(1, at=1:Nvec, labels=NA)
	
	if(!is.null(Vlines))
	{
		Vlines.def <- list(v=seq(1.5, Nvec-.5), col="lightgray")
		Vlines.def[names(Vlines)] <- Vlines
		Vlines <- Vlines.def
		do.call("abline", Vlines)
	}
	
	box()
	
	Points.def <- list(x=1:Nvec, y=vec, pch=3, cex=1, col="black")
	Points.def[names(Points)] <- Points
	Points <- Points.def
	do.call("points", Points)
	
	par(xpd=TRUE)
	
	usr <- par("usr")
	Xlabels.def <- list(x=1:Nvec, y=usr[3]-0.03*abs(usr[4]-usr[3]), srt=45, cex=.75, labels=nam, pos=2, offset=0)
	Xlabels.def[names(Xlabels)] <- Xlabels
	Xlabels <- Xlabels.def
	do.call("text", Xlabels)
	
	par(xpd=FALSE)
	
	if(pick)
		identify(x=1:Nvec, vec, nam)
}




#' Variability Chart for Hierarchical Models.
#' 
#' Function \code{varPlot} determines the sequence of variables in the model formula and uses this information to construct
#' the variability chart. 
#' 
#' This function implements a variability-chart, known from, e.g. JMP (JMP, SAS Institute Inc., Cary, NC). 
#' Arbitrary models can be specified via parameter 'form'. Formulas will be reduced to a simple hierarchical structure 
#' ordering factor-variables according to the order of appearance in 'form'. This is done to make function \code{varPlot} 
#' applicable to any random model considered in this package. 
#' Even if there are main factors, neither one being above or below another main factor, these are forced into a hierachy.
#' Besides the classic scatterplot, where observations are plotted in sub-classes emerging from the model formula, a plot of
#' standard deviations (SD) or coefficients of variation (CV) is provided (type=2) or both types of plots together (type=3).
#' 
#' @param Data          (data.frame) with the data
#' @param form          (formula) object specifying the model, NOTE: any crossed factors are reduced to last term of the crossing structure, i.e.
#'                      "a:b" is reduced to "b", "a:b:c" is reduced to "c". 
#' @param Title			(list) specifying all parameters applicable in function \code{\link{title}} for printing main- or sub-titles to plots. If 'type==3',
#'                      these settings will apply to each plot. For individual settings specify a list with two elements, where each element is a
#' 						list itself specifying all parameters of function 'title'. The first one is used for the variability chart, 
#' 						the second one for the SD or CV plot. Set to NULL to omit any titles.
#' @param keep.order    (logical) TRUE = the ordering of factor-levels is kept as provided by 'Data', FALSE = factor-levels are sorted on 
#'                      and within each level of nesting.
#' @param type          (integer) specifying the type of plot to be used, options are 1 = regular scatterplot, 2 = plot of the standard deviation,
#'                      3 = both type of plots.
#' @param VSpace		(numeric) vector of the same length as there are variance components, specifying the proportion of vertical space assigned
#'                      to each variance component in the tabular indicating the model structure. These elements have to sum to 1, otherwise equal
#' 						sizes will be used for each VC.
#' @param VARtype       (character) either "SD" (standard deviation) or "CV" (coefficient of variation), controls which type of measures is used to
#'                      report variability in plots when 'type' is set to either 2 or  (see 'type' above). Note that all parameters which apply to
#'                      the SD-plot will be used for the CV-plot in case 'VARtype="CV"'.
#' @param htab          (numeric) value 0 < htab < 1 specifying the height of the table representing the experimental design. This value represents
#'                      the proportion in relation to the actual plotting area, i.e. htab=1 mean 50\% of the vertical space is reserved for the table.
#' @param VarLab        (list) specifying all parameters applicable in function \code{\link{text}}, used to add labels within the table environment refering
#'                      to the nesting structure. This can be a list of lists, where the i-th list corresponds to the i-th variance component, counted
#'                      in bottom-up direction, i.e. starting from the most general variance component ('day' in the 1st example).
#' @param YLabel        (list) specifying all parameters applicable in function \code{\link{mtext}}, used for labelling the Y-axis.
#' @param SDYLabel      (list) specifying all parameters applicable in function \code{\link{mtext}}, used for labelling the Y-axis.
#' @param Points        (list) specifying all parameters applicable in function \code{\link{points}}, used to specify scatterplots per lower-end factor-level
#'                      (e.g. 'run' in formula run/day). If list-elements "col", "pch", "bg" and "cex" are lists themselves with elements "var" and "col"/"pch"/"bg"/"cex", 
#' 						where the former specifies a variable used for assigning colors/symbols/backgrounds/sizes according to the class-level of
#' 						variable "var", point-colors/plotting-symbols/plotting-symbol backgrounds/plotting-symbol sizes can be used for indicating
#' 						specific sub-classes not addressed by the model/design or indicate any sort of information (see examples).
#' 						Note the i-th element of 'col'/'pch' refers of the i-th element of unique(Data$var), even if 'var' is an integer variable.
#' @param SDs           (list) specifying all parameters applicable in function \code{\link{points}}, used to specify the appearance of SD-plots.
#' @param SDline        (list) specifying all parameters applicable in function \code{\link{lines}}, used to specify the (optional) line joining individual SDs,
#'                      Set to NULL to omit.
#' @param BG            (list) specifying the background for factor-levels of a nested factor. This list is passed on to function \code{\link{rect}} after element
#'                      'var', which identifies the factor to be used for coloring, has been removed. If not set to NULL and no factor has been specified by the
#'                      user, the top-level factor is selected by default. If this list contains element 'col.table=TRUE', the same coloring schema is used
#' 						in the table below at the corresponding row/factor (see examples). Addionally, list-elment 'col.bg=FALSE' can be used to turn off
#' 						BG-coloring, e.g. if only the the respective row in the table below should be color-coded (defaults to 'col.bg=TRUE').
#' 						When specifying as many colors as there are factor-levels, the same color will be applied to a factor-level automatically. 
#' 						This is relevant for factors, which are not top-level (bottom in the table).
#'                      Example: BG=list(var="run", col=c("white", "lightgray"), border=NA) draws the background for alternating levels of factor "run" 
#'                      white and gray for better visual differentiation. Set to NULL to omit. Use list( ..., col="white", border="gray") for using gray 
#'                      vertical lines for separation. See argument 'VLine' for additional highlighting options of factor-levels.
#' @param VLine			(list) specifying all parameters applicable in \code{\link{lines}} optionally separating levels of one or multiple variables
#' 						as vertical lines. This is useful in addition to 'BG' (see examples), where automatically 'border=NA' will be set that 'VLine' will
#' 						take full effect. If this list contains element 'col.table=TRUE', vertical lines will be extended to the table below the plot.
#' @param HLine			(list) specifying all parameters applicable in function \code{\link{abline}} to add horizontal lines. Only horizontal lines can be
#' 						set specifying the 'h' parameter. 'HLine=list()' will use default settings. 'HLine=NULL' will omit horizontal lines.
#' 						In case 'type=3', two separate lists can be specified where the first list applies to the variability chart and the second list
#' 						to the SD-/CV-chart.
#' @param ylim          (numeric) vector of length two, specifying the limits in Y-direction, if not set these values will be determined automatically.
#' 						In case of plot 'type=3' this can also be a list of two ylim-vectors, first corresponding to the variability chart, second to the
#' 						plot of error variability per replicate group
#' @param Join          (list) specifying all parameter applicable in function \code{\link{lines}} controlling how observed values within lower-level factor-levels,
#' 						are joined. Set to NULL to omit.
#' @param JoinLevels	(list) specifying all arguments applicable in function \code{\link{lines}}, joining factor-levels nested within higher order factor levels,
#' 						list-element "var" specifies this variable
#' @param Mean          (list) passed to function \code{\link{points}} specifying plotting symbols used to indicate mean values per lower-level factor-level, 
#' 						set equal to NULL to omit. 
#' @param MeanLine		(list) passed to function \code{\link{lines}} specifying the appearance of horizontal lines indicating mean values of factor levels. 
#' 						The factor variable for which mean-values of factor-levels are plotted can be specified via list-element "var" accepting any factor 
#' 						variable specified in 'form'. List element "mar" takes values in [0;.5] setting the left and right margin size of mean-lines.
#' 						Set equal to NULL to omit. Use 'var="int"' for specifying the overall mean (grand mean, intercept).
#' 						If this list contains logical 'join' which is set to TRUE, these mean lines will be joined. If list-element "top" is set to TRUE, 
#' 						these lines will be plotted on top, which is particularily useful for very large datasets.
#' @param Boxplot		(list) if not NULL, a boxplot of all values within the smallest possible subgroup (replicates) will be added to the plot,
#' 						On can set list-elements 'col.box="gray65"', 'col.median="white"', 'col.whiskers="gray65"' specifying different colors and 'lwd=3'
#' 						for the line width of the median-line and whiskers-lines as well as 'jitter=1e3' controlling the jittering of points around the
#' 						center of the box in horizontal direction, smallest possible value is 5 meaning the largest amount of jittering (1/5 in both directions)
#' 						value is)
#' @param VCnam         (list) specifying the text-labels (names of variance components) appearing as axis-labels. These parameters are passed to function
#'                      \code{\link{mtext}}. Parameter 'side' can only be set to 2 (left) or 4 (right) controlling where names of variance components appear. 
#' 						Set to NULL to omit VC-names. 
#' @param useVarNam     (logical) TRUE = each factor-level specifier is pasted to the variable name of the current variable and used as list-element name, 
#'                               FALSE = factor-level specifiers are used as names of list-elements; the former is useful when factor levels are indicated
#'                               as integers, e.g. days as 1,2,..., the latter is useful when factor levels are already unique, e.g. day1, day2, ... .
#' @param max.level		(integer) specifying the max. number of levels of a nested factor in order to draw vertical lines. If there are too many levels a black
#' 						area will be generated by many vertical lines. Level names will also be omitted.
#' @param ...			further graphical parameters passed on to function 'par', e.g. use 'mar' for specification of margin widths. Note, that not all of them
#' 						will have an effect, because some are fixed ensuring that a variability chart is drawn.
#' 
#' @return (invisibly) returns 'Data' with additional variable 'Xcoord' giving X-coordinates of each observation
#' 
#' @author Andre Schuetzenmeister \email{andre.schuetzenmeister@@roche.com}
#' 
#' @examples
#' \dontrun{
#' 
#' # load data (CLSI EP05-A2 Within-Lab Precision Experiment)
#' data(dataEP05A2_3)
#' 
#' # two additional classification variables (without real interpretation)
#' dataEP05A2_3$user <- sample(rep(c(1,2), 40))
#' dataEP05A2_3$cls2 <- sample(rep(c(1,2), 40))
#' 
#' # plot data as variability-chart, using automatically determined parameter 
#' # settings (see 'dynParmSet')
#' varPlot(y~day/run, dataEP05A2_3)
#' 
#' # display intercept (total mean)
#' varPlot(y~day/run, dataEP05A2_3, MeanLine=list(var="int"))
#' 
#' # use custom VC-names
#' varPlot(y~day/run, dataEP05A2_3, VCnam=list(text=c("_Day", "_Run")))
#' 
#' # re-plot now also indicating dayly means as blue horizontal lines
#' varPlot(y~day/run, dataEP05A2_3, MeanLine=list(var=c("day", "int"), col="blue"))
#' 
#' # now use variable-names in names of individual factor-levels and use a different 
#' # notation of the nesting structure
#' varPlot(y~day+day:run, dataEP05A2_3, useVarNam=TRUE)
#' 
#' # rotate names of VCs to fit into cells
#' varPlot(	y~day+day:run, dataEP05A2_3, useVarNam=TRUE, 
#' 			VarLab=list(list(font=2, srt=60), list(srt=90)))
#' 
#' # use alternating backgrounds for each level of factor "day" 
#' # (top-level factor is default) 
#' # use a simplified model formula (NOTE: only valid for function 'varPlot')
#' varPlot(y~day+run, dataEP05A2_3, BG=list(col=c("gray70", "gray90"), border=NA))
#' 
#' # now also color the corresponding row in the table accordingly
#' varPlot( y~day+run, dataEP05A2_3, 
#'          BG=list(col=c("gray70", "gray90"), border=NA, col.table=TRUE))
#' 
#' # assign different point-colors according to a classification variable
#' # not part of the model (artificial example in this case)
#' varPlot( y~day+day:run, dataEP05A2_3, mar=c(1,5,1,7), VCnam=list(side=4),
#'          Points=list(col=list(var="user", col=c("red", "green"))) )
#' 
#' # always check order of factor levels before annotating
#' order(unique(dataEP05A2_3$user))
#' 
#' # add legend to right margin
#' legend.m(fill=c("green", "red"), legend=c("User 1", "User 2"))
#' 
#' # assign different plotting symbols according to a classification
#' # variable not part of the model
#' varPlot( y~day+day:run, dataEP05A2_3, mar=c(1,5,1,7), VCnam=list(side=4), 
#'          Points=list(pch=list(var="user", pch=c(2, 8))) )
#' 
#' # add legend to right margin
#' legend.m(pch=c(8,2), legend=c("User 1", "User 2"))
#' 
#' # assign custom plotting symbols by combining 'pch' and 'bg'
#' varPlot( y~day+day:run, dataEP05A2_3, 
#'          Points=list(pch=list(var="user", pch=c(21, 24)),
#'                      bg=list( var="user", bg=c("lightblue", "yellow"))) )
#' 
#' # assign custom plotting symbols by combining 'pch', 'bg', and 'cex'      
#' varPlot( y~day+day:run, dataEP05A2_3,                                    
#'          Points=list(pch=list(var="user", pch=c(21, 24)),                
#'                      bg =list(var="user", bg=c("lightblue", "yellow")),
#'                      cex=list(var="user",  cex=c(2,1))) )
#' 
#' # now combine point-coloring and plotting symbols
#' # to indicate two additional classification variables
#' varPlot( y~day+day:run, dataEP05A2_3, mar=c(1,5,1,10), 
#'          VCnam=list(side=4, cex=1.5),
#'          Points=list(col=list(var="user", col=c("red", "darkgreen")), 
#'                      pch=list(var="cls2", pch=c(21, 22)),
#'                      bg =list(var="user", bg =c("orange", "green"))) )
#' 
#' # add legend to (right) margin
#'  legend.m( margin="right", pch=c(21, 22, 22, 22), 
#'            pt.bg=c("white", "white", "orange", "green"), 
#'            col=c("black", "black", "white", "white"), 
#'            pt.cex=c(1.75, 1.75, 2, 2), 
#'            legend=c("Cls2=1", "Cls2=2", "User=2", "User=1"),
#'            cex=1.5)
#' 
#' # use blue lines between each level of factor "run" 
#' varPlot(y~day/run, dataEP05A2_3, BG=list(var="run", border="blue"))
#' 
#' # plot SDs for each run
#' varPlot(y~day+day:run, dataEP05A2_3, type=2)
#' 
#' # use CV instead of SD
#' varPlot(y~day/run, dataEP05A2_3, type=2, VARtype="CV")
#' 
#' # now plot variability-chart and SD-plot in one window
#' varPlot(y~day/run, dataEP05A2_3, type=3, useVarNam=TRUE)
#' 
#' # now further customize the plot
#' varPlot( y~day/run, dataEP05A2_3, BG=list(col=c("lightgray", "gray")),
#'          YLabel=list(font=2, col="blue", cex=1.75, text="Custom Y-Axis Label"),
#'          VCnam=list(col="red", font=4, cex=2),
#'          VarLab=list(list(col="blue", font=3, cex=2), list(cex=1.25, srt=-15)))
#' 
#' # create variability-chart of the example dataset in the CLSI EP05-A2 
#' # guideline (listed on p.25)
#' data(Glucose,package="VCA")
#' varPlot(result~day/run, Glucose, type=3)
#' 
#' # use individual settings of 'VarLab' and 'VSpace' for each variance component
#' varPlot(result~day/run, Glucose, type=3, 
#'         VarLab=list(list(srt=45, col="red", font=2), 
#'         list(srt=90, col="blue", font=3)), VSpace=c(.25, .75))
#' 
#' # set individual titles for both plot when 'type=3'
#' # and individual 'ylim' specifications
#'  varPlot(result~day/run, Glucose, type=3, 
#'			Title=list(	list(main="Variability Chart"), 
#'						list(main="Plot of SD-Values")),
#'			ylim=list(	c(230, 260), c(0, 10)))
#' 
#' # more complex experimental design
#' data(realData)
#' Data <- realData[realData$PID == 1,]
#' varPlot(y~lot/calibration/day/run, Data, type=3)
#' 
#' # order levels in the tablular environment
#' varPlot(y~lot/calibration/day/run, Data, keep.order=FALSE)
#' # keeping the order as in the data set (default) was different
#' varPlot(y~lot/calibration/day/run, Data, keep.order=TRUE)
#' 
#' # improve visual appearance of the plot by alternating bg-colors
#' # for variable "calibration"
#' varPlot(y~lot/calibration/day/run, Data, type=3, keep.order=FALSE,
#'         BG=list(var="calibration", col=c("white", "lightgray")))
#' 
#' # add horizontal lines indicating mean-value for each factor-level of all variables
#' varPlot(y~lot/calibration/day/run, Data, type=3, keep.order=FALSE,
#'         BG=list(var="calibration", 
#'                 col=c("lightgray","antiquewhite2","antiquewhite4",
#'                       "antiquewhite1","aliceblue","antiquewhite3",
#'                       "white","antiquewhite","wheat" ), 
#'                 col.table=TRUE),
#'         MeanLine=list(var=c("lot", "calibration", "day", "int"), 
#'                       col=c("orange", "blue", "green", "magenta"), 
#'                       lwd=c(2,2,2,2)))
#' 
#' # now also highlight bounds between factor levels of "lot" and "day" 
#' # as vertical lines and extend them into the table (note that each 
#' # variable needs its specific value for 'col.table')
#'  varPlot(y~lot/calibration/day/run, Data, type=3, keep.order=FALSE,
#'			BG=list(var="calibration", 
#'					col=c(	"aquamarine","antiquewhite2","antiquewhite4",
#'							"antiquewhite1","aliceblue","antiquewhite3",
#'                       	"white","antiquewhite","wheat" ), 
#'					col.table=TRUE),
#'			MeanLine=list(	var=c("lot", "calibration", "day", "int"), 
#'							col=c("orange", "blue", "darkgreen", "magenta"), 
#'							lwd=c(2,2,2,2)),
#'			VLine=list(	var=c("lot", "day"), col=c("black", "skyblue1"),
#'						lwd=c(2, 1), col.table=c(TRUE, TRUE)))
#' 
#' # one can use argument 'JoinLevels' to join factor-levels or a variable
#' # nested within a higher-level factor, 'VLine' is used to separate levels
#' # of variables "calibration" and "lot" with different colors
#'  varPlot(y~calibration/lot/day/run, Data, 
#'          BG=list(var="calibration", 
#'                  col=c("#f7fcfd","#e5f5f9","#ccece6","#99d8c9",
#'                        "#66c2a4","#41ae76","#238b45","#006d2c","#00441b"), 
#'                  col.table=TRUE), 
#'          VLine=list(var=c("calibration", "lot"), 
#'                     col=c("black", "darkgray"), lwd=c(2,1), col.table=TRUE), 
#'          JoinLevels=list(var="lot", col=c("#ffffb2","orangered","#feb24c"), 
#'                          lwd=c(2,2,2)), 
#'          MeanLine=list(var="lot", col="blue", lwd=2))
#' 
#' # same plot demonstrating additional features applicable via 'Points' 
#'  varPlot(y~calibration/lot/day/run, Data, 
#'          BG=list(var="calibration", 
#'                  col=c("#f7fcfd","#e5f5f9","#ccece6","#99d8c9",
#'                        "#66c2a4","#41ae76","#238b45","#006d2c","#00441b"), 
#'                  col.table=TRUE), 
#'          VLine=list(var=c("calibration", "lot"), 
#'                     col=c("black", "mediumseagreen"), lwd=c(2,1), 
#'                     col.table=c(TRUE,TRUE)), 
#'          JoinLevels=list(var="lot", col=c("lightblue", "cyan", "yellow"), 
#'                          lwd=c(2,2,2)), 
#'          MeanLine=list(var="lot", col="blue", lwd=2),
#'          Points=list(pch=list(var="lot", pch=c(21, 22, 24)), 
#'                      bg =list(var="lot", bg=c("lightblue", "cyan", "yellow")), 
#'                      cex=1.25))
#' 
#' # depict measurements as boxplots
#' data(VCAdata1)
#' datS5 <- subset(VCAdata1, sample==5)
#' varPlot(y~device/day, datS5, Boxplot=list()) 
#' 
#' # present points as jitter-plot around box-center
#' 	varPlot(y~device/day, datS5, 
#'			Boxplot=list(jitter=1, col.box="darkgreen"),
#'			BG=list(var="device", col=paste0("gray", c(60, 70, 80)),
#'					col.table=TRUE),
#'			Points=list(pch=16, 
#'						col=list(var="run", col=c("blue", "red"))), 
#'			Mean=list(col="black", cex=1, lwd=2),
#' 			VLine=list(var="day", col="white")) 
#' # add legend
#' legend( "topright", legend=c("run 1", "run 2"),
#'         fill=c("blue", "red"), box.lty=0, border="white")
#' }

varPlot <- function(form, Data, keep.order=TRUE, 
					type=c(1L, 2L, 3L)[1], VARtype="SD", htab=.5,
					Title=NULL, VSpace=NULL,
					VarLab=list(cex=.75, adj=c(.5, .5)),
					YLabel=list(text="Value", side=2, line=3.5, cex=1.5),
					SDYLabel=list(side=2, line=2.5),
					Points=list(pch=16, cex=.5, col="black"),
					SDs=list(pch=16, col="blue", cex=.75),
					SDline=list(lwd=1, lty=1, col="blue"),
					BG=list(border="lightgray", col.table=FALSE),
					VLine=list(lty=1, lwd=1, col="gray90"),
					HLine=NULL,
					Join=list(lty=1, lwd=1, col="gray"),
					JoinLevels=NULL,
					Mean=list(pch=3, col="red", cex=.5),
					MeanLine=NULL,
					Boxplot=NULL,
					VCnam=list(cex=.75, col="black", line=0.25),
					useVarNam=FALSE, 
					ylim=NULL, max.level=25, ...)
{        
	stopifnot(identical(class(Data),"data.frame"))
	stopifnot(class(form) == "formula")
	stopifnot(nrow(Data) > 2)                                               # at least 2 observations for estimating a variance
	
	args <- list(...)
	
	VARtype <- match.arg(toupper(VARtype), c("SD", "CV"))
	
	form <- terms(form, simplify=TRUE)
	stopifnot(attr(form, "response") == 1)
	resp <- rownames(attr(form, "factors"))[1]
	
	IntVal <- mean(Data[,resp], na.rm=TRUE)									# grand mean, possibly needed for MeanLine-functionality
	
	nest <- attr(form, "term.labels")
	if(length(nest) == 0)
		stop("Models, where the intercept is the only coefficient, are not supported!")
	
	nest <- sapply(nest, function(x){
				x <- unlist(strsplit(x, ":"))
				return(x[length(x)])
			})
	
	tab <- table(nest)
	if(any(tab > 1))
	{
		ind <- which(tab > 1)
		warning("Term(s) ", paste("'", nest[ind], "'", sep=""), " occurring multiple times as last sub-term in: ",paste(paste("'",names(nest), "'", sep=""), collapse=", "),". It will be kept only once!")
		nest <- unique(nest)
	}
	
	Nvc  <- length(nest)
	
	if(!is.null(Title))
	{
		stopifnot(class(Title) == "list")
		
		if(is(Title[[1]], "list"))
			stopifnot( class(Title[[2]]) == "list" )
		else																# replicate title settings for both possible plots
			Title <- list(Title, Title)
	}
	
	if( is.null(VSpace) || !is.numeric(VSpace) || sum(VSpace) != 1 )
		VSpace <- rep(1/Nvc, Nvc)                					 		# fraction of the table reserved for vertical names is automatically set (e.g. run/part)
	
	
	
	if( !is.null(BG) && is.null(BG$var) )
		BG$var <- nest[1]                                   				# use top-level factor for separating factor-levels if not otherwise specified 
	
	if(!keep.order)
	{
		expr <- "Data <- Data[order("
		
		for(i in 1:length(nest))
			expr <- paste0(expr, "Data[,\"", nest[i],"\"]", ifelse(i < length(nest), ",", ""))
		expr <- paste0(expr, "),]")
		eval(parse(text=expr))
	}
		
	if(nest[1] != "1")                                         				# travers nesting structure and build nested list
	{
		lst <- buildList(Data=Data, Nesting=nest, Current=nest[1], resp=resp,
				useVarNam=useVarNam, Points=Points)
#				keep.order=keep.order, useVarNam=useVarNam, Points=Points)
	}
	else
	{
		lst <- as.list(Data[,resp])
		
		names(lst) <- paste("rep", 1:nrow(Data), sep="")
		attr(lst, "Nelem") <- rep(1, length(lst))
		
		# point colors used to indicated class levels?
		
		if( (is.list(Points$col)) && ("var" %in% names(Points$col)) && (Points$col$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$col$var])) > length(unique(Points$col$col)))          # not enough colors, need to be recycled
			{
				Points$col$col <- rep(Points$col$col, ceiling(length(unique(Data[,Points$col$var])) / length(unique(Points$col$col))))
			}
			tmp <- 1:length(unique(Data[,Points$col$var]))
			names(tmp) <- unique(Data[,Points$col$var])
			attr(lst, "Color") <- Points$col$col[tmp[Data[,Points$col$var]]]                   # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			attr(lst, "Color") <- rep(Points$col, nrow(Data))
		}
		
		# plotting symbols used to indicated class levels?
		
		if( (is.list(Points$pch)) && ("var" %in% names(Points$pch)) && (Points$pch$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$pch$var])) > length(unique(Points$pch$pch)))          # not enough colors, need to be recycled
			{
				Points$pch$pch <- rep(Points$pch$pch, ceiling(length(unique(Data[,Points$pch$var])) / length(unique(Points$pch$pch))))
			}
			tmp <- 1:length(unique(Data[,Points$pch$var]))
			names(tmp) <- unique(Data[,Points$pch$var])
			attr(lst, "Symbol") <- Points$pch$pch[tmp[Data[,Points$pch$var]]]                   # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			attr(lst, "Symbol") <- rep(Points$pch, nrow(Data))
		}
		
		# plotting symbol backgrounds used to indicated class levels?
		
		if( (is.list(Points$bg)) && ("var" %in% names(Points$bg)) && (Points$bg$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$bg$var])) > length(unique(Points$bg$bg)))          # not enough colors, need to be recycled
			{
				Points$bg$bg <- rep(Points$bg$bg, ceiling(length(unique(Data[,Points$bg$var])) / length(unique(Points$bg$bg))))
			}
			tmp <- 1:length(unique(Data[,Points$bg$var]))
			names(tmp) <- unique(Data[,Points$bg$var])
			attr(lst, "BG") <- Points$bg$bg[tmp[Data[,Points$bg$var]]]                   # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			attr(lst, "BG") <- rep(Points$bg, nrow(Data))
		}
		
		# plotting symbol size used to indicated class levels?
		
		if( (is.list(Points$cex)) && ("var" %in% names(Points$cex)) && (Points$cex$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$cex$var])) > length(unique(Points$cex$cex)))          # not enough colors, need to be recycled
			{
				Points$cex$cex <- rep(Points$cex$cex, ceiling(length(unique(Data[,Points$cex$var])) / length(unique(Points$cex$cex))))
			}
			tmp <- 1:length(unique(Data[,Points$cex$var]))
			names(tmp) <- unique(Data[,Points$cex$var])
			attr(lst, "CEX") <- Points$cex$cex[tmp[Data[,Points$cex$var]]]                   # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			attr(lst, "CEX") <- rep(Points$cex, nrow(Data))
		}
	}

	Nelem <- attr(lst, "Nelem")                                             # number of bottom-level sub-classes per top-level factor-level  
	Nbin <- attr(lst, "Nbin")                                               # number of bins, i.e. scatterplots
	Xdiff <- length(lst)/sum(Nelem)                                         # basic cell-width of the tabular        
	
	Range 		<- range(Data[, resp], na.rm=TRUE)                          # plotting limits (Y-direction) for the variability-plot    
	Range[1] 	<- Range[1] - diff(Range) * .1
	Range[2] 	<- Range[2] + diff(Range) * .1
	Range 		<- range(pretty(Range))
	Range2		<- NULL
		
	if( !is.null(ylim) )                                                    # user-defined Y-limits
	{
		if (type == 1) {
			if( is.numeric(ylim) && length(ylim) > 1)
				Range <- ylim[1:2]
		} else if(type == 2) {
			Range2 <- ylim[1:2]
		} else {
			if(is.list(ylim))
			{
				if( is.numeric(ylim[[1]]) && length(ylim[[1]]) > 1)
					Range <- ylim[[1]][1:2]
				
				if( is.numeric(ylim[[2]]) && length(ylim[[2]]) > 1)
					Range2 <- ylim[[2]][1:2]
				
				ylim <- ylim[[1]]
			} else {						# assume that only variability chart is meant
				Range <- ylim[1:2]
			}
		}
	}
	
	YLim <- Range
	
	YLim[1] <- YLim[1] - diff(YLim) * htab
	
	if(VARtype == "SD")
		SDrange <- attr(lst, "SDrange")                                     # plotting limits (Y-direction) for the SD-plot
	if(VARtype == "CV")
		SDrange <- attr(lst, "CVrange")                                     # use CV instead of SD
	
	SDrange[1] 	<- SDrange[1] - diff(SDrange) * .05
	SDrange[2] 	<- SDrange[2] + diff(SDrange) * .1
	SDrange 	<- range(pretty(SDrange))
	SDYLim 		<- SDrange
	if(!is.null(Range2))
	{
		SDrange <- Range2
		SDYLim  <- Range2
	} else {
		Range2 <- SDrange
	}
	
	SDYLim[1] 	<- SDYLim[1] - diff(SDYLim) * htab

	if(type == 3L)
		MFROW <- c(2,1)
	#else
	#	MFROW <- c(1,1)

	if(!is.null(VCnam))
	{
		VCnam.default <- list(cex=.75, col="black", line=0.35, side=2)
		VCnam.default[names(VCnam)] <- VCnam
		VCnam <- VCnam.default
		if(!VCnam$side %in% c(2, 4))
			VCnam$side <- 2
		VCnam$adj <- ifelse(VCnam$side == 2, 1, 0)
	}

	mar <- c(	1,
				5.1, 
				ifelse(is.null(Title), 1, 3.5),
				ifelse(VCnam$side == 2, 1, 5.1))
	
#	if(is.null(Title))
#		mar <- c(1,4.1,1,1) 
#	else
#		mar <- c(2,4.1,3.5,1)
	
	if(!is.null(args))
	{
		if(!"mar" %in% names(args))
			args[["mar"]] <- mar
		args[["xaxs"]]  <- "i"
		args[["yaxs"]]  <- "i"
		
		if(type == 3L)						# only if two plots are requested at once, otherwise it may interfer with 'layout'
			args[["mfrow"]] <- MFROW
	}
	else
	{
		if(type == 3L)
			args <- list(mar=mar, xaxs="i", yaxs="i", mfrow=MFROW)
		else
			args <- list(mar=mar, xaxs="i", yaxs="i")	
	}
	
	old.par <- do.call("par", args)											# set graphical parameters
	
	Ybound   <- YLim[1]														# generate Y-limits for the tabular environment(s)
	SDYbound <- SDYLim[1]
	
	for(i in 1:Nvc)
	{
		Ybound   <- c(Ybound,     Ybound[length(  Ybound)] + VSpace[i] * abs(diff(c(Range[1], YLim[1]))) )
		SDYbound <- c(SDYbound, SDYbound[length(SDYbound)] + VSpace[i] * abs(diff(c(SDrange[1], SDYLim[1]))) )
	}	
	
	Xbound <- c(0, cumsum(Xdiff * Nelem))
	
	VarLab.default <- list(cex=.75, adj=c(.5, .5))
	tmpList <- list()
	for(i in 1:Nvc)																			# replicate default-settings list as many times as there are VCs
		tmpList[[i]] <- VarLab.default
	VarLab.default <- tmpList
	
	if(any(sapply(VarLab, class) == "list"))												# VarLab consists of element(s) which is/are list(s)
	{
		for(i in 1:length(VarLab.default))
		{
			if(is(VarLab[[i]], "list"))
				VarLab.default[[i]][names(VarLab[[i]])] <- VarLab[[i]]
		}
	}
	else																					# default settings used for each VC (no user-specification as list of lists)
	{
		for(i in 1:length(VarLab.default))
		{
			VarLab.default[[i]][names(VarLab)] <- VarLab
		}
	}
	VarLab <- VarLab.default
	
	YLabel.default <- list(text="Value", side=2, line=3.5, cex=1.5, at=mean(Range))
	YLabel.default[names(YLabel)] <- YLabel
	YLabel <- YLabel.default
	
	SDYLabel.default <- list(text=ifelse(VARtype=="SD", "SD", "CV %"), side=2, line=2, at=mean(SDrange))
	SDYLabel.default[names(SDYLabel)] <- SDYLabel
	SDYLabel <- SDYLabel.default
	
	Points.default <- list(pch=16, cex=.5, col="black")
	Points.default[names(Points)] <- Points
	Points <- Points.default
	
	if(!is.null(Boxplot))
	{
		Boxplot.default <- list( col.box="gray65", col.median="white",
								 col.whiskers="gray65", lwd=2, jitter=1e3)
		Boxplot.default[names(Boxplot)] <- Boxplot
		Boxplot <- Boxplot.default
		if(!is.null(Boxplot$jitter) && Boxplot$jitter < 5)
			Boxplot$jitter <- 5
	}
	
	SDs.default <- list(pch=16, col="blue", cex=.75)
	SDs.default[names(SDs)] <- SDs
	SDs <- SDs.default
	
	if(!is.null(BG))
	{
		BG.default <- list(border=NA, col.table=FALSE, col.bg=TRUE)
		BG.default[names(BG)] <- BG
		BG <- BG.default
		
		if(!is.null(BG$col) && !is.null(BG$var))			# color(s) and variable specified
		{
			Lvls <- unique(Data[,BG$var])
			
			if(length(BG$col) == length(Lvls))
			{
				names(BG$col) <- Lvls						# same color will be used for same level
			}
		}
	}
	
	if(!is.null(SDline))
	{
		SDline.default <- list(lwd=1, lty=1, col="blue")
		SDline.default[names(SDline)] <- SDline
		SDline <- SDline.default
	}
	
#	if(!is.null(VCnam))
#	{
#		VCnam.default <- list(cex=.75, col="black", line=0.25, side=2)
#		VCnam.default[names(VCnam)] <- VCnam
#		VCnam <- VCnam.default
#	}
	
	if(!is.null(Join))
	{
		Join.default <- list(lty=1, lwd=1, col="gray")
		Join.default[names(Join)] <- Join   
		Join <- Join.default
	}
	
	if (!is.null(Mean)) {
		Mean.default <- list(pch = 3, col = "yellow", cex = 0.35)
		Mean.default[names(Mean)] <- Mean
		Mean <- Mean.default
	}
	
	if(!is.null(JoinLevels) && is.list(JoinLevels) && JoinLevels$var %in% nest[2:length(nest)])
	{
		JoinLevelsLevels <- unique(as.character(Data[, JoinLevels$var]))
		if(is.null(JoinLevels$col) || length(JoinLevels$col) < length(JoinLevelsLevels))
		{
			JoinLevels$col <- sample(colors()[1:length(JoinLevelsLevels)])
			warning("Random selection of colors used because too few user-specified colors detected!")
		}
		JLcolorMap <- JoinLevels$col
		names(JLcolorMap) <- JoinLevelsLevels
		JoinLevels$col <- NULL									# information not needed any more
	}
	
	# environment for recording plotting coordinates and used as interims storage for plotting information
	rec.env <- environment()
	rec.env$dat <- list()
	rec.env$VLineCollection <- list()							# also use it for collecting vertical line information
	rec.env$JoinLevelsCollection <- list()						# and for joining factor level means
	rec.env$JoinCollection <- list()
	rec.env$MeanLineCollection <- list()
	rec.env$MeansCollection <- list()
	rec.env$PointCollection <- list()
	
	#abline(h=Ybound[Nvc+1])
	
	tlNames <- names(lst)
	
	global.Points <- list()
	
	# adding tabluar environment at the bottom and scatterplot of observed values
	#
	# lst       	... (list) to be processed 
	# xlim      	... (numeric) vector specifying the X-limits for the current list
	# yval      	... (numeric) vector specifying the Y-coordinates of horizontal lines
	# Xdiff     	... (numeric) value specifying the width of a base cell (cell corresponding to a node)
	# type      	... (integer) see 'type' above
	# VARtype   	... (character) see 'VARtype' above
	# StatsList 	... (list) for descriptive statistics
	# VarLab		... (list of lists) specifying the labelling style in the tabular-environment
	# MeanLine		... (list) of function arguments specifying factor-levels means
	# VLine			... (list) of function arguments for vertically separating factor-levels
	# Intercept 	... (list) of function arguments specifying the horizontal intercept-line
	# JoinLevels	... (list) of functin arguments specifying lines joining factor-level means, nested within higher order factor
	# draw.vertical ... (logical) indicating whether vertical lines in the table should be drawn or not, depends on 'max.level'
	
	processList <- function(lst, xlim, yval, Xdiff, type, VARtype, StatsList, index=1, 
			VarLab, MeanLine, VLine, Intercept, JoinLevels, draw.vertical=TRUE)
	{
		Xlower <- xlim[1]    
		
		Stats  <- attr(lst, "Stats")
		Nelem  <- attr(lst, "Nelem")
		Nlevel <- attr(lst, "Nlevel")
		
		Names <- names(lst)
		
		col.table <- FALSE
		
		if( !is.null(BG))                                                           # draw vertical lines between or color background for factor-levels
		{
			if( BG$var == attr(lst, "factor") )                                  	# apply BG-list to the current factor
			{
				tmpBG <- BG
				ColNames <- names(BG$col)											# might be NULL
				
				col.table <- tmpBG$col.table										# color respective row in table below?
				col.bg    <- tmpBG$col.bg											# color the background?
				tmpBG$col.table <- tmpBG$col.bg <- NULL
			}               
			else
				tmpBG <- NULL
		}
		else																		# background will not be colored
		{
			tmpBG <- NULL
			
			if(!is.null(Intercept))													# horizontal intercept line will not be covered by 'BG'
				do.call("lines", Intercept)
		}
		
		
		Factor <- attr(lst, "factor")												# current factor-variable to be processed
		
		for(i in 1:length(lst))
		{  
			tmp <- lst[[i]]    
			tmp.draw.vertical <- Nlevel[i] <= max.level								# no further sub-division if too many levels
			
			Xupper <- Xlower + Xdiff * Nelem[i]
			
			if( !is.null(tmpBG) )                                                   # apply BG-settings
			{
				tmpBG$factor    <- NULL
				tmpBG$xleft     <- Xlower
				tmpBG$xright    <- Xupper
				
				if(type %in% c(1,3))
				{
					tmpBG$ybottom   <- Range[1]										# outside of 'processList' defined
					tmpBG$ytop      <- Range[2]
				}
				else if(type %in% c(2,3))
				{
					tmpBG$ybottom   <- SDYbound[length(SDYbound)] 
					tmpBG$ytop      <- SDYLim[2] * 2
				}
				
				if( length(BG$col) > 1 )
				{
					if(!is.null(ColNames))
						tmpBG$col <- BG$col[Names[i]]
					else
						tmpBG$col <- BG$col[index]
					last.index <- index
				}
				
				tmpBG$var <- NULL													# "var" is no graphical parameter
				if(col.bg)
					do.call("rect", tmpBG)                                          # draw the rectangle as background for the specified group
				
				if(col.table)														# color levels for the factor determining BG-colors
				{
					tmpBG$ybottom <- yval[1]
					tmpBG$ytop    <- yval[2]
					
					if(is.na(tmpBG$border))											# ensure that border is always drawn in the tabular part
					{						
						tmpBG$border <- "black"
						do.call("rect", tmpBG)
						tmpBG$border <- NA
					}
					else
						do.call("rect", tmpBG)
				}
				
				if( length(BG$col) > 1 )
				{
					if( index + 1 > length(BG$col) )
						index <- 1
					else
						index <- index + 1                                          # use color (i+1) or go back to 1st color
				}
			}
			
			if(draw.vertical || i == length(lst))
				lines(x=rep(Xupper, 2), y=yval[1:2])                                # draw vertical lines of the table       
			
			if(is.list(tmp))                                                        # recursive descent
			{	
				VarLabel <- VarLab[[1]]												# use different variable name because VarLab is passed down and is not allowed to be manipulated
				
				VarLabel$x=mean(c(Xlower, Xupper))
				VarLabel$y=mean(yval[1:2])
				VarLabel$labels=Names[i]
				
				if(draw.vertical)
					do.call("text", args=VarLabel)
				
				if(!tmp.draw.vertical)
				{
					text(mean(c(Xlower, Xupper)),  mean(yval[2:3]), 
							paste("N=", Nlevel[i], sep=""), cex=.75)
				}
				
				StatsList <- processList(	lst=tmp, xlim=c(Xlower, Xupper), yval=yval[-1], Xdiff=Xdiff, type=type, 
											VARtype=VARtype, StatsList=StatsList, index=index, VarLab=VarLab[-1],
											MeanLine=MeanLine, VLine=VLine, Intercept=Intercept, JoinLevels=JoinLevels,
											draw.vertical=tmp.draw.vertical)
				index <- attr(StatsList, "index")
			}
			else                                                                    # leaf-node reached (numeric vector)
			{
				VarLabel <- VarLab[[1]]
				VarLabel$x=mean(c(Xlower, Xupper))
				VarLabel$y=mean(yval[1:2])
				VarLabel$labels=Names[i]
				
				if(draw.vertical)
					do.call("text", args=VarLabel)
				
				if(type == 1)
				{
					Points$x=rep(mean(mean(c(Xlower, Xupper))), length(tmp))        # add points to plot (X-coordinates)
					
					stylim <- tmp < ylim[1]											# smaller than ylim
					gtylim <- tmp > ylim[2]
					
					tmp.points <- tmp
					
					if(any( stylim | gtylim ))
					{
						tmp.points[stylim | gtylim] <- NA
					}
					
					Points$y <- tmp.points						
					
					rec.env$dat$x <- c(rec.env$dat$x, Points$x)						# record coordinates for later use
				}
				else
				{
					SDs$x=mean(c(Xlower, Xupper))
					SDs$y=ifelse(VARtype=="SD", Stats$SD[i], Stats$CV[i])
					StatsList$SDvec <- c(StatsList$SDvec, SDs$y)
					names(StatsList$SDvec)[length(StatsList$SDvec)] <- SDs$x
				}
				
				StatsList$Nobs <- c(StatsList$Nobs, length(na.omit(tmp)))           # record number of non-NA observation
				
				if(!is.null(Join) && type == 1)
				{
					Join$x=Points$x
					
					if(any( stylim | gtylim ))										# set extreme values to ylim
					{
						tmp.points <- tmp
						
						if(any(stylim))
							tmp.points[stylim] <- ylim[1]
						if(any(gtylim))
							tmp.points[gtylim] <- ylim[2]
						
						Join$y <- tmp.points
					}
					else
						Join$y=Points$y
					
					#do.call("lines", args=Join)
					rec.env$JoinCollection[[length(rec.env$JoinCollection)+1]] <- Join
				}
				
				if(!is.null(Mean) && type == 1)
				{
					Mean$x=Points$x[1]
					Mean$y=Stats$Mean[i]
					mstylim <- Mean$y < ylim[1]
					mgtylim <- Mean$y > ylim[2]
					if(any(mstylim))												# do plot mean-value if outside the plotting region
						Mean$y[mstylim] <- NA
					if(any(mgtylim))
						Mean$y[mgtylim] <- NA
					
					#do.call("points", args=Mean)
					rec.env$MeanCollection[[length(rec.env$MeanCollection) + 1]] <- Mean
				}
				
				Points$col <- attr(tmp, "Color")
				Points$pch <- attr(tmp, "Symbol")
				Points$bg  <- attr(tmp, "BG")
				Points$cex <- attr(tmp, "CEX")
				
				if(type == 1)
				{
					#do.call("points", args=Points)
					rec.env$PointsCollection[[length(rec.env$PointsCollection) + 1]] <- Points
				}
				else
				{
					if(!is.null(Range2))					# SD-points outside ylim will not be plotted 
					{
						SD.idx <- which(SDs$y < Range2[1])
						if(length(SD.idx) > 0)
							{
								SDs$x <- SDs$x[-SD.idx]
								SDs$y <- SDs$y[-SD.idx]
							}
					}
		
					do.call("points", args=SDs)
				}
				
#################
				if(!is.null(Boxplot))
					rec.env$BoxCollection <- Boxplot
			}
			
			if( !is.null(MeanLine) && !is.null(Factor) && Factor %in% MeanLine$var)
			{
				var.ind <- which(MeanLine$var == Factor)
				MeanLineClone <- MeanLine
				for(e in 1:length(MeanLineClone))
				{
					MeanLineClone[[e]] <- MeanLineClone[[e]][var.ind]				# only keep parameter values for current factor-variable
				}
				MeanLine.default <- list(lty=1, lwd=1, col="red")
				MeanLine.default[names(MeanLineClone)] <- MeanLineClone
				MeanLineClone <- MeanLine.default
				
				tmp.Mean     <- MeanLineClone
				if("mar" %in% names(MeanLineClone) && is.numeric(MeanLineClone$mar) && MeanLineClone$mar >= 0 && MeanLineClone$mar < .5 )
				{
					tmpXdiff   <- diff(c(Xlower, Xupper))	
					tmp.Mean$x <- c(Xlower+MeanLineClone$mar*tmpXdiff, Xupper-MeanLineClone$mar*tmpXdiff)
				}
				else
					tmp.Mean$x <- c(Xlower, Xupper)
				tmp.Mean$y   <- rep(Stats$Mean[i], 2)
				tmp.Mean$var <- NULL
				
				Mstylim <- tmp.Mean$y < ylim[1]
				Mgtylim <- tmp.Mean$y > ylim[2]
				
				if(any(Mstylim))
					tmp.Mean$y[Mstylim] <- NA
				if(any(Mgtylim))
					tmp.Mean$y[Mgtylim] <- NA
				
				#do.call("lines", tmp.Mean)
				rec.env$MeanLineCollection[[length(rec.env$MeanLineCollection) + 1]] <- tmp.Mean
			}
			
			nestInd <- which(nest == Factor)
			
			if( !is.null(VLine) && !is.null(Factor) && Factor %in% VLine$var)		# skip last vertical line
			{
				drawVLine <- TRUE
				
				if(nestInd > 1)														# not upper-most factor
				{
					if(nest[nestInd-1] %in% VLine$var)								# preceding factor in nesting structure also specified
					{
						drawVLine <- i < length(lst)
					}
				}
				else
				{
					if(i == length(lst))
						drawVLine <- FALSE											# omit last vertical line for upper-most factor
				}
				
				if(drawVLine)														# do not draw last vertical line, this separates levels of the factor one level above
				{
					var.ind <- which(VLine$var == Factor)
					VLineClone <- VLine
					for(e in 1:length(VLineClone))									# only keep parameter values for current factor-variable
					{
						VLineClone[e] <- VLineClone[[e]][var.ind]
						if(is.na(VLineClone[e]))									# not enough arguments provided, use last value
							VLineClone[e] <- tail(VLineClone[[e]])
					}
					
					VLine.default <- list(var=nest[1], lty=1, lwd=1, col="gray90")
					VLine.default[names(VLineClone)] <- VLineClone
					VLineClone <- VLine.default
					
					tmpVLine <- VLineClone
					tmpVLine$x <- c(Xupper, Xupper)
					if("col.table" %in% names(tmpVLine) && is.logical(tmpVLine$col.table) && isTRUE(tmpVLine$col.table))
					{
						if(type %in% c(1, 3))
							tmpVLine$y <- c(yval[1], Range[2]+abs(diff(Range)))
						if(type %in% c(2, 3))
							tmpVLine$y <- c(yval[1], SDYLim[2]*2)
					}
					else
					{
						if(type %in% c(1, 3))
							tmpVLine$y <- c(Range[1], Range[2]+abs(diff(Range)))
						if(type %in% c(2, 3))
							tmpVLine$y <- c(SDrange[1], SDYLim[2]*2)
					}
					tmpVLine$col.table <- NULL
					tmpVLine$var <- NULL
					
					#do.call("lines", tmpVLine)
					rec.env$VLineCollection[[length(rec.env$VLineCollection) + 1]] <- tmpVLine
				}
			}
			
			
			precFactor <- NULL
			if(nestInd > 1)
				precFactor <- nest[nestInd-1]											# preceding factor in nesting hierarchy
			
			if( !is.null(JoinLevels) && !is.null(Factor) && !is.null(precFactor) && Factor %in% JoinLevels$var)	# joining mean-values of current factor-variable
			{
				if(length(rec.env$JoinLevelsCollection[[Names[i]]]) == 0)
				{
					JoinLevelsClone <- JoinLevels
					
					JoinLevels.default <- list(lty=1, lwd=1)
					JoinLevels.default[names(JoinLevelsClone)] <- JoinLevelsClone
					JoinLevelsClone <- JoinLevels.default
					JoinLevelsClone$col <- JLcolorMap[Names[i]]							# use correct color for current factor-level
					
					JoinLevelsClone$x <- mean(c(Xlower, Xupper))
					JoinLevelsClone$y <- Stats$Mean[i]
					JoinLevelsClone$var <- NULL
					
					rec.env$JoinLevelsCollection[[Names[i]]] <- JoinLevelsClone
				}
				else																	# only append X- and Y-coordinates
				{
					rec.env$JoinLevelsCollection[[Names[i]]]$x <- c(rec.env$JoinLevelsCollection[[Names[i]]]$x, mean(c(Xlower, Xupper)))
					rec.env$JoinLevelsCollection[[Names[i]]]$y <- c(rec.env$JoinLevelsCollection[[Names[i]]]$y, Stats$Mean[i])
					rec.env$JoinLevelsCollection[[Names[i]]]$col <- JLcolorMap[Names[i]]
				}
			}
			
			if(!is.null(tmpBG))														# all BG-drawing finished
			{
				if(!is.null(Intercept))												# horizontal intercept line will not be covered by 'BG'
					do.call("lines", Intercept)
			}
			
			Xlower <- Xupper
		}
		abline(h=yval[2])
		
		attr(StatsList, "index") <- index                                           # remember last values of index
		invisible(StatsList)
	}
	
	MeanLineOnTop <- FALSE															# default
	
	if(!is.null(MeanLine$top))
	{
		MeanLineOnTop <- MeanLine$top
		MeanLine$top  <- NULL 
	}
	
	InterceptMeanLine <- NULL
	
	if(is.list(MeanLine) && "int" %in% MeanLine$var)								# draw horizontal line indicating the intercept if specified
	{		
		var.ind <- which(MeanLine$var == "int")
		
		MeanLineClone <- MeanLine
		for(e in 1:length(MeanLineClone))
		{
			MeanLineClone[[e]] <- MeanLineClone[[e]][var.ind]						# only keep parameter values for current factor-variable
			
			if(is.na(MeanLineClone[[e]]))											# remove if not set by the user (defaults will be applied)
				MeanLineClone[[e]] <- NULL
		}
		Intercept <- list(lty=3, lwd=2, col="gray")									# default settings
		Intercept[names(MeanLineClone)] <- MeanLineClone
		Intercept$var <- NULL
		
		if("mar" %in% names(Intercept) && is.numeric(Intercept$mar) && Intercept$mar >= 0 && Intercept$mar < .5 )
		{
			Xlower <- Xbound[1]
			Xupper <- Xbound[2]
			tmpXdiff   <- diff(c(Xlower, Xupper))	
			Intercept$x <- c(Xlower+MeanLineClone$mar*tmpXdiff, Xupper-Intercept$mar*tmpXdiff)
		}
		else
			Intercept$x <- range(Xbound)
		
		Intercept$y <- rep(IntVal, 2)	
		
		if(MeanLineOnTop)															# plot it on top of measurements
		{
			InterceptMeanLine <- Intercept
			Intercept <- NULL
		}
	}
	else
		Intercept <- NULL
	
	if(type %in% c(1,3))
	{
		plot(1, axes=FALSE, xlab="", ylab="", ylim=YLim, xlim=c(0, length(lst)), type="n")
		
#		if(!is.null(Grid))
#		{			
#			USR	<- par("usr")
#			Grid.def <- list(v=pretty(USR[1:2]), h=pretty(USR[3:4]), lty=3, col="gray90")
#			Grid.def[names(Grid)] <- Grid
#			Grid <- Grid.def
#			do.call("abline", Grid)			
#			rect(USR[1], USR[3], USR[2], Range[1], col="white", border="white")
#		}
#		
		tmp.at <- pretty(Range)
		if(any(tmp.at < min(Range)))
			tmp.at <- tmp.at[-which(tmp.at < min(Range))]
		axis(2, at=tmp.at, las=1) 
		#axis(2, at=pretty(Range))
		
		do.call("mtext", args=YLabel)
		
		if(!is.null(Title))
			do.call("title", args=Title[[1]])
		
		StatsList <- list(SDvec=numeric(), Nobs=integer())
		
		StatsList <- processList(lst=lst, xlim=range(Xbound), yval=Ybound, Xdiff=Xdiff, type=1, 
				StatsList=StatsList, VARtype=VARtype, VarLab=VarLab, MeanLine=MeanLine, 
				Intercept=Intercept, VLine=VLine, JoinLevels=JoinLevels)   		  		
		
		if(!is.null(HLine))
		{
			HLine.def1 <- list(h=tmp.at, col="gray90", lty=3)
			if(type == 3 && is(HLine[[1]], "list"))
				HLine.def1[names(HLine[[1]])] <- HLine[[1]]
			else
				HLine.def1[names(HLine)] <- HLine
			HLine1 		<- HLine.def1
			HLine1$v 	<- NULL
			oor   		<- which(HLine1$h <= min(Range))
			if(length(oor) > 0)
				HLine1$h <- HLine1$h[-oor]
			do.call("abline", HLine1)
		}
		
		box()
		
		if(length(rec.env$VLineCollection) > 0)
		{
			for(i in 1:length(rec.env$VLineCollection))								# now actually draw vertical lines, avoiding BG-coloring to partially cover them
				do.call("lines", rec.env$VLineCollection[[i]])
			
			rec.env$VLineCollection <- NULL											# should not be returned
		}
		
		if(length(rec.env$JoinLevelsCollection) > 0)
		{
			for(i in 1:length(rec.env$JoinLevelsCollection))						# now actually draw lines joining factor-level means
				do.call("lines", rec.env$JoinLevelsCollection[[i]])
			
			rec.env$JoinLevelsCollection <- NULL
		}
		
		if(length(rec.env$JoinCollection) > 0)										# vertical lines joining observations
		{
			for(i in 1:length(rec.env$JoinCollection))
				do.call("lines", rec.env$JoinCollection[[i]])
			
			rec.env$JoinCollection <- NULL
		}
		
		if(!MeanLineOnTop)															# mean-lines in front
		{
			if(length(rec.env$MeanLineCollection) > 0)									# mean line per factor-level
			{
				for(i in 1:length(rec.env$MeanLineCollection))
					do.call("lines", rec.env$MeanLineCollection[[i]])
				
				rec.env$MeanLineCollection <- NULL
			}
		}
		
		if(length(rec.env$BoxCollection) > 0)
		{
			drawBox <- function(y, x, xdiff, col.box="gray65", col.median="white",
								col.whiskers="gray65", lwd=3, jitter=FALSE)
			{
				bp 		<- boxplot(y, plot=F)									# get stats
				Xlim 	<- x + c(-1, 1) * xdiff/3
				rect(	Xlim[1], bp$stats[2,1], Xlim[2], bp$stats[4,1], 		# box
						col=col.box, border=NA)
				lines(	Xlim, rep(bp$stats[3,1], 2), col=col.median, lwd=lwd)	# median
				lines(	rep(x, 2), bp$stats[1:2,1], col=col.whiskers, lwd=lwd)	# whiskers
				lines(	rep(x, 2), bp$stats[4:5,1], col=col.whiskers, lwd=lwd)
				lines(	x + c(-1, 1) * xdiff/5, rep(bp$stats[1,1], 2), 
						col=col.whiskers, lwd=lwd)
				lines(	x + c(-1, 1) * xdiff/5, rep(bp$stats[5,1], 2), 
						col=col.whiskers, lwd=lwd)
			}
			for(i in 1:length(rec.env$PointsCollection))
			{
				tmp <- c(list(	y=rec.env$PointsCollection[[i]]$y, 
						 		x=rec.env$PointsCollection[[i]]$x[1],
								xdiff=Xdiff),
						 rec.env$BoxCollection )

				 do.call("drawBox", tmp)
			}
			rec.env$BoxCollection <- NULL
		}
		
		if(length(rec.env$PointsCollection) > 0)									# observations
		{
			for(i in 1:length(rec.env$PointsCollection))
			{
				if(!is.null(Boxplot))
				{
					if(!is.null(Boxplot$jitter) && Boxplot$jitter)
					{
						jitter <- runif(length(rec.env$PointsCollection[[i]]$y),
										-Xdiff/Boxplot$jitter, Xdiff/Boxplot$jitter)
						jitter <- sample(jitter)
						rec.env$PointsCollection[[i]]$x <-  rec.env$PointsCollection[[i]]$x + jitter
					}
				}
				do.call("points", rec.env$PointsCollection[[i]])
			}
			rec.env$PointsCollection <- NULL
		}
		
		if(length(rec.env$MeanCollection) > 0)										# Mean values in factor-levels of variable one above error
		{
			for(i in 1:length(rec.env$MeanCollection))
				do.call("points", rec.env$MeanCollection[[i]])
			
			rec.env$MeanCollection <- NULL
		}
		
		if(MeanLineOnTop)
		{
			if(!is.null(InterceptMeanLine))
				do.call("lines", InterceptMeanLine)
			
			if(length(rec.env$MeanLineCollection) > 0)									# mean line per factor-level
			{
				for(i in 1:length(rec.env$MeanLineCollection))
					do.call("lines", rec.env$MeanLineCollection[[i]])
				
				rec.env$MeanLineCollection <- NULL
			}
		}
		
		if(!is.null(VCnam))
		{
			VCnam$at <- Ybound[-length(Ybound)]+diff(Ybound[1:2])/2
			if(is.null(VCnam$text))																			# do not overwrite user-specified text
				VCnam$text <- nest
			VCnam$las <- 1

			do.call("mtext", args=VCnam)
		}
	}
	
	if(type %in% c(2,3))                                                                        			# add a 2nd plot
	{
		plot(1, axes=FALSE, xlab="", ylab="", ylim=SDYLim, xlim=c(0, length(lst)), type="n")
				
		if(SDrange[1] == 0)
			abline(h=0, lty=2, col="gray")
		
		tmp.at <- pretty(Range2)
		if(any(tmp.at < min(Range2)))
			tmp.at <- tmp.at[-which(tmp.at < min(Range2))]
		
		axis(2, at=tmp.at, las=1) 
		
		abline(h=0, lty=2, col="gray")
		
		if(!is.null(HLine))
		{
			HLine.def2 <- list(h=tmp.at, col="gray90", lty=3)
			if(type == 3 && is(HLine[[2]], "list"))
				HLine.def2[names(HLine[[2]])] <- HLine[[2]]
			else
				HLine.def2[names(HLine)] <- HLine
			HLine2 		<- HLine.def2
			HLine2$v 	<- NULL
			oor   		<- which(HLine2$h <= min(SDrange))
			if(length(oor) > 0)
				HLine2$h <- HLine2$h[-oor]

			do.call("abline", HLine2)
		}
		
		#axis(2, at=pretty(SDrange)[-1])   
		
		do.call("mtext", args=SDYLabel)
		
		if(!is.null(Title))
			do.call("title", args=Title[[2]])
		
		StatsList <- list(SDvec=numeric(), Nobs=integer())
		#SDvec <- numeric()
		StatsList <- processList(	lst=lst, xlim=range(Xbound), yval=SDYbound, Xdiff=Xdiff, type=2, StatsList=StatsList, 
									VARtype=VARtype, VarLab=VarLab, MeanLine=MeanLine, Intercept=Intercept, VLine=VLine,
									JoinLevels=JoinLevels)   
		
		if(length(rec.env$VLineCollection) > 0)
		{
			for(i in 1:length(rec.env$VLineCollection))								# now actually draw vertical lines, avoiding BG-coloring to partially cover them
				do.call("lines", rec.env$VLineCollection[[i]])
			
			rec.env$VLineCollection <- NULL											# should not be returned
		}
		
		box()
		


		if(!is.null(Range2))
		{
			# adjust plotting region to user-specification by restricting the plotting region
			USR 	<- par("usr")
			PLT 	<- par("plt")
			PLT2 	<- PLT
			PLT2[3] <- PLT[4] - diff(Range2) * diff(PLT[3:4]) / (diff(USR[3:4]))
			par(plt=PLT2)
		}	
		if(!is.null(SDline))
		{
			SDline$x <- as.numeric(names(StatsList$SDvec))
			SDline$y <- StatsList$SDvec
			do.call("lines", args=SDline)
		}
		
		if(!is.null(Range2))			# 
			par(plt=PLT)
		
		if(!is.null(VCnam))
		{
#			VCnam$side=2
			VCnam$at <- SDYbound[-length(SDYbound)]+diff(SDYbound[1:2])/2
			if(is.null(VCnam$text))																			# do not overwrite user-specified text
				VCnam$text <- nest
			VCnam$las <- 1
			VCnam$adj <- 1
			do.call("mtext", args=VCnam)
		}
	}  
	
	#if(type == 3)                                          # adds number of observation per bin to the plot (between variability chart and SD/CV-plot)
	#{
	#    tmp <- StatsList$Nobs
	#    len <- length(tmp)
	#    rxb <- range(Xbound)
	#    dx <- diff(rxb)/(len+1)
	#    mtext(side=3, line=1.25, at=0-diff(rxb)*.1, text="Obs:", cex=.75)
	#    mtext(side=3, at=seq(rxb[1]+dx/2, rxb[2]-dx/2, length.out=len), text=tmp, cex=.75, line=1.25)
	#}
	
	old.par$yaxs <- old.par$xaxs <- "i"						# unfortunatly need to be maintained in order to further add elements to the plot
	old.par$mar <- par("mar")
	par(old.par)
	
	Data$Xcoord <- rec.env$dat$x
	
	invisible(Data)
}








#' Build a Nested List.
#'
#' Function \code{buildList} creates a nested-list reflecting the hierarchical structure of a fully-nested model, respectively, the imputed
#' hierarchical structure of the data (see details).
#' 
#' This function is not intended to be used directly and serves as helper function for \code{\link{varPlot}}.
#' Each factor-level, on each level of nesting is accompanied with a set of descriptive statistics, such as mean, median, var, sd, ... which can be evaluated
#' later on. These information are used in function \code{varPlot}, which implements a variability chart.
#' Note, that this function is also used if data does not correspond to a fully-nested design, i.e. the hierarchical structure is
#' inferred from the model formula. The order of main factors (not nested within other factors) appearing in the model formula determines
#' the nesting structure imputed in order to plot the data as variability chart.
#' 
#' @param Data          (data.frame) with the data
#' @param Nesting       (character) vector specifying the nesting structure with the top-level variable name
#'                      as 1st element and the variance component one above the residual error as last element
#' @param Current       (character) string specifying the current level which has to be processed
#' @param resp          (character) string specifying the name of the response variable (column in 'Data')
#' @param keep.order    (logical) TRUE = the ordering of factor-levels is kept as provided by 'Data', FALSE = factor-levels are sorted on 
#'                      and within each level of nesting 
#' @param useVarNam     (logical) TRUE = each factor-level specifier is pasted to the variable name of the current variable and used as list-element name, 
#'                               FALSE = factor-level specifiers are used as names of list-elements; the former is useful when factor levels are indicated
#'                               as integers, e.g. days as 1,2,..., the latter is useful when factor levels are already unique, e.g. day1, day2, ...
#' @param sep           (character) string specifying the separator-string in case useVarNam=TRUE
#' @param na.rm         (logical) TRUE = NAs will be removed before computing the descriptive statistics AND NAs will be omitted when counting number of elements, 
#'                               FALSE = if there are NAs, this will result in NAs for the descriptive statistics  
#' @param Points        (list) specifying all parameters applicable to function 'points', used to specify scatterplots per lower-end factor-level
#'                      (e.g. run/part in EP05-A2 experiments). If list-element "col" is itself a list with elements "var" and "col", where the former
#'                      specifies a variable used for assigning colors "col" according to the class-level of "var", point-colors can be used for indicating
#'                      specific sub-classes not addressed by the model/design (see examples).
#' 
#' @return (list) which was recursively built, representing the data of the fully-nested as hierarchy
#' 
#' @author Andre Schuetzenmeister \email{andre.schuetzenmeister@@roche.com}
#' 
#' @examples
#' 
#' \dontrun{
#' # load data (CLSI EP05-A2 Within-Lab Precision Experiment)
#' data(dataEP05A2_3)
#' 
#' # build a list representing the hierarichal structure of a fully-nested model
#' # there needs to be a distinct hierarchy for being able to plot the data
#' # as variability chart (this function is not exported)
#' lst <- VCA:::buildList(Data=dataEP05A2_3, Nesting=c("day", "run"), Current="day", resp="y")
#' }

buildList <- function(	Data, Nesting, Current, resp, keep.order=TRUE, 
						useVarNam=TRUE, sep="", na.rm=TRUE, 
						Points=list(pch=16, cex=.5, col="black"))
{
	lev <- unique(as.character(Data[,Current]))
	
	if( is.list(Points))                                            # initial call
	{
		# check whether point-colors have to be used for indicating class-levels
		
		if( (is.list(Points$col)) && ("var" %in% names(Points$col)) && (Points$col$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$col$var])) > length(unique(Points$col$col)))          # not enough colors, need to be recycled
			{
				Points$col$col <- rep(Points$col$col, ceiling(length(unique(Data[,Points$col$var])) / length(unique(Points$col$col))))
			}
			tmp <- 1:length(unique(Data[,Points$col$var]))
			names(tmp) <- unique(Data[,Points$col$var])
			Data$PointsColor <- Points$col$col[tmp[as.character(Data[,Points$col$var])]]                         # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			Data$PointsColor <- rep(Points$col, nrow(Data))
		}
		
		# check whether plotting-symbols have to be used for indicating class-levels
		
		if( (is.list(Points$pch)) && ("var" %in% names(Points$pch)) && (Points$pch$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$pch$var])) > length(unique(Points$pch$pch)))          # not enough plotting symbols, need to be recycled
			{
				Points$pch$pch <- rep(Points$pch$pch, ceiling(length(unique(Data[,Points$pch$var])) / length(unique(Points$pch$pch))))
			}
			tmp <- 1:length(unique(Data[,Points$pch$var]))
			names(tmp) <- unique(Data[,Points$pch$var])
			Data$PointsPCH <- Points$pch$pch[tmp[as.character(Data[,Points$pch$var])]]                         # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			Data$PointsPCH <- rep(Points$pch, nrow(Data))
		}
		
		# check whether plotting-symbol backgrounds have to be used for indicating some sort of information
		
		if( (is.list(Points$bg)) && ("var" %in% names(Points$bg)) && (Points$bg$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$bg$var])) > length(unique(Points$bg$bg)))          # not enough background colors, need to be recycled
			{
				Points$bg$bg <- rep(Points$bg$bg, ceiling(length(unique(Data[,Points$bg$var])) / length(unique(Points$bg$bg))))
			}
			tmp <- 1:length(unique(Data[,Points$bg$var]))
			names(tmp) <- unique(Data[,Points$bg$var])
			Data$PointsBG <- Points$bg$bg[tmp[as.character(Data[,Points$bg$var])]]                         # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			Data$PointsBG <- rep(Points$bg, nrow(Data))
		}
		
		# check whether plotting-symbol sizes have to be used for indicating some sort of information
		
		if( (is.list(Points$cex)) && ("var" %in% names(Points$cex)) && (Points$cex$var %in% colnames(Data)) )
		{
			if(length(unique(Data[,Points$cex$var])) > length(unique(Points$cex$cex)))          # not enough background colors, need to be recycled
			{
				Points$cex$cex <- rep(Points$cex$cex, ceiling(length(unique(Data[,Points$cex$var])) / length(unique(Points$cex$cex))))
			}
			tmp <- 1:length(unique(Data[,Points$cex$var]))
			names(tmp) <- unique(Data[,Points$cex$var])
			Data$PointsCEX <- Points$cex$cex[tmp[as.character(Data[,Points$cex$var])]]                         # assign colors to class-levels of Points$col$var variable
		}
		else
		{
			Data$PointsCEX <- rep(Points$cex, nrow(Data))
		}
		
		Points <- NULL
	}
	
	if(!keep.order)
	{
		suppressWarnings(levInt <- as.integer(lev))
		if(!any(is.na(levInt)))
			lev <- lev[sort(levInt, index.return=TRUE)$ix]
		else
			lev <- sort(lev)
	}
	
	lst <- vector("list", length=length(lev))
	attr(lst, "factor") <- Current
	
	Mean <- Median <- SD <- CV <- Nelem <- Nlevel <- numeric(length(lev))
	Nbin <- 0
	SDrange <- c(Inf, -Inf)
	CVrange <- c(Inf, -Inf)
	
	if(useVarNam)
	{
		names(lst) <- paste(Current, lev, sep=sep)
	}
	else
		names(lst) <- lev
	
	for(i in 1:length(lev))
	{
		tmpData <- Data[which(Data[,Current] == lev[i]),]
		
		Mean[i] <- mean(tmpData[,resp], na.rm=TRUE)
		Median[i] <- median(tmpData[,resp], na.rm=TRUE)
		SD[i] <- sd(tmpData[,resp], na.rm=TRUE)        
		CV[i] <- SD[i]*100/Mean[i]
		
		if(Current == Nesting[length(Nesting)])                     # last level above residual error
		{
			lst[[i]] <- tmpData[, resp]
			attr(lst[[i]], "Color")  <- tmpData$PointsColor
			attr(lst[[i]], "Symbol") <- tmpData$PointsPCH 
			attr(lst[[i]], "BG")	 <- tmpData$PointsBG
			attr(lst[[i]], "CEX")	 <- tmpData$PointsCEX
			
			if(na.rm)                                                # na.rm=TRUE?
				lst[[i]] <- na.omit(lst[[i]])
			
			Nelem[i] <- length(unique(tmpData[,Current]))            # number of bottom-level classes
			Nbin <- Nbin + 1
			
#            Mean[i] <- mean(lst[[i]])
#            Median[i] <- median(lst[[i]])
#            SD[i] <- sd(lst[[i]])            
#            CV[i] <- SD[i]*100/Mean[i]
			
			if(!is.na(SD[i]) && SD[i] < SDrange[1])                                   # range of all SD-values   
				SDrange[1] <- SD[i] 
			if(!is.na(SD[i]) && SD[i] > SDrange[2])
				SDrange[2] <- SD[i]
			
			if(!is.na(CV[i]) && CV[i] < CVrange[1])                                   # range of all CV-values   
				CVrange[1] <- CV[i] 
			if(!is.na(CV[i]) && CV[i] > CVrange[2])
				CVrange[2] <- CV[i]
		}   
		else
		{   
			lst[[i]] <- buildList(	Data=tmpData, Nesting=Nesting, 
									Current=Nesting[which(Nesting == Current)+1], 
									resp=resp, keep.order=keep.order, 
									useVarNam, Points=Points)
			Nelem[i] <- sum(attr(lst[[i]], "Nelem"))
			Nbin <- Nbin + attr(lst[[i]], "Nbin")
			
			tmpSD <- attr(lst[[i]], "SDrange")
			
			if(tmpSD[1] < SDrange[1])                               # refresh SD-range values
				SDrange[1] <- tmpSD[1]
			if(tmpSD[2] > SDrange[2])
				SDrange[2] <- tmpSD[2]
			
			tmpCV <- attr(lst[[i]], "CVrange")
			
			if(tmpCV[1] < CVrange[1])                                  # range of all CV-values   
				CVrange[1] <- tmpCV[1] 
			if(tmpCV[2] > CVrange[2])
				CVrange[2] <- tmpCV[2]
		} 
		Nlevel[i] <- length(lst[[i]])
	}
	attr(lst, "Nelem")   <- Nelem
	attr(lst, "Nlevel")  <- Nlevel
	attr(lst, "Nbin")    <- Nbin
	attr(lst, "SDrange") <- SDrange
	attr(lst, "CVrange") <- CVrange
	
	attr(lst, "Stats") <- list(Mean=Mean, Median=Median, SD=SD, CV=CV)
	
	return(lst)
}


#' Standard 'plot' Method for 'VCA' S3-Objects.
#' 
#' Create a variability chart from a 'VCA'-object, i.e. from a fitted model.
#' 
#' This function extracts the data and the model-formula from a fitted 'VCA'-object and calls function \code{\link{varPlot}}
#' accepting all its arguments. Please see the documention of function \code{\link{varPlot}} for a detailed description.
#' 
#' It will not be differentiated between fixed and random effects when calling this function on a fitted linear mixed model.
#' 
#' @param x         (VCA) object 
#' @param ...       additional arguments to be passed to or from methods.
#' 
#' @return 	nothing, instead a plot is generated
#' 
#' @author Andre Schuetzenmeister \email{andre.schuetzenmeister@@roche.com}
#' 
#' @seealso \code{\link{varPlot}}, \code{\link{anovaVCA}},\code{\link{remlVCA}}, \code{\link{anovaMM}},\code{\link{remlMM}}
#' 
#' @method plot VCA
#' 
#' @examples 
#' \dontrun{
#' data(dataEP05A2_1)
#' fit <- anovaVCA(y~day/run, dataEP05A2_1)
#' 
#' # standard plot without any extras
#' plot(fit)
#' 
#' # plot with some additional features
#' plot(fit, MeanLine=list(var=c("int", "day"), col=c("cyan", "blue"), lwd=c(2,2)))
#' 
#' # more complex model
#' data(realData)
#' Data <- realData[realData$PID == 1,]
#' fit2 <- anovaVCA(y~(calibration+lot)/day/run, Data)
#' plot(fit2, 
#' 		BG=list(var="calibration",
#' 				col=c("#f7fcfd","#e5f5f9","#ccece6","#99d8c9",
#' 				      "#66c2a4","#41ae76","#238b45","#006d2c","#00441b"),
#' 				col.table=TRUE),
#'		VLine=list(var=c("calibration", "lot"),
#' 				   col=c("black", "darkgray"), lwd=c(2,1), col.table=TRUE),
#' 		JoinLevels=list(var="lot", col=c("#ffffb2","orangered","#feb24c"),
#' 				        lwd=c(2,2,2)),
#' 		MeanLine=list(var="lot", col="blue", lwd=2))
#'
#' }

plot.VCA <- function(x, ...)
{
	Data <- x$data
	form <- x$formula
	varPlot(form=form, Data=Data, ...)
}



#' Add Legend to Margin.
#' 
#' This function accepts all parameters applicable in and forwards them to function \code{\link{legend}}.
#' There will be only made some modifications to the X-coordinate ensuring that the legend is plotted in
#' the right margin of the graphic device. Make sure that you have reserved sufficient space in the right
#' margin, e.g. 'plot.VFP(....., mar=c(4,5,4,10))'.
#' 
#' @param x			(character, numeric) either one of the character strings "center","bottomright", "bottom", "bottomleft", 
#' 					"left", "topleft", "top", "topright", "right" or a numeric values specifying the X-coordinate in user
#' 					coordinates
#' @param y			(numeric) value specifying the Y-coordiante in user coordinates, only used in case 'x' is numeric
#' @param margin	(character) string specifying in which part of the margin the legend shall be added, choices are
#' 					"right", "bottomright", "bottom", "bottomleft", 
#' 					"left", "topleft", "top", "topright" with "right" being the default
#' @param offset	(numeric) value in [0, 0.5] specifying the offset as fraction in regard to width of the right margin
#' @param ...		all parameters applicable in function \code{\link{legend}}
#' 
#' @author Andre Schuetzenmeister \email{andre.schuetzenmeister@@roche.com}
#'  
#' @examples 
#' \dontrun{
#' 
#' par( mar=c(10,10,10,10) )
#' plot(1, type="n", axes=FALSE, xlab="", ylab="")
#' box()
#' # add legend to different regions within the 'margin'
#' legend.m(margin="topleft", 		fill="black",	legend=c("topleft"))
#' legend.m(margin="top", 			fill="red", 	legend=c("top"))
#' legend.m(margin="topright", 		fill="blue",	legend=c("topright"))
#' legend.m(margin="right", 		fill="green",	legend=c("right"))
#' legend.m(margin="bottomright", 	fill="yellow",	legend=c("bottomright"))
#' legend.m(margin="bottom", 		fill="orange",	legend=c("bottom"))
#' legend.m(margin="bottomleft", 	fill="cyan",	legend=c("bottomleft"))
#' legend.m(margin="left", 			fill="magenta", legend=c("left"))
#' 
#' data(dataEP05A2_3)
#' dataEP05A2_3$user <- sample(rep(c(1,2), 40))
#'  
#' varPlot( y~day+day:run, dataEP05A2_3, mar=c(1,5,1,7), VCnam=list(side=4), 
#' 	        Points=list(pch=list(var="user", pch=c(2, 8))) )
#' # always check order of factor levels before annotating
#' order(unique(dataEP05A2_3$user))
#' legend.m(pch=c(8,2), legend=c("User 1", "User 2"))
#' 
#' # using different colors 
#' varPlot( y~day+day:run, dataEP05A2_3, mar=c(1,5,1,7), VCnam=list(side=4),
#'          Points=list(col=list(var="user", col=c("red", "green"))) )
#' legend.m(fill=c("green", "red"), legend=c("User 1", "User 2"))
#'
#' # two additional classification variables
#' dataEP05A2_3$user <- sample(rep(c(1,2), 40))
#' dataEP05A2_3$cls2 <- sample(rep(c(1,2), 40))
#' 
#' # now combine point-coloring and plotting symbols
#' # to indicate two additional classification variables
#' varPlot( y~day+day:run, dataEP05A2_3, mar=c(1,5,1,10),
#'          VCnam=list(side=4, cex=1.5),
#'          Points=list(col=list(var="user", col=c("red", "darkgreen")),
#'                      pch=list(var="cls2", pch=c(21, 22)),
#'                      bg =list(var="user", bg =c("orange", "green"))) )
#'
#' # add legend to (right) margin
#' legend.m(margin="right", pch=c(21, 22, 22, 22), 
#'          pt.bg=c("white", "white", "orange", "green"),  
#'          col=c("black", "black", "white", "white"), 
#'          pt.cex=c(1.75, 1.75, 2, 2),
#'          legend=c("Cls2=1", "Cls2=2", "User=2", "User=1"), 
#'          cex=1.5)
#' }

legend.m <- function(	x=c("center","bottomright", "bottom", "bottomleft", 
							"left", "topleft", "top", "topright", "right"), 
						y=NULL, 
						margin=c(	"right", "bottomright", "bottom", "bottomleft", 
									"left", "topleft", "top", "topright"),
						offset=.05, ...)
{
	stopifnot(	is.numeric(x) || is.character(x) )
	if(is.character(x))
	{
		x <- match.arg(x[1], choices=c("center","bottomright", "bottom", "bottomleft", 
						"left", "topleft", "top", "topright", "right"))
	}else
	{
		stopifnot(is.numeric(y))
	}
	margin <- match.arg(margin[1], 
						choices=c(	"right", "bottomright", "bottom", "bottomleft", 
									"left", "topleft", "top", "topright"))
	
	par(xpd=TRUE)
	args <- list(...)
	
	if(!"xjust" %in% names(args))
		xjust <- 0.5
	else
		xjust <- args$xjust
	
	if(!"yjust" %in% names(args))
		yjust <- 0.5
	else
		yjust <- args$yjust
	
	USR  <- par("usr")
	PLT  <- par("plt")
	FIG  <- par("fig")
	
	if(margin == "right")
	{
		wm 		<- FIG[2] - PLT[2]			# width of the margin
		hm 		<- PLT[4] - PLT[3]			# height of the margin
		Left	<- PLT[2]
		Bottom	<- PLT[3]
	}
	else if(margin == "topright")
	{
		wm 		<- FIG[2] - PLT[2]						
		hm 		<- FIG[4] - PLT[4]
		Left	<- PLT[2]
		Bottom	<- PLT[4]
	}
	else if(margin == "bottomright")
	{
		wm 		<- FIG[2] - PLT[2]						
		hm 		<- PLT[3] - FIG[3]
		Left	<- PLT[2]
		Bottom	<- FIG[3]
	}
	else if(margin == "bottomleft")
	{
		wm 		<- PLT[1] - FIG[1]						
		hm 		<- PLT[3] - FIG[3]
		Left	<- FIG[1]
		Bottom	<- FIG[3]
	}
	else if(margin == "bottom")
	{
		wm 		<- PLT[2] - PLT[1]						
		hm 		<- PLT[3] - FIG[3]
		Left	<- PLT[1] 
		Bottom	<- FIG[3]
	}
	else if(margin == "top")
	{
		wm 		<- PLT[2] - PLT[1]
		hm 		<- FIG[4] - PLT[4]
		Left	<- PLT[1]
		Bottom	<- PLT[4]
	}
	else if(margin == "left")
	{
		wm 		<- PLT[1] - FIG[1]	
		hm 		<- PLT[3] - FIG[3]
		Left	<- FIG[1]
		Bottom	<- PLT[3]
	}
	else if(margin == "topleft")
	{
		wm 		<- PLT[1] - FIG[1]
		hm 		<- FIG[4] - PLT[4]
		Left	<- FIG[1]
		Bottom	<- PLT[4]
	}	
	
	if(is.character(x))
	{
		X.orig	<- x
		xjust 	<- 0.5							# defaults to center
		x 		<- Left + 0.5 * wm
		yjust   <- 0.5
		y		<- Bottom + 0.5 * hm
		
		if(grepl("left", X.orig))
		{
			xjust 	<- 0
			x 		<- Left + offset * wm
		}		
		if(grepl("right", X.orig))
		{
			xjust 	<- 1
			x 		<- Left + (1-offset) * wm
		}
		if(grepl("top", X.orig))
		{
			yjust 	<- 1
			y 		<- Bottom + (1-offset) * hm
		}
		if(grepl("bottom", X.orig))
		{
			yjust 	<- 0
			y 		<- Bottom + offset * hm
		}
	}
	
	x <- grconvertX(x, from="nic", to="user")
	y <- grconvertY(y, from="nic", to="user")
	
	args$x 		<- x
	args$y 		<- y
	
	if(!"xjust" %in% names(args))
		args$xjust 	<- xjust
	if(!"yjust" %in% names(args))
		args$yjust 	<- yjust
	
	do.call(legend, args)
	par(xpd=FALSE)
}

Try the VCA package in your browser

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

VCA documentation built on May 29, 2024, 1:48 a.m.