NOTE: This correspondence is intended for communication of project progress among funders and collaborators only. This information is preliminary and is subject to revision. It is being provided to meet the need for timely best science. The information is provided on the condition that neither the U.S. Geological Survey nor the U.S. Government shall be held liable for any damages resulting from the authorized or unauthorized use of the information.
knitr::opts_chunk$set(echo = TRUE) target_ESG <- "Semiarid_Warm_SandyUplands" user <- "Anna" #user <- "VPN" library(dplyr) library(kableExtra) library(STMdevelopment)
# TODO insert dot graphic and text about the status of the ESD (e.g. "Provisional"). Might help # to create a function to automatically insert the description based on the # status input
EDIT_map(target_ESG = target_ESG, user = user, maxZoomStyle = "public")
Areas shown in blue indicate the maximum mapped extent of this ecological site. Other ecological sites likely occur within the highlighted areas. It is also possible for this ecological site to occur outside of highlighted areas if detailed soil survey has not been completed or recently updated.
This ecological site group includes a variety of upland ecological sites that have deeper sandy soils without significant rock content. The group was defined by placing ecological sites into groups by separating soils, climate, and geomorphic features to best differentiate reference vegetation production values and documented ecological states as documented by Nauman et al. (2022).
esds <- esd_data_pull(user=user, target_ESG = target_ESG) comps <- comp_data_pull(user=user, target_ESG = target_ESG) num_esds <- nrow(esds)
This ecological site group includes r num_esds
ecological sites. These sites were correlated to the group based on average soil, climate and geomorphic properties of the soil components linked to each ecological site.
linked_esd_names <- esds[,c("ecoclassid","ecoclassname")] %>% mutate(ecoclassid = paste0("[", ecoclassid, "](https://edit.jornada.nmsu.edu/catalogs/esd/", substr(ecoclassid, 2, 5), "/", ecoclassid, ")")) ## Associated ecological sites format_tables_EDIT_style(data = linked_esd_names, col.names=c("ESD Code","ESD Name"), caption = "Ecological Sites associated with this ESG", row.names = F) #knitr::kable(x = esds[,c("ecoclassid","ecoclassname")], col.names=c("ESD Code","ESD Name"), caption = NULL,row.names = F)
# TODO create table of dominant plant functional groups and species esg_prod_list <- esg_production_pull(user=user, target_ESG = target_ESG) dom_plants_df <- dplyr::tribble(~Item, ~Description, "Trees", as.character(esg_prod_list[["Tree.df"]]$COMMON_NAME)[1:5], "Shrubs", as.character(esg_prod_list[["Shrub.df"]]$COMMON_NAME)[1:5], "Grasses",as.character(esg_prod_list[["Grass.df"]]$COMMON_NAME)[1:5], "Forbs", as.character(esg_prod_list[["Forb.df"]]$COMMON_NAME)[1:5]) format_tables_EDIT_style(data = dom_plants_df, caption = "Dominant plant species") #knitr::kable(x = dom_plants_df, caption = "Table 1. Dominant plant species")
TODO insert "Download full description" link to a PDF
# TODO create table of landforms, elevations, slopes, water table depth, flooding duration, flooding frequency, ponding frequency # ideally should be able to tab between US and metric system measurements landforms <- unlist(strsplit(esds$landfrms[1], split=",")) for(e in 2:nrow(esds)){ esdlfs <- unlist(strsplit(esds[e,c("landfrms")],split=",")) landforms <- append(landforms,esdlfs) } landforms <- landforms[!landforms %in% "NA"] lfsum <- factor(landforms) lfsum <- reorder(lfsum,lfsum,FUN=function(x) -length(x)) landforms_unique <- unique(landforms)
These sites occur on upland positions with mostly gentle slopes on a variety of landforms (n=r length(landforms_unique)
). Some areas can have steeper slopes, but these are not typical of the group.
# Slope slope_ave <- mean(comps@site$slope_r.x) slope_75th <- unname(quantile(comps@site$slope_r.x, probs = 0.75, na.rm = T)) slope_max <- max(comps@site$slope_r.x, na.rm = T) slope_sd <- sd(comps@site$slope_r.x, na.rm = T) slope_field <- paste("Slopes average",format(round(slope_ave, 1), nsmall = 1), "% with a standard deviation of",format(round(slope_sd, 1), nsmall = 1), "and are generally less than", format(round(slope_75th, 1), nsmall = 1), "but can be up to", format(round(slope_max, 1), nsmall = 1),"%",sep=" ") phys.df <- dplyr::tribble(~Item, ~Description, "Landforms (Top 10)", as.character(unique(lfsum))[1:10], "Slope", slope_field, "Flooding Frequency","None", "Aspect", "Aspect is not a significant factor") format_tables_EDIT_style(data = phys.df, caption = "Representative physiographic features") #knitr::kable(x = phys.df, caption = "Table 2. Representative physiographic features")
Climate is generally semiarid and warm with aridity index (precipitation / potential evapotranspiration) values averaging r format(round(mean(esds$aimean), 2), nsmall = 2)
and ranging from r format(round(min(esds$aimean), 2), nsmall = 2)
to r format(round(max(esds$aimean), 2), nsmall = 2)
. The average maximum temperatures (Celsius) of the hottest month range from r format(round(min(esds$maxtempmean), 2), nsmall = 2)
to r format(round(max(esds$maxtempmean), 2), nsmall = 2)
, and minimum temperatures of the coldest month range from r format(round(min(esds$mintempmean), 2), nsmall = 2)
to r format(round(max(esds$mintempmean), 2), nsmall = 2)
. Warm season (June to September) precipitation makes up r format(round(mean(esds$pptrt*100), 0), nsmall = 0)
% of total precipitation on average, but can range from r format(round(min(esds$pptrt*100), 0), nsmall = 0)
% to r format(round(max(esds$pptrt*100), 0), nsmall = 0)
%.
clim.df <- dplyr::tribble(~Item, ~Description, "Frost Free Period (days)", paste(format(round(min(comps@site$ffd_r), 0), nsmall = 0),format(round(mean(comps@site$ffd_r), 0), nsmall = 0),format(round(max(comps@site$ffd_r), 0), nsmall = 0),sep=", "), "Mean Annual Precipitation (in)", paste(format(round(min(comps@site$reannualprecip_r/25.4,na.rm=T), 1), nsmall = 1),format(round(mean(comps@site$reannualprecip_r/25.4,na.rm=T), 1), nsmall = 1),format(round(max(comps@site$reannualprecip_r/25.4,na.rm=T), 1), nsmall = 1),sep=", ")) format_tables_EDIT_style(data = clim.df, caption = "Representative climatic features (min, mean, max)") #knitr::kable(x = clim.df, caption = "Table 3. Representative climatic features (min, mean, max)")
# TODO create line chart and bar charts of monthly high/low precip that you can tab between
# TODO create line chart and bar charts of monthly high/low temps that you can tab between
These sites neither benefit significantly from run-in moisture nor experience excessive loss of moisture from runoff.
Soils in this group are moderately deep or deeper to bedrock and are composed primarily of alluvium and eolian sediments. Surface and subsurface horizons very high sand content (>75% on average) with textures including loamy sand, sand, and sandy loam. Soils are non-saline and non sodic and can have up to 10% calcium carbonate, but generally have less than 5% carbonates. Soil pH ranges from r min(c(round(min(comps@horizons[comps@horizons$hzdept_r<30,c("ph1to1h2o_r")],na.rm=T), 0), round(min(comps@horizons[comps@horizons$hzdept_r>30,c("ph1to1h2o_r")],na.rm=T), 0)))
to r max(c(round(max(comps@horizons[comps@horizons$hzdept_r<30,c("ph1to1h2o_r")],na.rm=T), 1), round(max(comps@horizons[comps@horizons$hzdept_r>30,c("ph1to1h2o_r")],na.rm=T), 1)))
. Water erosion hazard is moderate and wind erosion hazard is severe.
## Prep soil variables for table surftext <- factor(esds$txtnm_surf) surftext <- reorder(surftext,surftext,FUN=function(x) -length(x)) surftext_uni <- levels(surftext) subtext <- factor(esds$txtnm_sub) subtext <- reorder(subtext,subtext,FUN=function(x) -length(x)) subtext_uni <- levels(subtext) drainage <- factor(comps@site$drainagecl.x) drainage <- reorder(drainage,drainage,FUN=function(x) -length(x)) drainage_uni <- levels(drainage)[1:3] ## Make Table of soil descriptors: # TODO Could update pH to be based on esd averages instead of comps. Also could update PM to grab ssurgo values soil.df <- dplyr::tribble(~Item, ~Description, "Parent Material", "alluvium and eolian sediments", "Surface Texture (0-30cm)", surftext_uni, "Subsurface Texture (>30cm)",subtext_uni, "Drainage", drainage_uni, "Soil Depth", paste(format(round(min(esds$depthmean,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$depthmean,na.rm=T), 0), nsmall = 0), " cm",sep=""), "Surface Rock Content %vol (0-30cm)", paste(format(round(min(esds$rock_surf,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$rock_surf,na.rm=T), 0), nsmall = 0), "%",sep=""), "Subsurface Rock Content %vol (>30cm)", paste(format(round(min(esds$rock_sub,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$rock_sub,na.rm=T), 0), nsmall = 0), "%",sep=""), "Surface Electrical Conductivity (0-30cm)", paste(format(round(min(esds$ec_surf,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$ec_surf,na.rm=T), 1), nsmall = 1), " dS/m",sep=""), "Subsurface Electrical Conductivity (>30cm)", paste(format(round(min(esds$ec_sub,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$ec_sub,na.rm=T), 1), nsmall = 1), " dS/m",sep=""), "Surface Sodium Adsorption Ratio (0-30cm)", paste(format(round(min(esds$sar_surf,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$sar_surf,na.rm=T), 1), nsmall = 1),sep=""), "Subsurface Sodium Adsorption Ratio (>30cm)", paste(format(round(min(esds$sar_sub,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$sar_sub,na.rm=T), 1), nsmall = 1),sep=""), "Surface 1:1 pH (0-30cm)", paste(format(round(min(comps@horizons[comps@horizons$hzdept_r<30,c("ph1to1h2o_r")],na.rm=T), 0), nsmall = 0),"-",format(round(max(comps@horizons[comps@horizons$hzdept_r<30,c("ph1to1h2o_r")],na.rm=T), 1), nsmall = 1),sep=""), "Subsurface 1:1 pH (>30cm)", paste(format(round(min(comps@horizons[comps@horizons$hzdept_r>30,c("ph1to1h2o_r")],na.rm=T), 0), nsmall = 0),"-",format(round(max(comps@horizons[comps@horizons$hzdept_r>30,c("ph1to1h2o_r")],na.rm=T), 1), nsmall = 1),sep=""), "Surface Calcium Carbonate (0-30cm)", paste(format(round(min(esds$caco3_surf,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$caco3_surf,na.rm=T), 0), nsmall = 0), "%",sep=""), "Subsurface Calcium Carbonate (>30cm)", paste(format(round(min(esds$caco3_sub,na.rm=T), 0), nsmall = 0),"-",format(round(max(esds$caco3_sub,na.rm=T), 0), nsmall = 0), "%",sep="")) format_tables_EDIT_style(data = soil.df, caption = "Representative soil features") #knitr::kable(x = soil.df, caption = "Table 4. Representative soil features")
Soil types correlated to this group include r length(unique(comps@site$cokey))
different components mapped in SSURGO. Soil taxonomic units include:
cat(sort(unique(comps@site$compname)), sep = ", ")
agg <- aqp::slab(comps, fm= ~ claytotal_r + silttotal_r + sandtotal_r + ec_r + caco3_r) lattice::xyplot(top ~ p.q50 | variable, data=agg, ylab='Depth (cm)', xlab='median bounded by 25th and 75th percentiles', lower=agg$p.q25, upper=agg$p.q75, ylim=c(201,-2), panel=aqp::panel.depth_function, alpha=0.25, sync.colors=TRUE, par.settings=list(superpose.line=list(col='RoyalBlue', lwd=2)), prepanel=aqp::prepanel.depth_function, cf=agg$contributing_fraction, cf.col='black', cf.interval=20, layout=c(5,1), strip=lattice::strip.custom(bg=grey(0.8)), scales=list(x=list(tick.number=4, alternating=3, relation='free')) )
These sites have high risk for wind erosion and dune mobilization, especially with drought or large disturbances. They also have potential for water erosion issues. Compared to reference states, alternative states may have higher bare ground, annual invasion, woody encroachment, and/or loss of perennial grasses.
apriori_stms <- read.csv(data_file_paths(user)$apriori_stms, stringsAsFactors = F, na.strings = c("NA", "", " ")) apriori_stms_target <- select(apriori_stms, any_of(c("State", "Plant_community", target_ESG))) n_ESDs <- apriori_stms_target[apriori_stms_target$Plant_community=="n_ESDs", target_ESG] colnames(apriori_stms_target) <- c("State", "Plant community", "Proportion of ESDs containing this plant community") format_tables_EDIT_style(data = apriori_stms_target[-nrow(apriori_stms_target),], caption = paste("State and plant community summary from Ecological Sites within this ESG.", n_ESDs, "complete STMs were available to include.")) %>% collapse_rows(columns = 1, valign = "top")
## Plot of reference production indicators used in paper resp_vars <- colnames(esds)[1:21] resp_vars <- resp_vars[!resp_vars %in% c("ecoclassid")] par(mar = c(4,8,4,2)) ## This works when knitted boxplot(esds[,resp_vars], horizontal = TRUE,las = 1, main = "Reference Production (lbs/Acre)")
# by functional group cover indicators <- c("AH_C3NativePerenGrassCover", "AH_C3IntroducedPerenGrassCover", "AH_C4NativePerenGrassCover", "AH_C4IntroducedPerenGrassCover", "AH_NativePerenForbCover", # Native perennial forbs "AH_IntroducedPerenForbCover", # Non-native perennial forbs "AH_NativeAnnGrassCover", # Native annual grasses "AH_IntroducedAnnGrassCover", # Non-native annual grasses "AH_NativeAnnForbCover", # Native annual forbs "AH_IntroducedAnnForbCover", # Non-native annual forbs "AH_ArtemisiaTridentataCover", # All big sagebrush species lumped together "BareSoilCover", # Bare soil "FH_TotalLitterCover", # Litter "CA_percent_100plus", # Canopy gaps > 100 cm "CA_percent_200plus", "FH_LichenCover", # Lichen + moss combined cover "FH_MossCover"#, #"SoilStab_all" # to represent cyano abundance... I think this is only marginally a community variable. If used in hierarchical clustering and Bray-Curtis NMDS, will need to standardize it to make units more similar to % cover units ) ann_grass_by_spp <- F ann_forb_by_spp <- F per_grass_by_spp <- F per_forb_by_spp <- F succulent_by_spp <- F shrub_by_spp <- T # All shrubs and sub-shrubs by species subshrub_by_spp <- T tree_by_spp <- T # All trees by species opuntia_combined <- T data_sources <- c(#"BadgerWash", #"CRCMonitoring", # drop if not needed for spatial representation "IM_NCPN", "LMF", "NRI", #"Parashant", # drop if not needed for spatial representation "AIM"#, #"VanScoyocThesis" # drop if not needed for spatial representation ) impute_gap_type <- c("CA_percent_100plus", "CA_percent_200plus") plot_data <- plot_data_pull(user=user, target_ESG = target_ESG, data_sources = data_sources, indicators = indicators, ann_grass_by_spp = ann_grass_by_spp, ann_forb_by_spp = ann_forb_by_spp, per_grass_by_spp = per_grass_by_spp, per_forb_by_spp = per_forb_by_spp, succulent_by_spp = succulent_by_spp, shrub_by_spp = shrub_by_spp, subshrub_by_spp = subshrub_by_spp, tree_by_spp = tree_by_spp, opuntia_combined = opuntia_combined, impute_gap_type = impute_gap_type ) %>% filter(!is.na(Month))
# TODO update I&M species codes to match current USDA Plants codes! probably a step for the plot_data_pull function? # remove incomplete rows - ordination won't work with NAs plot_data_clean <- na.omit(plot_data) %>% # NRI has a separate plot code for each time a plot was sampled, but all other data sets have the same plot # code for all sampling times. Make a similar code for NRI. mutate(PlotCode_NoYear = ifelse(test = SourceKey == "NRI_UCRB", yes = paste0(SourceKey, "_", SiteName, "_", substr(PlotID, 5, nchar(PlotID))), no = PlotCode)) # Some plots were sampled multiple times - I'll include only the first year that a plot has complete data plot_data_first <- plot_data_clean %>% #plot_data_clean %>% dplyr::group_by(PlotCode_NoYear) %>% dplyr::arrange(.data=., Year, .by_group = TRUE) %>% dplyr::filter(dplyr::row_number()==1) %>% dplyr::ungroup() # remove plots with SGU probability (i.e. certainty of prediction) in the 10th percentile SGU_prob_raster <- raster::raster("C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/Maps/UCRB_SGUs_ProbMax/UCRB_SGUs_ProbMax.tif") file_paths <- data_file_paths(user) plot_locations <- sf::st_read(dsn = file.path(file_paths$plotnet_processed, "PlotLocations"), layer = "all_plot-years_2022-11-08", quiet=TRUE) %>% distinct() if("NRI" %in% data_sources){ nri_locations <- sf::st_read(dsn = file_paths$nri, layer = "NRI_UCRB_plot-years_2022-11-03", quiet = TRUE) plot_locations <- dplyr::filter(plot_locations, !grepl(pattern = "^NRI_", x=PlotCode)) %>% dplyr::bind_rows(., nri_locations) %>% distinct() } plot_SGU_prob <- sf::st_as_sf(raster::extract(x = SGU_prob_raster, y = plot_locations, sp = T)) plot_data_first <- left_join(plot_data_first, select(plot_SGU_prob, PlotCode, UCRB_SGUs_ProbMax)) %>% distinct() certainty_cutoff <- quantile(plot_data_first$UCRB_SGUs_ProbMax, probs = 0.1) plot_data_first <- filter(plot_data_first, UCRB_SGUs_ProbMax > certainty_cutoff) # make the clustering and ordination data frame - can't include ANY columns except the actual variables! ord.df <- dplyr::select(plot_data_first, -SourceKey, -PlotID, -SiteName, -PlotName, -Year, -Longitude_NAD83, -Latitude_NAD83, -Month, -Day, -UCRB_SGUs_ProbMax, -geometry, -PlotCode_NoYear ) %>% tibble::column_to_rownames("PlotCode") # keep an ID code for the plot as the row name to prevent data scrambling problems # If using soil stability, standardize variables to make units more similar # Here, dividing by the max possible value for each measurement type (cover values # divided by 100, soil stability divided by 6) if("SoilStab_all" %in% indicators){ ord.df.cover <- select(ord.df, -SoilStab_all)/100 ord.df.soilstab <- select(ord.df, SoilStab_all)/6 ord.df <- cbind(ord.df.cover, ord.df.soilstab) } # remove rare species sp_pa <- mutate(rowwise(ord.df), across(everything(), function(x){if(x>0){1}else{0}})) # make a presence/absence data frame sp_keep <- names(colSums(sp_pa)[which(colSums(sp_pa)>2)]) ord.df <- select(ord.df, all_of(sp_keep)) # remove unknown species if present ord.df <- select(ord.df, -any_of(c("UNKS", "UNKPG", "UNKAF", "UNKPF", "PG1", "PG01", "AF1")))
Field data from were collected by field crews with the Natural Resource Conservation Service's (NRCS) National Resource Inventory (NRI) program, the Bureau of Land Management's (BLM) Assessment, Inventory, and Monitoring (AIM) and Landscape Monitoring Framework (LMF) programs, and the Nationa Park Service's (NPS) Inventory and Monitoring Northern Colorado Plateau Network (NCPN) program between r min(plot_data_clean$Year)
and r max(plot_data_clean$Year)
. Plots within the target ESG with SGU classification probability less than or equal to the 10th percentile (r certainty_cutoff
%) were excluded, resulting in a total of r nrow(ord.df)
plots for this ESG. Plant and soil cover were measured using line-point intercept and canopy gap methods (Herrick et al., 2017). Plot-level indicators (Table \@ref(tab:doc-plot-indicators)) were calculated using the terradactyl package in R (McCord et al., 2022). When plots were sampled in multiple years, only the first sampling year was used in STM development. r if(!is.null(impute_gap_type)){paste("For plots that were missing annual & perennial gap data but had perennial-only gap data (primarily I&M-NCPN), annual & perennial gap values were imputed from perennial-only gaps and annual herbaceous cover using a linear model built from LMF and NRI plots that had both types of gap (r-squared of 0.83 and 0.81 for 100-cm and 200-cm gap predictions, respectively).")}
rm(plot_data_clean, plot_data) indicator_descriptions <- make_indicator_descriptions(indicators = indicators, shrub_by_spp = shrub_by_spp, subshrub_by_spp = subshrub_by_spp, tree_by_spp = tree_by_spp, opuntia_combined = opuntia_combined) format_tables_EDIT_style(data = indicator_descriptions[,-1], caption = "Indicators used in ordination and clustering analysis to develop plant communities") %>% collapse_rows(columns = 1, valign = "top")
# are the data clusterable? Use the Hopkin's statistic (Lawson, Richard, and Jurs, 1990; see https://www.datanovia.com/en/lessons/assessing-clustering-tendency/ for explanation) # H values > 0.5 indicate clustering tendency (i.e. if H is below 0.5, the data set is uniformly distributed and clusters are not meaningful) res_LoamyUplands <- factoextra::get_clust_tendency(data = ord.df, n=nrow(ord.df)-1, graph = FALSE) res_LoamyUplands$hopkins_stat
# cluster and indicator species analysis library(cluster) # for cluster analysis library(dendextend) library(labdsv) library(foreach) library(vegan) library(ggplot2) # initial clustering dist.df <- vegan::vegdist(ord.df, method = "bray", na.rm = T) # creates Bray-Curtis distance matrix
# This chunk determines the best combination of number of clusters and fuzziness for the data. It is SLOOOOWWW so better to run it before knitting to get the right clustering parameters and then not run it when knitting the descriptive document. library(parallel) library(doParallel) # get index values for all combos of membership exponent and number of clusters indices_arg <- c("ch", #"duda", won't work with the ranking scheme "cindex", #"gamma", # takes a long time #"beale", # unclear how this one works "ccc", "ptbiserial", #"gplus", # takes a long time "db", "silhouette", "dunn" #, #"gap" # takes a long time ) indices_name <- c("CH", #"Duda", "Cindex", #"gamma", # takes a long time #"Beale", "CCC", "Ptbiserial", #"gplus", # takes a long time "DB", "Silhouette", "Dunn" #, #"gap" # takes a long time ) memb_exps <- seq(1.1, 1.7, # start with 2.0, can reduce value to rerun if some larger values don't give results by = 0.1) cl <- makeCluster(spec = 3, # number of cores to use type = "PSOCK", methods = FALSE) registerDoParallel(cl) cluster_eval_fanny <- foreach(memb_exp=memb_exps, .packages = c("STMdevelopment", "dplyr", "cluster"), .combine = "rbind") %dopar% { nbmetrics_fanny_temp <- NbClust_fanny(data = ord.df, diss = dist.df, #chord.df, distance = NULL, min.nc = 2, max.nc = 12, method = "fanny", index = "all", # could also try alllong to get a few more metrics - this is SLOW alphaBeale = 0.1, memb.exp= memb_exp # 1.2 ) cluster_eval_fanny_temp <- nbmetrics_fanny_temp$All.index %>% as.data.frame() %>% mutate(Number_clusters = rownames(.), Memb_exp = memb_exp) } registerDoSEQ() stopCluster(cl) cluster_eval_fanny_indices <- select(cluster_eval_fanny, any_of(c("Memb_exp", "Number_clusters", indices_name))) # calculate percent worst than best value for each memb. exp. X num. clusters combo for each index pct_off_best_higher <- function(x, all_vals){ best <- max(all_vals) pct_off <- ((best-x)/best)*100 return(pct_off) } pct_off_best_lower <- function(x, all_vals){ best <- min(all_vals) pct_off <- ((x-best)/best)*100 return(pct_off) } cluster_eval_fanny_indices_rank <- cluster_eval_fanny_indices %>% rowwise() %>% mutate(CH_rank = pct_off_best_higher(CH, cluster_eval_fanny_indices$CH), Cindex_rank = pct_off_best_lower(Cindex, cluster_eval_fanny_indices$Cindex), CCC_rank = pct_off_best_higher(CCC, cluster_eval_fanny_indices$CCC), Ptbiserial_rank = pct_off_best_higher(Ptbiserial, cluster_eval_fanny_indices$Ptbiserial), DB_rank = pct_off_best_lower(DB, cluster_eval_fanny_indices$DB), Silhouette_rank = pct_off_best_higher(Silhouette, cluster_eval_fanny_indices$Silhouette), Dunn_rank = pct_off_best_higher(Dunn, cluster_eval_fanny_indices$Dunn) ) %>% mutate(overall_rank = sum(CH_rank, # lowest overall rank wins! Cindex_rank, CCC_rank, Ptbiserial_rank, DB_rank, Silhouette_rank, Dunn_rank, na.rm = T)) best_n_clust <- as.numeric(cluster_eval_fanny_indices_rank[[which.min(cluster_eval_fanny_indices_rank$overall_rank), "Number_clusters"]]) best_memb_exp <- as.numeric(cluster_eval_fanny_indices_rank[[which.min(cluster_eval_fanny_indices_rank$overall_rank), "Memb_exp"]]) # VISUALLY CHECK for ties and ranking weirdness before proceeding!!
k_groups_fuzzy <- 4 #2 it really wants to be 2!! memb_exp <- 1.1 #1.1 set.seed(1) clust_fuzz <- fanny(x=dist.df, k = k_groups_fuzzy, diss = T, #metric = "euclidean", memb.exp = # this affects how crisp/fuzzy the clusters are - closer to 1 is crisper memb_exp ) grp_fuzz <- clust_fuzz$clustering plot_data_first$PlantCommunity_fuzzy <- as.factor(grp_fuzz)
Plots were clustered into ecological communities using the FANNY fuzzy clustering algorithm (Kaufman and Rousseeuw, 1990) in the "cluster" R package (Maechler et al., 2021) with Bray-Curtis distance ("vegan" package; Oksanen et al., 2020). A suite of clustering indices were used to determine the most appropriate number of clusters and degree of fuzziness (i.e. membership exponent), including Calinski and Harabasz (1974), C-index (Hubert and Levin, 1976), cubic clustering criteria (Sarle, 1983), point-biserial correlation (Milligan 1980, 1981), Davies and Bouldin (1979), silhouette width (Rousseeuw 1987), and Dunn's index (1974). Values between 1 and 12 possible clusters and 1.1 to 1.3 membership exponents were tested. The final clustering solution used r k_groups_fuzzy
clusters with a membership exponent of r memb_exp
.
# another slow chunk - run before knitting and then don't evaluate in the knit step. NMDS_scree(ord.df)
ord_dims <- 3 # change manually based on the plot results #run NMS ordination set.seed(1) ord <- metaMDS(ord.df, k=ord_dims, # number of dimensions trymax = 30)
The clustering results were visualized in ordination spaced, using non-metric multidimensional scaling (NMDS) with Bray-Curtis distance ("vegan" package in R). Based on testing ordination stress for 1 to 5 dimensions, ordination stress is acceptable with r ord_dims
dimensions, and stress reduction is minimal with additional dimensions. The stress for the ordination with r ord_dims
dimensions is r round(ord$stress, 3)
.
# 2D plotting pal_veg2 <- RColorBrewer::brewer.pal(n=k_groups_fuzzy, name = "Dark2") # # manually update based on indicator species # group_labels_fuzzy <- c(paste0("Mixed sagebrush, native per. # grasses, & cheatgrass (n = ", nrow(filter(plot_data_first, PlantCommunity_fuzzy==1)), ")"), # paste0("Open pinyon-juniper woodland # (n = ", nrow(filter(plot_data_first, PlantCommunity_fuzzy==2)), ")"), # paste0("Mixed pinyon-juniper & # sagebrush (n = ", nrow(filter(plot_data_first, PlantCommunity_fuzzy==3)), ")"), # paste0("Native perennial grassland # (n = ", nrow(filter(plot_data_first, PlantCommunity_fuzzy==4)), ")")) group_labels_fuzzy <- as.character(1:clust_fuzz$k.crisp) par(mfrow=c(2,2)) # Axes 1x2 plot(ord, choices = c(1,2), type = "n", # plot the axes xlim = c(-1, 1), ylim = c(-1, 1)) points(ord, choices = c(1,2), display = "sites", # plot points - can choose "sites" or "species" col=pal_veg2[plot_data_first$PlantCommunity_fuzzy], pch = 21, cex = .6, bg=pal_veg2[plot_data_first$PlantCommunity_fuzzy]) ordiellipse(ord, plot_data_first$PlantCommunity_fuzzy, col=pal_veg2, lwd = 2, label = F, choices = c(1,2)) # plot your groups # can use oriellipse, orihull, or ordispider to plot groups, depending on your needs # Axes 3x2 plot(ord, choices = c(3,2), type = "n", xlim = c(-1, 1), ylim = c(-1, 1)) points(ord, choices = c(3,2), display = "sites", col=pal_veg2[plot_data_first$PlantCommunity_fuzzy], pch = 21, cex = .6, bg=pal_veg2[plot_data_first$PlantCommunity_fuzzy]) ordiellipse(ord, plot_data_first$PlantCommunity_fuzzy, col=pal_veg2, lwd = 2, label = F, choices = c(3,2)) # Axes 1x3 plot(ord, choices = c(1,3), type = "n", xlim = c(-1, 1), ylim = c(-1, 1)) points(ord, choices = c(1,3), display = "sites", col=pal_veg2[plot_data_first$PlantCommunity_fuzzy], pch = 21, cex = .6, bg=pal_veg2[plot_data_first$PlantCommunity_fuzzy]) ordiellipse(ord, plot_data_first$PlantCommunity_fuzzy, col=pal_veg2, lwd = 2, label = F, choices = c(1,3)) # Legend plot(ord, type = "n", axes=FALSE, display = "sites", col=pal_veg2[plot_data_first$PlantCommunity_fuzzy], xlab = "", ylab = "") legend(x="center", legend = group_labels_fuzzy, fill = pal_veg2, title = "Plant communities", bty = "n", y.intersp = 1.5) par(mfrow=c(1,1))
# save clustering results fuzzy_cluster_membership <- as.data.frame(clust_fuzz$membership) fuzzy_cluster_membership$PlotCode <- row.names(clust_fuzz$membership) fuzzy_cluster_membership$best_group <- clust_fuzz$clustering colnames(fuzzy_cluster_membership) <- c(paste0("Group_", 1:4, "_membership"), "PlotCode", "Best_group") group_names <- data.frame(Best_group = 1:5, Best_group_name = c("C4 & C3 perennial grassland", "Depauperate grassland", "Sagebrush & C3 perennial grass shrubland", "Gambel oak shrubland", "Non-native annual grassland")) fuzzy_cluster_membership <- left_join(fuzzy_cluster_membership, group_names) # get the plot location and date sampled (use first date) plot_locations_first <- plot_locations %>% mutate(DateSampled = as.Date(paste(Year, Month, Day, sep = "-"))) %>% arrange(DateSampled) %>% group_by(PlotCode) %>% summarise(DateSampled = first(DateSampled)) fuzzy_cluster_memb_sf <- left_join(fuzzy_cluster_membership, plot_locations_first) # sf::st_write(fuzzy_cluster_memb_sf, # dsn = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/Maps", # layer = paste0("SW_LoamyUplands_fuzzyclusters_", Sys.Date(), ".shp"), # driver = "ESRI Shapefile") # fuzzy_cluster_memb_sf_shareable <- filter(fuzzy_cluster_memb_sf, !grepl(pattern = "NRI", x=PlotCode)) # # sf::st_write(fuzzy_cluster_memb_sf_shareable, # dsn = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/Maps", # layer = paste0("SW_LoamyUplands_fuzzyclust_share_", Sys.Date(), ".shp"), # driver = "ESRI Shapefile") # states <- sf::st_read("C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/GIS/Political boundaries/cb_2018_us_state_5m/cb_2018_us_state_5m.shp") # fourcorners <- filter(states, NAME %in% c("Utah", "Colorado", "Arizona", "New Mexico")) # # fuzzy_cluster_memb_sf_mike <- sf::st_filter(sf::st_as_sf(fuzzy_cluster_memb_sf, crs=sf::st_crs(plot_locations)), # sf::st_transform(fourcorners, crs=sf::st_crs(plot_locations))) %>% # mutate(Source = ifelse(test = grepl(x=PlotCode, pattern = "^AIM"), # yes = "AIM", # no = NA)) %>% # mutate(Source = ifelse(test = grepl(x=PlotCode, pattern = "^LMF"), # yes = "LMF", # no = Source)) %>% # mutate(Source = ifelse(test = grepl(x=PlotCode, pattern = "^IM_NCPN"), # yes = "NPS", # no = Source)) %>% # mutate(Source = ifelse(test = grepl(x=PlotCode, pattern = "^NRI"), # yes = "NRI", # no = Source)) # # sf::st_write(fuzzy_cluster_memb_sf_mike, # dsn = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/Maps", # layer = paste0("SW_LoamyUplands_fuzzyclust_mike_", Sys.Date(), ".shp"), # driver = "ESRI Shapefile")
# TODO create custom diagram of states knitr::include_graphics("C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Analyses/Semiarid_Warm_SandyUplands_boxandarrow_DRAFT.JPG")
# TODO legend for custom diagram if needed # scratch pad: # All states described here contain some introduced species (e.g. *Bromus tectorum*; see Tables \@ref(tab:comm1-1-dominant-spp), \@ref(tab:comm2-1-dominant-spp), \@ref(tab:comm3-1-dominant-spp), \@ref(tab:comm4-1-dominant-spp), and \@ref(tab:comm5-1-dominant-spp) for details).
The data used to develop this STM were sourced from large monitoring data sets designed to sample landscapes with a variety of current and historic land use. The model does not describe reference communities because these data do not include targeted sampling of relict plant communities with histories of little alteration to disturbance regimes. Several states described here contain some introduced species (e.g. Bromus tectorum; see Tables \@ref(tab:comm3-1-dominant-spp) and \@ref(tab:comm4-1-dominant-spp) for details).
For furthur information on reference states as well as drivers of transitions, see the Ecological Site Descriptions for the sites listed in Table \@ref(tab:assoc-eco-sites).
Click on state and transition labels to scroll to the respective text.
TODO create boxes for each state that link to the state description below
# load descriptive data plot_data_descriptive <- plot_data_pull(user=user, target_ESG = target_ESG, data_sources = data_sources, indicators = c("TotalFoliarCover", "AH_WoodyCover", "AH_ShrubCover", # TODO what to do about subshrubs?? "AH_TreeCover", "AH_ForbGrassCover", "AH_PerenForbGrassCover", "AH_AnnForbGrassCover", "SoilStab_all", "AH_ArtemisiaTridentataCover"), ann_grass_by_spp = T, ann_forb_by_spp = T, per_grass_by_spp = T, per_forb_by_spp = T, succulent_by_spp = T, shrub_by_spp = T, subshrub_by_spp = T, tree_by_spp = T, opuntia_combined = F ) %>% filter(!is.na(Month)) %>% left_join(select(plot_data_first, PlotCode, Year, PlantCommunity_fuzzy) , .) colnames(plot_data_descriptive)[which(colnames(plot_data_descriptive)=="AH_ArtemisiaTridentataCover")] <- "ARTR2" # summarize species occurrance descriptive.df <- dplyr::select(plot_data_descriptive, -SourceKey, -PlotID, -SiteName, -PlotName, -Year, -Longitude_NAD83, -Latitude_NAD83, -Month, -Day, -PlantCommunity_fuzzy, -TotalFoliarCover, -AH_WoodyCover, -AH_ShrubCover, -AH_TreeCover, -AH_ForbGrassCover, -AH_PerenForbGrassCover, -AH_AnnForbGrassCover, -SoilStab_all ) %>% tibble::column_to_rownames("PlotCode") # keep an ID code for the plot as the row name to prevent data scrambling problems sp_pa_desc <- mutate(rowwise(descriptive.df), across(everything(), function(x){if(x>0){1}else{0}})) # make a presence/absence data frame sp_keep_desc <- names(colSums(sp_pa_desc)[which(colSums(sp_pa_desc)>0)]) descriptive.df <- select(descriptive.df, all_of(sp_keep_desc)) plot_data_descriptive <- select(plot_data_descriptive, SourceKey, PlotID, SiteName, PlotName, Year, PlotCode, Longitude_NAD83, Latitude_NAD83, Month, Day, PlantCommunity_fuzzy, TotalFoliarCover, AH_WoodyCover, AH_ShrubCover, AH_TreeCover, AH_ForbGrassCover, AH_PerenForbGrassCover, AH_AnnForbGrassCover, SoilStab_all, all_of(sp_keep_desc)) #ord.df <- select(ord.df, -any_of(c("UNKS", "UNKPG", "UNKAF", "UNKPF", "PG1", "PG01", "AF1"))) ## load climate data AI_raster <- raster::raster(file_paths$ai_raster) plot_AI <- sf::st_as_sf(raster::extract(x = AI_raster, y = plot_locations, sp = T)) %>% mutate(AI = ai_et0_WesternUSEcoregions*0.0001 # AI rasters are actually AI*10,000 so that they #can be stored as integers - need to put back into standard AI units ) %>% sf::st_drop_geometry() rm(AI_raster) climate_rasters <- raster::stack(file_paths$prism_rasters) prism_vals <- raster::extract(x=climate_rasters, y=plot_locations, sp = T) %>% as(., "sf") sf::st_geometry(prism_vals) <- NULL colnames(prism_vals)[which(colnames(prism_vals) %in% c("PRISM_ppt_30yr_normal_800mM2_annual_asc", "PRISM_tmean_30yr_normal_800mM2_annual_asc", "PRISM_tmax_30yr_normal_800mM2_annual_asc", "PRISM_tmin_30yr_normal_800mM2_annual_asc" ))] <- c("Ann_ppt_mm", "Ann_tmean", "Ann_tmax", "Ann_tmin") rm(climate_rasters) plot_data_descriptive <- left_join(plot_data_descriptive, select(plot_AI, PlotCode, Year, AI)) %>% left_join(., select(prism_vals, PlotCode, Year, Ann_ppt_mm, Ann_tmean, Ann_tmax, Ann_tmin)) ## species richness and diversity plot_data_descriptive$shannon_diversity <- vegan::diversity(x=descriptive.df, index = "shannon") plot_data_descriptive$sp_richness <- vegan::specnumber(x=descriptive.df) # AERO aero <- read.csv(file_paths$aero) plot_aero <- aero %>% select(ProjectName, PlotID, horizontal_flux_total_MN, horizontal_flux_total_LPI, horizontal_flux_total_UPI) %>% setNames(c("SiteName", "PlotName", "horizontal_flux_total_MN", "horizontal_flux_total_LPI", "horizontal_flux_total_UPI")) plot_data_descriptive <- left_join(plot_data_descriptive, plot_aero)
# # consider including species present in >10% of plots: # names(colSums(sp_pa)[which(colSums(sp_pa)>(nrow(plot_data_first)*0.10))]) # # also consider including species with high mean cover across all plots: # mean_cover <- summarise(plot_data_first, across(.cols = AH_C3IntroducedPerenGrassCover:AH_OpuntiaCover, .fns = mean)) %>% # select(-any_of(c("Longitude_NAD83", "Latitude_NAD83", "Year", "Month", "Day"))) %>% # t() %>% # as.data.frame() # # row.names(mean_cover)[which(mean_cover$V1>=0.9)] # Consider switching to plotly to allow users to hover and see exact quantile and outlier values, e.g.: # plotly::plot_ly(x=plot_data_descriptive$PlantCommunity_fuzzy, # y=plot_data_descriptive$horizontal_flux_total_UPI, # split=plot_data_descriptive$PlantCommunity_fuzzy, # type='box') plot_data_first_tall <- select(plot_data_first, SourceKey, PlotID, SiteName, PlotName, Year, PlotCode, #PlantCommunity, PlantCommunity_fuzzy, AH_C3IntroducedPerenGrassCover, AH_C3NativePerenGrassCover, AH_C4NativePerenGrassCover, AH_IntroducedAnnForbCover, AH_IntroducedAnnGrassCover, AH_IntroducedPerenForbCover, AH_NativeAnnForbCover, AH_NativeAnnGrassCover, AH_NativePerenForbCover, BareSoilCover, FH_TotalLitterCover, AH_ArtemisiaTridentataCover, CA_percent_200plus, AH_C4IntroducedPerenGrassCover, FH_LichenMossCover, CA_percent_100plus, ATCA2, ERNA10, CHVI8, EPVI, PUTR2, GUSA2, JUOS, PIED, QUGA ) %>% tidyr::pivot_longer(cols = c(AH_C3IntroducedPerenGrassCover, AH_C3NativePerenGrassCover, AH_C4NativePerenGrassCover, AH_IntroducedAnnForbCover, AH_IntroducedAnnGrassCover, AH_IntroducedPerenForbCover, AH_NativeAnnForbCover, AH_NativeAnnGrassCover, AH_NativePerenForbCover, BareSoilCover, FH_TotalLitterCover, AH_ArtemisiaTridentataCover, CA_percent_200plus, AH_C4IntroducedPerenGrassCover, FH_LichenMossCover, CA_percent_100plus, CHVI8, EPVI, GUSA2, JUOS, PIED ), names_to = "CoverType", values_to = "PctCover") spp_list <- read.csv(file_paths$species_list) cover_funcgrps <- sort(c("AH_C3IntroducedPerenGrassCover", "AH_C3NativePerenGrassCover", "AH_C4NativePerenGrassCover", "AH_IntroducedAnnForbCover", "AH_IntroducedAnnGrassCover", "AH_IntroducedPerenForbCover", "AH_NativeAnnForbCover", "AH_NativeAnnGrassCover", "AH_NativePerenForbCover", "BareSoilCover", "FH_TotalLitterCover", "AH_ArtemisiaTridentataCover", "CA_percent_200plus", "AH_C4IntroducedPerenGrassCover", "FH_LichenMossCover", "CA_percent_100plus")) funcgrps_names <- arrange(filter(indicator_descriptions, Indicator_code %in% cover_funcgrps), Indicator_code)$Indicator names(cover_funcgrps) <- funcgrps_names cover_spp <- sort(c("CHVI8", "EPVI", "GUSA2", "JUOS", "PIED" )) spp_names <- paste0( filter(spp_list, SpeciesCode %in% cover_spp & SpeciesState=="AIM")$ScientificName, " (", filter(spp_list, SpeciesCode %in% cover_spp & SpeciesState=="AIM")$CommonName, ")") names(cover_spp) <- spp_names cover_labels <- sort(c(cover_funcgrps, cover_spp)) functypes_grps <- c("TotalFoliarCover", #"AH_WoodyCover", #AH_ForbGrassCover, "AH_ShrubCover", "AH_TreeCover", "AH_PerenForbGrassCover", "AH_AnnForbGrassCover" ) functypes_cover_states <- ggplot(data = select(plot_data_descriptive, any_of(c("PlantCommunity_fuzzy", functypes_grps))) %>% tidyr::pivot_longer(cols = all_of(functypes_grps), names_to = "CoverType", values_to = "PctCover"), aes(x=PlantCommunity_fuzzy, y=PctCover, fill=CoverType)) + geom_boxplot() + scale_fill_brewer(type = "qual", palette = "Set3", name = "Functional type", labels = c("Annual herbaceous", "Perennial herbaceous", #"Woody", "Shrub", "Tree", "Total foliar cover") ) + scale_x_discrete(labels=group_labels_fuzzy) + xlab(NULL) + ylab("Cover (%)") + ggtitle("Plant functional groups") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, color = "black"), axis.text.y = element_text(color = "black")) woody_grps <- c("AH_ArtemisiaTridentataCover", "CHVI8", "EPVI", "GUSA2", "JUOS", "PIED" ) woody_cover_states <- ggplot(data = filter(plot_data_first_tall, CoverType %in% woody_grps), aes(x=PlantCommunity_fuzzy, y=PctCover, fill=CoverType)) + geom_boxplot() + scale_fill_brewer(type = "qual", palette = "Set3", name = "Species", labels = names(cover_labels[which(cover_labels %in% woody_grps)])) + scale_x_discrete(labels=group_labels_fuzzy) + xlab(NULL) + ylab("Cover (%)") + ggtitle("Woody plants") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, color = "black"), axis.text.y = element_text(color = "black")) perenherb_grps <- c("AH_C3IntroducedPerenGrassCover", "AH_C3NativePerenGrassCover", "AH_C4IntroducedPerenGrassCover", "AH_C4NativePerenGrassCover", "AH_IntroducedPerenForbCover", "AH_NativePerenForbCover") perenherb_cover_states <- ggplot(data = filter(plot_data_first_tall, CoverType %in% perenherb_grps), aes(x=PlantCommunity_fuzzy, y=PctCover, fill=CoverType)) + geom_boxplot() + scale_fill_brewer(type = "qual", palette = "Set3", name = "Functional group", labels = names(cover_labels[which(cover_labels %in% perenherb_grps)])) + scale_x_discrete(labels=group_labels_fuzzy) + xlab(NULL) + ylab("Cover (%)") + ggtitle("Perennial herbaceous plants") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, color = "black"), axis.text.y = element_text(color = "black")) annherb_grps <- c("AH_IntroducedAnnForbCover", "AH_IntroducedAnnGrassCover", "AH_NativeAnnForbCover", "AH_NativeAnnGrassCover") annherb_cover_states <- ggplot(data = filter(plot_data_first_tall, CoverType %in% annherb_grps), aes(x=PlantCommunity_fuzzy, y=PctCover, fill=CoverType)) + geom_boxplot() + scale_fill_brewer(type = "qual", palette = "Set3", name = "Functional group", labels = names(cover_labels[which(cover_labels %in% annherb_grps)])) + scale_x_discrete(labels=group_labels_fuzzy) + xlab(NULL) + ylab("Cover (%)") + ggtitle("Annual herbaceous plants") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, color = "black"), axis.text.y = element_text(color = "black")) ground_grps <- c("BareSoilCover", "FH_TotalLitterCover", "CA_percent_200plus", "FH_LichenMossCover", "CA_percent_100plus") ground_cover_states <- ggplot(data = filter(plot_data_first_tall, CoverType %in% ground_grps), aes(x=PlantCommunity_fuzzy, y=PctCover, fill=CoverType)) + geom_boxplot() + scale_fill_brewer(type = "qual", palette = "Set3", name = "Functional group", labels = names(cover_labels[which(cover_labels %in% ground_grps)])) + scale_x_discrete(labels=group_labels_fuzzy) + xlab("Plant community") + ylab("Cover (%)") + ggtitle("Ground cover") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, color = "black"), axis.text.y = element_text(color = "black")) cover_states <- cowplot::plot_grid(functypes_cover_states + theme(legend.position = "none"), woody_cover_states + theme(legend.position = "none"), perenherb_cover_states + theme(legend.position = "none"), annherb_cover_states + theme(legend.position = "none"), ground_cover_states + theme(legend.position = "none"), ncol = 1, axis = "lr", labels = "auto") cover_axes <- cowplot::plot_grid(cowplot::get_legend(functypes_cover_states), cowplot::get_legend(woody_cover_states), cowplot::get_legend(perenherb_cover_states), cowplot::get_legend(annherb_cover_states), cowplot::get_legend(ground_cover_states), ncol = 1) cowplot::plot_grid(cover_states, cover_axes, ncol = 2, rel_widths = c(1, 0.5))
cowplot::plot_grid( cowplot::plot_grid( ggplot(data = plot_data_descriptive, aes(x=PlantCommunity_fuzzy, y=AI)) + geom_boxplot() + scale_x_discrete(labels=group_labels_fuzzy) + xlab(NULL) + ylab("Aridity index") + ylim(c(0, 0.45)) + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)), ggplot(data = plot_data_descriptive, aes(x=PlantCommunity_fuzzy, y=Ann_ppt_mm)) + geom_boxplot() + scale_x_discrete(labels=group_labels_fuzzy) + xlab(NULL) + ylab("Annual precipitation (mm)") + ylim(c(0, 650)) + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)), nrow = 1, labels = c("a", "b")), ggplot(data = select(plot_data_descriptive, PlantCommunity_fuzzy, Ann_tmean, Ann_tmax, Ann_tmin) %>% tidyr::pivot_longer(c(Ann_tmean, Ann_tmax, Ann_tmin), names_to = "temptype", values_to = "tempdegrees"), aes(fill=PlantCommunity_fuzzy, y=tempdegrees, x=temptype)) + geom_boxplot() + scale_fill_discrete(name="Plant community", labels=group_labels_fuzzy) + scale_x_discrete(labels=c("Maximum", "Mean", "Minimum")) + xlab(NULL) + ylab("Temperature (C)") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)), nrow = 2, labels = c(NA, "c"))
ggplot(data = plot_data_descriptive, aes(x=PlantCommunity_fuzzy, y=SoilStab_all)) + geom_boxplot() + scale_x_discrete(labels=group_labels_fuzzy) + ylim(c(0, 6)) + xlab("Plant community") + ylab("Mean soil stability rating") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1))
cowplot::plot_grid(ggplot(data = plot_data_descriptive, aes(x=PlantCommunity_fuzzy, y=shannon_diversity)) + geom_boxplot() + scale_x_discrete(labels=group_labels_fuzzy) + xlab("Plant community") + ylab("Shannon diversity index") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)), ggplot(data = plot_data_descriptive, aes(x=PlantCommunity_fuzzy, y=sp_richness)) + geom_boxplot() + scale_x_discrete(labels=group_labels_fuzzy) + xlab("Plant community") + ylab("Number of species") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)), nrow = 1, labels = "auto")
# dust_tall <- plot_data_descriptive %>% # select(PlotID, PlantCommunity_fuzzy, horizontal_flux_total_MN, horizontal_flux_total_LPI, horizontal_flux_total_UPI) %>% # tidyr::pivot_longer(cols = c(horizontal_flux_total_MN, horizontal_flux_total_LPI, horizontal_flux_total_UPI), # names_to = "dust_statistic", # values_to = "dust_flux") ggplot(data = plot_data_descriptive, aes(x=PlantCommunity_fuzzy, y=horizontal_flux_total_MN)) + geom_boxplot() + scale_x_discrete(labels=group_labels_fuzzy) + #ylim(c(0, 12)) + xlab("Plant community") + ylab("Mean horizontal dust flux") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) # TODO add log transformation to y axis
TODO write state description
TODO write state description
dom_spp_grp4 <- get_dominant_spp(data = filter(plot_data_descriptive, PlantCommunity_fuzzy==4), species_cols = colnames(descriptive.df), user = user) dom_spp_grp4_pub <- filter( select(dom_spp_grp4, Indicator, ScientificName, CommonName, SpeciesCode, MeanCover, pct_plots), pct_plots >=10) %>% mutate(MeanCover = round(MeanCover, 2), pct_plots = round(pct_plots, 2)) %>% setNames(c("Functional group", "Scientific name", "Common name", "USDA Plants code", "Mean cover (%)", "Percent of plots where present")) format_tables_EDIT_style(data = dom_spp_grp4_pub, caption = "Community 1.1 plant community composition") %>% collapse_rows(columns = 1, valign = "top")
# TODO create pie charts of community composition by functional group. May need to change "production" to "percent cover" to fit with our data
# TODO create table of production (or percent cover) by functional type # knitr::kable(x = dom_plant_spp_tab, caption = "Table 5. Annual production by plant type")
# TODO create table of ground cover by type # knitr::kable(x = dom_plant_spp_tab, caption = "Table 6. Ground cover")
# TODO create table of func type percent cover at different heights # knitr::kable(x = dom_plant_spp_tab, caption = "Table 7. Canopy structure (% cover)")
TODO write state description
dom_spp_grp2 <- get_dominant_spp(data = filter(plot_data_descriptive, PlantCommunity_fuzzy==2), species_cols = colnames(descriptive.df), user = user) dom_spp_grp2_pub <- filter( select(dom_spp_grp2, Indicator, ScientificName, CommonName, SpeciesCode, MeanCover, pct_plots), pct_plots >=10) %>% mutate(MeanCover = round(MeanCover, 2), pct_plots = round(pct_plots, 2)) %>% setNames(c("Functional group", "Scientific name", "Common name", "USDA Plants code", "Mean cover (%)", "Percent of plots where present")) format_tables_EDIT_style(data = dom_spp_grp2_pub, caption = "Community 2.1 plant community composition") %>% collapse_rows(columns = 1, valign = "top")
TODO write state description
TODO write state description
dom_spp_grp1 <- get_dominant_spp(data = filter(plot_data_descriptive, PlantCommunity_fuzzy==1), species_cols = colnames(descriptive.df), user = user) dom_spp_grp1_pub <- filter( select(dom_spp_grp1, Indicator, ScientificName, CommonName, SpeciesCode, MeanCover, pct_plots), pct_plots >=10) %>% mutate(MeanCover = round(MeanCover, 2), pct_plots = round(pct_plots, 2)) %>% setNames(c("Functional group", "Scientific name", "Common name", "USDA Plants code", "Mean cover (%)", "Percent of plots where present")) format_tables_EDIT_style(data = dom_spp_grp1_pub, caption = "Community 3.1 plant community composition") %>% collapse_rows(columns = 1, valign = "top")
TODO write state description
TODO write state description
dom_spp_grp3 <- get_dominant_spp(data = filter(plot_data_descriptive, PlantCommunity_fuzzy==3), species_cols = colnames(descriptive.df), user = user) dom_spp_grp3_pub <- filter( select(dom_spp_grp3, Indicator, ScientificName, CommonName, SpeciesCode, MeanCover, pct_plots), pct_plots >=10) %>% mutate(MeanCover = round(MeanCover, 2), pct_plots = round(pct_plots, 2)) %>% setNames(c("Functional group", "Scientific name", "Common name", "USDA Plants code", "Mean cover (%)", "Percent of plots where present")) format_tables_EDIT_style(data = dom_spp_grp3_pub, caption = "Community 4.1 plant community composition") %>% collapse_rows(columns = 1, valign = "top")
# TODO create table of production (or percent cover) by species, with species linking to their USDA Plants profile # knitr::kable(x = dom_plant_spp_tab, caption = "Table 8. Community 1.1 plant community composition")
TODO write wildlife narrative
TODO write hydrology narrative
TODO write recreation narrative
TODO write wood production narrative (probably "None")
TODO grazing narrative could go here
Herrick, J. E., Van Zee, J. W., McCord, S. E., Courtright, E. M., Karl, J. W., & Burkett, L. M. (2017). Monitoring Manual for Grassland, Shrubland, and Savanna Ecosystems (Second ed. Vol. 1: Core Methods). Las Cruces, New Mexico: USDA-ARS Jornada Experimental Range.
Maechler, M., Rousseeuw, P., Struyf, A., Hubert, M., Hornik, K.(2021). cluster: Cluster Analysis Basics and Extensions. R package version 2.1.2.
McCord, S. E., Brehm, J. R., Burnett, S. H., Dietrich, C., Edwards, B., Metz, L. J., Hernandez Narvaez, M., Pierson, F., Ramirez, K. S., Stauffer, N. G., Webb, N. P., and Tweedie, C. E. (2022). A framework and toolset for standardizing agroecosystem indicators. Ecological Indicators, 144. doi:10.1016/j.ecolind.2022.109511
Nauman, T. W., Burch, S. S., Humphries, J. T., Knight, A. C., & Duniway, M. C. (2022). A Quantitative Soil-Geomorphic Framework for Developing and Mapping Ecological Site Groups. Rangeland Ecology & Management, 81, 9-33. doi:10.1016/j.rama.2021.11.003
Oksanen, J., F. Guillaume Blanchet, Michael Friendly, Roeland Kindt, Pierre Legendre, Dan McGlinn, Peter R. Minchin, R. B. O'Hara, Gavin L. Simpson, Peter Solymos, M. Henry H. Stevens, Eduard Szoecs and Helene Wagner (2020). vegan: Community Ecology Package. R package version 2.5-7. https://CRAN.R-project.org/package=vegan
Interpreting Indicators of Rangeland Health is a qualitative assessment protocol used to determine ecosystem condition based on benchmark characteristics described in the Reference Sheet. A suite of 17 (or more) indicators are typically considered in an assessment. The ecological site(s) representative of an assessment location must be known prior to applying the protocol and must be verified based on soils and climate. Current plant community cannot be used to identify the ecological site.
# TODO create table with author, author contact info, date, approver, approved date, and "Composition (Indicators 10 and 12) based on" info for the IIRH reference sheet # knitr::kable(x = dom_plant_spp_tab)
TODO fill in the 17 IIRH indicators for the reference state if available
TODO link to PDF of the reference sheet
TODO look into options for printing from the RMarkdown HTML format
# 1. plant functional group join plot with ordination # 2. lattice of abiotic gradients that distinguish the states # aero # rhem if available # soil stability # climate? # 3. NPP # 4. biodiversity # 5 remote sensing variance # 6. Functional type cover grouped by functional type and colored by state # 7. Disturbance and land use # MTBS # Grazing? # PJ treatments # SWreGAP protection classes plot_data_descriptive$PlantCommunity_fuzzy_reordered <- factor(plot_data_descriptive$PlantCommunity_fuzzy, levels = c("1", "3", "4", "2", "5"), labels = c("Grassland", "Shrubland", "Mesic shrubland", "Perennial grass loss", "Invaded"), ordered = T) pal_veg2_reordered <- c(pal_veg2[1], pal_veg2[3], pal_veg2[4], pal_veg2[2], pal_veg2[5]) # 1. plant functional group join plot with ordination species_scores <- as.data.frame(scores(ord, choices = 1:3, display = "species")) # get axis scores for each response type plot_scores <- as.data.frame(scores(ord, choices = 1:3, display = "sites")) # get the axis scores for each site plot_scores$PlotCode <- rownames(plot_scores) plot_scores <- left_join(plot_scores, select(plot_data_descriptive, PlotCode, PlantCommunity_fuzzy, PlantCommunity_fuzzy_reordered)) # this bit gets the arrow length and direction info ord.fit <- envfit(ord ~ AH_PerenForbGrassCover + AH_AnnForbGrassCover + AH_ShrubCover + AH_TreeCover + TotalFoliarCover, data = plot_data_descriptive, na.rm=T, choices=1:3) vect_functypes <- as.data.frame(ord.fit$vectors$arrows*sqrt(ord.fit$vectors$r)) vect_functypes$var <- rownames(vect_functypes) vect_functypes$r <- ord.fit$vectors$r vect_functypes$varnames <- c("Perennial herbaceous", "Annual herbaceous", "Shrub", "Tree", "Total foliar") # # since I have 3 ordination axes, I'm filtering just to axis 1 and 2 for this plot vect_functypes_12 <- filter(vect_functypes, abs(NMDS3)<abs(NMDS1)|abs(NMDS3)<abs(NMDS2)) vect_functypes_32 <- filter(vect_functypes, abs(NMDS1)<abs(NMDS3)|abs(NMDS1)<abs(NMDS2)) vect_functypes_13 <- filter(vect_functypes, abs(NMDS2)<abs(NMDS1)|abs(NMDS2)<abs(NMDS3)) # make pretty plot ggplot! ord_pretty_plot <- function(points = plot_scores, vectors = vect_functypes, x_axis = "NMDS1", y_axis = "NMDS2", palette = pal_veg2_reordered, #group_labels = group_labels_short, labels = T){ ord_plot <- ggplot(data = points, aes_string(x=x_axis, y=y_axis)) + geom_point(data = points, aes_string(x=x_axis, y=y_axis, color="PlantCommunity_fuzzy_reordered"), size=.5) + stat_ellipse(data=points, aes_string(x=x_axis, y=y_axis, color="PlantCommunity_fuzzy_reordered"), level = 0.5, lwd = 1) + scale_color_manual(values =palette, name="Plant community"#, # set custom colors for ellipses #labels=group_labels ) + geom_segment(data = vectors, # make the arrows with environmental vars aes_string(x="0", xend=x_axis, y="0", yend=y_axis), arrow = arrow(length = unit(0.25, "cm")), lwd=1, colour="black", inherit.aes = F) + coord_fixed() + lims(x=c(-1.25,1.25), y=c(-1.25,1.25)) + theme_bw() + theme(axis.text = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank()) if(labels){ ord_plot <- ord_plot + ggrepel::geom_label_repel(data = vectors, # put labels on the arrows aes_string(x=x_axis, y=y_axis, label="varnames"), size=3, colour="black") } return(ord_plot) } ord_plot12 <- ord_pretty_plot(x_axis = "NMDS1", y_axis = "NMDS2", vectors = vect_functypes_12) ord_plot32 <- ord_pretty_plot(x_axis = "NMDS3", y_axis = "NMDS2", vectors = vect_functypes_32) ord_plot13 <- ord_pretty_plot(x_axis = "NMDS1", y_axis = "NMDS3", vectors = vect_functypes_13) ord_plot_legend <- cowplot::get_legend(ord_plot12) ord_plot_all <- cowplot::plot_grid(ord_plot12 + theme(legend.position = "none"), ord_plot32 + theme(legend.position = "none"), ord_plot13 + theme(legend.position = "none"), ord_plot_legend, nrow = 2, labels = c("a", "b", "c")) highrestiff(plot_obj = ord_plot_all, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Ordination/SW_LoamyUplands_NMDS_ellipses_functypes.tif", width_in = 6, height_in = 6, resolution_dpi = 300) ord_plot12_nolabels <- ord_pretty_plot(x_axis = "NMDS1", y_axis = "NMDS2", vectors = vect_functypes_12, labels = F) ord_plot32_nolabels <- ord_pretty_plot(x_axis = "NMDS3", y_axis = "NMDS2", vectors = vect_functypes_32, labels = F) ord_plot13_nolabels <- ord_pretty_plot(x_axis = "NMDS1", y_axis = "NMDS3", vectors = vect_functypes_13, labels = F) ord_plot_legend_nolabels <- cowplot::get_legend(ord_plot12_nolabels) ord_plot_all_nolabels <- cowplot::plot_grid(ord_plot12_nolabels + theme(legend.position = "none"), ord_plot32_nolabels + theme(legend.position = "none"), ord_plot13_nolabels + theme(legend.position = "none"), ord_plot_legend_nolabels, nrow = 2, labels = c("a", "b", "c")) highrestiff(plot_obj = ord_plot_all_nolabels, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Ordination/SW_LoamyUplands_NMDS_ellipses_functypes_nolabels.tif", width_in = 6, height_in = 6, resolution_dpi = 300) # 2. lattice of abiotic gradients that distinguish the states violin_pretty_plot <- function(dat = plot_data_descriptive, y_var = "horizontal_flux_total_MN", y_lab = "Total horizontal dust flux", fill_var = NULL, fill_name = "Plant community", scaling = "area"){ if(!is.null(fill_var)){ violin_plot <- ggplot(data = dat, aes_string(x="PlantCommunity_fuzzy_reordered", y=y_var, fill = fill_var)) + scale_fill_manual(values =pal_veg2_reordered, name= fill_name, # set custom colors for ellipses #labels=group_labels_short ) }else{ violin_plot <- ggplot(data = dat, aes_string(x="PlantCommunity_fuzzy_reordered", y=y_var)) } violin_plot <- violin_plot + geom_violin(scale = scaling, trim = F, na.rm = T) + geom_boxplot(width=0.1) + #scale_x_discrete(labels=group_labels_short) + xlab(NULL) + ylab(y_lab) + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10, color = "black"), axis.text.y = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), legend.position = "none") return(violin_plot) } box_pretty_plot <- function(dat = plot_data_descriptive, y_var = "horizontal_flux_total_MN", y_lab = "Total horizontal dust flux", fill_var = "PlantCommunity_fuzzy_reordered", palette = pal_veg2_reordered){ box_plot <- ggplot(data = dat, aes_string(x="PlantCommunity_fuzzy_reordered", y=y_var, fill = fill_var)) + geom_boxplot() + #scale_x_discrete(labels=group_labels_short) + scale_fill_manual(values =palette, name="Plant community", # set custom colors for ellipses #labels=group_labels_short ) + xlab(NULL) + ylab(y_lab) + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10, color = "black"), axis.text.y = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), legend.position = "none") return(box_plot) } # aero aero_plot <- box_pretty_plot(dat = filter(plot_data_descriptive, !is.na(horizontal_flux_total_MN)), y_var = "horizontal_flux_total_MN", y_lab = "Horizontal dust flux (g/m*day)"#, #scaling = "width" ) + scale_y_log10(breaks = scales::trans_breaks("log10", function(x) 10^x), labels = scales::trans_format("log10", scales::label_math(10^.x))) #+ #annotation_logticks(sides = "l") aero_plot # soil stability soilstab_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "SoilStab_all", y_lab = "Soil stability") soilstab_plot # climate? AI_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "AI", y_lab = "Aridity index") AI_plot ppt_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "Ann_ppt_mm", y_lab = "Mean ann. ppt. (mm)") ppt_plot tmean_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "Ann_tmean", y_lab = "Mean ann. temp. (C)") tmean_plot # rhem if available # put them all together abiotic_plots <- cowplot::plot_grid(aero_plot + xlab(NULL) + scale_x_discrete(labels=NULL), soilstab_plot + xlab(NULL) + scale_x_discrete(labels=NULL), ppt_plot, tmean_plot, nrow = 2, rel_heights = c(0.6, 1), align = "v", labels = "auto") abiotic_plots highrestiff(plot_obj = abiotic_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_abiotic_boxplot_2.tif", width_in = 6, height_in = 6, resolution_dpi = 300) # put them in a different layout because we love drafts climate_plots <- cowplot::plot_grid(ppt_plot, tmean_plot, AI_plot, nrow = 1, labels = "auto") climate_plots highrestiff(plot_obj = climate_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_climate_boxplot.tif", width_in = 9, height_in = 3.5, resolution_dpi = 300) soils_plots <- cowplot::plot_grid(aero_plot, soilstab_plot, nrow = 1, labels = "auto") soils_plots highrestiff(plot_obj = soils_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_soils_boxplot.tif", width_in = 6, height_in = 3.5, resolution_dpi = 300) # 3. NPP # NPP data is from the RAP 3.0 partitioned NPP data set (Robinson et al., 2019). I downloaded the predicted NPP values within a 35-m radius of the AIM, LMF, NRI, and NCPN plot centers using a Google Earth Engine script. If we continue using NPP for other ESGs, we will probably need to continue downloading point values for RAP products through GEE - the raster file sizes are enormous! # Robinson, N. P., Allred, B. W., Naugle, D. E., & Jones, M. O. (2019). Patterns of rangeland productivity and land ownership: Implications for conservation and management. Ecologial Applications, 23(3), e01862. NPP_files <- list.files("C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/RAP_SWLoamyUplands/NPP", full.names = T) npp <- read.csv(NPP_files[1]) npp$Year <- as.numeric(substr(NPP_files[1], 124, 127)) for(file in NPP_files[-1]){ npp_temp <- read.csv(file) npp_temp$Year <- as.numeric(substr(file, 124, 127)) npp <- bind_rows(npp, npp_temp) rm(npp_temp) } npp <- select(npp, PlotCod, Year, afgNPP, pfgNPP, shrNPP, treNPP) %>% setNames(c("PlotCode", "Year", "afgNPP", "pfgNPP", "shrNPP", "treNPP")) plot_data_descriptive <- left_join(plot_data_descriptive, npp) afg_npp_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "afgNPP", y_lab = "Ann. herb. NPP (g C/m2)") afg_npp_plot pfg_npp_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "pfgNPP", y_lab = "Per. herb. NPP (g C/m2)") pfg_npp_plot shr_npp_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "shrNPP", y_lab = "Shrub NPP (g C/m2)") shr_npp_plot tre_npp_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "treNPP", y_lab = "Tree NPP (g C/m2)") tre_npp_plot pft_npp_plots <- cowplot::plot_grid(afg_npp_plot + xlab(NULL) + scale_x_discrete(labels=NULL), pfg_npp_plot + xlab(NULL) + scale_x_discrete(labels=NULL), shr_npp_plot, tre_npp_plot, nrow = 2, rel_heights = c(0.6, 1), align = "v", labels = "auto") pft_npp_plots highrestiff(plot_obj = pft_npp_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_pftnpp_boxplot.tif", width_in = 6, height_in = 6.5, resolution_dpi = 300) # combining npp to herbaceous, woody, and total plot_data_descriptive$herbNPP <- plot_data_descriptive$afgNPP + plot_data_descriptive$pfgNPP plot_data_descriptive$woodyNPP <- plot_data_descriptive$shrNPP + plot_data_descriptive$treNPP plot_data_descriptive$totalNPP <- plot_data_descriptive$herbNPP + plot_data_descriptive$woodyNPP herb_npp_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "herbNPP", y_lab = "Herb. NPP (g C/m2)") herb_npp_plot woody_npp_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "woodyNPP", y_lab = "Woody NPP (g C/m2)") woody_npp_plot total_npp_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "totalNPP", y_lab = "Total NPP (g C/m2)") total_npp_plot gh_npp_plots <- cowplot::plot_grid(herb_npp_plot + xlab(NULL) + scale_x_discrete(labels=NULL), woody_npp_plot + xlab(NULL) + scale_x_discrete(labels=NULL), total_npp_plot, nrow = 3, rel_heights = c(0.6, 0.6, 1), align = "v", labels = "auto") gh_npp_plots highrestiff(plot_obj = gh_npp_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_ghnpp_boxplot.tif", width_in = 3, height_in = 9, resolution_dpi = 300) highrestiff(plot_obj = total_npp_plot, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_totalnpp_boxplot_2.tif", width_in = 3, height_in = 3.5, resolution_dpi = 300) rm(npp) # 4. biodiversity diversity_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "shannon_diversity", y_lab = "Shannon diversity index") diversity_plot richness_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "sp_richness", y_lab = "Number of species") richness_plot biodiversity_plots <- cowplot::plot_grid(richness_plot, diversity_plot, total_npp_plot, #nrow = 2, rel_heights = c(0.6, 1), nrow = 1, labels = "auto") biodiversity_plots highrestiff(plot_obj = biodiversity_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_biodiversity_boxplot_2.tif", #width_in = 3, width_in = 9, #height_in = 6.5, height_in = 3.5, resolution_dpi = 300) # 5. remote sensing variance # Ideally, we should probably do this from the LandCART total foliar cover predictions, but obtaining those rasters for the whole UCRB is very slow. For now, we'll use the sum of the RAP 3.0 plant functional type cover values for each plot to represent total foliar cover instead. The RAP values were downloaded from within a 35-m radius of each point in this ESG using a Google Earth Engine script. Consider switching to LandCART, since it actually predicts a total foliar cover value, when raster generation and downloading for the UCRB 1995-2019 (April 1 - Nov 1) is complete. library(broom) # for easy handling of many linear model parts library(purrr) # for functional programming, eep! library(tidyr) VegCover_files <- list.files("C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/RAP_SWLoamyUplands/VegCover", full.names = T) VegCover <- read.csv(VegCover_files[1]) VegCover$Year <- as.numeric(substr(VegCover_files[1], 134, 137)) for(file in VegCover_files[-1]){ VegCover_temp <- read.csv(file) VegCover_temp$Year <- as.numeric(substr(file, 134, 137)) VegCover <- bind_rows(VegCover, VegCover_temp) rm(VegCover_temp) } VegCover <- select(VegCover, PlotCod, Bst_grp, Bst_gr_, Year, AFG, BGR, LTR, PFG, SHR, TRE) %>% setNames(c("PlotCode", "PlantCommunity_fuzzy", "PlantCommunity_name", "Year", "AFG", "BGR", "LTR", "PFG", "SHR", "TRE")) %>% rowwise() %>% mutate(TotalFoliar = sum(AFG, PFG, SHR, TRE, na.rm = T)) sampleyears <- select(plot_data_descriptive, PlotCode, Year) %>% setNames(c("PlotCode", "Year_sampled")) VegCover <- left_join(VegCover, sampleyears) VegCover_10yrs <- VegCover %>% # select the 10 years up to and including the field sampling year mutate(Years_to_sampling = Year_sampled - Year) %>% filter(Years_to_sampling >=0 & Years_to_sampling <10) VegCover_variance <- VegCover_10yrs %>% select(PlotCode, PlantCommunity_fuzzy, Year, Years_to_sampling, TotalFoliar) %>% as_tibble() %>% nest(data = c(Year, Years_to_sampling, TotalFoliar)) %>% # create an internal data frame for each plot mutate(model = map(data, ~lm(TotalFoliar ~ Years_to_sampling, data = .))) %>% # create a linear model from each plot's data frame mutate(data = map(model, augment), # get residuals for each model and add to internal plot data frames model_glance = map(model, glance)) %>% # get model summary statistics mutate(resid_variance = map_dbl(data, ~var(.$.resid)), # get the variance of the detrended data (aka residuals) resid_AC1 = map_dbl(data, ~acf(x = .$.resid, type = "correlation", lag.max = 1, plot = F)$acf[2]) # get the lag-1 autocorrelation of the detrended data ) %>% select(-data, -model) %>% unnest(model_glance) # do we care that most of the linear models are not significant and only about half have an R-squared > 0.1? # make figures plot_data_descriptive <- left_join(plot_data_descriptive, select(VegCover_variance, PlotCode, resid_variance, resid_AC1)) variance_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "resid_variance", y_lab = "Variance") variance_plot AC1_plot <- box_pretty_plot(dat = plot_data_descriptive, y_var = "resid_AC1", y_lab = "Lag-1 autocorrelation") AC1_plot RSvariability_plots <- cowplot::plot_grid(variance_plot + xlab(NULL) + scale_x_discrete(labels=NULL), AC1_plot, nrow = 2, rel_heights = c(0.6, 1), labels = "auto") RSvariability_plots highrestiff(plot_obj = RSvariability_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_RSvariability_boxplot.tif", width_in = 3, height_in = 6, resolution_dpi = 300) variance_violin <- violin_pretty_plot(dat = plot_data_descriptive, y_var = "resid_variance", y_lab = "Variance", fill_var = "PlantCommunity_fuzzy_reordered") variance_violin highrestiff(plot_obj = variance_violin, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Violin/SW_LoamyUplands_variance_violin.tif", width_in = 3, height_in = 3.5, resolution_dpi = 300) rm(VegCover, VegCover_10yrs, VegCover_variance) # 6. Functional type cover grouped by functional type and colored by state functypes_grps <- c("TotalFoliarCover", #"AH_WoodyCover", #AH_ForbGrassCover, "AH_ShrubCover", "AH_TreeCover", "AH_PerenForbGrassCover", "AH_AnnForbGrassCover" ) functypes_cover_plot <- ggplot(data = select(plot_data_descriptive, any_of(c("PlantCommunity_fuzzy_reordered", functypes_grps))) %>% tidyr::pivot_longer(cols = all_of(functypes_grps), names_to = "CoverType", values_to = "PctCover"), aes(fill=PlantCommunity_fuzzy_reordered, y=PctCover, x=CoverType)) + geom_boxplot() + scale_fill_manual(values = pal_veg2_reordered, name = "Plant community"#, #labels = group_labels_short ) + scale_x_discrete(labels=c("Annual herbaceous", "Perennial herbaceous", "Shrub", "Tree", "Total foliar")) + xlab("Plant functional type") + ylab("Cover (%)") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10, color = "black"), axis.text.y = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank()) functypes_cover_plot highrestiff(plot_obj = functypes_cover_plot, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/Boxplot/SW_LoamyUplands_pftcover_boxplot_2.tif", width_in = 6, height_in = 3.5, resolution_dpi = 300) # 7. Disturbance and land use # MTBS # Grazing? # PJ treatments # SWreGAP protection classes # MTBS library(sf) library(lubridate) mtbs <- st_read(dsn = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/GIS/Disturbance_LandUse/mtbs_permis_DD_2020.shp") fuzzy_cluster_memb_sf <- st_as_sf(fuzzy_cluster_memb_sf, crs = st_crs(plot_locations)) %>% st_transform(., crs=st_crs(mtbs)) mtbs_plots <- st_join(x=fuzzy_cluster_memb_sf, y=mtbs) %>% select(PlotCode, Best_group, Best_group_name, DateSampled, Ig_Date, geometry) %>% mutate(Burned_presampling = ifelse(test = Ig_Date < DateSampled, yes = TRUE, no = FALSE)) %>% group_by(PlotCode) %>% summarise(Best_group = first(Best_group), Best_group_name = first(Best_group_name), DateSampled = first(DateSampled), Times_burned_presampling = length(which(Burned_presampling)), Date_burned_1 = sort(Ig_Date)[1], Date_burned_2 = sort(Ig_Date)[2]) %>% st_drop_geometry() write.csv(mtbs_plots, "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Analyses/SW_LoamyUplands_burnedplots.csv", row.names = F) rm(mtbs) mtbs_plots$PlantCommunity_fuzzy_reordered <- factor(mtbs_plots$Best_group, levels = c("1", "3", "4", "2", "5"), labels = c("Grassland", "Shrubland", "Mesic shrubland", "Perennial grass loss", "Invaded"), ordered = T) mtbs_summary <- mtbs_plots %>% group_by(PlantCommunity_fuzzy_reordered) %>% summarise(pct_Burned = (length(which(Times_burned_presampling > 0))/n())*100) burn_plot <- ggplot(data = mtbs_summary, aes(x=PlantCommunity_fuzzy_reordered, y=pct_Burned, fill = PlantCommunity_fuzzy_reordered)) + geom_col() + scale_fill_manual(values = pal_veg2_reordered) + xlab(NULL) + ylab("% of plots burned") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10, color = "black"), axis.text.y = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), legend.position = "none") burn_plot # GAP protection classes GAP_raster <- raster::raster("C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/GIS/Disturbance_LandUse/PADUS_GAP_Status_Code1.tif") plot_GAP <- sf::st_as_sf(raster::extract(x = GAP_raster, y = fuzzy_cluster_memb_sf, sp = T)) %>% sf::st_drop_geometry() plot_data_descriptive <- left_join(plot_data_descriptive, select(plot_GAP, PlotCode, PADUS_GAP_Status_Code1)) GAP_proportions <- plot_data_descriptive %>% group_by(PlantCommunity_fuzzy_reordered) %>% # NOTE the PADUS codes are stored in the "GAP_Sts" attribute, but extraction pulls the raster value, # which is different! Checked attribute table in Arc to sort it out! summarise(pct_BiodivProt_NatDistPermitted = (length(which(PADUS_GAP_Status_Code1 == 4))/n())*100, pct_BiodivProt_NatDistSupressed = (length(which(PADUS_GAP_Status_Code1 == 3))/n())*100, pct_MultiUse_ExtractPermitted = (length(which(PADUS_GAP_Status_Code1 == 1))/n())*100, pct_NoBiodivProctect = (length(which(PADUS_GAP_Status_Code1 == 2))/n())*100, pct_PrivateLand = (length(which(is.na(PADUS_GAP_Status_Code1)))/n())*100) %>% pivot_longer(data=., cols = c(pct_BiodivProt_NatDistPermitted, pct_BiodivProt_NatDistSupressed, pct_MultiUse_ExtractPermitted, pct_NoBiodivProctect, pct_PrivateLand), names_to = "ProtectionStatus", values_to = "Percent") protection_plot <- ggplot(data = GAP_proportions, aes(x=ProtectionStatus, y=Percent, fill=PlantCommunity_fuzzy_reordered)) + geom_col() + scale_fill_manual(values = pal_veg2_reordered, name = "Plant community" ) + scale_x_discrete(labels=c("1 - Biodiversity protected, natural disturbance permitted", "2 - Biodiversity protected, natural disturbance suppressed", "3 - Multi-use, extraction permitted", "4 - No biodiversity protection", "Private land")) + xlab("PADUS protection status") + ylab("% plots in plant community") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10, color = "black"), axis.text.y = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), plot.margin = unit(c(2,2,2,5), "mm") ) protection_plot highrestiff(plot_obj = protection_plot, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/SW_LoamyUplands_PADUSprotection_flipped_barplot.tif", width_in = 6, height_in = 3.5, resolution_dpi = 300) protection_plot2 <- ggplot(data = GAP_proportions, aes(fill=ProtectionStatus, y=Percent, x=PlantCommunity_fuzzy)) + geom_col() + scale_fill_manual(values = c("seagreen", "yellowgreen", "gray58", "dodgerblue", "palegoldenrod"), name = "PADUS protection status", labels = c("1 - Biodiversity protected, natural disturbance permitted", "2 - Biodiversity protected, natural disturbance suppressed", "3 - Multi-use, extraction permitted", "4 - No biodiversity protection", "Private land")) + scale_x_discrete(labels= group_labels_short) + xlab(NULL) + ylab("% plots in plant community") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10, color = "black"), axis.text.y = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), legend.key.size = unit(1.5, 'lines')) protection_plot2 highrestiff(plot_obj = protection_plot2, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/SW_LoamyUplands_PADUSprotection_dodge_barplot.tif", width_in = 6, height_in = 3.5, resolution_dpi = 300) GAP_proportions_byclass <- plot_data_descriptive %>% mutate(PADUS_GAP_Status_lumped = ifelse(test = PADUS_GAP_Status_Code1 %in% c(3,4), yes = "Protected public", no = NA)) %>% mutate(PADUS_GAP_Status_lumped = ifelse(test = PADUS_GAP_Status_Code1 %in% c(1,2), yes = "Unprotected public", no = PADUS_GAP_Status_lumped)) %>% mutate(PADUS_GAP_Status_lumped = ifelse(test = is.na(PADUS_GAP_Status_Code1), yes = "Private", no = PADUS_GAP_Status_lumped)) %>% group_by(PADUS_GAP_Status_lumped) %>% summarise(pct_Grassland = (length(which(PlantCommunity_fuzzy_reordered == "Grassland"))/n())*100, pct_Shrubland = (length(which(PlantCommunity_fuzzy_reordered == "Shrubland"))/n())*100, pct_MesicShrubland = (length(which(PlantCommunity_fuzzy_reordered == "Mesic shrubland"))/n())*100, pct_GrassLoss = (length(which(PlantCommunity_fuzzy_reordered == "Perennial grass loss"))/n())*100, pct_Invaded = (length(which(PlantCommunity_fuzzy_reordered == "Invaded"))/n())*100) %>% pivot_longer(data=., cols = c(pct_Grassland, pct_Shrubland, pct_MesicShrubland, pct_GrassLoss, pct_Invaded), names_to = "PlantCommunity", values_to = "Percent") %>% mutate(PlantCommunity_fuzzy_reordered = factor(PlantCommunity, levels = c("pct_Grassland", "pct_Shrubland", "pct_MesicShrubland", "pct_GrassLoss", "pct_Invaded"), labels = c("Grassland", "Shrubland", "Mesic shrubland", "Perennial grass loss", "Invaded"), ordered = T)) protection_lumped_plot <- ggplot(data = GAP_proportions_byclass, aes(x=PADUS_GAP_Status_lumped, y=Percent, fill=PlantCommunity_fuzzy_reordered)) + geom_col() + scale_fill_manual(values = pal_veg2_reordered, name = "Plant community" ) + #scale_x_discrete(labels=c("Protected - public", "Unprotected - public" "Private land")) + xlab(NULL) + ylab("% of plots in protection group") + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10, color = "black"), axis.text.y = element_text(size = 10, color = "black"), axis.title = element_text(size = 12), panel.grid.major = element_blank(), panel.grid.minor = element_blank()#, #plot.margin = unit(c(2,2,2,5), "mm") ) protection_lumped_plot disturbance_plots <- cowplot::plot_grid(burn_plot, protection_lumped_plot, nrow = 1, rel_widths = c(0.7, 1), labels = "auto") disturbance_plots highrestiff(plot_obj = disturbance_plots, file = "C:/Users/aknight/Documents/Telework_Backups/V_drive/ANNA_KNIGHT/ESG/STM/Figures/SW_LoamyUplands_disturbanceproctection_barplot.tif", width_in = 7, height_in = 3.5, resolution_dpi = 300) rm(GAP_raster, plot_GAP)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.