Here is a quick demonstration of how functionality from the AQP package can be used to answer complex soils-related questions. In these examples the profileApply() function is used to iterate over a collection of soil profiles, and compute several metrics of soil development:

  • depth of maximum clay content
  • clay/RF content within the PCS
  • clay content within the argillic horizon
  • thickness of argillic horizon
  • depth to argillic horizon

Example Session

# libraries: you may have to install these
library(soilDB)
library(Hmisc)
library(ape)
library(lattice)
library(cluster)
library(reshape)
# load example dataset
data(gopheridge)
## aggregates of horizon data, moved into @site
# soil depth using an in-line function, note that max(SPC) returns the bottom-most depth
# this may not be the same as 'soil depth' when Cr or R horizons are present
gopheridge$soil.depth <- profileApply(gopheridgefunction(x) max(x))
# horizon-thickness weighted mean (beware of NA)
f.wt.prop <- function(xprop) {h <- horizons(x)w <- with(hhzdepb-hzdept)wtd.mean(h[[prop]]weights=w)}
# get hz mid-point of hz with max clay content
f.max.clay.depth <- function(x) {
        h <- horizons(x)
        max.clay <- which.max(h$clay)
        with(h[max.clay](hzdept+hzdepb) / 2)
        }
# apply by-profile, returning a vector, assigned to new column in @site
gopheridge$wt.clay <- profileApply(gopheridgef.wt.propprop='clay')
gopheridge$wt.rf <- profileApply(gopheridgef.wt.propprop='total_frags_pct')
gopheridge$max.clay.depth <- profileApply(gopheridgef.max.clay.depth)
# compute aggregate (wt.mean) clay within particle-size control section
# note that this requires conditional eval of data that may contain NA
# see ?slab and ?soil.slot for details on the syntax
f.pcs.prop <- function(xprop) {
  # these are accessed from @site
  sv <- c(x$psctopdepthx$pscbotdepth)
  # test for missing PCS data
  if(any(is.na(sv)))
    return(NA)
  # create formula from named property
  fm <- as.formula(paste('~'prop))
  # return just the (weighted) mean, accessed from @horizons
  s <- slab(xfmslab.structure=svslab.fun=meanna.rm=TRUE)
  return(s$value)
}
# compute the weighted-mean of some property within a given diagnostic horizon
# note that this requires conditional eval of data that may contain NA
# see ?slab and ?soil.slot for details on the syntax
# note that function expects certain columns within 'x'
f.diag.wt.prop <- function(xd.hzprop) {
        # extract diagnostic horizon data
        d <- diagnostic_hz(x)
        # subset to the requested diagnostic hz
        d <- d[d$diag_kind == d.hz]
        # if missing return NA
        if(nrow(d) == 0)
                return(NA)
       
        # extract depths and check for missing
        sv <- c(d$featdeptd$featdepb)
        if(any(is.na(sv)))
                return(NA)
       
        # create formula from named property
        fm <- as.formula(paste('~'prop))
        # return just the (weighted) mean, accessed from @horizons
        s <- slab(xfmslab.structure=svslab.fun=meanna.rm=TRUE)
        return(s$value)
}
# conditional eval of thickness of some diagnostic feature or horizon
# will return a vector of length(x), you can save to @site
f.diag.thickness <- function(xd.hz) {
        # extract diagnostic horizon data
        d <- diagnostic_hz(x)
        # subset to the requested diagnostic hz
        d <- d[d$diag_kind == d.hz]
        # if missing return NA
        if(nrow(d) == 0)
                return(NA)
       
        # compute thickness
        thick <- d$featdepb - d$featdept
        return(thick)
}
# conditional eval of top hz depth of diagnostic feature or horizon
# will return a vector of length(x), you can save to @site
f.diag.top <- function(xd.hz) {
        # extract diagnostic horizon data
        d <- diagnostic_hz(x)
        # subset to the requested diagnostic hz
        d <- d[d$diag_kind == d.hz]
        # if missing return NA
        if(nrow(d) == 0)
                return(NA)
       
        # return top depth
        return(d$featdept)
}
# compute wt.mean clay within PCS, save to @site
gopheridge$pcs.clay <- profileApply(gopheridgef.pcs.propprop='clay')
# compute wt.mean clay within argillic horizon, save to @site
gopheridge$argillic.clay <- profileApply(gopheridgef.diag.wt.propd.hz='argillic horizon'prop='clay')
# compute argillic hz thickness, save to @site
gopheridge$argillic.thick <- profileApply(gopheridgef.diag.thicknessd.hz='argillic horizon')
# compute depth to paralithic contact, if present, save to @site
gopheridge$paralithic.contact.depth <- profileApply(gopheridgef.diag.topd.hz='paralithic contact')
# order by presence of paralithic contact
new.order <- order(gopheridge$paralithic.contact)
par(mar=c(0,0,0,0))
plot(gopheridgename='hzname'plot.order=new.order)
# annotate paralithic contact with lines
x.pos <- 1:length(gopheridge)
lines(x.posgopheridge$paralithic.contact.depth[new.order]lty=2col='black')
# plot and annotate with max clay depths
par(mar=c(0,0,0,0))
plot(gopheridgename='hzname')
x.pos <- 1:length(gopheridge)
points(x.posgopheridge$max.clay.depthcol='white'pch=21cex=0.75bg='black')
lines(x.posgopheridge$max.clay.depthcol='black'lty=2)
X