Soil Taxonomy is a theoretical framework for organizing soil information, typically at scales coarser than most land management decisions. Soil series are a practical (but limited in geographic scope) framework for organizing soil/landscape combinations close to the scale of land management decisions.
So, what is the practical manifestation of a single subgroup from Soil Taxonomy (abruptic durixeralfs), in terms of soil series concepts?
The following code, links, and notes may serve as a starting point for those interested in learning more about Soil Taxonomy. This is a work in progress.
# AQP suite, be sure to use the development versions from GitHub
library(aqp)
library(soilDB)
library(sharpshootR)
# used for visualization
library(latticeExtra)
library(tactile)
library(cluster)
library(ape)
library(FactoMineR)
# mapping a subgroup
library(sf)
library(terra)
library(spData)
library(rasterVis)
library(viridisLite)
# subgroup "explainer"
library(SoilTaxonomy)
library(data.tree)
Get a snapshot of the Soil Classification database. This contains the current classification for all soil series.
# latest version, synced quarterly
u <- 'https://github.com/ncss-tech/SoilWeb-data/raw/main/files/SC-database.csv.gz'
tf <- tempfile()
download.file(u, destfile = tf)
SC <- read.csv(tf, stringsAsFactors = FALSE)
Identify all of the soil series associated with a specific subgroup
taxa: abruptic durixeralfs and download basic morphology and
climate summaries via fetchOSD
.
# select series names for a single subgroup
SC <- SC[which(SC$taxsubgrp == 'abruptic durixeralfs'), ]
s <- SC$soilseriesname
# get OSD morphology and extended summaries
osd <- fetchOSD(soils = s, extended = TRUE)
Explain subgroup taxa via SoilTaxonomy package.
cat(explainST('abruptic durixeralfs'))
abruptic durixeralfs | | | | abrupt textural change | | | presence of a duripan | | xeric soil moisture regime | soils with an argillic, kandic, or natric horizon
Automatic to the subgroup, via SoilTaxonomy package.
taxonTree(c('durixeralfs', 'rhodoxeralfs'), special.chars = c("\u251c","\u2502", "\u2514", "\u2500 "))
Soil Taxonomy └─ alfisols └─ xeralfs ├─ durixeralfs │ ├─ natric durixeralfs │ ├─ vertic durixeralfs │ ├─ aquic durixeralfs │ ├─ abruptic haplic durixeralfs │ ├─ abruptic durixeralfs │ ├─ haplic durixeralfs │ └─ typic durixeralfs └─ rhodoxeralfs ├─ lithic rhodoxeralfs ├─ vertic rhodoxeralfs ├─ petrocalcic rhodoxeralfs ├─ calcic rhodoxeralfs ├─ inceptic rhodoxeralfs └─ typic rhodoxeralfs
We have to do this manually.
# subset SC database to required columns
SC <- SC[, c('soilseriesname', 'taxorder', 'taxsuborder', 'taxgrtgroup', 'taxsubgrp', 'taxclname')]
# normalization via lower case
SC$taxclname <- tolower(SC$taxclname)
SC$soilseriesname <- tolower(SC$soilseriesname)
# remove subgroup component of family spec
SC$f <- NA_character_
for(i in 1:nrow(SC)) {
SC$f[i] <- gsub(pattern = SC$taxsubgrp[i], replacement = '', SC$taxclname[i], fixed = TRUE)
}
# remove white space
SC$f <- trimws(SC$f, which = 'both')
# required columns only, smaller data.tree
v <- c('taxorder', 'taxsuborder', 'taxgrtgroup', 'taxsubgrp', 'f', 'soilseriesname', 'path')
# init data.tree object
SC$path <- sprintf("ST/%s/%s/%s/%s/%s/%s", SC$taxorder, SC$taxsuborder, SC$taxgrtgroup, SC$taxsubgrp, SC$f, SC$soilseriesname)
n <- as.Node(SC[, v], pathName = 'path')
## missing family / series result in an ugly tree, prune accordingly
# prune missing family / series
pf <- function(i) {
# NA due to left join
# note odd approach required, matching to 'NA' vs. is.na()
if(GetAttribute(i, 'name') == 'NA') {
return(FALSE)
} else {
return(TRUE)
}
}
## print
# print(n, limit = NULL, pruneFun = pf)
levelName 1 ST 2 °--alfisols 3 °--xeralfs 4 °--durixeralfs 5 °--abruptic durixeralfs 6 ¦--fine-loamy, mixed, superactive, thermic 7 ¦ °--bellota 8 ¦--ashy, glassy, frigid 9 ¦ °--borealis 10 ¦--fine, kaolinitic, thermic 11 ¦ °--chesterton 12 ¦--clayey-skeletal, kaolinitic, thermic 13 ¦ °--clough 14 ¦--very-fine, smectitic, frigid 15 ¦ °--deunah 16 ¦--fine, mixed, active, thermic 17 ¦ ¦--eastbiggs 18 ¦ ¦--redding 19 ¦ ¦--san joaquin 20 ¦ °--yuvas 21 ¦--fine, smectitic, mesic 22 ¦ °--fairylawn 23 ¦--clayey, smectitic, frigid, shallow 24 ¦ ¦--furshur 25 ¦ ¦--nicholflat 26 ¦ °--ponina 27 ¦--fine, illitic, thermic 28 ¦ °--gloria 29 ¦--clayey, mixed, active, thermic, shallow 30 ¦ ¦--keyes 31 ¦ °--vistarobles 32 ¦--fine, smectitic, thermic 33 ¦ °--madera 34 ¦--fine, vermiculitic, thermic 35 ¦ °--moda 36 ¦--fine, smectitic, frigid 37 ¦ °--thacker 38 °--fine-loamy, mixed, active, thermic 39 °--thermalito
Link to SoilWeb subgroup taxa tree for abruptic durixeralfs.
# get 800m extent map
e <- taxaExtent('abruptic durixeralfs', level = 'subgroup')
# aggregate via focal mean using 5x5 moving window
# visualization purposes only
a <- terra::aggregate(e, fact = 5, fun = mean, na.rm = TRUE)
data("us_states")
us_states <- st_transform(us_states, 5070)
us_states <- st_crop(us_states, e)
# simple figure
plot(a, axes = FALSE, col = mako(50), mar = c(3, 1, 3, 3))
plot(st_geometry(us_states), add = TRUE)
title('Abruptic Durixeralfs')
mtext('pixel values represent percent of area\nwithin 800m grid cells', side = 1, line = 1.5)
Cluster series based on annual climate summaries but don’t plot it yet.
# control centers symbol and size here
res <- vizAnnualClimate(osd$climate.annual, s = 'SAN JOAQUIN', IQR.cex = 1.1, cex=1.1, pch=18)
Series associated with abruptic durixeralfs subgroup, arranged according to annual climate summaries.
par(mar = c(0,0,1,2))
plotProfileDendrogram(osd$SPC, clust = res$clust, scaling.factor = 0.075, width = 0.25, y.offset = 0.5, name.style = 'center-center', shrink = TRUE, max.depth = 150, depth.axis = list(cex = 0.8))
mtext('Abruptic Durixeralfs', side = 1, at = 0.5, adj = 0, line = -1.5, font=4)
mtext('sorted by annual climate summaries', side = 3, at = 0.5, adj = 0, line = -1.5, font = 1, cex = 1.1)