knitr::opts_chunk$set(echo = FALSE) options(knitr.kable.NA = '') has_ttest <- FALSE
### set graphical parameters `set_par` <- function (panels = 1, pty = 's', ...) { mgp <- c(2, 0.4, 0) mar <- c(4, 4, 0.5, 0.5) oma <- c(0, 0, 0, 0) auto_rowcol <- function(n = panels) { if (n <= 3) c(1, n) else if (n <= 6) c(2, (n + 1)%/%2) else if (n <= 12) c(3, (n + 2)%/%3) else c(ceiling(n/(nr <- ceiling(sqrt(n)))), nr) } mfrow <- auto_rowcol() if (panels > 4) panels <- 4 switch(as.character(panels), `4` = par(mfrow = mfrow, mgp = mgp, mar = mar, pty = pty, oma = oma, bty = 'L', las = 1, cex.lab = 1.2, tcl = -0.2, ...), `3` = par(mfrow = mfrow, mgp = mgp, mar = mar, pty = pty, oma = oma, bty = 'L', las = 1, cex.lab = 1.4, cex.axis = 1.2, tcl = -0.2, ...), `2` = par(mfrow = mfrow, mgp = mgp, mar = mar, pty = pty, oma = oma, bty = 'L', las = 1, cex.axis = 0.85, tcl = -0.2, ...), `1` = par(mfrow = mfrow, mgp = mgp, mar = mar, pty = pty, oma = oma, bty = 'L', las = 1, cex.axis = 0.85, tcl = -0.2, ...)) } ### get years of sampling `get_years` <- function (pick, ...) { rng <- range(d[d$wa==pick, 'year'], na.rm=TRUE) ### <<-------------------- if (any(!is.finite(rng))) c(NA,NA) else rng } ### get recent decade `get_recent_decade` <- function (pick, ...) { s <- sort(unique(a[a$warea==pick, 'tenyr'])) s <- s[length(s)] if (any(!is.finite(s))) c(NA) else s } ### get number of plots `get_n` <- function (pick, ...) { n <- unique(a[a$warea==pick, 'n_plots']) if (any(!is.finite(n))) c(NA) else n } ### table function with 5-y mean `get_tab` <- function (pick, xvar, col.names, caption, format){ tab <- a[which(a$warea==pick), xvar] if (missing(col.names)) col.names <- xvar if (missing(caption)) caption <- NULL if (missing(format)) format <- 'pandoc' j <- xvar[xvar != 'tenyr'] # dont consider year column tab <- tab[rowSums(is.na(tab[,j])) != ncol(tab[,j]),] tab[tab %in% c('NA','***NA***','NaN','***NaN***')] <- NA knitr::kable(x = tab, format = format, row.names = F, align = c('l', rep('r',NCOL(tab)-1)), longtable = T, booktabs = T, caption = caption, col.names = col.names) }
\vspace*{\fill} \begin{wrapfigure}{l}{1.8cm} \includegraphics{usfs.png} \end{wrapfigure}
\
Air Resource Management Program
USDA Forest Service
1400 Independence Ave SW
Washington, DC 20250
\
\newpage
Each Wilderness area managed by the USDA Forest Service must report annually on progress toward “wildernesses meeting baseline performance for preserving wilderness character”, otherwise known as Wilderness Stewardship Performance (WSP). Among other elements, Wilderness Air Quality Values (WAQVs) are a marker of natural quality of Wilderness character (WSP Guidebook 2020). This report streamlines annual WSP reporting for WAQVs from lichen-based Critical Loads (CLs).
WAQVs are supported by monitoring changes in “sensitive receptors” like epiphytic macrolichens. Lichens are impacted by air pollutants and therefore indicate changing conditions. Workers long ago established links between lichen tissue content and the atmospheric deposition of pollutants like oxidized nitrogen (NO$_x$), reduced nitrogen (NH$_x$) and oxidized sulfur (SO$_x$). Each species also has a unique and individualistic pollution tolerance, so observing collective responses is far more robust than any single species alone.
For WSP reporting of air quality, “sensitive receptor indicators” can be estimated by the presence and abundance of lichen species. Nationally-consistent FHM/FIA surveys of lichen communities let us score and classify species by their relative pollution tolerance, and to calculate community metrics across all species at a site. Community metrics are more robust than tracking single species because the absence of a single species offers only circumstantial evidence of air quality (what if the species simply wasn't able to disperse to or tolerate microclimate at that site?). The joint absence of multiple pollution-intolerant species provides much stronger evidence of air-quality impacts.
Individual species' sensitivities are the value of N or S deposition (kg ha^-1^ y^-1^) at which the species was most frequently detected. Air scores for each site are the community-weighted mean of species' sensitivities, for all species present (kg ha^-1^ y^-1^). Therefore, site air scores represent the collective pollution tolerance of co-occurring species: "clean" sites have fairly low air scores driven by pollution-intolerant species, while "polluted" sites have higher air scores driven by pollution-tolerant ones. Air scores are reported as both raw scores (without regard to climate) and as climate-adjusted scores (which permit nationwide comparison).
A critical load (CL) is the air pollutant deposition rate expected to result in harmful ecosystem changes. Exceedance is the degree to which locally observed deposition goes beyond an established CL. Lichen-based CLs were established in prior work (Geiser et al. 2019). Specifically, lichen-based CLs are the deposition rates at which we expect changes in 1) total species richness, 2) sensitive species richness, 3) forage lichen abundance, 4) cyanolichen abundance, and 5) community compositions (see critical loads we used).
Conservation rankings follow NatureServe (https://www.natureserve.org/). Species considered 'at risk' here are those ranked by NatureServe as Critically Imperiled, Imperiled, or Vulnerable, and/or those ranked by COSEWIC as Endangered, Special Concern, or Threatened.
We used critical loads from Table 3 in Geiser et al. (2019). These critical loads are invariant to geography and so apply nationwide. Critical loads in this table are deposition levels resulting in $\geq20\%$ decrease in the richness or abundance of the given lichen metric.
\
tab <- data.frame(Metric = c('Species richness', 'Sensitive species richness', 'Forage lichen abundance', 'Cyanolichen abundance', 'Comm composition shifts'), Ndep = c(3.5, 3.1, 1.9, 1.3, 1.5), Sdep = c(6.0, 2.5, 2.6, 2.3, 2.7)) knitr::kable(tab, format='pandoc', row.names = F, longtable = T, booktabs = T, caption = 'Critical loads used in this report (kg ha^-1^ y^-1^).', escape = F, col.names = c('Lichen metric', 'N deposition', 'S deposition'))
See the article:
Geiser, L., P. Nelson, S. Jovan, H. Root, and C. Clark. 2019. Assessing ecological risks from atmospheric deposition of nitrogen and sulfur to US forests using epiphytic macrolichens. Diversity 11 (87):1-29. doi:10.3390/d11060087{target="_blank"}
\
\begin{wrapfigure}{r}{5.0cm} \includegraphics{bothlichens.png} \end{wrapfigure}
Lichens are commonly divided into "functional groups" based on ecological functions: three common groups are forage lichens, cyanolichens and matrix lichens.
Forage lichens (including "old-man's beard" and "witches' hair" lichens) provide food, nesting materials and suitable habitat for many animals (from insects to amphibians to small mammals). Large ungulates like deer, elk or caribou may depend on forage lichens to eat during lean winter months. Loss of these pollution-sensitive lichens might have implications for the wildlife that depend on them.
Cyanolichens (including "lungwort" lichens) contain cyanobacterial partners that convert nitrogen from the air into a form which forest vegetation can absorb -- when cyanolichens fall to the forest floor and release this nitrogen, they serve as "forest fertilizer"! Cyanolichens are among the most intolerant of poor air quality, so their absence may indicate deteriorating conditions.
Matrix lichens are any not included as forage or cyanolichens. They are often more pollution-tolerant than those two groups, and perform an array of ecosystem functions. These functions include rainfall interception, water cycle buffering, animal habitat, and bioindication of forest health.
Paying attention to changes in lichen communities is informative -- losses of lichen functional groups can indicate declines in the ecosystem functions that they provide.
\newpage
r input$xrng <- get_years(pick = rx()) n <- get_n(pick = rx()) dec <- get_recent_decade(pick = rx())
Number of surveyed plots: r n
Field sampling dates: r rng[1] to r rng[2]
Most recent ten-year sampling period: r dec
### plot the valuebar for observed CMAQ deposition `plot_valuebar` <- function(pick, brk=b, select='n', ...){ b <- brk x <- a[which(a$warea==pick), paste0('cmaq_',select,'_3yroll.mean')] arrlab <- 'OBSERVED\nCMAQ deposition' if(all(is.na(x))) { # try lichen airscores if CMAQ is missing x <- a[which(a$warea==pick), paste0(select,'_airscore.mean')] arrlab <- 'OBSERVED\nlichen airscores' } if(all(is.na(x))) { # no more tries x <- 999 } obs <- x[length(x)] # take most recent one par(mar=c(4,0.3,0.3,0.3), las=1, tcl=-0.2, mgp=c(1.8,0.4,0)) n <- 100 xseq <- seq(-0.5, 20.0, len=n) plot(xseq, seq(1, 10, len=n), type='n', bty='n', yaxt='n', ylab='', ylim=c(1,13.4), yaxs='i', xaxs='r', ...) cc <- viridis::inferno(5,begin=0.1,end=0.9,alpha=seq(0.7,1,len=5)) rect(-9, -10, b[1], 20, col=cc[1], border=NA) rect(b[1], -10, b[2], 20, col=cc[2], border=NA) rect(b[2], -10, b[3], 20, col=cc[3], border=NA) rect(b[3], -10, b[4], 20, col=cc[4], border=NA) rect(b[4], -10, 99, 20, col=cc[5], border=NA) text(x=b[1] - 0.5*(b[1]-(-1)), y=13.1, 'Acceptable', pos=1, cex=0.6) text(x=b[2] - 0.5*(b[2]-b[1]), y=13.1, 'Marginal', pos=1, cex=0.6) text(x=b[3] - 0.5*(b[3]-b[2]), y=13.1, 'Concerning', pos=1, cex=0.6) text(x=b[4] - 0.5*(b[4]-b[3]), y=13.1, 'Poor', pos=1, cex=0.6) text(x=21 - 0.5*(21-b[4]), y=13.1, 'Very poor', pos=1, cex=0.6) if (obs >= 999) { text(x=10, y=10, 'No data available...', pos=1, cex=0.7, font=2) } else if (obs > 20 & obs < 999) { arrows(20.2, 6.5, 20.2, 1, length=0.20, angle=15, lwd=3, col=1) text(x=18.8, y=10, arrlab, pos=1, cex=0.7, font=2) } else { arrows(obs, 6.5, obs, 1, length=0.20, angle=15, lwd=3, col=1) text(x=obs, y=10, arrlab, pos=1, cex=0.7, font=2) } } b <- c(2.5, 6, 9.9, 14) # loosely based on 20/50/80 plot_valuebar(pick = rx(), brk=b, select='n', xlab=expression(N~deposition~'('*kg~ha^-1~yr^-1*')'))
\
### plot the valuebar for observed CMAQ S deposition b <- b + (b * 0.05) # new (loosely based on 20/50/80) plot_valuebar(pick = rx(), brk=b, select='s', xlab=expression(S~deposition~'('*kg~ha^-1~yr^-1*')'))
\newpage
x <- d[d$wa==rx(), ] u <- sort(unique(na.omit(x[,'tenyr']))) # get unique time periods len <- length(u) if(len < 2) { has_ttest <- FALSE } else { u <- u[(len-1):(len)] # take *most recent* two periods x <- droplevels(x[x$tenyr %in% u,,drop=T]) x <- x[order(x$tenyr), ] ### either tenyr have *ALL* NA? (ttest still works if *SOME* NA) `f` <- function(k) all(is.na(k)) ### control logic to select CMAQ > AIRSCORE > NONE (nitrogen) if (!any(tapply(x$cmaq_n_3yroll, list(x$tenyr), f))) { fmla_n <- as.formula(cmaq_n_3yroll ~ tenyr) } else if (!any(tapply(x$n_airscore, list(x$tenyr), f))) { fmla_n <- as.formula(n_airscore ~ tenyr) } else { fmla_n <- NULL } ### control logic to select CMAQ > AIRSCORE > NONE (sulfur) if (!any(tapply(x$cmaq_s_3yroll, list(x$tenyr), f))) { fmla_s <- as.formula(cmaq_s_3yroll ~ tenyr) } else if (!any(tapply(x$s_airscore, list(x$tenyr), f))) { fmla_s <- as.formula(s_airscore ~ tenyr) } else { fmla_s <- NULL } ### ensure minimum sample size `get_n_per_grp` <- function (x, na.rm = TRUE) { n <- length(x) - sum(is.na(x)) return(c(n=n)) } n_n <- tryCatch(aggregate(fmla_n, data=x, get_n_per_grp), error=function(e) return(NULL)) n_s <- tryCatch(aggregate(fmla_s, data=x, get_n_per_grp), error=function(e) return(NULL)) ### control logic, if both are NULL or too-small sample-size, ### then dont do the ttests if ((is.null(fmla_n) & is.null(fmla_s)) | (any(n_n[,2] < 2) & any(n_s[,2] < 2))) { has_ttest <- FALSE } else { ### UNPAIRED 2-sample ttest (plots NOT paired...) t1 <- tryCatch(t.test(fmla_n,data=x), error=function(e)return(NULL)) t3 <- tryCatch(t.test(fmla_s,data=x), error=function(e)return(NULL)) ### descriptive summary table `f` <- function (x, na.rm = TRUE) { if (!is.numeric(x)) return(NULL) m <- mean(x, na.rm = na.rm) s <- stats::sd(x, na.rm = na.rm) na <- sum(is.na(x)) n <- length(x) - na se <- s/sqrt(n - 1) ci <- 1.96 * se return(c(mean=round(m,3), ci=round(ci,3), n=n)) } tab1 <- tryCatch(do.call(data.frame, aggregate(fmla_n, data=x, f)), error=function(e)return(NULL)) tab3 <- tryCatch(do.call(data.frame, aggregate(fmla_s, data=x, f)), error=function(e)return(NULL)) has_ttest <- TRUE ### if ttests failed then cancel ttests if (is.null(t1) & is.null(t3)) { has_ttest <- FALSE } } }
r if(has_ttest) {paste0('## Significant trends\n\nFrom the decade ', u[1], ' to ', u[2], ', a two-sample _t_-test revealed ', ifelse(t1$p.value < 0.05,'a ','NO '), 'SIGNIFICANT trend in N deposition, and ', ifelse(t3$p.value < 0.05,'a ','NO '), 'SIGNIFICANT trend in S deposition. ', 'See figure for test results.')}
### PLOT `plot_ci` <- function (tab, mod, ylab, ...) { nm <- tab[,1] y <- tab[,2] yci <- tab[,3] n <- tab[,4] x <- 1:2 ylm <- c(0, # min(c(y, y - yci), na.rm=T) - 0.4, max(c(y, y + yci), na.rm=T) + 0.1) if (all(is.infinite(ylm))) return(NULL) if (anyNA(ylm)) { ylm[which(is.na(ylm))] <- NULL } if (missing(ylab)) { ylb <- expression(Air~score~(kg~ha^'-1'~y^'-1')) } else { ylb <- ylab } plot(x=x, y=tab[,2], pch=16, ylab=ylb, xlab='', xaxt='n', xlim=c(0.4,2.6), ylim=ylm, ...) xlb <- paste0(nm,'\n','n = ', n) axis(1, at=x, labels=xlb, tick=F, line=1, cex.axis=0.9) w <- 0.05 segments(x0=x, x1=x, y0=y-yci, y1=y+yci, lwd=1.75) segments(x0=x-w, x1=x+w, y0=y+yci, y1=y+yci, lwd=1.75) segments(x0=x-w, x1=x+w, y0=y-yci, y1=y-yci, lwd=1.75) `add_text` <- function (x, y, labels, ...) { text(x = graphics::grconvertX(x, from = "npc", to = "user"), y = graphics::grconvertY(y, from = "npc", to = "user"), labels = labels, adj = 0, ...) } add_text(0.05,0.19, lab=paste0('t = ', formatC(round(mod$statistic,1),digits=3)), cex=0.8) add_text(0.05,0.12, lab=ifelse(mod$p.value < 0.01, 'p < 0.01', paste0('p = ',formatC(round(mod$p.value,2),digits=4))), cex=0.8) add_text(0.05,0.05, lab=paste0('Est. diff. = ', formatC(round(diff(mod$estimate),1),digits=2)), cex=0.8) } ### setup labeling if (grepl('cmaq', names(tab1)[2])) { ylb_n <- expression(CMAQ~value~(kg~N~ha^'-1'~y^'-1')) } else if (grepl('airscore', names(tab1)[2])) { ylb_n <- expression(N~air~score~(kg~N~ha^'-1'~y^'-1')) } else { ylb_n <- NA } if (grepl('cmaq', names(tab3)[2])) { ylb_s <- expression(CMAQ~value~(kg~S~ha^'-1'~y^'-1')) } else if (grepl('airscore', names(tab3)[2])) { ylb_s <- expression(S~air~score~(kg~S~ha^'-1'~y^'-1')) } else { ylb_s <- NA } ### plot set_par(2) par(mgp=c(1.3,0.4,0)) try(plot_ci(tab1, t1, ylab=ylb_n), silent=TRUE) try(plot_ci(tab3, t3, ylab=ylb_s), silent=TRUE) rm(get_n_per_grp, plot_ci, len, x, u, f, t1, t3, tab1, tab3, ylb_n, ylb_s, fmla_n, fmla_s)
r if(has_ttest) {"Power analysis: a _t_-test would require _n_ = 17 plots per time interval to detect a change of 1 kg ha^-1^ y^-1^ with 80\\% probability.\\newpage"}
### map wilderness boundary and sampling locations `map_points` <- function (pick=pick, pts=d, shp=w, ras=dem, lwd.cont=1, ...) { aea_prj <- "+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-110 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m" trg_prj <- projection(shp) shp <- shp[shp$NAME == pick,] # select your wilderness pts <- pts[pts$wa == pick,] # select your Wilderness if(all(is.na(pts$lon)) | all(is.na(pts$lon))) { e <- extent(shp) * 1.25 has_points <- FALSE } else { has_points <- TRUE `make_xy` <- function(xy, crs, ...){ xy <- as.data.frame(xy) xy <- xy[!(is.na(xy$lon) | is.na(xy$lat)),] # rm NAs coordinates(xy) <- ~lon+lat proj4string(xy) <- CRS('+init=epsg:4269') # NAD83 dec degree return(spTransform(xy, crs)) } ppts <- make_xy(pts, crs=trg_prj) near <- c(rgeos::gDistance(spTransform(ppts, CRS(aea_prj)), spTransform(shp, CRS(aea_prj)), byid=T) < 3000) # reject pts > 3 km ppts <- ppts[near,] e <- cbind(matrix(extent(shp)),matrix(extent(ppts))) e <- c(min(e[1,]),max(e[2,]),min(e[3,]),max(e[4,])) # combined ext `haversine` <- function(loc1, loc2) { # geographic distance lat1 <- loc1[1] * pi / 180 lon1 <- loc1[2] * pi / 180 lat2 <- loc2[1] * pi / 180 lon2 <- loc2[2] * pi / 180 R <- 6371.0 # avg earth radius [km] dlat <- lat2 - lat1 dlon <- lon2 - lon1 a <- sin(dlat/2)^2 + cos(lat1) * cos(lat2) * sin(dlon/2)^2 c <- 2 * atan2(sqrt(a), sqrt(1-a)) return(R * c) } dy <- haversine(c(e[3],e[1]), c(e[4],e[1])) # kilometers y-axis dx <- haversine(c(e[3],e[1]), c(e[3],e[2])) # kilometers x-axis e <- extent(e) if (dy > dx) { e[1:2] <- matrix(e * dy/dx)[1:2,] # expand x } else { e[3:4] <- matrix(e * dx/dy)[3:4,] # expand y } dx <- as.numeric( # how large a scale bar to make? as.character( cut(dx,breaks=c(0,5,50,200,Inf),labels=c(1,5,10,50),incl=T) ) ) e <- extent(e) * 1.25 # slight expansion } ras <- disaggregate(crop(ras, e), 10, method='bilinear') ### custom function for filled contours.... `fillcont` <- function (x, maxpixels = 1e+05, ...) { `fc` <- function ( x = seq(0, 1, length.out = nrow(z)), y = seq(0, 1, length.out = ncol(z)), z, xlim = range(x, finite = TRUE), ylim = range(y, finite = TRUE), zlim = range(z, finite = TRUE), levels = pretty(zlim, nlevels), nlevels = 20, color.palette = function(n) viridis::viridis(n,begin=0,end=0.9), col = color.palette(length(levels) - 1), ...) { if (missing(z)) { if (!missing(x)) { if (is.list(x)) { z <- x$z y <- x$y x <- x$x } else { z <- x x <- seq.int(0, 1, length.out = nrow(z)) } } else stop("no 'z' matrix specified") } else if (is.list(x)) { y <- x$y x <- x$x } if (any(diff(x) <= 0) || any(diff(y) <= 0)) stop("increasing 'x' and 'y' values expected") plot.new() plot.window(xlim, ylim, "", xaxs = "i", yaxs = "i", asp = NA) .filled.contour(x, y, z, levels, col) } x <- sampleRegular(x, maxpixels, asRaster = T, useGDAL = T) X <- xFromCol(x, 1:ncol(x)) Y <- yFromRow(x, nrow(x):1) Z <- t(matrix(getValues(x), ncol = x@ncols, byrow=T)[nrow(x):1,]) lonlat <- couldBeLonLat(x, warnings = FALSE) asp <- list(...)$asp if (is.null(asp)) { if (lonlat) { ym <- mean(c(x@extent@ymax, x@extent@ymin)) asp <- 1/cos((ym * pi)/180) } else { asp <- 1 } fc(x = X, y = Y, z = Z, asp = asp, ...) } else { fc(x = X, y = Y, z = Z, ...) } } fillcont(ras, nlevels=20, lwd=lwd.cont) plot(shp, col='transparent', border='red', add=T, lwd=2) if (has_points) { points(ppts, pch=16, col='#000000', cex=1) scalebar(d=dx, xy=c(e[1] + abs(diff(e[1:2]))*0.05, e[3] + abs(diff(e[3:4]))*0.08), type='bar', divs=2, below=paste0('\n', dx, ' km'), adj=c(0.5,-1), lab='', lwd=2, cex=0.7, font=2) } } ### some wilderness areas too large to plot (memory overrun on server) too_big <- c( 'Oregon Islands Wilderness', 'Frank Church-River of No Return Wilderness', 'Kootznoowoo Wilderness', 'Boundary Waters Canoe Area Wilderness' # 'Daniel J. Evans Wilderness' # <<--- AKA Olympic NP, should be ok ) # error catch -- dont plot if shape too large to plot (server memory overrun) if (rx() %in% too_big) { plot(1:10, 1:10, type='n', axes=FALSE, ann=FALSE) text(5,5,labels='Map not available',adj=c(0.5,0.5)) } else { # error catch -- dont plot if valid name not in shapefile tryCatch(map_points(pick = rx(), pts=d, shp=w, ras=dem), error = function(e) { plot(1:10, 1:10, type='n', axes=FALSE, ann=FALSE) text(5,5,labels='Map not available',adj=c(0.5,0.5)) }) } # ### error catch -- dont plot if valid name not in shapefile # `try_map` <- function() { # tryCatch(map_points(pick = rx(), pts=d, shp=w, ras=dem), # error = function(e) { # plot(1:10, 1:10, type='n', axes=FALSE, ann=FALSE) # text(5,5,labels='Map not available',adj=c(0.5,0.5)) # }) # } # try_map() # # map_points(pick = rx(), pts=d, shp=w, ras=dem)
\newpage
You can think of site scores (or "air scores") as the "average" position of species along an air quality gradient. Technically, these are the community-weighted mean of species' sensitivities, where sensitivities are defined as the value of N or S deposition (kg ha^-1^ y^-1^) at which each species was most frequently detected. We report both raw and climate-adjusted scores, and their nationwide ranking as a percentile.^[See "Indicator: Air scores" section for more information.].
get_tab(pick = rx(), xvar=c('tenyr','n_airscore.mean','nadj.mean','cmaq_n_3yroll.mean'), col.names=c('Ten-year interval', 'Lichen airscore (raw)', 'Lichen airscore (adj)', 'CMAQ model'), caption= 'Site scores for estimated nitrogen deposition (N), as a *mean* (kg ha^-1^ y^-1^).')
get_tab(pick = rx(), xvar=c('tenyr','p_n_airscore.mean','p_n_adj.mean','p_n_cmaq.mean'), col.names=c('Ten-year interval', 'Lichen airscore (raw)', 'Lichen airscore (adj)', 'CMAQ model'), caption= 'Site scores for estimated nitrogen deposition (N), as a *percentile* (0-100).')
get_tab(pick = rx(), xvar=c('tenyr','s_airscore.mean','sadj.mean','cmaq_s_3yroll.mean'), col.names=c('Ten-year interval', 'Lichen airscore (raw)', 'Lichen airscore (adj)', 'CMAQ model'), caption= 'Site scores for estimated sulfur deposition (S), as a *mean* (kg ha^-1^ y^-1^).')
get_tab(pick = rx(), xvar=c('tenyr','p_s_airscore.mean','p_s_adj.mean','p_s_cmaq.mean'), col.names=c('Ten-year interval', 'Lichen airscore (raw)', 'Lichen airscore (adj)', 'CMAQ model'), caption= 'Site scores for estimated sulfur deposition (S), as a *percentile* (0-100).')
\newpage
Exceedances are the departure of raw or climate-adjusted lichen airscores from established lichen-based critical loads^[See "Critical loads we used" section.], expressed as the mean of a ten-year period. Negative values indicate the "safety margin" below the critical load threshold, positive values indicate exceedance above the threshold.
get_tab(pick = rx(), xvar=c('tenyr', 'n_raw_ex_sr.mean', 'n_raw_ex_sr_sens.mean', 'n_raw_ex_sr_forag.mean', 'n_raw_ex_sr_cyano.mean', 'n_raw_ex_compn.mean'), col.names=c('Ten-year interval', 'Richness', 'Sensitive richness', 'Forage lichen abund', 'Cyanolichen abund', 'Comm comp'), caption= 'CL exceedances for mean nitrogen deposition (kg N ha^-1^ y^-1^), based on raw lichen airscores.')
get_tab(pick = rx(), xvar=c('tenyr', 'n_adj_ex_sr.mean', 'n_adj_ex_sr_sens.mean', 'n_adj_ex_sr_forag.mean', 'n_adj_ex_sr_cyano.mean', 'n_adj_ex_compn.mean'), col.names=c('Ten-year interval', 'Richness', 'Sensitive richness', 'Forage lichen abund', 'Cyanolichen abund', 'Comm comp'), caption= 'CL exceedances for mean nitrogen deposition (kg N ha^-1^ y^-1^), based on climate-adjusted lichen airscores.')
get_tab(pick = rx(), xvar=c('tenyr', 's_raw_ex_sr.mean', 's_raw_ex_sr_sens.mean', 's_raw_ex_sr_forag.mean', 's_raw_ex_sr_cyano.mean', 's_raw_ex_compn.mean'), col.names=c('Ten-year interval', 'Richness', 'Sensitive richness', 'Forage lichen abund', 'Cyanolichen abund', 'Comm comp'), caption= 'CL exceedances for mean sulfur deposition (kg S ha^-1^ y^-1^), based on raw lichen airscores.')
get_tab(pick = rx(), xvar=c('tenyr', 's_adj_ex_sr.mean', 's_adj_ex_sr_sens.mean', 's_adj_ex_sr_forag.mean','s_adj_ex_sr_cyano.mean', 's_adj_ex_compn.mean'), col.names=c('Ten-year interval', 'Richness', 'Sensitive richness', 'Forage lichen abund', 'Cyanolichen abund', 'Comm comp'), caption= 'CL exceedances for mean sulfur deposition (kg S ha^-1^ y^-1^), based on climate-adjusted lichen airscores.')
\newpage
`get_richness` <- function (pick, ...) { NROW(s[[which(names(s) == pick)]]) } spp_obs <- get_richness(pick = rx()) spp_exp <- 4.01 + 25.81*log(n) # log-linear eqn
Number of surveyed plots: r n
Number of species observed: r spp_obs
Number of species expected: r round(spp_exp, 1)
\
x <- 1:2500 # n plots sampled y <- 4.01 + 25.81*log(x) # n species expected set_par(1) plot(x, y, type='l', log='x', xaxt='n', xlim=c(1,2500), xlab=bquote(Plots~sampled~(log[10]~scale)), ylab='Species expected') atx <- outer(1:9, 10^(0:3)) labx <- ifelse(log10(atx) %% 1 == 0, atx, NA) axis(1, at=atx, labels=labx, las=1) lines(rep(n,2), c(spp_exp,spp_obs)) points(n, spp_exp, pch=21, cex=1.2, bg='white') points(n, spp_obs, pch=16, cex=1.2) legend('topleft', pch=c(16,21), bty='n', cex=0.7, pt.cex=1.0, legend=c(paste0('Observed = ', spp_obs), paste0('Expected = ', round(spp_exp,1))))
`get_natureserve` <- function (pick, ...) { tab <- s[[which(names(s) == pick)]] rich <- NROW(tab) nsens <- round(sum(tab$n_class == 'oligotroph') / sum(tab$n_class != '') * 100, 1) ssens <- round(sum(tab$s_class == 'sensitive') / sum(tab$s_class != '') * 100, 1) atrisk <- sum(1 * grepl('1|2|3|Endanger|Concern|Threaten', tab$consrv_stat)) list(rich=rich, nsens=nsens, ssens=ssens, atrisk=atrisk) } x <- get_natureserve(pick = rx())
Number of species observed in total: r x$rich
Number of species with 'at risk' conservation status: r x$atrisk
Percentage of rated species with 'sensitive (oligotroph)' N tolerance: r x$nsens\%
Percentage of rated species with 'sensitive' S tolerance: r x$ssens\%
List of all macrolichens in plots at r input$x from r rng[1] to r rng[2]. True diversity is likely greater because plot footprints are just a fraction of total Wilderness area. Functional groups ('Fxl grp') are cyanolichens ('cyano') and forage lichens ('forage')(see descriptions). Sensitivity for each species ranges from low to high tolerance for nitrogen (oligo-, meso-, eutroph) and sulfur (sensitive, intermediate, tolerant). Critical loads ('CL') are deposition levels resulting in >20\% decrease in occurrence. Conservation from NatureServe (https://www.natureserve.org/) assigns each species both a global (G) and state (S) rank on a scale of 1 to 5 (1=Critically imperiled; 2=Imperiled; 3=Vulnerable; 4=Apparently secure; 5=Secure).
`get_tab_species` <- function (pick, ...) { tab <- s[which(names(s) == pick)][[1]] tab <- tab[order(tab$n_critload, tab$s_critload, tab$fxlgp),] knitr::kable(x = tab, format = 'markdown', ### format = 'pandoc', row.names = FALSE, longtable = TRUE, booktabs = TRUE, caption = NULL, ### 'Macrolichen species list.', escape = FALSE, col.names = c('Species','Fxl grp','N sensitivity','N CL', 'S sensitivity','S CL','Conservation') ) } get_tab_species(pick = rx())
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.