Nothing
.validatePipelineDef <- function(object){
e <- c()
if( !is.list(object@functions) ||
!all(vapply(object@functions, is.function, logical(1))) )
e <- c("`functions` should be a (named) list of functions!")
if(!all(vapply( object@functions, FUN.VALUE=logical(1),
FUN=function(x) "x" %in% names(formals(x)))))
e <- c(e, "Each function should at least take the argument `x`.")
isf <- function(x) is.null(x) || is.function(x)
if( !is.list(object@aggregation) ||
!all(vapply(object@aggregation, isf, logical(1))) )
stop("`aggregation` should be a list of functions and/or NULL slots!")
if( !is.list(object@evaluation) ||
!all(vapply(object@evaluation, isf, logical(1))) )
stop("`evaluation` should be a list of functions and/or NULL slots!")
if(!all(names(object@descriptions)==names(object@functions)))
e <- c(e, "descriptions do not match functions.")
if(!all(names(object@evaluation)==names(object@functions)))
e <- c(e, "evaluation do not match functions.")
if(!all(names(object@aggregation)==names(object@functions)))
e <- c(e, "aggregation do not match functions.")
args <- unlist( lapply( object@functions,
FUN=function(x){ setdiff(names(formals(x)), "x") }) )
if(any(duplicated(args))) e <- c(e, paste("Some arguments (beside `x`) is",
"used in more than one step, which is not currently supported."))
if(length( wa <- setdiff(names(object@defaultArguments),args) )>0)
e <- c(e, paste("The following default arguments are not in the pipeline's
functions:", paste(wa, collapse=", ")))
if(length(e) == 0) TRUE else e
}
#' @import methods
#' @exportClass PipelineDefinition
setClass( "PipelineDefinition",
slots=representation( functions="list", descriptions="list",
evaluation="list", aggregation="list",
initiation="function",
defaultArguments="list", misc="list" ),
prototype=prototype( functions=list(), descriptions=list(),
evaluation=list(), aggregation=list(),
initiation=identity,
defaultArguments=list(), misc=list() ),
validity=.validatePipelineDef )
#' PipelineDefinition
#'
#' Creates on object of class `PipelineDefinition` containing step functions,
#' as well as optionally step evaluation and aggregation functions.
#'
#' @param functions A list of functions for each step
#' @param descriptions A list of descriptions for each step
#' @param evaluation A list of optional evaluation functions for each step
#' @param aggregation A list of optional aggregation functions for each step
#' @param initiation A function ran when initiating a dataset
#' @param defaultArguments A lsit of optional default arguments
#' @param misc A list of whatever.
#' @param verbose Whether to output additional warnings (default TRUE).
#'
#' @return An object of class `PipelineDefinition`, with the slots functions,
#' descriptions, evaluation, aggregation, defaultArguments, and misc.
#'
#' @aliases PipelineDefinition-class
#' @seealso \code{\link{PipelineDefinition-methods}},
#' \code{\link{addPipelineStep}}. For an example pipeline, see
#' \code{\link{scrna_pipeline}}.
#' @export
#' @examples
#' PipelineDefinition(
#' list( step1=function(x, meth1){ get(meth1)(x) },
#' step2=function(x, meth2){ get(meth2)(x) } )
#' )
PipelineDefinition <- function( functions, descriptions=NULL, evaluation=NULL,
aggregation=NULL, initiation=identity,
defaultArguments=list(),
misc=list(), verbose=TRUE ){
if(!is.list(functions) || !all(vapply(functions, is.function, logical(1))))
stop("`functions` should be a (named) list of functions!")
n <- names(functions)
if(is.null(n))
n <- names(functions) <- paste0("step",seq_len(length(functions)))
descriptions <- .checkInputList(descriptions, functions, FALSE)
evaluation <- .checkInputList(evaluation, functions)
aggregation2 <- .checkInputList(aggregation, functions)
names(aggregation2)<-names(evaluation)<-names(descriptions)<-names(functions)
for(f in names(aggregation2)){
if(is.null(aggregation2[[f]]) && !is.null(evaluation[[f]]) &&
!(f %in% names(aggregation)))
aggregation2[[f]] <- defaultStepAggregation
}
if(is.null(misc)) misc <- list()
x <- new("PipelineDefinition", functions=functions,descriptions=descriptions,
evaluation=evaluation, aggregation=aggregation2,
initiation=initiation, defaultArguments=defaultArguments, misc=misc)
w <- which( !vapply(x@aggregation, is.null, logical(1)) &
vapply(x@evaluation, is.null, logical(1)) )
if(verbose && length(w)>0){
warning(paste("An aggregation is defined for some steps that do not have",
"a defined evaluation function: ",
paste(names(x@functions)[w], collapse=", "),
"It is possible that evaluation is performed by the step's",
"function itself.") )
}
x
}
.checkInputList <- function( x, fns, containsFns=TRUE,
name=deparse(substitute(x)) ){
name <- paste0("`",name,"`")
if(!is.null(x)){
if(length(x)!=length(fns)){
if(is.null(names(x)))
stop("If ", name, " does not have the same length as the number of ",
"steps, its slots should be named.")
if(length(unknown <- setdiff(names(x),names(fns)))>0)
stop("Some elements of ",name," (",paste(unknown,collapse=", "),")",
"are unknown.")
x <- lapply(names(fns), FUN=function(f){
if(is.null(x[[f]])) return(NULL)
x[[f]]
})
names(x) <- names(fns)
}
if( !is.null(names(x)) ){
if(!all(names(x)==names(fns)) )
stop("The names of ",name," should match those of `functions`")
}
}else{
x <- lapply(fns,FUN=function(x) NULL)
}
if(containsFns &&
!all(vapply(x, FUN=function(x) is.null(x) || is.function(x), logical(1))))
stop(name," should be a list of functions")
x
}
#' Methods for \code{\link{PipelineDefinition}} class
#' @name PipelineDefinition-methods
#' @rdname PipelineDefinition-methods
#' @aliases PipelineDefinition-method
#' @seealso \code{\link{PipelineDefinition}}, \code{\link{addPipelineStep}}
#' @param object An object of class \code{\link{PipelineDefinition}}
#' @return Depends on the method.
#' @examples
#' pd <- mockPipeline()
#' length(pd)
#' names(pd)
#' pd$step1
#' pd[2:1]
NULL
#' @rdname PipelineDefinition-methods
#' @importMethodsFrom methods show
#' @importFrom knitr opts_current
setMethod("show", signature("PipelineDefinition"), function(object){
# colors and bold are going to trigger errors when rendered in a knit, so
# we disable them when rendering
isKnit <- tryCatch( isTRUE(getOption('knitr.in.progress')) ||
length(knitr::opts_current$get())>0,
error=function(e) FALSE)
fns <- unlist(lapply(names(object@functions), FUN=function(x){
x2 <- x
if(!isKnit) x2 <- paste0("\033[1m",x,"\033[22m")
y <- lapply( names(formals(object@functions[[x]])), FUN=function(n){
if(!is.null(def <- object@defaultArguments[[n]]))
n <- paste0(n,"=",deparse(def,100,FALSE))
n
})
y <- paste0(" - ", x2, "(", paste(unlist(y), collapse=", "), ")")
if(!is.null(object@evaluation[[x]]) || !is.null(object@aggregation[[x]]))
y <- paste0(y, ifelse(isKnit, " * ", " \033[34m*\033[39m "))
if(!is.null(object@descriptions[[x]])){
x2 <- object@descriptions[[x]]
if(!isKnit) x2 <- paste0("\033[3m",x2,"\033[23m")
y <- paste(y, x2, sep="\n")
}
y
}))
cat("A PipelineDefinition object with the following steps:\n")
cat(paste(fns,collapse="\n"))
cat("\n")
})
#' get names of PipelineDefinition steps
#' @rdname PipelineDefinition-methods
#' @param x An object of class \code{\link{PipelineDefinition}}
setMethod("names", signature("PipelineDefinition"), function(x){
names(x@functions)
})
#' set names of PipelineDefinition steps
#' @rdname PipelineDefinition-methods
#' @param value Replacement values
setMethod("names<-", signature("PipelineDefinition"), function(x, value){
if(any(duplicated(value))) stop("Some step names are duplicated!")
names(x@functions) <- value
names(x@evaluation) <- value
names(x@aggregation) <- value
names(x@descriptions) <- value
validObject(x)
x
})
#' @rdname PipelineDefinition-methods
#' @param name The step name
setMethod("$", signature("PipelineDefinition"), function(x, name){
x@functions[[name]]
})
#' @rdname PipelineDefinition-methods
setMethod("length", signature("PipelineDefinition"), function(x){
length(x@functions)
})
#' @rdname PipelineDefinition-methods
#' @param i The index(es) of the steps
setMethod("[",signature("PipelineDefinition"), function(x, i){
new("PipelineDefinition", functions=x@functions[i],
descriptions=x@descriptions[i], evaluation=x@evaluation[i],
aggregation=x@aggregation[i], misc=x@misc)
})
#' @rdname PipelineDefinition-methods
setMethod("as.list",signature("PipelineDefinition"), function(x){
x@functions
})
#' @exportMethod arguments
#' @rdname PipelineDefinition-methods
setGeneric("arguments", function(object) args(object))
#' @rdname PipelineDefinition-methods
setMethod("arguments",signature("PipelineDefinition"), function(object){
lapply(object@functions, FUN=function(x){ setdiff(names(formals(x)), "x") })
})
#' @rdname PipelineDefinition-methods
#' @exportMethod defaultArguments
setGeneric("defaultArguments", function(object) NULL)
#' @exportMethod defaultArguments<-
#' @rdname PipelineDefinition-methods
setGeneric("defaultArguments<-", function(object, value) NULL)
#' @rdname PipelineDefinition-methods
setMethod("defaultArguments",signature("PipelineDefinition"), function(object){
object@defaultArguments
})
#' @rdname PipelineDefinition-methods
setMethod( "defaultArguments<-",signature("PipelineDefinition"),
function(object, value){
object@defaultArguments <- value
validObject(object)
object
})
#' @exportMethod stepFn
#' @rdname PipelineDefinition-methods
setGeneric("stepFn", function(object, step=NULL, type) standardGeneric("stepFn"))
#' @param step The name of the step for which to set or get the function
#' @param type The type of function to set/get, either `functions`,
#' `evaluation`, `aggregation`, `descriptions`, or `initiation` (will parse
#' partial matches)
#' @rdname PipelineDefinition-methods
setMethod("stepFn", signature("PipelineDefinition"),
function(object, step=NULL, type){
ft <- c("functions","evaluation","aggregation","descriptions","initiation")
type <- match.arg( type, ft )
if(is.null(step)) return(slot(object, type))
step <- match.arg(step, names(object))
slot(object, type)[[step]]
})
#' @exportMethod stepFn<-
#' @rdname PipelineDefinition-methods
setGeneric( "stepFn<-",
function(object, step, type, value) standardGeneric("stepFn<-") )
#' @rdname PipelineDefinition-methods
setMethod( "stepFn<-", signature("PipelineDefinition"),
function(object, step, type, value){
ft <- c("functions","evaluation","aggregation","descriptions","initiation")
type <- match.arg(type, ft)
if(type!="descriptions" && !is.null(value) && !is.function(value))
stop("Replacement value should be a function.")
if(type=="initiation"){
slot(object, type) <- value
}else{
step <- match.arg(step, names(object))
slot(object, type)[[step]] <- value
}
if(type=="evaluation" && !is.null(value)){
# also add the default aggregation:
if(is.null(slot(object, "aggregation")[[step]]))
slot(object, "aggregation")[[step]] <- defaultStepAggregation
}
object
})
#' addPipelineStep
#'
#' Add a step to an existing \code{\link{PipelineDefinition}}
#'
#' @param object A \code{\link{PipelineDefinition}}
#' @param name The name of the step to add
#' @param after The name of the step after which to add the new step. If NULL,
#' will add the step at the beginning of the pipeline.
#' @param slots A optional named list with slots to fill for that step (i.e.
#' `functions`, `evaluation`, `aggregation`, `descriptions` - will be parsed)
#'
#' @return A \code{\link{PipelineDefinition}}
#' @seealso \code{\link{PipelineDefinition}},
#' \code{\link{PipelineDefinition-methods}}
#' @importFrom methods is slot
#' @export
#'
#' @examples
#' pd <- mockPipeline()
#' pd
#' pd <- addPipelineStep(pd, name="newstep", after="step1",
#' slots=list(description="Step that does nothing..."))
#' pd
addPipelineStep <- function(object, name, after=NULL, slots=list()){
if(!is(object, "PipelineDefinition"))
stop("object should be a PipelineDefinition")
if(name %in% names(object)) stop("There is already a step with that name!")
if(!is.null(after) && !(after %in% names(object)))
stop("`after` should either be null or the name of a step.")
n <- c("functions","evaluation","aggregation","descriptions")
if(length(slots)>0)
names(slots) <- vapply( names(slots), choices=n, FUN=match.arg,
FUN.VALUE=character(1) )
if(!all(names(slots) %in% n))
stop(paste("fns should be a function or a list",
"with one or more of the following names:\n", paste(n,collapse=", ")))
if(is.null(after)){
i1 <- vector("integer")
i2 <- seq_along(names(object))
}else{
w <- which(names(object)==after)
i1 <- seq_len(w)
i2 <- seq.int(from=w+1, to=length(object))
if(w==length(object)) i2 <- vector("integer")
}
ll <- list(NULL)
names(ll) <- name
for(f in n) slot(object,f) <- c(slot(object,f)[i1], ll, slot(object,f)[i2])
for(f in names(slots)) stepFn(object, name, f) <- slots[[f]]
if(is.null(stepFn(object, name, "functions")))
stepFn(object, name, "functions") <- identity
validObject(object)
object
}
#' mockPipeline
#'
#' A mock `PipelineDefinition` for use in examples.
#'
#' @return a `PipelineDefinition`
#' @export
#'
#' @examples
#' mockPipeline()
mockPipeline <- function(){
PipelineDefinition(
list( step1=function(x, meth1){ get(meth1)(x) },
step2=function(x, meth2){ get(meth2)(x) } ),
evaluation=list( step2=function(x) c(mean=mean(x), max=max(x)) ),
descriptions=list( step1="This steps applies meth1 to x.",
step2="This steps applies meth2 to x."),
defaultArguments=list(meth1=c("log","sqrt"), meth2="cumsum")
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.