Nothing
# functions for argillic horizon detection in SPCs
# getArgillicBounds - upper and lower boundary of argillic horizon
# crit.clay.argillic - argillic horizon threshold function
# argillic.clay.increase.depth() - top depth of argillic
#' Estimate upper and lower boundary of argillic diagnostic subsurface horizon
#'
#' @param p A SoilProfileCollection
#' @param hzdesgn the name of the column/attribute containing the horizon designation; default="hzname"
#' @param clay.attr the name of the column/attribute containing the clay content; default="clay"
#' @param texcl.attr the name of the column/attribute containing the textural class (used for finding sandy horizons); default="texcl"
#' @param require_t require a "t" subscript for positive identification of upper and lower bound of argillic? default: TRUE
#' @param bottom.pattern regular expression passed to \code{estimateSoilDepth} to match the lower boundary of the soil. default is "Cr|R|Cd" which approximately matches paralithic, lithic and densic contacts.
#' @param lower.grad.pattern this is a pattern for adjusting the bottom depth of the argillic horizon upwards from the bottom depth of the soil. The absence of illuviation is used as a final control on horizon pattern matching.
#' @param sandy.texture.pattern this is a pattern for matching sandy textural classes: `-S$|^S$|COS$|L[^V]FS$|[^L]VFS$|LS$|LFS$`
#' @param verbose Print out information about 't' subscripts, sandy textures, plow layers and lower gradational horizons?
#' @param vertical.distance Vertical distance in which clay increase must be met. Default `30` cm
#' @param simplify Return a length 2 vector with upper and lower boundary when `p` has length 1? Default `TRUE`.
#'
#' @description \code{getArgillicBounds} estimates the upper and lower boundary of argillic diagnostic subsurface horizon for a profile in a single-profile SoilProfileCollection object (`p`).
#'
#' The upper boundary is where the clay increase threshold is met. The function uses \code{crit.clay.argillic} as the threshold function for determining whether a clay increase occurs and \code{get.increase.matrix} to determine whether the increase is met, whether vertical distance of increase is sufficiently small, and in which horizon.
#'
#'@details The lower boundary is first approximated as the depth to a lithic/paralithic/densic contact, or some other horizon matchable by a custom regular expression pattern. Subsequently, that boundary is extended upwards to the end of "evidence of illuviation."
#'
#' The depth to contact is estimated using 'bottom.pattern' "Cr|R|Cd" by default. It matches anything containing Cr, R or Cd.
#'
#' The lower gradational horizon regular expression ‘lower.grad.pattern' default is `^[2-9]*B*CB*[^rtd]*[1-9]*$}`. It matches anything that starts with a lithologic discontinuity (or none) and a C master horizon designation. May contain B as second horizon designation in transitional horizon. May not contain 'r' or 't' subscript.
#'
#' The minimum thickness of the argillic horizon is dependent on whether all subhorizons are "sandy" or not. The \code{sandy.texture.pattern} default `-S$|^S$|COS$|L[^V]FS$|[^L]VFS$|LS$|LFS$` captures USDA textural class fine earth fractions that meet "sandy" particle size class criteria.
#'
#' There also is an option ‘require_t' to omit the requirement for evidence of eluviation in form of 't' subscript in 'hzdesgn'. Even if "t" subscript is not required for positive identification, the presence of lower gradational C horizons lacking 't' will still be used to modify the lower boundary upward from a detected contact, if needed. If this behavior is not desired, just set 'lower.grad.pattern' to something that will not match any horizons in your data.
#'
#' @author Andrew G. Brown
#'
#' @return Returns a numeric vector; first value is top depth, second value is bottom depth. If as.list is TRUE, returns a list with top depth named "ubound" and bottom depth named "lbound". If `p` has more than one profile or if `simplify = FALSE` the result is a data.frame containing profile ID, upper and lower boundary columns.
#'
#' @export
#'
#' @examples
#' data(sp1, package = 'aqp')
#' depths(sp1) <- id ~ top + bottom
#' site(sp1) <- ~ group
#'
#' # set required metadata
#' hzdesgnname(sp1) <- 'name'
#' hztexclname(sp1) <- 'texture'
#' hzmetaname(sp1, 'clay') <- 'prop'
#'
#' x <- getArgillicBounds(sp1)
#' x
#'
getArgillicBounds <- function(p,
hzdesgn = hzdesgnname(p, required = TRUE),
clay.attr = hzmetaname(p, 'clay', required = TRUE),
texcl.attr = hztexclname(p, required = TRUE),
require_t = TRUE,
bottom.pattern = "Cr|R|Cd",
lower.grad.pattern = "^[2-9]*B*CB*[^rtd]*[1-9]*$",
sandy.texture.pattern = "-S$|^S$|COS$|L[^V]FS$|[^L]VFS$|LS$|LFS$",
vertical.distance = 30,
simplify = TRUE,
verbose = FALSE) {
hz <- horizons(p)
hzd <- horizonDepths(p)
if (is.null(hzdesgn) || !hzdesgn %in% horizonNames(p)) {
stop("Horizon designation column (", hzdesgn, ") does not exist.")
}
if (is.null(clay.attr) | (!clay.attr %in% horizonNames(p))) {
stop("Horizon clay content column (", texcl.attr, ") does not exist.")
}
if (is.null(texcl.attr) || !texcl.attr %in% horizonNames(p)) {
stop("Horizon texture class column (", texcl.attr, ") does not exist.")
}
# get upper bound...
mss <- getMineralSoilSurfaceDepth(p, hzdesgn = hzdesgn, simplify = FALSE)
pld <- getPlowLayerDepth(p, hzdesgn = hzdesgn, simplify = FALSE)
# estimate the thickness of the soil profile
# (you will need to specify alternate pattern if Cr|R|Cd
# doesn't match your contacts)
soil.depth <- minDepthOf(p, pattern = bottom.pattern, hzdesgn = hzdesgn, simplify = FALSE)
# find all horizons with t subscripts; some old/converted horizons have all capital letters
has_t <- grepl(as.character(hz[[hzdesgn]]), pattern = "[Tt]")
# clay attribute and clay thresholds
ivar <- hz[[clay.attr]]
thresh <- crit.clay.argillic(ivar) # threshold.fun()
# in lieu of plow layer and LD, the clay increase depth determines upper bound
# upper.bound <- argillic.clay.increase.depth(p, clay.attr)
.N <- NULL; .SD <- NULL; increase.var <- NULL; threshold.vector <- NULL; vdist <- NULL; middepth <- NULL; .hzID <- NULL
dt <- data.table::data.table(
id = hz[[idname(p)]],
middepth = hz[[hzd[1]]] + (hz[[hzd[2]]] - hz[[hzd[1]]]) / 2, # TODO: evaluate midpoint
increase.var = ivar,
threshold.vector = thresh,
.hzID = 1:nrow(hz)
)
.dmax <- function(x, n) {
res <- c(0, diff(c(0, x[-n])))
if(length(res) == 0)
return(NA_real_)
res
}
dt$vdist <- dt[, list(vdist = .dmax(middepth, .N)), by = "id"]$vdist
increase.met <- dt[, list(increase.met = .hzID[which(increase.var[2:.N] > threshold.vector[1:(.N - 1)] &
vdist[2:.N] <= vertical.distance)[1]]),
by = "id"]$increase.met
upper.bound <- hz[increase.met + 1, ][[hzd[1]]]
lower.bound <- rep(NA, length(upper.bound))
# handle case where Ap disturbs upper bound of argillic
shallowest_t <- minDepthOf(p, pattern = "[Tt]", hzdesgn = hzdesgn, simplify = FALSE)
idx2 <- which(pld[[hzd[2]]] == shallowest_t[[hzd[1]]] & pld[[hzd[2]]] != 0)
upper.bound[idx2] <- shallowest_t[[hzd[1]]][idx2]
# or a lithologic discontinuity overrides the clay increase req
depth_ld <- depthOf(p, pattern = "^[2-9].*[Tt]", hzdesgn = hzdesgn, simplify = FALSE)
idx3 <- which(shallowest_t[[hzd[1]]] %in% depth_ld[[hzd[1]]])
upper.bound[idx3] <- shallowest_t[[hzd[1]]][idx3]
deepest_t <- maxDepthOf(p, pattern = "[Tt]", hzdesgn = hzdesgn, top = FALSE, simplify = FALSE)
c_idx <- minDepthOf(p, pattern = lower.grad.pattern, hzdesgn = hzdesgn, simplify = FALSE)
idx4 <- which(!is.na(upper.bound))
lower.bound[idx4] <- pmin(deepest_t[[hzd[2]]], c_idx[[hzd[1]]], na.rm = TRUE)[idx4]
# calculate baseline minimum rhickness
min.thickness <- pmax(7.5, soil.depth[[hzd[1]]] / 10)
textures <- data.table::data.table(glom(p, upper.bound, lower.bound, df = TRUE))
# sandy textures require 15cm minimum thickness
idx5 <- which(textures[, list(is_sandy = all(grepl(sandy.texture.pattern, .SD[[texcl.attr]]))),
by = eval(idname(p))]$is_sandy)
min.thickness[idx5] <- 15
idx6 <- which((lower.bound - upper.bound ) < min.thickness)
upper.bound[idx6] <- NA
lower.bound[idx6] <- NA
if (length(p) == 1 && simplify) {
return(c(ubound = upper.bound, lbound = lower.bound))
}
res <- data.frame(id = profile_id(p), ubound = upper.bound, lbound = lower.bound)
colnames(res)[1] <- idname(p)
.as.data.frame.aqp(res, aqp_df_class(p))
}
#' Determines threshold (minimum) clay content for argillic upper bound
#'
#' Given a vector or matrix of "eluvial" horizon clay contents (\%),
#' \code{crit.clay.argillic()} returns a vector or matrix of minimum clay
#' contents (thresholds) that must be met for an argillic horizon clay
#' increase.
#'
#' Uses the standard equations for clay contents less than 15 \%, between 15
#' and 40 \%, and greater than 40 \%. Based on the clay increase criteria in
#' the definition of the argillic horizon from 12th Edition Keys to Soil
#' Taxonomy (Soil Survey Staff, 2014).
#'
#'
#' @param eluvial_clay_content A numeric vector or matrix containing clay
#' contents of potential "eluvial" horizons. May contain \code{NA}.
#' @return A vector or matrix (input-dependent) containing minimum "illuvial"
#' horizon clay contents (thresholds) to be met for argillic horizon clay
#' increase.
#' @note This function is intended for identifying clay content threshold
#' required for an argillic horizon. These thresholds may not apply depending
#' on the specifics of your soil. E.g. if the upper part of argillic has been
#' plowed (has Ap immediately over upper boundary) the clay increase
#' requirement can be waived (Soil Survey Staff, 2014).
#' @author Andrew Gene Brown
#' @seealso \code{\link{getArgillicBounds}}, \code{\link{get.increase.matrix}}
#' @references Soil Survey Staff. 2014. Keys to Soil Taxonomy, 12th ed.
#' USDA-Natural Resources Conservation Service, Washington, DC.
#' @keywords manip
#' @export
#' @examples
#'
#' # crit.clay.argillic uses different equations for clay content
#' # less than 15 %, between 15 and 40 %, and >40 %
#'
#' crit.clay.argillic(eluvial_clay_content=c(5, 20, 45))
#'
crit.clay.argillic <- function(eluvial_clay_content) {
# eluvial clay content is a numeric vector or matrix subsettable with logical vectors based on clay (NA omitted)
buf <- eluvial_clay_content
idx.mask <- is.na(buf)
buf[idx.mask] <- 0
idx.lt15 <- buf < 15
idx.lt15[idx.mask] <- FALSE
idx.gt40 <- buf >= 40
idx.gt40[idx.mask] <- FALSE
idx.other <- (buf >= 15) & (buf < 40)
idx.other[idx.mask] <- FALSE
buf[idx.lt15] <- eluvial_clay_content[idx.lt15] + 3
buf[idx.gt40] <- eluvial_clay_content[idx.gt40] + 8
buf[idx.other] <- 1.2*eluvial_clay_content[idx.other]
return(round(buf))
}
#' Return upper boundary of argillic horizon
#'
#' Returns the top depth of the argillic horizon as a numeric vector.
#'
#' Uses \code{crit.clay.argillic} to determine threshold clay increase, and
#' \code{get.increase.matrix} to determine where increase is met within a
#' vertical distance of 30 cm.
#'
#'
#' @param p A single-profile \code{SoilProfileCollection} object.
#' @param clay.attr OPTIONAL: horizon attribute name referring to clay content.
#' default: `clay`
#' @return A numeric vector containing top depth of argillic horizon, if
#' present, or NA.
#' @author Andrew Gene Brown
#' @seealso \code{getArgillicBounds}, \code{get.increase.matrix},
#' \code{crit.clay.argillic}
#' @keywords manip
#' @export
#' @examples
#'
#' data(sp1, package = 'aqp')
#' depths(sp1) <- id ~ top + bottom
#' site(sp1) <- ~ group
#'
#' p <- sp1[1]
#' attr <- 'prop' # clay contents
#' foo <- argillic.clay.increase.depth(p, clay.attr = attr)
#' foo
#'
argillic.clay.increase.depth <- function(p, clay.attr = 'clay') {
vd <- 30
return(get.increase.depths(p, attr = clay.attr,
threshold.fun = crit.clay.argillic,
vertical.distance = vd)[1])
}
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.