Nothing
#' Get top or bottom depths of horizons matching a regular expression pattern
#'
#' @description The \code{depthOf} family of functions calculate depth of occurrence of a horizon designation pattern, or any other value that can be coerced to character and matched with a regular expression.
#'
#' If you need all depths of occurrence for a particular pattern, \code{depthOf} is what you are looking for. \code{minDepthOf} and \code{maxDepthOf} are wrappers around \code{depthOf} that return the minimum and maximum depth. They are all set up to handle missing values and missing "contacts" with the target pattern.
#'
#' @param p a SoilProfileCollection
#' @param pattern a regular expression to match in the horizon designation column. See:\code{hzdesgn}
#' @param FUN a function that returns a single value, and takes argument `na.rm`
#' @param top should the top (TRUE) or bottom (FALSE) depth be returned for matching horizons? Default: \code{TRUE}.
#' @param hzdesgn column name containing horizon designations. Default: \code{guessHzDesgnName(p)}
#' @param no.contact.depth depth to assume that contact did not occur.
#' @param no.contact.assigned depth to assign when a contact did not occur.
#' @param simplify logical. Return single profile results as vector (default: `TRUE`) or `data.frame` (`FALSE`)
#' @param na.rm logical. Remove `NA`? (default: `TRUE`)
#' @return a numeric vector containing specified depth(s) of horizons matching a pattern. If `length(p) > 1` then a _data.frame_ containing profile ID, horizon ID, top or bottom depths, horizon designation and pattern.
#'
#' @author Andrew G. Brown
#'
#' @export depthOf
#' @export minDepthOf
#' @export maxDepthOf
#' @aliases maxDepthOf minDepthOf
#' @examples
#' # construct a fake profile
#' spc <- data.frame(id=1, taxsubgrp = "Lithic Haploxerepts",
#' hzname = c("A","AB","Bw","BC","R"),
#' hzdept = c(0, 20, 32, 42, 49),
#' hzdepb = c(20, 32, 42, 49, 200),
#' clay = c(19, 22, 22, 21, NA),
#' texcl = c("l","l","l", "l","br"),
#' d_value = c(5, 5, 5, 6, NA),
#' m_value = c(2.5, 3, 3, 4, NA),
#' m_chroma = c(2, 3, 4, 4, NA))
#'
#' # promote to SoilProfileCollection
#' depths(spc) <- id ~ hzdept + hzdepb
#' hzdesgnname(spc) <- 'hzname'
#' hztexclname(spc) <- 'texcl'
#'
#' # multiple horizons contain B
#' depthOf(spc, "B")
#'
#' # deepest top depth of horizon containing B
#' maxDepthOf(spc, "B")
#'
#' # shallowest top depth
#' minDepthOf(spc, "B")
#'
#' # deepest bottom depth
#' maxDepthOf(spc, "B", top = FALSE)
#'
#' # deepest bottom depth above 35cm
#' maxDepthOf(spc, "B", top = FALSE, no.contact.depth = 35)
#'
#' # assign infinity (Inf) if B horizon does not start within 10cm
#' minDepthOf(spc, "B", no.contact.depth = 10, no.contact.assigned = Inf)
#'
depthOf <- function(p,
pattern,
FUN = NULL,
top = TRUE,
hzdesgn = hzdesgnname(p, required = TRUE),
no.contact.depth = NULL,
no.contact.assigned = NA_real_,
na.rm = TRUE,
simplify = TRUE) {
if (!inherits(p, 'SoilProfileCollection'))
stop("`p` must be a SoilProfileCollection")
if (length(pattern) != 1)
stop("`pattern` must be unit length character vector containing a regular expression")
# pass through FUN argument if specified
if (!is.null(FUN)) {
return(.funDepthOf(
p = p,
pattern = pattern,
FUN = FUN,
top = top,
hzdesgn = hzdesgn,
no.contact.depth = no.contact.depth,
no.contact.assigned = no.contact.assigned,
na.rm = na.rm,
simplify = simplify
))
}
id <- idname(p)
hid <- hzidname(p)
# if the user has not specified a column containing horizon designations
if (is.null(hzdesgn) || !hzdesgn %in% horizonNames(p)) {
stop("Horizon designation column (", hzdesgn, ") does not exist.")
}
# get horizons matching designation pattern
hz.match <- horizons(p)[grepl(pattern, p[[hzdesgn]]),]
subsite <- data.frame(idn = profile_id(p), stringsAsFactors = FALSE)
# get top or bottom depth, based on `top` argument
depthcol <- horizonDepths(p)[ifelse(top, 1, 2)]
res <- hz.match[[depthcol]]
# remove results greater than the cutoff depth: `no.contact.depth`
if (any(res > no.contact.depth)) {
hz.match <- hz.match[-which(res > no.contact.depth),]
res <- hz.match[[depthcol]]
}
# if no horizons match, return `no.contact.assigned`
if (nrow(hz.match) == 0) {
if (length(p) == 1 && simplify) {
return(no.contact.assigned)
}
# if no horizons in any profile match, make a conformal result
emptyres <- data.frame(
idn = as.character(subsite[["idn"]]),
hidn = NA,
depth = no.contact.assigned,
hzname = NA
)
colnames(emptyres) <- c(id, hid, depthcol, hzdesgn)
emptyres$pattern <- pattern
return(emptyres)
}
# if there are non-NA results, return all of them
if (length(res) > 0) {
# backwards compatible: simplify=TRUE and SPC length is 1
if (length(p) == 1 && simplify) {
return(res)
}
dfres <- data.table::data.table(idn = as.character(hz.match[[id]]),
hidn = hz.match[[hid]],
depth = res,
hzname = hz.match[[hzdesgn]])[subsite, on = "idn"]
# filter out horizons that are > no.contact.depth, keep NA
if (!is.null(no.contact.depth)) {
dfres <- dfres[which(dfres$depth <= no.contact.depth | is.na(dfres$depth)),]
}
# replace NA with no.contact.assigned
dfres$depth[is.na(dfres$depth)] <- no.contact.assigned
colnames(dfres) <- c(id, hid, depthcol, hzdesgn)
dfres$pattern <- pattern
return(as.data.frame(dfres))
}
# otherwise:
return(no.contact.assigned)
}
.funDepthOf <- function(p,
pattern,
FUN,
top = TRUE,
hzdesgn = hzdesgnname(p, required = TRUE),
no.contact.depth = NULL,
no.contact.assigned = NA,
na.rm = TRUE,
simplify = TRUE) {
id <- idname(p)
depthcol <- horizonDepths(p)[ifelse(top, 1, 2)]
# depthOf returns all top or bottom depths of horizons matching `hzdesgn`
res <- data.table::as.data.table(depthOf(p = p,
pattern = pattern,
FUN = NULL,
top = top,
hzdesgn = hzdesgn,
no.contact.depth = no.contact.depth,
no.contact.assigned = no.contact.assigned,
na.rm = na.rm,
simplify = FALSE))
# otherwise, return the FUN value)) {
idx <- numeric(0)
naldx <- is.na(res[[depthcol]])
if (all(naldx)) {
naldx <- logical(0)
idx <- res[, .I[1], by = id]$V1
} else {
# handle warnings about e.g. no non-missing arguments to FUN
idx <- res[, .I[which(.SD[[depthcol]] %in% suppressWarnings(FUN(.SD[[depthcol]], na.rm = na.rm)))][1], by = id, .SDcols = depthcol]$V1
idx <- idx[!is.na(idx)]
}
idx2 <- sort(unique(c(which(naldx), idx)))
res <- res[idx2, .SD[1, ], by = id, .SDcols = c(hzidname(p), depthcol, hzdesgn, "pattern")]
if (length(p) == 1 && simplify) {
return(res[[depthcol]])
}
.as.data.frame.aqp(res, aqp_df_class(p))
}
# maxDepthOf is a wrapper around depthOf to return a single, maximum value
#' @rdname depthOf
#' @export
maxDepthOf <- function(p,
pattern,
top = TRUE,
hzdesgn = hzdesgnname(p, required = TRUE),
no.contact.depth = NULL,
no.contact.assigned = NA,
na.rm = TRUE,
simplify = TRUE) {
.funDepthOf(
p = p,
pattern = pattern,
FUN = max,
top = top,
hzdesgn = hzdesgn,
no.contact.depth = no.contact.depth,
no.contact.assigned = no.contact.assigned,
na.rm = na.rm,
simplify = simplify
)
}
# minDepthOf is a wrapper around depthOf to return a single, minimum value\
#' @rdname depthOf
#' @export
minDepthOf <- function(p,
pattern,
top = TRUE,
hzdesgn = hzdesgnname(p, required = TRUE),
no.contact.depth = NULL,
no.contact.assigned = NA,
na.rm = TRUE,
simplify = TRUE) {
.funDepthOf(
p = p,
pattern = pattern,
FUN = min,
top = top,
hzdesgn = hzdesgn,
no.contact.depth = no.contact.depth,
no.contact.assigned = no.contact.assigned,
na.rm = na.rm,
simplify = simplify
)
}
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.