932ec5c4 |
.validatePipelineDef <- function(object){
|
93cfa123 |
|
932ec5c4 |
e <- c()
|
93cfa123 |
if( !is.list(object@functions) ||
!all(vapply(object@functions, is.function, logical(1))) )
|
932ec5c4 |
e <- c("`functions` should be a (named) list of functions!")
|
93cfa123 |
if(!all(vapply( object@functions, FUN.VALUE=logical(1),
FUN=function(x) "x" %in% names(formals(x)))))
|
932ec5c4 |
e <- c(e, "Each function should at least take the argument `x`.")
isf <- function(x) is.null(x) || is.function(x)
|
93cfa123 |
if( !is.list(object@aggregation) ||
!all(vapply(object@aggregation, isf, logical(1))) )
|
932ec5c4 |
stop("`aggregation` should be a list of functions and/or NULL slots!")
|
93cfa123 |
if( !is.list(object@evaluation) ||
!all(vapply(object@evaluation, isf, logical(1))) )
|
932ec5c4 |
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
|
7dbc17c4 |
#' @exportClass PipelineDefinition
|
932ec5c4 |
setClass( "PipelineDefinition",
slots=representation( functions="list", descriptions="list",
evaluation="list", aggregation="list",
|
eebbb8b3 |
initiation="function",
|
932ec5c4 |
defaultArguments="list", misc="list" ),
prototype=prototype( functions=list(), descriptions=list(),
evaluation=list(), aggregation=list(),
|
eebbb8b3 |
initiation=identity,
|
932ec5c4 |
defaultArguments=list(), misc=list() ),
validity=.validatePipelineDef )
#' PipelineDefinition
|
7dbc17c4 |
#'
#' Creates on object of class `PipelineDefinition` containing step functions,
#' as well as optionally step evaluation and aggregation functions.
|
932ec5c4 |
#'
#' @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
|
eebbb8b3 |
#' @param initiation A function ran when initiating a dataset
|
932ec5c4 |
#' @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.
|
7dbc17c4 |
#'
#' @aliases PipelineDefinition-class
|
a53f1220 |
#' @seealso \code{\link{PipelineDefinition-methods}},
#' \code{\link{addPipelineStep}}. For an example pipeline, see
#' \code{\link{scrna_pipeline}}.
|
932ec5c4 |
#' @export
|
54a0f1cc |
#' @examples
#' PipelineDefinition(
#' list( step1=function(x, meth1){ get(meth1)(x) },
#' step2=function(x, meth2){ get(meth2)(x) } )
#' )
|
932ec5c4 |
PipelineDefinition <- function( functions, descriptions=NULL, evaluation=NULL,
|
eebbb8b3 |
aggregation=NULL, initiation=identity,
defaultArguments=list(),
|
932ec5c4 |
misc=list(), verbose=TRUE ){
|
93cfa123 |
if(!is.list(functions) || !all(vapply(functions, is.function, logical(1))))
|
932ec5c4 |
stop("`functions` should be a (named) list of functions!")
n <- names(functions)
|
a53f1220 |
if(is.null(n))
n <- names(functions) <- paste0("step",seq_len(length(functions)))
|
54a0f1cc |
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
|
932ec5c4 |
}
if(is.null(misc)) misc <- list()
|
a53f1220 |
x <- new("PipelineDefinition", functions=functions,descriptions=descriptions,
evaluation=evaluation, aggregation=aggregation2,
initiation=initiation, defaultArguments=defaultArguments, misc=misc)
|
93cfa123 |
w <- which( !vapply(x@aggregation, is.null, logical(1)) &
vapply(x@evaluation, is.null, logical(1)) )
|
a53f1220 |
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
|
932ec5c4 |
}
|
54a0f1cc |
.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)
}
|
93cfa123 |
if(containsFns &&
!all(vapply(x, FUN=function(x) is.null(x) || is.function(x), logical(1))))
|
54a0f1cc |
stop(name," should be a list of functions")
x
}
|
7dbc17c4 |
#' 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}}
|
3fceaac8 |
#' @return Depends on the method.
|
36e3057a |
#' @examples
#' pd <- mockPipeline()
#' length(pd)
#' names(pd)
#' pd$step1
#' pd[2:1]
|
7dbc17c4 |
NULL
#' @rdname PipelineDefinition-methods
#' @importMethodsFrom methods show
|
c728308f |
#' @importFrom knitr opts_current
|
932ec5c4 |
setMethod("show", signature("PipelineDefinition"), function(object){
|
c728308f |
# 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)
|
93cfa123 |
fns <- unlist(lapply(names(object@functions), FUN=function(x){
|
c728308f |
x2 <- x
if(!isKnit) x2 <- paste0("\033[1m",x,"\033[22m")
|
93cfa123 |
y <- lapply( names(formals(object@functions[[x]])), FUN=function(n){
|
eebbb8b3 |
if(!is.null(def <- object@defaultArguments[[n]]))
n <- paste0(n,"=",deparse(def,100,FALSE))
n
})
|
93cfa123 |
y <- paste0(" - ", x2, "(", paste(unlist(y), collapse=", "), ")")
|
932ec5c4 |
if(!is.null(object@evaluation[[x]]) || !is.null(object@aggregation[[x]]))
|
c728308f |
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")
}
|
932ec5c4 |
y
|
93cfa123 |
}))
|
932ec5c4 |
cat("A PipelineDefinition object with the following steps:\n")
cat(paste(fns,collapse="\n"))
cat("\n")
})
|
7558b7b2 |
#' get names of PipelineDefinition steps
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
#' @param x An object of class \code{\link{PipelineDefinition}}
|
932ec5c4 |
setMethod("names", signature("PipelineDefinition"), function(x){
names(x@functions)
})
|
7558b7b2 |
#' set names of PipelineDefinition steps
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
|
20c06813 |
#' @param value Replacement values
|
7dbc17c4 |
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
})
|
932ec5c4 |
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
|
20c06813 |
#' @param name The step name
|
932ec5c4 |
setMethod("$", signature("PipelineDefinition"), function(x, name){
x@functions[[name]]
})
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
|
932ec5c4 |
setMethod("length", signature("PipelineDefinition"), function(x){
length(x@functions)
})
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
|
20c06813 |
#' @param i The index(es) of the steps
|
932ec5c4 |
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)
})
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
|
932ec5c4 |
setMethod("as.list",signature("PipelineDefinition"), function(x){
x@functions
})
|
7dbc17c4 |
#' @exportMethod arguments
|
20c06813 |
#' @rdname PipelineDefinition-methods
|
7dbc17c4 |
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") })
|
932ec5c4 |
})
|
20c06813 |
#' @rdname PipelineDefinition-methods
|
243e2f21 |
#' @exportMethod defaultArguments
setGeneric("defaultArguments", function(object) NULL)
#' @exportMethod defaultArguments<-
|
20c06813 |
#' @rdname PipelineDefinition-methods
|
243e2f21 |
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
})
|
7dbc17c4 |
#' @exportMethod stepFn
|
20c06813 |
#' @rdname PipelineDefinition-methods
|
b04a719a |
setGeneric("stepFn", function(object, step=NULL, type) standardGeneric("stepFn"))
|
7dbc17c4 |
#' @param step The name of the step for which to set or get the function
|
243e2f21 |
#' @param type The type of function to set/get, either `functions`,
#' `evaluation`, `aggregation`, `descriptions`, or `initiation` (will parse
#' partial matches)
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
|
a53f1220 |
setMethod("stepFn", signature("PipelineDefinition"),
|
b04a719a |
function(object, step=NULL, type){
|
210e0cb3 |
ft <- c("functions","evaluation","aggregation","descriptions","initiation")
type <- match.arg( type, ft )
|
b04a719a |
if(is.null(step)) return(slot(object, type))
|
7dbc17c4 |
step <- match.arg(step, names(object))
slot(object, type)[[step]]
})
#' @exportMethod stepFn<-
|
20c06813 |
#' @rdname PipelineDefinition-methods
|
a53f1220 |
setGeneric( "stepFn<-",
function(object, step, type, value) standardGeneric("stepFn<-") )
|
7dbc17c4 |
#' @rdname PipelineDefinition-methods
|
a53f1220 |
setMethod( "stepFn<-", signature("PipelineDefinition"),
function(object, step, type, value){
ft <- c("functions","evaluation","aggregation","descriptions","initiation")
type <- match.arg(type, ft)
|
d409ddaa |
if(type!="descriptions" && !is.null(value) && !is.function(value))
|
243e2f21 |
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
}
|
4cf5460d |
if(type=="evaluation" && !is.null(value)){
# also add the default aggregation:
if(is.null(slot(object, "aggregation")[[step]]))
slot(object, "aggregation")[[step]] <- defaultStepAggregation
}
|
7dbc17c4 |
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
|
a53f1220 |
#' @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)
|
7dbc17c4 |
#'
#' @return A \code{\link{PipelineDefinition}}
|
a53f1220 |
#' @seealso \code{\link{PipelineDefinition}},
#' \code{\link{PipelineDefinition-methods}}
|
7dbc17c4 |
#' @importFrom methods is slot
|
932ec5c4 |
#' @export
|
7dbc17c4 |
#'
#' @examples
|
54a0f1cc |
#' pd <- mockPipeline()
|
7dbc17c4 |
#' pd
|
54a0f1cc |
#' pd <- addPipelineStep(pd, name="newstep", after="step1",
|
7dbc17c4 |
#' slots=list(description="Step that does nothing..."))
#' pd
addPipelineStep <- function(object, name, after=NULL, slots=list()){
|
a53f1220 |
if(!is(object, "PipelineDefinition"))
stop("object should be a PipelineDefinition")
|
7dbc17c4 |
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")
|
a53f1220 |
if(length(slots)>0)
|
93cfa123 |
names(slots) <- vapply( names(slots), choices=n, FUN=match.arg,
FUN.VALUE=character(1) )
|
a53f1220 |
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=", ")))
|
7dbc17c4 |
if(is.null(after)){
i1 <- vector("integer")
i2 <- seq_along(names(object))
}else{
w <- which(names(object)==after)
|
36e3057a |
i1 <- seq_len(w)
i2 <- seq.int(from=w+1, to=length(object))
|
7dbc17c4 |
if(w==length(object)) i2 <- vector("integer")
|
932ec5c4 |
}
|
7dbc17c4 |
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
|
54a0f1cc |
}
#' 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)) ),
|
d29e21a7 |
descriptions=list( step1="This steps applies meth1 to x.",
step2="This steps applies meth2 to x."),
|
54a0f1cc |
defaultArguments=list(meth1=c("log","sqrt"), meth2="cumsum")
)
}
|