Nothing
# profileApply
# if (!isGeneric("profileApply"))
setGeneric("profileApply", function(object, FUN,
simplify = TRUE,
frameify = FALSE,
chunk.size = 100,
column.names = NULL,
APPLY.FUN = lapply,
...) standardGeneric("profileApply"))
# made internal by AGB 2020/01/19
#
# analog to apply():
# operates by profile, returns vector of length(object) or nrow(object)
.profileApply <- function(object, FUN, simplify=TRUE, ...) {
# get profile IDs
pIDs <- profile_id(object)
# iterate over SPC, spliting into a list of SPC_i ... SPC_n
l <- lapply(seq_along(pIDs), function(i) do.call(FUN, list(object[i, ], ...)))
names(l) <- pIDs
# optionally simplify
if (simplify)
return(unlist(l))
return(l)
}
#' Iterate over profiles in a SoilProfileCollection
#'
#' @name profileApply
#'
#' @description Iterate over all profiles in a SoilProfileCollection, calling \code{FUN} on a single-profile SoilProfileCollection for each step.
#'
#' @param object a SoilProfileCollection
#'
#' @param FUN a function to be applied to each profile within the collection
#'
#' @param simplify logical, should the result be simplified to a vector? default: TRUE; see examples
#'
#' @param frameify logical, should the result be collapsed into a data.frame? default: FALSE; overrides simplify argument; see examples
#'
#' @param chunk.size numeric, size of "chunks" for faster processing of large SoilProfileCollection objects; default: 100
#'
#' @param column.names character, optional character vector to replace frameify-derived column names; should match length of colnames() from FUN result; default: NULL
#'
#' @param ... additional arguments passed to FUN
#'
#' @param APPLY.FUN function, optional alternate `lapply()`-like function for processing chunks. For example `future.apply::future_lapply()` for processing chunks in parallel. Default `base::lapply()`.
#'
#' @return When simplify is TRUE, a vector of length nrow(object) (horizon data) or of length length(object) (site data). When simplify is FALSE, a list is returned. When frameify is TRUE, a data.frame is returned. An attempt is made to identify idname and/or hzidname in the data.frame result, safely ensuring that IDs are preserved to facilitate merging profileApply result downstream.
#'
#' @aliases profileApply,SoilProfileCollection-method
#' @docType methods
#' @rdname profileApply
#' @export
#' @examples
#'
#' data(sp1)
#' depths(sp1) <- id ~ top + bottom
#'
#' # estimate soil depth using horizon designations
#' profileApply(sp1, estimateSoilDepth, name='name')
#'
#' # scale a single property 'prop' in horizon table
#' # scaled = (x - mean(x)) / sd(x)
#' sp1$d <- profileApply(sp1, FUN=function(x) round(scale(x$prop), 2))
#' plot(sp1, name='d')
#'
#' # compute depth-wise differencing by profile
#' # note that our function expects that the column 'prop' exists
#' f <- function(x) { c(x$prop[1], diff(x$prop)) }
#' sp1$d <- profileApply(sp1, FUN=f)
#' plot(sp1, name='d')
#'
#' # compute depth-wise cumulative sum by profile
#' # note the use of an anonymous function
#' sp1$d <- profileApply(sp1, FUN=function(x) cumsum(x$prop))
#' plot(sp1, name='d')
#'
#' # compute profile-means, and save to @site
#' # there must be some data in @site for this to work
#' site(sp1) <- ~ group
#' sp1$mean_prop <- profileApply(sp1, FUN=function(x) mean(x$prop, na.rm=TRUE))
#'
#' # re-plot using ranks defined by computed summaries (in @site)
#' plot(sp1, plot.order=rank(sp1$mean_prop))
#'
#' ## iterate over profiles, calculate on each horizon, merge into original SPC
#'
#' # example data
#' data(sp1)
#'
#' # promote to SoilProfileCollection
#' depths(sp1) <- id ~ top + bottom
#' site(sp1) <- ~ group
#'
#' # calculate horizon thickness and proportional thickness
#' # returns a data.frame result with multiple attributes per horizon
#' thicknessFunction <- function(p) {
#' hz <- horizons(p)
#' depthnames <- horizonDepths(p)
#' res <- data.frame(profile_id(p), hzID(p),
#' thk=(hz[[depthnames[[2]]]] - hz[[depthnames[1]]]))
#' res$hz_prop <- res$thk / sum(res$thk)
#' colnames(res) <- c(idname(p), hzidname(p), 'hz_thickness', 'hz_prop')
#' return(res)
#' }
#'
#' # list output option with simplify=F, list names are profile_id(sp1)
#' list.output <- profileApply(sp1, thicknessFunction, simplify = FALSE)
#' head(list.output)
#'
#' # data.frame output option with frameify=TRUE
#' df.output <- profileApply(sp1, thicknessFunction, frameify = TRUE)
#' head(df.output)
#'
#' # since df.output contains idname(sp1) and hzidname(sp1),
#' # it can safely be merged by a left-join via horizons<- setter
#' horizons(sp1) <- df.output
#'
#' plot(density(sp1$hz_thickness, na.rm=TRUE), main="Density plot of Horizon Thickness")
#'
#' ## iterate over profiles, subsetting horizon data
#'
#' # example data
#' data(sp1)
#'
#' # promote to SoilProfileCollection
#' depths(sp1) <- id ~ top + bottom
#' site(sp1) <- ~ group
#'
#' # make some fake site data related to a depth of some importance
#' sp1$dep <- profileApply(sp1, function(i) {round(rnorm(n=1, mean=mean(i$top)))})
#'
#' # custom function for subsetting horizon data, by profile
#' # keep horizons with lower boundary < site-level attribute 'dep'
#' fun <- function(i) {
#' # extract horizons
#' h <- horizons(i)
#' # make an expression to subset horizons
#' exp <- paste('bottom < ', i$dep, sep='')
#' # subset horizons, and write-back into current SPC
#' slot(i, 'horizons') <- subset(h, subset=eval(parse(text=exp)))
#' # return modified SPC
#' return(i)
#' }
#'
#' # list of modified SoilProfileCollection objects
#' l <- profileApply(sp1, fun, simplify=FALSE)
#'
#' # re-combine list of SoilProfileCollection objects into a single SoilProfileCollection
#' sp1.sub <- pbindlist(l)
#'
#' # graphically check
#' par(mfrow=c(2,1), mar=c(0,0,1,0))
#' plot(sp1)
#' points(1:length(sp1), sp1$dep, col='red', pch=7)
#' plot(sp1.sub)
#'
setMethod('profileApply', signature(object = 'SoilProfileCollection'), function(object, FUN,
simplify = TRUE,
frameify = FALSE,
chunk.size = 100,
column.names = NULL,
APPLY.FUN = lapply,
...) {
if (simplify & frameify) {
# simplify and frameify are both TRUE -- ignore simplify argument
simplify <- FALSE
}
# total number of profiles we have to iterate over
n <- length(object)
# split the SPC of size n into chunk.size chunks
chunk <- sort(1:n %% max(1, round(n / chunk.size))) + 1
# we first iterate over list of chunks of size chunk.size, keeping lists smallish
# by dividing by a tunable factor -- set as 100 by default
# then we iterate through each chunk, calling FUN on each element (profile)
# then, concatenate the result into a list (or vector if simplify == TRUE)
res <- do.call('c', APPLY.FUN(split(1:n, chunk), function(idx) {
.profileApply(object[idx,], FUN, simplify, ...)
}))
# return profile IDs if it makes sense for result
if (length(res) == length(object))
names(res) <- profile_id(object)
# combine a list (one element per profile) into data.frame result
if (!simplify & frameify) {
# make sure the first result is a data.frame (i.e. FUN returns a data.frame)
if (inherits(res[[1]], 'data.frame')) {
# make a big data.frame
if (requireNamespace("data.table")) {
res <- .as.data.frame.aqp(data.table::rbindlist(res, fill = TRUE), aqp_df_class(object))
} else {
res <- .as.data.frame.aqp(do.call('rbind', res), aqp_df_class(object))
}
if (!is.null(column.names))
colnames(res) <- column.names
} else {
warning("first result does not inherit `data.frame` and frameify is TRUE. defaulting to list output.", call. = FALSE)
}
}
if (simplify)
return(unlist(res))
return(res)
})
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.