From 9b55ead551ae45a66d53a05f4da62a29a449b31d Mon Sep 17 00:00:00 2001 From: Ritterm Date: Thu, 22 May 2025 15:54:56 -0700 Subject: [PATCH 01/40] Create joinAquPointCount.R --- R/joinAquPointCount.R | 340 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 R/joinAquPointCount.R diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R new file mode 100644 index 0000000..d5902c9 --- /dev/null +++ b/R/joinAquPointCount.R @@ -0,0 +1,340 @@ +#' @title Join NEON aquatic plant point count data into a single table with merged taxonomic identifications + +#' @author Madaline Ritter \email{ritterm1@battelleecology.org} \cr + +#' @description Join the 'apc_pointTransect', 'apc_perTaxon', 'apc_taxonomyProcessed' and 'apc_morphospecies' tables to generate a single table that contains point count data with taxonomic identifications for each sampleID. Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. +#' +#' @details Input data may be provided either as a list or as individual tables. However, if both list and table inputs are provided at the same time the function will error out. For table joining to be successful, inputs must contain data from the same site x month combination(s) for all tables. +#' +#' In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apc_taxonomyProcessed', 'apc_perTaxon', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. +#' +#' If a single sample in 'apc_taxonomyProcessed' contains multiple macroalgae species, each species will be represented as a separate row in 'apc_pointTransect' for every point associated with that sampleID. +#' +#' @param inputDataList A list object comprised of Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Count tables (DP1.20072.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. [list] +#' +#' @param inputPoint The 'apc_pointTransect' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputPerTax The 'apc_perTaxon' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputTaxProc The 'apc_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputMorph The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @return A table containing point transect data with all associated taxonomic information for each point where targetTaxaPresent == 'Y'. +#' +#' @examples +#' \dontrun{ +#' # Obtain NEON Aquatic Plant Point Count data +#' apc <- neonUtilities::loadByProduct( +#' dpID = "DP1.20072.001", +#' site = "all", +#' startdate = "2018-07", +#' enddate = "2018-08", +#' tabl = "all", +#' check.size = FALSE +#' ) +#' +#' # Join downloaded root data +#' df <- neonPlants::joinAquPointCount( +#' inputDataList = apc, +#' inputPoint = NA, +#' inputPerTax = NA, +#' inputTaxProc = NA, +#' inputMorph = NA +#' ) +#' +#' } +#' +#' @export joinPointCounts + + + +joinAquPointCount <- function(inputDataList, + inputPoint = NA, + inputPerTax = NA, + inputTaxProc = NA, + inputMorph = NA) { + + ### Test that user has supplied arguments as required by function #### + + ### Verify user-supplied inputDataList object contains correct data if not NA + if (!missing(inputDataList)) { + + # Check that input is a list + if (!inherits(inputDataList, "list")) { + stop(glue::glue("Argument 'inputDataList' must be a list object from neonUtilities::loadByProduct(); + supplied input object is {class(inputDataList)}")) + } + + # Check that required tables within list match expected names + listExpNames <- c("apc_pointTransect", "apc_perTaxon") + + # Determine dataType or stop with appropriate message + if (length(setdiff(listExpNames, names(inputDataList))) > 0) { + stop(glue::glue("Required tables missing from 'inputDataList':", + '{paste(setdiff(listExpNames, names(inputDataList)), collapse = ", ")}', + .sep = " ")) + } + } else { + + inputDataList <- NULL + + } # end missing conditional + + + ### Verify table inputs are NA if inputDataList is supplied + if (inherits(inputDataList, "list") & (!is.logical(inputPoint) | !is.logical(inputPerTax) | !is.logical(inputTaxProc) | !is.logical(inputMorph))) { + stop("When 'inputDataList' is supplied all table input arguments must be NA") + } + + + ### Verify all table inputs are data frames if inputDataList is NA + if (is.null(inputDataList) & + (!inherits(inputPoint, "data.frame") | !inherits(inputPerTax, "data.frame") | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame"))) { + + stop("Data frames must be supplied for all table inputs if 'inputDataList' is missing") + + } + + + ### Conditionally define input tables #### + if (inherits(inputDataList, "list")) { + + apPoint <- inputDataList$apc_pointTransect + apPerTax <- inputDataList$apc_perTaxon + apTaxProc <- inputDataList$apc_taxonomyProcessed + apMorph <- inputDataList$apc_morphospecies + apVouch <- inputDataList$apc_voucher + apVouchProc <- inputDataList$apc_voucherTaxonomyProcessed + + } else { + + apPoint <- inputPoint + apPerTax <- inputPerTax + apTaxProc <- inputTaxProc + apMorph <- inputMorph + apVouch <- inputVouch + apVouchProc <- inputVouchProc + + } + + + + ### Verify input tables contain required columns and data #### + + ### Verify 'apPoint' table contains required data + # Check for required columns + pointExpCols <- c("namedLocation", "pointNumber", "collectDate", "targetTaxaPresent") + + if (length(setdiff(pointExpCols, colnames(apPoint))) > 0) { + stop(glue::glue("Required columns missing from 'inputPoint':", '{paste(setdiff(pointExpCols, colnames(apPoint)), collapse = ", ")}', + .sep = " ")) + } + + # Check for data + if (nrow(apPoint) == 0) { + stop(glue::glue("Table 'inputPoint' has no data.")) + } + + + + ### Verify 'apPerTax' table contains required data + # Check for required columns + perTaxExpCols <- c("namedLocation", "pointNumber", "collectDate", "sampleID", "aquaticPlantType", "growthForm", "taxonID", "scientificName", "morphospeciesID", "identificationQualifier") + + if (length(setdiff(perTaxExpCols, colnames(apPerTax))) > 0) { + stop(glue::glue("Required columns missing from 'inputPerTax':", '{paste(setdiff(perTaxExpCols, colnames(apPerTax)), collapse = ", ")}', + .sep = " ")) + } + + # Check for data + if (nrow(apPerTax) == 0) { + stop(glue::glue("Table 'inputPerTax' has no data.")) + } + + + + ### Verify 'apTaxProc' table contains required data if data exists + taxProcExpCols <- c("sampleID", "acceptedTaxonID") + + # Check for data + if(exists("apTaxProc")){ + if (is.null(apTaxProc) | nrow(apTaxProc) == 0) { + message(glue::glue("Warning: Table 'inputTaxProc' has no data. Join will not include processed taxonomy data.")) + } else { + + # Check for required columns if data exists + if (length(setdiff(taxProcExpCols, colnames(apTaxProc))) > 0) { + stop(glue::glue("Required columns missing from 'inputTaxProc':", '{paste(setdiff(taxProcExpCols, colnames(apTaxProc)), collapse = ", ")}', + .sep = " ")) + } + } + } + + + ### Verify 'apMorph' table contains required data if data exists + morphExpCols <- c("sampleID") + + # Check for data + if(exists("apMorph")){ + if(is.null(apMorph) | !is.null(apMorph)){ + message("Warning: Table 'inputMorph' has no data. Join will not include identifications from the morphospecies table.") + } else { + + # Check for required columns if data exists + if (length(setdiff(morphExpCols, colnames(apMorph))) > 0) { + stop(glue::glue("Required columns missing from 'inputMorph':", '{paste(setdiff(morphExpCols, colnames(apMorph)), collapse = ", ")}', + .sep = " ")) + } + } + } + + + #### for testing #### + + # inputDataList <- readRDS("C:/Users/ritterm1/OneDrive - National Ecological Observatory Network/Ritter/neonPlants/apc_allTabs_2023data.rds") + # apPoint <- inputDataList$apc_pointTransect + # apPerTax <- inputDataList$apc_perTaxon + # apTaxProc <- inputDataList$apc_taxonomyProcessed + # apMorph <- inputDataList$apc_morphospecies + # apVouch <- inputDataList$apc_voucher + # apVouchProc <- inputDataList$apc_voucherTaxonomyProcessed + + + ### Join apPerTax and apTaxProc tables + + if (!is.null(apTaxProc) && nrow(apTaxProc) > 0) { + # message("Join taxonomyProcessed taxonomic identifications.") + # Select needed columns from apTaxProc + apTaxProc <- apTaxProc %>% + dplyr::select(-"uid", -"domainID", -"siteID", -"namedLocation", -"collectDate", -"morphospeciesID") %>% + dplyr::rename(taxonID=acceptedTaxonID) + + # Update expert taxonomist identifications + apJoin1 <- apPerTax %>% + dplyr::left_join(apTaxProc, by = "sampleID", suffix = c("_perTax", "_taxProc"), relationship = "many-to-many") %>% + dplyr::mutate( + sampleCondition = dplyr::case_when( + !is.na(.data$sampleCondition_perTax)&!is.na(.data$sampleCondition_taxProc)~paste0("perTaxon ",.data$sampleCondition_perTax," | taxonProcessed ",.data$sampleCondition_taxProc), + !is.na(.data$sampleCondition_perTax)&is.na(.data$sampleCondition_taxProc)~paste0("perTaxon ",.data$sampleCondition_perTax), + is.na(.data$sampleCondition_perTax)&!is.na(.data$sampleCondition_taxProc)~paste0("taxonProcessed ",.data$sampleCondition_taxProc), + TRUE ~ NA), + taxonIDSourceTable = dplyr::case_when( + !is.na(.data$taxonID_taxProc)~"apc_taxonomyProcessed", + is.na(.data$taxonID_taxProc)&!is.na(.data$taxonID_perTax)~"apc_perTaxon", + TRUE~NA), + tempTaxonID = dplyr::if_else(!is.na(taxonID_taxProc), taxonID_taxProc, taxonID_perTax), + scientificName = dplyr::if_else(!is.na(taxonID_taxProc), scientificName_taxProc, scientificName_perTax), + identificationHistoryID = dplyr::case_when( + !is.na(.data$identificationHistoryID_perTax)&!is.na(.data$identificationHistoryID_taxProc)~paste0(.data$identificationHistoryID_perTax," | ",.data$identificationHistoryID_taxProc), + is.na(.data$identificationHistoryID_taxProc)&!is.na(.data$identificationHistoryID_perTax)~.data$identificationHistoryID_perTax, + !is.na(.data$identificationHistoryID_taxProc)&is.na(.data$identificationHistoryID_perTax)~.data$identificationHistoryID_taxProc, + TRUE~NA), + perTaxonDataQF = dataQF_perTax, + taxProcessedDataQF = dataQF_taxProc, + perTaxonPublicationDate = publicationDate_perTax, + taxProcessedPublicationDate = publicationDate_taxProc, + taxProcessedRelease = release_taxProc, + perTaxonRelease = release_perTax, + phylum = dplyr::if_else(!is.na(taxonID_taxProc), phylum_taxProc, phylum_perTax), + division = dplyr::if_else(!is.na(taxonID_taxProc), division_taxProc, division_perTax), + class = dplyr::if_else(!is.na(taxonID_taxProc), class_taxProc, class_perTax), + order = dplyr::if_else(!is.na(taxonID_taxProc), order_taxProc, order_perTax), + family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_perTax), + genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_perTax), + section = dplyr::if_else(!is.na(taxonID_taxProc), section_taxProc, section_perTax), + specificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), specificEpithet_taxProc, specificEpithet_perTax), + infraspecificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), infraspecificEpithet_taxProc, infraspecificEpithet_perTax), + variety = dplyr::if_else(!is.na(taxonID_taxProc), variety_taxProc, variety_perTax), + form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_perTax), + scientificNameAuthorship = dplyr::if_else(!is.na(taxonID_taxProc), scientificNameAuthorship_taxProc, scientificNameAuthorship_perTax), + identificationQualifier = dplyr::if_else(!is.na(taxonID_taxProc), identificationQualifier_taxProc, identificationQualifier_perTax), + identificationReferences = dplyr::if_else(!is.na(taxonID_taxProc), identificationReferences_taxProc, identificationReferences_perTax), + taxonRank = dplyr::if_else(!is.na(taxonID_taxProc), taxonRank_taxProc, taxonRank_perTax), + remarks = dplyr::case_when( + !is.na(.data$remarks_perTax)&!is.na(.data$remarks_taxProc) ~ paste0("perTaxon remarks - ", .data$remarks_perTax, " | taxonProcessed remarks - ", .data$remarks_taxProc ), + is.na(.data$remarks_taxProc)&!is.na(.data$remarks_perTax) ~ paste0("perTaxon remarks - ", .data$remarks_perTax), + !is.na(.data$remarks_taxProc)&is.na(.data$remarks_perTax) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), + TRUE ~ NA), + identifiedBy = dplyr::if_else(!is.na(taxonID_taxProc), identifiedBy_taxProc, identifiedBy_perTax), + identifiedDate = dplyr::if_else(!is.na(identifiedDate_taxProc), identifiedDate_taxProc, identifiedDate_perTax) + + ) %>% + select(-matches("_taxProc"), + -matches("_perTax"), + -"targetTaxaPresent", -"uid") + + } else { + message("No data joined from apc_taxonomyProcessed table.") + # rename columns if no taxProc join + apJoin1 <- apPerTax %>% + mutate(tempTaxonID = taxonID, + remarks = dplyr::if_else(is.na(remarks), NA, paste0("perTaxon remarks - ", remarks)), + perTaxonRelease = release, + taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apc_perTaxon"), + perTaxonDataQF = dataQF, + perTaxonPublicationDate = publicationDate) %>% + select(-"taxonID", -"release", -"dataQF", -"publicationDate", -"uid") + } + + ### Join apJoin1 and apMorph tables + + # Select needed columns from apMorph + if(!is.null(apMorph) && nrow(apMorph) > 0){ + # message("Join morphospecies taxonomic identifications.") + apMorph <- apMorph %>% + dplyr::select("taxonID", + "scientificName", + "morphospeciesID", + "identificationQualifier", + "identificationReferences", + "identifiedBy", + # "identifiedDate", + "dataQF" + ) + + # Update morphospecies taxon identifications + apJoin2 <- apJoin1 %>% + dplyr::mutate( + morphospeciesID = dplyr::if_else(!is.na(morphospeciesID), paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), morphospeciesID) + ) %>% + dplyr::left_join(apMorph, by = "morphospeciesID", suffix = c("_perTax", "_morph")) %>% + # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% + dplyr::mutate( + taxonIDSourceTable = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), "apc_morphospecies", taxonIDSourceTable), + acceptedTaxonID = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), taxonID, tempTaxonID), + scientificName = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), scientificName_morph, scientificName_perTax), + identificationQualifier = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationQualifier_morph, identificationQualifier_perTax), + identificationReferences = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationReferences_morph, identificationReferences_perTax), + identifiedBy = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identifiedBy_morph, identifiedBy_perTax), + identifiedDate = NA #not currently in pub table + #dataQF = ifelse(!is.na(taxonID), )#either that may be relevant? + + ) %>% + dplyr::select( + -"taxonID", -"tempTaxonID", + -matches("_morph"), + -matches("_perTax") + ) + } else { + message("No data joined from apc_morphospecies table.") + apJoin2 <- apJoin1 + } + + + ### Join apPoint and apPerTax tables + + # Update morphospecies taxon identifications + apJoin3 <- apPoint %>% + dplyr::left_join(apJoin2, by = c("domainID", "siteID", "namedLocation", "pointNumber", "collectDate", "eventID"), suffix = c("_point", "_perTax")) %>% + dplyr::mutate( + remarks = dplyr::case_when( + !is.na(.data$remarks_perTax)&!is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point, " | ", .data$remarks_perTax), + !is.na(.data$remarks_perTax)&is.na(.data$remarks_point) ~ .data$remarks_perTax, + is.na(.data$remarks_perTax)&!is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point), + TRUE ~ NA) + ) + + return(apJoin3) + +} #function closer From 2f13a4f9c6488a4fc96aaf3884def2f46c20b6fe Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 30 May 2025 15:17:24 -0700 Subject: [PATCH 02/40] create aqu join function --- R/joinAquClipHarvest.R | 366 +++++++++++++++++++++++++++++++++++++++++ R/joinAquPointCount.R | 12 +- 2 files changed, 372 insertions(+), 6 deletions(-) create mode 100644 R/joinAquClipHarvest.R diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R new file mode 100644 index 0000000..a226a5e --- /dev/null +++ b/R/joinAquClipHarvest.R @@ -0,0 +1,366 @@ +#' @title Join NEON aquatic plant clip harvest data into a single table with merged taxonomic identifications + +#' @author Madaline Ritter \email{ritterm1@battelleecology.org} \cr + +#' @description Join the 'apl_clipHarvest', 'apl_biomass', 'apl_taxonomyProcessed' and 'apc_morphospecies' tables to generate a single table that contains clip harvest data with taxonomic identifications for each sampleID. Data inputs are NEON Aquatic Plant Bryophyte Macroalgae Clip Harvest (DP1.20066.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. +#' +#' @details Input data may be provided either as a list or as individual tables. However, if both list and table inputs are provided at the same time the function will error out. For table joining to be successful, inputs must contain data from the same site x month combination(s) for all tables. +#' +#' Only data from bout 2 (midsummer sampling) is returned in the joined output table, as other bouts do not include taxonomy data. If the input does not include any bout 2 data, the function will error out. +#' +#' In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. +#' +#' @param inputDataList A list object comprised of Aquatic Plant Bryophyte Macroalgae Clip Harvest tables (DP1.20066.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. [list] +#' +#' @param inputBio The 'apl_biomass' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputClip The 'apl_clipHarvest' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputTaxProc The 'apl_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputMorph The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @return A table containing clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent == 'Y'. +#' +#' @examples +#' \dontrun{ +#' # Obtain NEON Aquatic Plant Point Count data +#' apl <- neonUtilities::loadByProduct( +#' dpID = "DP1.20066.001", +#' site = "all", +#' startdate = "2018-07", +#' enddate = "2018-08", +#' tabl = "all", +#' check.size = FALSE +#' ) +#' +#' # Join downloaded root data +#' df <- neonPlants::joinAquClipHarvest( +#' inputDataList = apl, +#' inputBio = NA, +#' inputClip = NA, +#' inputTaxProc = NA, +#' inputMorph = NA +#' ) +#' +#' } +#' +#' @export joinClipHarvest + + + +joinAquClipHarvest <- function(inputDataList, + inputBio = NA, + inputClip = NA, + inputTaxProc = NA, + inputMorph = NA) { + + ### Test that user has supplied arguments as required by function #### + + ### Verify user-supplied inputDataList object contains correct data if not NA + if (!missing(inputDataList)) { + + # Check that input is a list + if (!inherits(inputDataList, "list")) { + stop(glue::glue("Argument 'inputDataList' must be a list object from neonUtilities::loadByProduct(); + supplied input object is {class(inputDataList)}")) + } + + # Check that required tables within list match expected names + listExpNames <- c("apl_biomass", "apl_clipHarvest") + + # Determine dataType or stop with appropriate message + if (length(setdiff(listExpNames, names(inputDataList))) > 0) { + stop(glue::glue("Required tables missing from 'inputDataList':", + '{paste(setdiff(listExpNames, names(inputDataList)), collapse = ", ")}', + .sep = " ")) + } + } else { + + inputDataList <- NULL + + } # end missing conditional + + + ### Verify table inputs are NA if inputDataList is supplied + if (inherits(inputDataList, "list") & (!is.logical(inputBio) | !is.logical(inputClip) | !is.logical(inputTaxProc) | !is.logical(inputMorph))) { + stop("When 'inputDataList' is supplied all table input arguments must be NA") + } + + + ### Verify all table inputs are data frames if inputDataList is NA + if (is.null(inputDataList) & + (!inherits(inputBio, "data.frame") | !inherits(inputClip, "data.frame") + # | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame") + )) { + + stop("Data frames must be supplied for table inputs if 'inputDataList' is missing") + + } + + + ### Conditionally define input tables #### + if (inherits(inputDataList, "list")) { + + apBio <- inputDataList$apl_biomass + apClip <- inputDataList$apl_clipHarvest + apTaxProc <- inputDataList$apl_taxonomyProcessed + apMorph <- inputDataList$apc_morphospecies + + } else { + + apBio <- inputBio + apClip <- inputClip + apTaxProc <- inputTaxProc + apMorph <- inputMorph + + } + + + + ### Verify input tables contain required columns and data #### + + ### Verify 'apBio' table contains required data + # Check for required columns + # bioExpCols <- c("sampleID", "taxonID", "scientificName", "morphospeciesID") + bioExpCols <- c("sampleID", "taxonID", "scientificName", "morphospeciesID", "identifiedDate", "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "division", "class", "order", "family", "genus", "section", "specificEpithet", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "remarks", "identifiedBy", "uid") + + + if (length(setdiff(bioExpCols, colnames(apBio))) > 0) { + stop(glue::glue("Required columns missing from 'inputBio':", '{paste(setdiff(bioExpCols, colnames(apBio)), collapse = ", ")}', + .sep = " ")) + } + + # Check for data + if (nrow(apBio) == 0) { + stop(glue::glue("Table 'inputBio' has no data.")) + } + + + + ### Verify 'apClip' table contains required data + # Check for required columns + clipExpCols <- c("namedLocation", "eventID", "boutNumber", "fieldID", "benthicArea", "domainID", "siteID", "startDate", "collectDate", "fieldIDCode", "recordedBy", "remarks") + + if (length(setdiff(clipExpCols, colnames(apClip))) > 0) { + stop(glue::glue("Required columns missing from 'inputClip':", '{paste(setdiff(clipExpCols, colnames(apClip)), collapse = ", ")}', + .sep = " ")) + } + + # Check for data + if (nrow(apClip) == 0) { + stop(glue::glue("Table 'inputClip' has no data.")) + } + + # Check for bout 2 data + if (nrow(apClip%>%filter(boutNumber == '2')) == 0){ + stop(glue::glue("The input data does not contain any bout 2 records. No taxonomy data to join.")) + } + + + ### Verify 'apTaxProc' table contains required data if data exists + taxProcExpCols <- c("sampleID", "taxonID", "identifiedDate", "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "division", "class", "order", "family", "genus", "section", "specificEpithet", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "remarks", "identifiedBy", "morphospeciesID", "uid", "domainID", "siteID", "namedLocation", "collectDate", "sampleCode") + + # Check for data + if(exists("apTaxProc")){ + if (is.null(apTaxProc) | nrow(apTaxProc) == 0) { + message(glue::glue("Warning: Table 'inputTaxProc' has no data. Join will not include processed taxonomy data.")) + } else { + + # Check for required columns if data exists + if (length(setdiff(taxProcExpCols, colnames(apTaxProc))) > 0) { + stop(glue::glue("Required columns missing from 'inputTaxProc':", '{paste(setdiff(taxProcExpCols, colnames(apTaxProc)), collapse = ", ")}', + .sep = " ")) + } + } + } + + + ### Verify 'apMorph' table contains required data if data exists + morphExpCols <- c("morphospeciesID", "taxonID", "scientificName", "identificationQualifier","identificationReferences", "identifiedBy", "dataQF" ) + + + # Check for data + if(exists("apMorph")){ + if(is.null(apMorph) | nrow(apMorph) == 0){ + message("Warning: Table 'inputMorph' has no data. Joined output does not include identifications from the morphospecies table.") + } else { + + # Check for required columns if data exists + if (length(setdiff(morphExpCols, colnames(apMorph))) > 0) { + stop(glue::glue("Required columns missing from 'inputMorph':", '{paste(setdiff(morphExpCols, colnames(apMorph)), collapse = ", ")}', + .sep = " ")) + } + } + } + + ### Verify that 'apClip' table contains bout 2 data + + ### Join apBio and apTaxProc tables using sampleID + + if (!is.null(apTaxProc) && nrow(apTaxProc) > 0) { + # message("Join taxonomyProcessed taxonomic identifications.") + # Select needed columns from apTaxProc + apTaxProc <- apTaxProc %>% + dplyr::select(-"uid", -"domainID", -"siteID", -"namedLocation", -"collectDate", -"morphospeciesID", -"sampleCode") + + # Update expert taxonomist identifications + apJoin1 <- apBio %>% + dplyr::left_join(apTaxProc, by = "sampleID", suffix = c("_bio", "_taxProc"), relationship = "many-to-many") %>% + dplyr::mutate( + + identifiedDate = dplyr::case_when( + !is.na(.data$taxonID_taxProc) ~ .data$identifiedDate_taxProc, + TRUE ~ .data$identifiedDate_bio), + + sampleCondition = dplyr::case_when( + !is.na(.data$sampleCondition_bio)&!is.na(.data$sampleCondition_taxProc)~paste0("biomass ",.data$sampleCondition_bio," | taxonProcessed ",.data$sampleCondition_taxProc), + !is.na(.data$sampleCondition_bio)&is.na(.data$sampleCondition_taxProc)~paste0("biomass ",.data$sampleCondition_bio), + is.na(.data$sampleCondition_bio)&!is.na(.data$sampleCondition_taxProc)~paste0("taxonProcessed ",.data$sampleCondition_taxProc), + TRUE ~ NA), + + taxonIDSourceTable = dplyr::case_when( + !is.na(.data$taxonID_taxProc)~"apl_taxonomyProcessed", + is.na(.data$taxonID_taxProc)&!is.na(.data$taxonID_bio)~"apl_biomass", + TRUE~NA), + + tempTaxonID = dplyr::if_else(!is.na(taxonID_taxProc), taxonID_taxProc, taxonID_bio), + + scientificName = dplyr::if_else(!is.na(taxonID_taxProc), scientificName_taxProc, scientificName_bio), + + identificationHistoryID = dplyr::case_when( + !is.na(.data$identificationHistoryID_bio)&!is.na(.data$identificationHistoryID_taxProc)~paste0(.data$identificationHistoryID_bio," | ",.data$identificationHistoryID_taxProc), + is.na(.data$identificationHistoryID_taxProc)&!is.na(.data$identificationHistoryID_bio)~.data$identificationHistoryID_bio, + !is.na(.data$identificationHistoryID_taxProc)&is.na(.data$identificationHistoryID_bio)~.data$identificationHistoryID_taxProc, + TRUE~NA), + + biomassDataQF = dataQF_bio, + taxProcessedDataQF = dataQF_taxProc, + + biomassPublicationDate = publicationDate_bio, + taxProcessedPublicationDate = publicationDate_taxProc, + + biomassRelease = release_bio, + taxProcessedRelease = release_taxProc, + + # phylum = dplyr::if_else(!is.na(taxonID_taxProc), phylum_taxProc, phylum_bio), + division = dplyr::if_else(!is.na(taxonID_taxProc), division_taxProc, division_bio), + class = dplyr::if_else(!is.na(taxonID_taxProc), class_taxProc, class_bio), + order = dplyr::if_else(!is.na(taxonID_taxProc), order_taxProc, order_bio), + family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_bio), + genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_bio), + section = dplyr::if_else(!is.na(taxonID_taxProc), section_taxProc, section_bio), + specificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), specificEpithet_taxProc, specificEpithet_bio), + # infraspecificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), infraspecificEpithet_taxProc, infraspecificEpithet_bio), + # variety = dplyr::if_else(!is.na(taxonID_taxProc), variety_taxProc, variety_bio), + # form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_bio), + scientificNameAuthorship = dplyr::if_else(!is.na(taxonID_taxProc), scientificNameAuthorship_taxProc, scientificNameAuthorship_bio), + identificationQualifier = dplyr::if_else(!is.na(taxonID_taxProc), identificationQualifier_taxProc, identificationQualifier_bio), + identificationReferences = dplyr::if_else(!is.na(taxonID_taxProc), identificationReferences_taxProc, identificationReferences_bio), + taxonRank = dplyr::if_else(!is.na(taxonID_taxProc), taxonRank_taxProc, taxonRank_bio), + + remarks = dplyr::case_when( + !is.na(.data$remarks_bio)&!is.na(.data$remarks_taxProc) ~ paste0("biomass remarks - ", .data$remarks_bio, " | taxonProcessed remarks - ", .data$remarks_taxProc ), + is.na(.data$remarks_taxProc)&!is.na(.data$remarks_bio) ~ paste0("biomass remarks - ", .data$remarks_bio), + !is.na(.data$remarks_taxProc)&is.na(.data$remarks_bio) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), + TRUE ~ NA), + + identifiedBy = dplyr::if_else(!is.na(taxonID_taxProc), identifiedBy_taxProc, identifiedBy_bio), + identifiedDate = dplyr::if_else(!is.na(identifiedDate_taxProc), identifiedDate_taxProc, identifiedDate_bio) + + ) %>% + select(-matches("_taxProc"), + -matches("_bio"), + -"targetTaxaPresent", -"uid") + + } else { + # message("No data joined from apl_taxonomyProcessed table.") + # rename columns if no taxProc join + apJoin1 <- apBio %>% + mutate(tempTaxonID = taxonID, + remarks = dplyr::if_else(is.na(remarks), NA, paste0("biomass remarks - ", remarks)), + perTaxonRelease = release, + taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apl_biomass"), + perTaxonDataQF = dataQF, + perTaxonPublicationDate = publicationDate) %>% + select(-"taxonID", -"release", -"dataQF", -"publicationDate", -"uid") + } + + ### Join apJoin1 and apMorph tables + + # Select needed columns from apMorph + if(!is.null(apMorph) && nrow(apMorph) > 0){ + # message("Join morphospecies taxonomic identifications.") + apMorph <- apMorph %>% + dplyr::select("taxonID", + "scientificName", + "morphospeciesID", + "identificationQualifier", + "identificationReferences", + "identifiedBy", + # "identifiedDate", + "dataQF" + ) + + # Update morphospecies taxon identifications + apJoin2 <- apJoin1 %>% + dplyr::mutate( + morphospeciesID = dplyr::if_else(!is.na(morphospeciesID), paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), morphospeciesID) + ) %>% + dplyr::left_join(apMorph, by = "morphospeciesID", suffix = c("_bio", "_morph")) %>% + # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% + dplyr::mutate( + taxonIDSourceTable = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), "apc_morphospecies", taxonIDSourceTable), + acceptedTaxonID = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), taxonID, tempTaxonID), + scientificName = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), scientificName_morph, scientificName_bio), + identificationQualifier = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationQualifier_morph, identificationQualifier_bio), + identificationReferences = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationReferences_morph, identificationReferences_bio), + identifiedBy = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identifiedBy_morph, identifiedBy_bio), + identifiedDate = NA, #not currently in pub table + morphospeciesDataQF = dataQF + + ) %>% + dplyr::select( + -"taxonID", -"tempTaxonID", -"dataQF", + -matches("_morph"), + -matches("_bio") + ) + } else { + # message("No data joined from apc_morphospecies table.") + apJoin2 <- apJoin1 %>% + mutate(acceptedTaxonID = tempTaxonID) %>% + select(-"tempTaxonID") + } + + + ### Join apClip and apBio tables + + # Update morphospecies taxon identifications + joinClipHarvest <- apClip %>% + dplyr::select(-"benthicArea", -"namedLocation", -"domainID", -"siteID", -"startDate", -"collectDate", -"fieldIDCode" ) %>% + dplyr::left_join(apJoin2, by = "fieldID", suffix = c("_clip", "_bio")) %>% + dplyr::mutate( + remarks = dplyr::case_when( + !is.na(.data$remarks_bio)&!is.na(.data$remarks_clip) ~ paste0("clipHarvest remarks - ", .data$remarks_clip, " | ", .data$remarks_bio), + !is.na(.data$remarks_bio)&is.na(.data$remarks_clip) ~ .data$remarks_bio, + is.na(.data$remarks_bio)&!is.na(.data$remarks_clip) ~ paste0("clipHarvest remarks - ", .data$remarks_clip), + TRUE ~ NA), + recordedBy = dplyr::if_else(!is.na(.data$recordedBy_clip), .data$recordedBy_clip, .data$recordedBy_bio), + clipDataQF = .data$dataQF, + clipPublicationDate = publicationDate, + clipRelease = release + ) %>% + dplyr::select( + -"dataQF", -"publicationDate", -"release", + -matches("_bio"), + -matches("_clip") + ) + + + # Filter out bout 1 and 3 data + joinClipHarvest <- joinClipHarvest %>% filter(boutNumber == '2') + + + return(joinClipHarvest) + +} #function closer diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index d5902c9..be62988 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -90,9 +90,11 @@ joinAquPointCount <- function(inputDataList, ### Verify all table inputs are data frames if inputDataList is NA if (is.null(inputDataList) & - (!inherits(inputPoint, "data.frame") | !inherits(inputPerTax, "data.frame") | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame"))) { + (!inherits(inputPoint, "data.frame") | !inherits(inputPerTax, "data.frame") + # | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame") + )) { - stop("Data frames must be supplied for all table inputs if 'inputDataList' is missing") + stop("Data frames must be supplied for table inputs if 'inputDataList' is missing") } @@ -113,8 +115,6 @@ joinAquPointCount <- function(inputDataList, apPerTax <- inputPerTax apTaxProc <- inputTaxProc apMorph <- inputMorph - apVouch <- inputVouch - apVouchProc <- inputVouchProc } @@ -325,7 +325,7 @@ joinAquPointCount <- function(inputDataList, ### Join apPoint and apPerTax tables # Update morphospecies taxon identifications - apJoin3 <- apPoint %>% + joinPointCounts <- apPoint %>% dplyr::left_join(apJoin2, by = c("domainID", "siteID", "namedLocation", "pointNumber", "collectDate", "eventID"), suffix = c("_point", "_perTax")) %>% dplyr::mutate( remarks = dplyr::case_when( @@ -335,6 +335,6 @@ joinAquPointCount <- function(inputDataList, TRUE ~ NA) ) - return(apJoin3) + return(joinPointCounts) } #function closer From 8578212353a0355c75526a7c857b346286c40933 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 13 Jun 2025 14:27:04 -0700 Subject: [PATCH 03/40] add test files --- R/joinAquPointCount.R | 13 +- tests/testthat/test_joinAquClipHarvest.R | 269 ++++++++++++++++++ tests/testthat/test_joinAquPointCount.R | 238 ++++++++++++++++ .../joinAquClipHarvest_testData_202307.rds | Bin 0 -> 43948 bytes .../joinAquPointCount_testData_202307.rds | Bin 0 -> 125492 bytes 5 files changed, 513 insertions(+), 7 deletions(-) create mode 100644 tests/testthat/test_joinAquClipHarvest.R create mode 100644 tests/testthat/test_joinAquPointCount.R create mode 100644 tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds create mode 100644 tests/testthat/testdata/joinAquPointCount_testData_202307.rds diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index be62988..43b38ab 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -106,8 +106,6 @@ joinAquPointCount <- function(inputDataList, apPerTax <- inputDataList$apc_perTaxon apTaxProc <- inputDataList$apc_taxonomyProcessed apMorph <- inputDataList$apc_morphospecies - apVouch <- inputDataList$apc_voucher - apVouchProc <- inputDataList$apc_voucherTaxonomyProcessed } else { @@ -124,7 +122,7 @@ joinAquPointCount <- function(inputDataList, ### Verify 'apPoint' table contains required data # Check for required columns - pointExpCols <- c("namedLocation", "pointNumber", "collectDate", "targetTaxaPresent") + pointExpCols <- c("domainID", "siteID", "namedLocation", "pointNumber", "collectDate", "eventID", "remarks") if (length(setdiff(pointExpCols, colnames(apPoint))) > 0) { stop(glue::glue("Required columns missing from 'inputPoint':", '{paste(setdiff(pointExpCols, colnames(apPoint)), collapse = ", ")}', @@ -140,7 +138,7 @@ joinAquPointCount <- function(inputDataList, ### Verify 'apPerTax' table contains required data # Check for required columns - perTaxExpCols <- c("namedLocation", "pointNumber", "collectDate", "sampleID", "aquaticPlantType", "growthForm", "taxonID", "scientificName", "morphospeciesID", "identificationQualifier") + perTaxExpCols <- c("sampleID", "taxonID", "scientificName", "morphospeciesID","sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "phylum", "division", "class", "order", "family", "genus", "section", "specificEpithet", "infraspecificEpithet", "variety", "form", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "taxonRank", "remarks", "identifiedBy", "identifiedDate", "uid") if (length(setdiff(perTaxExpCols, colnames(apPerTax))) > 0) { stop(glue::glue("Required columns missing from 'inputPerTax':", '{paste(setdiff(perTaxExpCols, colnames(apPerTax)), collapse = ", ")}', @@ -155,7 +153,7 @@ joinAquPointCount <- function(inputDataList, ### Verify 'apTaxProc' table contains required data if data exists - taxProcExpCols <- c("sampleID", "acceptedTaxonID") + taxProcExpCols <- c( "sampleID", "acceptedTaxonID", "scientificName", "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "phylum", "division", "class", "order", "family", "genus", "section", "specificEpithet", "infraspecificEpithet", "variety", "form", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "taxonRank", "remarks", "identifiedBy", "identifiedDate", "morphospeciesID", "uid", "domainID", "siteID", "namedLocation", "collectDate") # Check for data if(exists("apTaxProc")){ @@ -173,11 +171,12 @@ joinAquPointCount <- function(inputDataList, ### Verify 'apMorph' table contains required data if data exists - morphExpCols <- c("sampleID") + morphExpCols<- c("taxonID", "scientificName", "morphospeciesID", "identificationQualifier", "identificationReferences", "identifiedBy", "dataQF") + # Check for data if(exists("apMorph")){ - if(is.null(apMorph) | !is.null(apMorph)){ + if(is.null(apMorph) | nrow(apMorph) == 0){ message("Warning: Table 'inputMorph' has no data. Join will not include identifications from the morphospecies table.") } else { diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R new file mode 100644 index 0000000..72bf678 --- /dev/null +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -0,0 +1,269 @@ +### Unit tests for joinAquClipHarvest function #### +### POC: Madaline Ritter, ritterm1@BattelleEcology.org + +### Download initial test data - DELETE +# clip_testDat <- neonUtilities::loadByProduct( +# dpID="DP1.20066.001", +# check.size=F, +# startdate = '2023-07', +# enddate = '2023-07', +# site = "all", +# # site = "SYCA", +# package='expanded', +# include.provisional = T, +# release = "LATEST", +# token = Sys.getenv('NEON_PAT')) +# +# clip_testDat$apl_identificationHistory <- NULL +# clip_testDat$apl_taxonomyRaw <- NULL +# clip_testDat$categoricalCodes_20066 <- NULL +# clip_testDat$issueLog_20066 <- NULL +# clip_testDat$readme_20066 <- NULL +# clip_testDat$validation_20066 <- NULL +# clip_testDat$variables_20066 <- NULL +# +# View(clip_testDat$apc_morphospecies) +# View(clip_testDat$apl_biomass) +# View(clip_testDat$apl_clipHarvest) +# View(clip_testDat$apl_taxonomyProcessed) +# +# saveRDS(clip_testDat, "C:/Users/ritterm1/Documents/GitHub/a_neonPackages/neonPlants/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds") +# testList <- readRDS("C:/Users/ritterm1/Documents/GitHub/a_neonPackages/neonPlants/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds") + + +### Read in test data +testList <- readRDS(test_that::test_path("testdata", "joinAquClipHarvest_testData_202307.rds")) +testBio <- testList$apl_biomass +testClip <- testList$apl_clipHarvest +testTaxProc <- testList$apl_taxonomyProcessed +testMorph <- testList$apc_morphospecies + + + +### Test: Function generates expected output type #### +# Test list input +testthat::test_that(desc = "Output type list input", { + + testthat::expect_type(object = joinAquClipHarvest(inputDataList = testList), + type = "list") + +}) + +# Test table input +testthat::test_that(desc = "Output type table input", { + + testthat::expect_type(object = joinAquClipHarvest(inputBio = testBio, + inputClip = testClip, + inputTaxProc = testTaxProc, + inputMorph = testMorph), + type = "list") +}) + + + +### Test: Function generates expected output class #### +# Test list input +testthat::test_that(desc = "Output class list input", { + + testthat::expect_s3_class(object = joinAquClipHarvest(inputDataList = testList), + class = "data.frame") +}) + +# Test table input +testthat::test_that(desc = "Output class table input", { + + testthat::expect_s3_class(object = joinAquClipHarvest(inputBio = testBio, + inputClip = testClip, + inputTaxProc = testTaxProc, + inputMorph = testMorph), + class = "data.frame") +}) + + + +### Test: Function generates data frame with expected dimensions using test data #### +## Test list input +# Check expected row number of output +testthat::test_that(desc = "Output data frame row number list input", { + + testthat::expect_identical(object = nrow(joinAquClipHarvest(inputDataList = testList)), + expected = as.integer(324)) +}) + + +# Check expected column number of output +testthat::test_that(desc = "Output data frame column number list input", { + + testthat::expect_identical(object = ncol(joinAquClipHarvest(inputDataList = testList)), + expected = as.integer(103)) +}) + + +## Test table inputs +# Check expected row number of output +testthat::test_that(desc = "Output data frame row number table input", { + + testthat::expect_identical(object = nrow(joinAquClipHarvest(inputBio = testBio, + inputClip = testClip, + inputTaxProc = testTaxProc, + inputMorph = testMorph)), + expected = as.integer(324)) +}) + +# Check expected column number of output +testthat::test_that(desc = "Output data frame row number table input", { + + testthat::expect_identical(object = ncol(joinAquClipHarvest(inputBio = testBio, + inputClip = testClip, + inputTaxProc = testTaxProc, + inputMorph = testMorph)), + expected = as.integer(103)) +}) + + + +### Test: Function generates data frame with expected dimensions using test data #### +## Test dataframe output +# Check 'acceptedTaxonID' is pulled from apc_taxonomyProcessed if taxProc data exists +testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { + + outDF <- joinAquClipHarvest(inputDataList = testList) + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$uid == '1bcced55-6162-4cc1-b91b-63edb4422f7f +']), + expected = "apl_taxonomyProcessed") +}) + + +# Check 'acceptedTaxonID' is pulled from apc_morphospecies if identification is in morphospecies table +testthat::test_that(desc = "Output data frame source: apc_morphospecies", { + + outDF <- joinAquClipHarvest(inputDataList = testList) + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'BLUE.20230717.AP1.Q2 +']), + expected = "apc_morphospecies") + + + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'BLUE.20230717.AP1.Q2 + ']), + expected = "LURE2") +}) + + +# Check 'acceptedTaxonID' is pulled from apl_biomass if identification is not in morphospecies or taxProcessed tables +testthat::test_that(desc = "Output data frame source: biomass", { + + outDF <- joinAquClipHarvest(inputDataList = testList) + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'CRAM.20230720.AP1.P2 +']), + expected = "apl_biomass") + + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'CRAM.20230720.AP1.P2 + + ']), + expected = "ISTE5") +}) + + + +### Tests: Generate expected errors for 'inputDataList' #### +# Test 'inputDataList' is a list +testthat::test_that(desc = "Argument 'inputDataList' is list object", { + + testthat::expect_error(object = joinAquClipHarvest(inputDataList = testBio), + regexp = "Argument 'inputDataList' must be a list object") +}) + +# Test 'inputDataList' contains required tables +testthat::test_that(desc = "Required tables present in 'inputDataList' input", { + + testthat::expect_error(object = joinAquClipHarvest(inputDataList = testList[1:2]), + regexp = "Required tables missing from 'inputDataList'") +}) + +# Test table inputs are NA if 'inputDataList' supplied +testthat::test_that(desc = "Table inputs NA when required", { + + testthat::expect_error(object = joinAquClipHarvest(inputDataList = testList, + inputPoint = testPoint), + regexp = "When 'inputDataList' is supplied all table input arguments must be NA") +}) + + + +### Tests: Generate expected errors with table inputs #### +testthat::test_that(desc = "Table inputs are data frames when required", { + + testthat::expect_error(object = joinAquClipHarvest(inputPoint = testPoint, + inputBio = testBio), + regexp = "Data frames must be supplied for table inputs if 'inputDataList' is missing") +}) + + + +### Test: Generate expected errors for issues with biomass table (works for inputDataList or inputBio source) #### +# Test when inputBio lacks required column +testthat::test_that(desc = "Table 'inputBio' missing column", { + + testthat::expect_error(object = joinAquClipHarvest(inputBio = testBio %>% + dplyr::select(-taxonID), + inputClip = testClip), + regexp = "Required columns missing from 'inputBio': taxonID") +}) + +# Test when inputBio has no data +testthat::test_that(desc = "Table 'inputBio' missing data", { + + testthat::expect_error(object = joinAquClipHarvest(inputBio = testBio %>% + dplyr::filter(taxonID == "coconut"), + inputClip = testClip), + regexp = "Table 'inputBio' has no data.") +}) + + +### Test: Generate expected errors for issues with clipHarvest table (works for inputDataList or inputClip source) #### +# Test when inputClip lacks required column +testthat::test_that(desc = "Table 'inputClip' missing column", { + + testthat::expect_error(object = joinAquClipHarvest(inputClip = testClip %>% + dplyr::select(-eventID), + inputBio = testBio), + regexp = "Required columns missing from 'inputClip': eventID") +}) + +# Test when inputClip has no data +testthat::test_that(desc = "Table 'inputClip' missing data", { + + testthat::expect_error(object = joinAquClipHarvest(inputClip = testClip %>% + dplyr::filter(eventID == "moon landing"), + inputBio = testBio), + regexp = "Table 'inputClip' has no data.") +}) + + + +### Test: Generate expected errors for issues with taxonomyProcessed table (works for inputDataList or inputTaxProc source) #### +# Test when inputTaxProc lacks required column +testthat::test_that(desc = "Table 'inputTaxProc' missing column", { + + testthat::expect_error(object = joinAquClipHarvest(inputTaxProc = testTaxProc %>% + dplyr::select(-acceptedTaxonID), + inputPoint = testPoint, + inputPerTax = testPerTax), + regexp = "Required columns missing from 'inputTaxProc': acceptedTaxonID") +}) + + + +### Test: Generate expected errors for issues with morphospecies table (works for inputDataList or inputMorph source) #### +# Test when inputMorph lacks required column +testthat::test_that(desc = "Table 'inputMorph' missing column", { + + testthat::expect_error(object = joinAquClipHarvest(inputMorph = testMorph %>% + dplyr::select(-taxonID), + inputPoint = testPoint, + inputPerTax = testPerTax), + regexp = "Required columns missing from 'inputMorph': taxonID") +}) + + + diff --git a/tests/testthat/test_joinAquPointCount.R b/tests/testthat/test_joinAquPointCount.R new file mode 100644 index 0000000..f8c108c --- /dev/null +++ b/tests/testthat/test_joinAquPointCount.R @@ -0,0 +1,238 @@ +### Unit tests for joinAquPointCount function #### +### POC: Madaline Ritter, ritterm1@BattelleEcology.org + + + +### Read in test data +testList <- readRDS(test_that::test_path("testdata", "joinAquPointCount_testData_202307.rds")) +testPoint <- testList$apc_pointTransect +testPerTax <- testList$apc_perTaxon +testTaxProc <- testList$apc_taxonomyProcessed +testMorph <- testList$apc_morphospecies + + + +### Test: Function generates expected output type #### +# Test list input +testthat::test_that(desc = "Output type list input", { + + testthat::expect_type(object = joinAquPointCount(inputDataList = testList), + type = "list") + +}) + +# Test table input +testthat::test_that(desc = "Output type table input", { + + testthat::expect_type(object = joinAquPointCount(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc, + inputMorph = testMorph), + type = "list") +}) + + + +### Test: Function generates expected output class #### +# Test list input +testthat::test_that(desc = "Output class list input", { + + testthat::expect_s3_class(object = joinAquPointCount(inputDataList = testList), + class = "data.frame") +}) + +# Test table input +testthat::test_that(desc = "Output class table input", { + + testthat::expect_s3_class(object = joinAquPointCount(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc, + inputMorph = testMorph), + class = "data.frame") +}) + + + +### Test: Function generates data frame with expected dimensions using test data #### +## Test list input +# Check expected row number of output +testthat::test_that(desc = "Output data frame row number list input", { + + testthat::expect_identical(object = nrow(joinAquPointCount(inputDataList = testList)), + expected = as.integer(3114)) +}) + + +# Check expected column number of output +testthat::test_that(desc = "Output data frame column number list input", { + + testthat::expect_identical(object = ncol(joinAquPointCount(inputDataList = testList)), + expected = as.integer(75)) +}) + + +## Test table inputs +# Check expected row number of output +testthat::test_that(desc = "Output data frame row number table input", { + + testthat::expect_identical(object = nrow(joinAquPointCount(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc, + inputMorph = testMorph)), + expected = as.integer(3114)) +}) + +# Check expected column number of output +testthat::test_that(desc = "Output data frame row number table input", { + + testthat::expect_identical(object = ncol(joinAquPointCount(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc, + inputMorph = testMorph)), + expected = as.integer(75)) +}) + + + +### Test: Function generates data frame with expected dimensions using test data #### +## Test dataframe output +# Check 'acceptedTaxonID' is pulled from apc_taxonomyProcessed if taxProc data exists +testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { + + outDF <- joinAquPointCount(inputDataList = testList) + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$uid == '1bc5392f-a567-4b6d-83b4-55ca74457ecd']), + expected = "apc_taxonomyProcessed") +}) + + +# Check 'acceptedTaxonID' is pulled from apc_morphospecies if identification is in morphospecies table +testthat::test_that(desc = "Output data frame source: apc_morphospecies", { + + outDF <- joinAquPointCount(inputDataList = testList) + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'KING.20230719.AP1.1.T1 +']), + expected = "apc_morphospecies") + + + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'KING.20230719.AP1.1.T1 + ']), + expected = "NAOF") +}) + + +# Check 'acceptedTaxonID' is pulled from apc_perTaxon if identification is not in morphospecies or taxProcessed tables +testthat::test_that(desc = "Output data frame source: perTaxon", { + + outDF <- joinAquPointCount(inputDataList = testList) + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'POSE.20230718.AP16.1.T7']), + expected = "apc_perTaxon") + + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'POSE.20230718.AP16.1.T7 + ']), + expected = "2PLANT") +}) + + + +### Tests: Generate expected errors for 'inputDataList' #### +# Test 'inputDataList' is a list +testthat::test_that(desc = "Argument 'inputDataList' is list object", { + + testthat::expect_error(object = joinAquPointCount(inputDataList = testPoint), + regexp = "Argument 'inputDataList' must be a list object") +}) + +# Test 'inputDataList' contains required tables +testthat::test_that(desc = "Required tables present in 'inputDataList' input", { + + testthat::expect_error(object = joinAquPointCount(inputDataList = testList[1:2]), + regexp = "Required tables missing from 'inputDataList'") +}) + +# Test table inputs are NA if 'inputDataList' supplied +testthat::test_that(desc = "Table inputs NA when required", { + + testthat::expect_error(object = joinAquPointCount(inputDataList = testList, + inputPoint = testPoint), + regexp = "When 'inputDataList' is supplied all table input arguments must be NA") +}) + + + +### Tests: Generate expected errors with table inputs #### +testthat::test_that(desc = "Table inputs are data frames when required", { + + testthat::expect_error(object = joinAquPointCount(inputPoint = testPoint, + inputPerTax = testPerTax), + regexp = "Data frames must be supplied for table inputs if 'inputDataList' is missing") +}) + + + +### Test: Generate expected errors for issues with pointCount table (works for inputDataList or inputPoint source) #### +# Test when inputPoint lacks required column +testthat::test_that(desc = "Table 'inputPoint' missing column", { + + testthat::expect_error(object = joinAquPointCount(inputPoint = testPoint %>% + dplyr::select(-taxonID), + inputPerTax = testPerTax), + regexp = "Required columns missing from 'inputPoint': taxonID") +}) + +# Test when inputPoint has no data +testthat::test_that(desc = "Table 'inputPoint' missing data", { + + testthat::expect_error(object = joinAquPointCount(inputPoint = testPoint %>% + dplyr::filter(taxonID == "coconut"), + inputPerTax = testPerTax), + regexp = "Table 'inputPoint' has no data.") +}) + + +### Test: Generate expected errors for issues with perTaxon table (works for inputDataList or inputPerTax source) #### +# Test when inputPerTax lacks required column +testthat::test_that(desc = "Table 'inputPerTax' missing column", { + + testthat::expect_error(object = joinAquPointCount(inputPerTax = testPerTax %>% + dplyr::select(-taxonID), + inputPoint = testPoint), + regexp = "Required columns missing from 'inputPerTax': taxonID") +}) + +# Test when inputPerTax has no data +testthat::test_that(desc = "Table 'inputPerTax' missing data", { + + testthat::expect_error(object = joinAquPointCount(inputPerTax = testPerTax %>% + dplyr::filter(taxonID == "coconut"), + inputPoint = testPoint), + regexp = "Table 'inputPerTax' has no data.") +}) + + + +### Test: Generate expected errors for issues with taxonomyProcessed table (works for inputDataList or inputTaxProc source) #### +# Test when inputTaxProc lacks required column +testthat::test_that(desc = "Table 'inputTaxProc' missing column", { + + testthat::expect_error(object = joinAquPointCount(inputTaxProc = testTaxProc %>% + dplyr::select(-acceptedTaxonID), + inputPoint = testPoint, + inputPerTax = testPerTax), + regexp = "Required columns missing from 'inputTaxProc': acceptedTaxonID") +}) + + + +### Test: Generate expected errors for issues with morphospecies table (works for inputDataList or inputMorph source) #### +# Test when inputMorph lacks required column +testthat::test_that(desc = "Table 'inputMorph' missing column", { + + testthat::expect_error(object = joinAquPointCount(inputMorph = testMorph %>% + dplyr::select(-taxonID), + inputPoint = testPoint, + inputPerTax = testPerTax), + regexp = "Required columns missing from 'inputMorph': taxonID") +}) + + + diff --git a/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds b/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds new file mode 100644 index 0000000000000000000000000000000000000000..ba5ff4050f64f73739b6f763aa3a369a28f66f47 GIT binary patch literal 43948 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 WwwGy>!Y<)E<6VYtnctGwj{FaI^EtNw literal 0 HcmV?d00001 From 6ccbd80475e64a1a35501ac56880ad7fc4b19cca Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 13 Jun 2025 14:40:23 -0700 Subject: [PATCH 04/40] fix test --- tests/testthat/test_joinAquClipHarvest.R | 2 +- tests/testthat/test_joinAquPointCount.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R index 72bf678..7a8d3da 100644 --- a/tests/testthat/test_joinAquClipHarvest.R +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -32,7 +32,7 @@ ### Read in test data -testList <- readRDS(test_that::test_path("testdata", "joinAquClipHarvest_testData_202307.rds")) +testList <- readRDS(testthat::test_path("testdata", "joinAquClipHarvest_testData_202307.rds")) testBio <- testList$apl_biomass testClip <- testList$apl_clipHarvest testTaxProc <- testList$apl_taxonomyProcessed diff --git a/tests/testthat/test_joinAquPointCount.R b/tests/testthat/test_joinAquPointCount.R index f8c108c..2def0cd 100644 --- a/tests/testthat/test_joinAquPointCount.R +++ b/tests/testthat/test_joinAquPointCount.R @@ -4,7 +4,7 @@ ### Read in test data -testList <- readRDS(test_that::test_path("testdata", "joinAquPointCount_testData_202307.rds")) +testList <- readRDS(testthat::test_path("testdata", "joinAquPointCount_testData_202307.rds")) testPoint <- testList$apc_pointTransect testPerTax <- testList$apc_perTaxon testTaxProc <- testList$apc_taxonomyProcessed From bc2f73739774aaad66b09eaf2411781a89e393d7 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 13 Jun 2025 14:55:58 -0700 Subject: [PATCH 05/40] correct export --- R/joinAquClipHarvest.R | 2 +- R/joinAquPointCount.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index a226a5e..037de39 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -45,7 +45,7 @@ #' #' } #' -#' @export joinClipHarvest +#' @export joinAquClipHarvest diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 43b38ab..8279f95 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -45,7 +45,7 @@ #' #' } #' -#' @export joinPointCounts +#' @export joinAquPointCount From 55276b85c4a44883bbf36616def71799564e16aa Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 13 Jun 2025 14:57:23 -0700 Subject: [PATCH 06/40] add aquJoins to man and namespace --- NAMESPACE | 2 ++ man/joinAquClipHarvest.Rd | 65 +++++++++++++++++++++++++++++++++++++++ man/joinAquPointCount.Rd | 65 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 man/joinAquClipHarvest.Rd create mode 100644 man/joinAquPointCount.Rd diff --git a/NAMESPACE b/NAMESPACE index c89e790..cc25c2c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,8 @@ export("%>%") export(estimatePheDurationByTag) export(estimatePheTransByTag) +export(joinAquClipHarvest) +export(joinAquPointCount) export(joinRootChem) export(scaleRootMass) export(stackPlantPresence) diff --git a/man/joinAquClipHarvest.Rd b/man/joinAquClipHarvest.Rd new file mode 100644 index 0000000..02e466a --- /dev/null +++ b/man/joinAquClipHarvest.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/joinAquClipHarvest.R +\name{joinAquClipHarvest} +\alias{joinAquClipHarvest} +\title{Join NEON aquatic plant clip harvest data into a single table with merged taxonomic identifications} +\usage{ +joinAquClipHarvest( + inputDataList, + inputBio = NA, + inputClip = NA, + inputTaxProc = NA, + inputMorph = NA +) +} +\arguments{ +\item{inputDataList}{A list object comprised of Aquatic Plant Bryophyte Macroalgae Clip Harvest tables (DP1.20066.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. \link{list}} + +\item{inputBio}{The 'apl_biomass' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputClip}{The 'apl_clipHarvest' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputTaxProc}{The 'apl_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputMorph}{The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} +} +\value{ +A table containing clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent == 'Y'. +} +\description{ +Join the 'apl_clipHarvest', 'apl_biomass', 'apl_taxonomyProcessed' and 'apc_morphospecies' tables to generate a single table that contains clip harvest data with taxonomic identifications for each sampleID. Data inputs are NEON Aquatic Plant Bryophyte Macroalgae Clip Harvest (DP1.20066.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. +} +\details{ +Input data may be provided either as a list or as individual tables. However, if both list and table inputs are provided at the same time the function will error out. For table joining to be successful, inputs must contain data from the same site x month combination(s) for all tables. + +Only data from bout 2 (midsummer sampling) is returned in the joined output table, as other bouts do not include taxonomy data. If the input does not include any bout 2 data, the function will error out. + +In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. +} +\examples{ +\dontrun{ +# Obtain NEON Aquatic Plant Point Count data +apl <- neonUtilities::loadByProduct( +dpID = "DP1.20066.001", +site = "all", +startdate = "2018-07", +enddate = "2018-08", +tabl = "all", +check.size = FALSE +) + +# Join downloaded root data +df <- neonPlants::joinAquClipHarvest( +inputDataList = apl, +inputBio = NA, +inputClip = NA, +inputTaxProc = NA, +inputMorph = NA +) + +} + +} +\author{ +Madaline Ritter \email{ritterm1@battelleecology.org} \cr +} diff --git a/man/joinAquPointCount.Rd b/man/joinAquPointCount.Rd new file mode 100644 index 0000000..c868a27 --- /dev/null +++ b/man/joinAquPointCount.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/joinAquPointCount.R +\name{joinAquPointCount} +\alias{joinAquPointCount} +\title{Join NEON aquatic plant point count data into a single table with merged taxonomic identifications} +\usage{ +joinAquPointCount( + inputDataList, + inputPoint = NA, + inputPerTax = NA, + inputTaxProc = NA, + inputMorph = NA +) +} +\arguments{ +\item{inputDataList}{A list object comprised of Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Count tables (DP1.20072.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. \link{list}} + +\item{inputPoint}{The 'apc_pointTransect' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputPerTax}{The 'apc_perTaxon' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputTaxProc}{The 'apc_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputMorph}{The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} +} +\value{ +A table containing point transect data with all associated taxonomic information for each point where targetTaxaPresent == 'Y'. +} +\description{ +Join the 'apc_pointTransect', 'apc_perTaxon', 'apc_taxonomyProcessed' and 'apc_morphospecies' tables to generate a single table that contains point count data with taxonomic identifications for each sampleID. Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. +} +\details{ +Input data may be provided either as a list or as individual tables. However, if both list and table inputs are provided at the same time the function will error out. For table joining to be successful, inputs must contain data from the same site x month combination(s) for all tables. + +In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apc_taxonomyProcessed', 'apc_perTaxon', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. + +If a single sample in 'apc_taxonomyProcessed' contains multiple macroalgae species, each species will be represented as a separate row in 'apc_pointTransect' for every point associated with that sampleID. +} +\examples{ +\dontrun{ +# Obtain NEON Aquatic Plant Point Count data +apc <- neonUtilities::loadByProduct( +dpID = "DP1.20072.001", +site = "all", +startdate = "2018-07", +enddate = "2018-08", +tabl = "all", +check.size = FALSE +) + +# Join downloaded root data +df <- neonPlants::joinAquPointCount( +inputDataList = apc, +inputPoint = NA, +inputPerTax = NA, +inputTaxProc = NA, +inputMorph = NA +) + +} + +} +\author{ +Madaline Ritter \email{ritterm1@battelleecology.org} \cr +} From b871c4d261e5e94f6690764a270a840df556bea5 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 13 Jun 2025 15:39:37 -0700 Subject: [PATCH 07/40] testing --- R/joinAquPointCount.R | 20 ++++++++++++++------ tests/testthat/test_joinAquClipHarvest.R | 16 +++++----------- tests/testthat/test_joinAquPointCount.R | 19 ++++++++----------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 8279f95..576aee8 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -104,8 +104,16 @@ joinAquPointCount <- function(inputDataList, apPoint <- inputDataList$apc_pointTransect apPerTax <- inputDataList$apc_perTaxon - apTaxProc <- inputDataList$apc_taxonomyProcessed - apMorph <- inputDataList$apc_morphospecies + if(!is.null(inputDataList$apc_taxonomyProcessed)){ + apTaxProc <- inputDataList$apc_taxonomyProcessed + }else{ + apTaxProc <- NA + } + if(!is.null(inputDataList$apc_morphospecies)){ + apMorph <- inputDataList$apc_morphospecies + }else{ + apMorph <- NA + } } else { @@ -156,8 +164,8 @@ joinAquPointCount <- function(inputDataList, taxProcExpCols <- c( "sampleID", "acceptedTaxonID", "scientificName", "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "phylum", "division", "class", "order", "family", "genus", "section", "specificEpithet", "infraspecificEpithet", "variety", "form", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "taxonRank", "remarks", "identifiedBy", "identifiedDate", "morphospeciesID", "uid", "domainID", "siteID", "namedLocation", "collectDate") # Check for data - if(exists("apTaxProc")){ - if (is.null(apTaxProc) | nrow(apTaxProc) == 0) { + if(is.data.frame(apTaxProc)){ + if (nrow(apTaxProc) == 0) { message(glue::glue("Warning: Table 'inputTaxProc' has no data. Join will not include processed taxonomy data.")) } else { @@ -175,8 +183,8 @@ joinAquPointCount <- function(inputDataList, # Check for data - if(exists("apMorph")){ - if(is.null(apMorph) | nrow(apMorph) == 0){ + if(is.data.frame(apMorph)){ + if(nrow(apMorph) == 0){ message("Warning: Table 'inputMorph' has no data. Join will not include identifications from the morphospecies table.") } else { diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R index 7a8d3da..ef0c383 100644 --- a/tests/testthat/test_joinAquClipHarvest.R +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -128,8 +128,7 @@ testthat::test_that(desc = "Output data frame row number table input", { testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$uid == '1bcced55-6162-4cc1-b91b-63edb4422f7f -']), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$uid == '1bcced55-6162-4cc1-b91b-63edb4422f7f')]), expected = "apl_taxonomyProcessed") }) @@ -138,13 +137,11 @@ testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { testthat::test_that(desc = "Output data frame source: apc_morphospecies", { outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'BLUE.20230717.AP1.Q2 -']), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP1.Q2')]), expected = "apc_morphospecies") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'BLUE.20230717.AP1.Q2 - ']), + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.AP1.Q2')]), expected = "LURE2") }) @@ -153,13 +150,10 @@ testthat::test_that(desc = "Output data frame source: apc_morphospecies", { testthat::test_that(desc = "Output data frame source: biomass", { outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'CRAM.20230720.AP1.P2 -']), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'CRAM.20230720.AP1.P2')]), expected = "apl_biomass") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'CRAM.20230720.AP1.P2 - - ']), + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'CRAM.20230720.AP1.P2')]), expected = "ISTE5") }) diff --git a/tests/testthat/test_joinAquPointCount.R b/tests/testthat/test_joinAquPointCount.R index 2def0cd..a07301d 100644 --- a/tests/testthat/test_joinAquPointCount.R +++ b/tests/testthat/test_joinAquPointCount.R @@ -100,7 +100,7 @@ testthat::test_that(desc = "Output data frame row number table input", { testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { outDF <- joinAquPointCount(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$uid == '1bc5392f-a567-4b6d-83b4-55ca74457ecd']), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$uid == '1bc5392f-a567-4b6d-83b4-55ca74457ecd')]), expected = "apc_taxonomyProcessed") }) @@ -109,13 +109,11 @@ testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { testthat::test_that(desc = "Output data frame source: apc_morphospecies", { outDF <- joinAquPointCount(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'KING.20230719.AP1.1.T1 -']), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'KING.20230719.AP1.1.T1')]), expected = "apc_morphospecies") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'KING.20230719.AP1.1.T1 - ']), + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'KING.20230719.AP1.1.T1')]), expected = "NAOF") }) @@ -124,11 +122,10 @@ testthat::test_that(desc = "Output data frame source: apc_morphospecies", { testthat::test_that(desc = "Output data frame source: perTaxon", { outDF <- joinAquPointCount(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[outDF$sampleID == 'POSE.20230718.AP16.1.T7']), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'POSE.20230718.AP16.1.T7')]), expected = "apc_perTaxon") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[outDF$sampleID == 'POSE.20230718.AP16.1.T7 - ']), + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'POSE.20230718.AP16.1.T7')]), expected = "2PLANT") }) @@ -174,16 +171,16 @@ testthat::test_that(desc = "Table inputs are data frames when required", { testthat::test_that(desc = "Table 'inputPoint' missing column", { testthat::expect_error(object = joinAquPointCount(inputPoint = testPoint %>% - dplyr::select(-taxonID), + dplyr::select(-pointNumber), inputPerTax = testPerTax), - regexp = "Required columns missing from 'inputPoint': taxonID") + regexp = "Required columns missing from 'inputPoint': pointNumber") }) # Test when inputPoint has no data testthat::test_that(desc = "Table 'inputPoint' missing data", { testthat::expect_error(object = joinAquPointCount(inputPoint = testPoint %>% - dplyr::filter(taxonID == "coconut"), + dplyr::filter(pointNumber == "999"), inputPerTax = testPerTax), regexp = "Table 'inputPoint' has no data.") }) From 7c9b7fd26b8037da07914718dd6e0f7632812e4b Mon Sep 17 00:00:00 2001 From: Ritterm Date: Mon, 16 Jun 2025 15:22:59 -0700 Subject: [PATCH 08/40] point count testing complete --- R/joinAquPointCount.R | 546 +++++++++++++++++------- tests/testthat/test_joinAquPointCount.R | 5 +- 2 files changed, 402 insertions(+), 149 deletions(-) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 576aee8..602d611 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -50,20 +50,22 @@ joinAquPointCount <- function(inputDataList, - inputPoint = NA, - inputPerTax = NA, - inputTaxProc = NA, - inputMorph = NA) { - + inputPoint = NA, + inputPerTax = NA, + inputTaxProc = NA, + inputMorph = NA) { ### Test that user has supplied arguments as required by function #### ### Verify user-supplied inputDataList object contains correct data if not NA if (!missing(inputDataList)) { - # Check that input is a list if (!inherits(inputDataList, "list")) { - stop(glue::glue("Argument 'inputDataList' must be a list object from neonUtilities::loadByProduct(); - supplied input object is {class(inputDataList)}")) + stop( + glue::glue( + "Argument 'inputDataList' must be a list object from neonUtilities::loadByProduct(); + supplied input object is {class(inputDataList)}" + ) + ) } # Check that required tables within list match expected names @@ -71,29 +73,46 @@ joinAquPointCount <- function(inputDataList, # Determine dataType or stop with appropriate message if (length(setdiff(listExpNames, names(inputDataList))) > 0) { - stop(glue::glue("Required tables missing from 'inputDataList':", - '{paste(setdiff(listExpNames, names(inputDataList)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required tables missing from 'inputDataList':", + '{paste(setdiff(listExpNames, names(inputDataList)), collapse = ", ")}', + .sep = " " + ) + ) } } else { - inputDataList <- NULL } # end missing conditional + # message(paste('inputDataList= ', inputDataList)) ### Verify table inputs are NA if inputDataList is supplied - if (inherits(inputDataList, "list") & (!is.logical(inputPoint) | !is.logical(inputPerTax) | !is.logical(inputTaxProc) | !is.logical(inputMorph))) { - stop("When 'inputDataList' is supplied all table input arguments must be NA") + # any_table_supplied <- !isTRUE(is.na(inputPoint)) || !isTRUE(is.na(inputPerTax)) || + # !isTRUE(is.na(inputTaxProc)) || !isTRUE(is.na(inputMorph)) + + # if (!missing(inputDataList) && !isTRUE(is.na(inputDataList)) && any_table_supplied) { + # stop("Provide either 'inputDataList' OR individual table inputs, not both.") + # } + # if (inherits(inputDataList, "list") & (!is.logical(inputPoint) | !is.logical(inputPerTax) | !is.logical(inputTaxProc) | !is.logical(inputMorph))) { + # stop("When 'inputDataList' is supplied all table input arguments must be NA") + # } + if (!is.null(inputDataList)) { + if (!isTRUE(is.na(inputPoint)) || !isTRUE(is.na(inputPerTax)) || + !isTRUE(is.na(inputTaxProc)) || + !isTRUE(is.na(inputMorph))) { + stop("When 'inputDataList' is supplied, all table input arguments must be NA.") + } } ### Verify all table inputs are data frames if inputDataList is NA - if (is.null(inputDataList) & - (!inherits(inputPoint, "data.frame") | !inherits(inputPerTax, "data.frame") - # | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame") - )) { - + if (is.null(inputDataList) & + ( + !is.data.frame(inputPoint) || !is.data.frame(inputPerTax) + # | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame") + )) { stop("Data frames must be supplied for table inputs if 'inputDataList' is missing") } @@ -101,27 +120,25 @@ joinAquPointCount <- function(inputDataList, ### Conditionally define input tables #### if (inherits(inputDataList, "list")) { - apPoint <- inputDataList$apc_pointTransect apPerTax <- inputDataList$apc_perTaxon - if(!is.null(inputDataList$apc_taxonomyProcessed)){ + if (!is.null(inputDataList$apc_taxonomyProcessed)) { apTaxProc <- inputDataList$apc_taxonomyProcessed - }else{ + } else{ apTaxProc <- NA } - if(!is.null(inputDataList$apc_morphospecies)){ + if (!is.null(inputDataList$apc_morphospecies)) { apMorph <- inputDataList$apc_morphospecies - }else{ + } else{ apMorph <- NA } } else { - apPoint <- inputPoint apPerTax <- inputPerTax apTaxProc <- inputTaxProc apMorph <- inputMorph - + } @@ -130,13 +147,26 @@ joinAquPointCount <- function(inputDataList, ### Verify 'apPoint' table contains required data # Check for required columns - pointExpCols <- c("domainID", "siteID", "namedLocation", "pointNumber", "collectDate", "eventID", "remarks") - + pointExpCols <- c( + "domainID", + "siteID", + "namedLocation", + "pointNumber", + "collectDate", + "eventID", + "remarks" + ) + if (length(setdiff(pointExpCols, colnames(apPoint))) > 0) { - stop(glue::glue("Required columns missing from 'inputPoint':", '{paste(setdiff(pointExpCols, colnames(apPoint)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputPoint':", + '{paste(setdiff(pointExpCols, colnames(apPoint)), collapse = ", ")}', + .sep = " " + ) + ) } - + # Check for data if (nrow(apPoint) == 0) { stop(glue::glue("Table 'inputPoint' has no data.")) @@ -146,11 +176,45 @@ joinAquPointCount <- function(inputDataList, ### Verify 'apPerTax' table contains required data # Check for required columns - perTaxExpCols <- c("sampleID", "taxonID", "scientificName", "morphospeciesID","sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "phylum", "division", "class", "order", "family", "genus", "section", "specificEpithet", "infraspecificEpithet", "variety", "form", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "taxonRank", "remarks", "identifiedBy", "identifiedDate", "uid") + perTaxExpCols <- c( + "sampleID", + "taxonID", + "scientificName", + "morphospeciesID", + "sampleCondition", + "identificationHistoryID", + "dataQF", + "publicationDate", + "release", + "phylum", + "division", + "class", + "order", + "family", + "genus", + "section", + "specificEpithet", + "infraspecificEpithet", + "variety", + "form", + "scientificNameAuthorship", + "identificationQualifier", + "identificationReferences", + "taxonRank", + "remarks", + "identifiedBy", + "identifiedDate", + "uid" + ) if (length(setdiff(perTaxExpCols, colnames(apPerTax))) > 0) { - stop(glue::glue("Required columns missing from 'inputPerTax':", '{paste(setdiff(perTaxExpCols, colnames(apPerTax)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputPerTax':", + '{paste(setdiff(perTaxExpCols, colnames(apPerTax)), collapse = ", ")}', + .sep = " " + ) + ) } # Check for data @@ -161,82 +225,164 @@ joinAquPointCount <- function(inputDataList, ### Verify 'apTaxProc' table contains required data if data exists - taxProcExpCols <- c( "sampleID", "acceptedTaxonID", "scientificName", "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "phylum", "division", "class", "order", "family", "genus", "section", "specificEpithet", "infraspecificEpithet", "variety", "form", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "taxonRank", "remarks", "identifiedBy", "identifiedDate", "morphospeciesID", "uid", "domainID", "siteID", "namedLocation", "collectDate") + taxProcExpCols <- c( + "sampleID", + "acceptedTaxonID", + "scientificName", + "sampleCondition", + "identificationHistoryID", + "dataQF", + "publicationDate", + "release", + "phylum", + "division", + "class", + "order", + "family", + "genus", + "section", + "specificEpithet", + "infraspecificEpithet", + "variety", + "form", + "scientificNameAuthorship", + "identificationQualifier", + "identificationReferences", + "taxonRank", + "remarks", + "identifiedBy", + "identifiedDate", + "morphospeciesID", + "uid", + "domainID", + "siteID", + "namedLocation", + "collectDate" + ) # Check for data - if(is.data.frame(apTaxProc)){ + if (is.data.frame(apTaxProc)) { if (nrow(apTaxProc) == 0) { - message(glue::glue("Warning: Table 'inputTaxProc' has no data. Join will not include processed taxonomy data.")) + message( + glue::glue( + "Warning: Table 'inputTaxProc' has no data. Join will not include processed taxonomy data." + ) + ) } else { - # Check for required columns if data exists if (length(setdiff(taxProcExpCols, colnames(apTaxProc))) > 0) { - stop(glue::glue("Required columns missing from 'inputTaxProc':", '{paste(setdiff(taxProcExpCols, colnames(apTaxProc)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputTaxProc':", + '{paste(setdiff(taxProcExpCols, colnames(apTaxProc)), collapse = ", ")}', + .sep = " " + ) + ) } } } - - + + ### Verify 'apMorph' table contains required data if data exists - morphExpCols<- c("taxonID", "scientificName", "morphospeciesID", "identificationQualifier", "identificationReferences", "identifiedBy", "dataQF") + morphExpCols <- c( + "taxonID", + "scientificName", + "morphospeciesID", + "identificationQualifier", + "identificationReferences", + "identifiedBy", + "dataQF" + ) # Check for data - if(is.data.frame(apMorph)){ - if(nrow(apMorph) == 0){ - message("Warning: Table 'inputMorph' has no data. Join will not include identifications from the morphospecies table.") + if (is.data.frame(apMorph)) { + if (nrow(apMorph) == 0) { + message( + "Warning: Table 'inputMorph' has no data. Join will not include identifications from the morphospecies table." + ) } else { - # Check for required columns if data exists if (length(setdiff(morphExpCols, colnames(apMorph))) > 0) { - stop(glue::glue("Required columns missing from 'inputMorph':", '{paste(setdiff(morphExpCols, colnames(apMorph)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputMorph':", + '{paste(setdiff(morphExpCols, colnames(apMorph)), collapse = ", ")}', + .sep = " " + ) + ) } } } - - #### for testing #### - - # inputDataList <- readRDS("C:/Users/ritterm1/OneDrive - National Ecological Observatory Network/Ritter/neonPlants/apc_allTabs_2023data.rds") - # apPoint <- inputDataList$apc_pointTransect - # apPerTax <- inputDataList$apc_perTaxon - # apTaxProc <- inputDataList$apc_taxonomyProcessed - # apMorph <- inputDataList$apc_morphospecies - # apVouch <- inputDataList$apc_voucher - # apVouchProc <- inputDataList$apc_voucherTaxonomyProcessed - - ### Join apPerTax and apTaxProc tables + ### Join apPerTax and apTaxProc tables #### + # message(paste('apTaxProc = ', apTaxProc)) + is.data.frame(apTaxProc) && nrow(apTaxProc) > 0 - if (!is.null(apTaxProc) && nrow(apTaxProc) > 0) { - # message("Join taxonomyProcessed taxonomic identifications.") + if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { # Select needed columns from apTaxProc - apTaxProc <- apTaxProc %>% - dplyr::select(-"uid", -"domainID", -"siteID", -"namedLocation", -"collectDate", -"morphospeciesID") %>% - dplyr::rename(taxonID=acceptedTaxonID) + apTaxProc <- apTaxProc %>% + dplyr::select( + -"uid", + -"domainID", + -"siteID", + -"namedLocation", + -"collectDate", + -"morphospeciesID" + ) %>% + dplyr::rename(taxonID = acceptedTaxonID) # Update expert taxonomist identifications - apJoin1 <- apPerTax %>% - dplyr::left_join(apTaxProc, by = "sampleID", suffix = c("_perTax", "_taxProc"), relationship = "many-to-many") %>% + apJoin1 <- apPerTax %>% + dplyr::left_join( + apTaxProc, + by = "sampleID", + suffix = c("_perTax", "_taxProc"), + relationship = "many-to-many" + ) %>% dplyr::mutate( sampleCondition = dplyr::case_when( - !is.na(.data$sampleCondition_perTax)&!is.na(.data$sampleCondition_taxProc)~paste0("perTaxon ",.data$sampleCondition_perTax," | taxonProcessed ",.data$sampleCondition_taxProc), - !is.na(.data$sampleCondition_perTax)&is.na(.data$sampleCondition_taxProc)~paste0("perTaxon ",.data$sampleCondition_perTax), - is.na(.data$sampleCondition_perTax)&!is.na(.data$sampleCondition_taxProc)~paste0("taxonProcessed ",.data$sampleCondition_taxProc), - TRUE ~ NA), + !is.na(.data$sampleCondition_perTax) & + !is.na(.data$sampleCondition_taxProc) ~ paste0( + "perTaxon ", + .data$sampleCondition_perTax, + " | taxonProcessed ", + .data$sampleCondition_taxProc + ),!is.na(.data$sampleCondition_perTax) & + is.na(.data$sampleCondition_taxProc) ~ paste0("perTaxon ", .data$sampleCondition_perTax), + is.na(.data$sampleCondition_perTax) & + !is.na(.data$sampleCondition_taxProc) ~ paste0("taxonProcessed ", .data$sampleCondition_taxProc), + TRUE ~ NA + ), taxonIDSourceTable = dplyr::case_when( - !is.na(.data$taxonID_taxProc)~"apc_taxonomyProcessed", - is.na(.data$taxonID_taxProc)&!is.na(.data$taxonID_perTax)~"apc_perTaxon", - TRUE~NA), - tempTaxonID = dplyr::if_else(!is.na(taxonID_taxProc), taxonID_taxProc, taxonID_perTax), - scientificName = dplyr::if_else(!is.na(taxonID_taxProc), scientificName_taxProc, scientificName_perTax), + !is.na(.data$taxonID_taxProc) ~ "apc_taxonomyProcessed", + is.na(.data$taxonID_taxProc) & + !is.na(.data$taxonID_perTax) ~ "apc_perTaxon", + TRUE ~ NA + ), + tempTaxonID = dplyr::if_else( + !is.na(taxonID_taxProc), + taxonID_taxProc, + taxonID_perTax + ), + scientificName = dplyr::if_else( + !is.na(taxonID_taxProc), + scientificName_taxProc, + scientificName_perTax + ), identificationHistoryID = dplyr::case_when( - !is.na(.data$identificationHistoryID_perTax)&!is.na(.data$identificationHistoryID_taxProc)~paste0(.data$identificationHistoryID_perTax," | ",.data$identificationHistoryID_taxProc), - is.na(.data$identificationHistoryID_taxProc)&!is.na(.data$identificationHistoryID_perTax)~.data$identificationHistoryID_perTax, - !is.na(.data$identificationHistoryID_taxProc)&is.na(.data$identificationHistoryID_perTax)~.data$identificationHistoryID_taxProc, - TRUE~NA), + !is.na(.data$identificationHistoryID_perTax) & + !is.na(.data$identificationHistoryID_taxProc) ~ paste0( + .data$identificationHistoryID_perTax, + " | ", + .data$identificationHistoryID_taxProc + ), + is.na(.data$identificationHistoryID_taxProc) & + !is.na(.data$identificationHistoryID_perTax) ~ .data$identificationHistoryID_perTax,!is.na(.data$identificationHistoryID_taxProc) & + is.na(.data$identificationHistoryID_perTax) ~ .data$identificationHistoryID_taxProc, + TRUE ~ NA + ), perTaxonDataQF = dataQF_perTax, taxProcessedDataQF = dataQF_taxProc, perTaxonPublicationDate = publicationDate_perTax, @@ -244,104 +390,212 @@ joinAquPointCount <- function(inputDataList, taxProcessedRelease = release_taxProc, perTaxonRelease = release_perTax, phylum = dplyr::if_else(!is.na(taxonID_taxProc), phylum_taxProc, phylum_perTax), - division = dplyr::if_else(!is.na(taxonID_taxProc), division_taxProc, division_perTax), + division = dplyr::if_else( + !is.na(taxonID_taxProc), + division_taxProc, + division_perTax + ), class = dplyr::if_else(!is.na(taxonID_taxProc), class_taxProc, class_perTax), order = dplyr::if_else(!is.na(taxonID_taxProc), order_taxProc, order_perTax), - family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_perTax), - genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_perTax), - section = dplyr::if_else(!is.na(taxonID_taxProc), section_taxProc, section_perTax), - specificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), specificEpithet_taxProc, specificEpithet_perTax), - infraspecificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), infraspecificEpithet_taxProc, infraspecificEpithet_perTax), - variety = dplyr::if_else(!is.na(taxonID_taxProc), variety_taxProc, variety_perTax), - form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_perTax), - scientificNameAuthorship = dplyr::if_else(!is.na(taxonID_taxProc), scientificNameAuthorship_taxProc, scientificNameAuthorship_perTax), - identificationQualifier = dplyr::if_else(!is.na(taxonID_taxProc), identificationQualifier_taxProc, identificationQualifier_perTax), - identificationReferences = dplyr::if_else(!is.na(taxonID_taxProc), identificationReferences_taxProc, identificationReferences_perTax), - taxonRank = dplyr::if_else(!is.na(taxonID_taxProc), taxonRank_taxProc, taxonRank_perTax), + family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_perTax), + genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_perTax), + section = dplyr::if_else( + !is.na(taxonID_taxProc), + section_taxProc, + section_perTax + ), + specificEpithet = dplyr::if_else( + !is.na(taxonID_taxProc), + specificEpithet_taxProc, + specificEpithet_perTax + ), + infraspecificEpithet = dplyr::if_else( + !is.na(taxonID_taxProc), + infraspecificEpithet_taxProc, + infraspecificEpithet_perTax + ), + variety = dplyr::if_else( + !is.na(taxonID_taxProc), + variety_taxProc, + variety_perTax + ), + form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_perTax), + scientificNameAuthorship = dplyr::if_else( + !is.na(taxonID_taxProc), + scientificNameAuthorship_taxProc, + scientificNameAuthorship_perTax + ), + identificationQualifier = dplyr::if_else( + !is.na(taxonID_taxProc), + identificationQualifier_taxProc, + identificationQualifier_perTax + ), + identificationReferences = dplyr::if_else( + !is.na(taxonID_taxProc), + identificationReferences_taxProc, + identificationReferences_perTax + ), + taxonRank = dplyr::if_else( + !is.na(taxonID_taxProc), + taxonRank_taxProc, + taxonRank_perTax + ), remarks = dplyr::case_when( - !is.na(.data$remarks_perTax)&!is.na(.data$remarks_taxProc) ~ paste0("perTaxon remarks - ", .data$remarks_perTax, " | taxonProcessed remarks - ", .data$remarks_taxProc ), - is.na(.data$remarks_taxProc)&!is.na(.data$remarks_perTax) ~ paste0("perTaxon remarks - ", .data$remarks_perTax), - !is.na(.data$remarks_taxProc)&is.na(.data$remarks_perTax) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), - TRUE ~ NA), - identifiedBy = dplyr::if_else(!is.na(taxonID_taxProc), identifiedBy_taxProc, identifiedBy_perTax), - identifiedDate = dplyr::if_else(!is.na(identifiedDate_taxProc), identifiedDate_taxProc, identifiedDate_perTax) + !is.na(.data$remarks_perTax) & + !is.na(.data$remarks_taxProc) ~ paste0( + "perTaxon remarks - ", + .data$remarks_perTax, + " | taxonProcessed remarks - ", + .data$remarks_taxProc + ), + is.na(.data$remarks_taxProc) & + !is.na(.data$remarks_perTax) ~ paste0("perTaxon remarks - ", .data$remarks_perTax),!is.na(.data$remarks_taxProc) & + is.na(.data$remarks_perTax) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), + TRUE ~ NA + ), + identifiedBy = dplyr::if_else( + !is.na(taxonID_taxProc), + identifiedBy_taxProc, + identifiedBy_perTax + ), + identifiedDate = dplyr::if_else( + !is.na(identifiedDate_taxProc), + identifiedDate_taxProc, + identifiedDate_perTax + ) - ) %>% - select(-matches("_taxProc"), - -matches("_perTax"), - -"targetTaxaPresent", -"uid") + ) %>% + select(-matches("_taxProc"),-matches("_perTax"),-"targetTaxaPresent", + -"uid") } else { message("No data joined from apc_taxonomyProcessed table.") # rename columns if no taxProc join - apJoin1 <- apPerTax %>% - mutate(tempTaxonID = taxonID, - remarks = dplyr::if_else(is.na(remarks), NA, paste0("perTaxon remarks - ", remarks)), - perTaxonRelease = release, - taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apc_perTaxon"), - perTaxonDataQF = dataQF, - perTaxonPublicationDate = publicationDate) %>% - select(-"taxonID", -"release", -"dataQF", -"publicationDate", -"uid") - } + apJoin1 <- apPerTax %>% + mutate( + tempTaxonID = taxonID, + remarks = dplyr::if_else(is.na(remarks), NA, paste0("perTaxon remarks - ", remarks)), + perTaxonRelease = release, + taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apc_perTaxon"), + perTaxonDataQF = dataQF, + perTaxonPublicationDate = publicationDate + ) %>% + select(-"taxonID", + -"release", + -"dataQF", + -"publicationDate", + -"uid") + } ### Join apJoin1 and apMorph tables # Select needed columns from apMorph - if(!is.null(apMorph) && nrow(apMorph) > 0){ + if (is.data.frame(apMorph) && nrow(apMorph) > 0) { # message("Join morphospecies taxonomic identifications.") apMorph <- apMorph %>% - dplyr::select("taxonID", - "scientificName", - "morphospeciesID", - "identificationQualifier", - "identificationReferences", - "identifiedBy", - # "identifiedDate", - "dataQF" + dplyr::select( + "taxonID", + "scientificName", + "morphospeciesID", + "identificationQualifier", + "identificationReferences", + "identifiedBy", + # "identifiedDate", + "dataQF" ) # Update morphospecies taxon identifications apJoin2 <- apJoin1 %>% + dplyr::mutate(morphospeciesID = dplyr::if_else( + !is.na(morphospeciesID), + paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), + morphospeciesID + )) %>% + dplyr::left_join(apMorph, + by = "morphospeciesID", + suffix = c("_perTax", "_morph")) %>% + # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% dplyr::mutate( - morphospeciesID = dplyr::if_else(!is.na(morphospeciesID), paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), morphospeciesID) - ) %>% - dplyr::left_join(apMorph, by = "morphospeciesID", suffix = c("_perTax", "_morph")) %>% - # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% - dplyr::mutate( - taxonIDSourceTable = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), "apc_morphospecies", taxonIDSourceTable), - acceptedTaxonID = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), taxonID, tempTaxonID), - scientificName = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), scientificName_morph, scientificName_perTax), - identificationQualifier = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationQualifier_morph, identificationQualifier_perTax), - identificationReferences = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationReferences_morph, identificationReferences_perTax), - identifiedBy = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identifiedBy_morph, identifiedBy_perTax), + taxonIDSourceTable = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + "apc_morphospecies", + taxonIDSourceTable + ), + acceptedTaxonID = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + taxonID, + tempTaxonID + ), + scientificName = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + scientificName_morph, + scientificName_perTax + ), + identificationQualifier = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + identificationQualifier_morph, + identificationQualifier_perTax + ), + identificationReferences = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + identificationReferences_morph, + identificationReferences_perTax + ), + identifiedBy = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + identifiedBy_morph, + identifiedBy_perTax + ), identifiedDate = NA #not currently in pub table #dataQF = ifelse(!is.na(taxonID), )#either that may be relevant? - ) %>% - dplyr::select( - -"taxonID", -"tempTaxonID", - -matches("_morph"), - -matches("_perTax") - ) + ) %>% + dplyr::select(-"taxonID", + -"tempTaxonID",-matches("_morph"),-matches("_perTax")) } else { message("No data joined from apc_morphospecies table.") apJoin2 <- apJoin1 } - ### Join apPoint and apPerTax tables - + ### Join apPoint and apPerTax tables + # Update morphospecies taxon identifications joinPointCounts <- apPoint %>% - dplyr::left_join(apJoin2, by = c("domainID", "siteID", "namedLocation", "pointNumber", "collectDate", "eventID"), suffix = c("_point", "_perTax")) %>% + dplyr::left_join( + apJoin2, + by = c( + "domainID", + "siteID", + "namedLocation", + "pointNumber", + "collectDate", + "eventID" + ), + suffix = c("_point", "_perTax") + ) %>% dplyr::mutate( remarks = dplyr::case_when( - !is.na(.data$remarks_perTax)&!is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point, " | ", .data$remarks_perTax), - !is.na(.data$remarks_perTax)&is.na(.data$remarks_point) ~ .data$remarks_perTax, - is.na(.data$remarks_perTax)&!is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point), - TRUE ~ NA) + !is.na(.data$remarks_perTax) & + !is.na(.data$remarks_point) ~ paste0( + "pointTransect remarks - ", + .data$remarks_point, + " | ", + .data$remarks_perTax + ),!is.na(.data$remarks_perTax) & + is.na(.data$remarks_point) ~ .data$remarks_perTax, + is.na(.data$remarks_perTax) & + !is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point), + TRUE ~ NA + ) ) return(joinPointCounts) - + } #function closer diff --git a/tests/testthat/test_joinAquPointCount.R b/tests/testthat/test_joinAquPointCount.R index a07301d..643f73f 100644 --- a/tests/testthat/test_joinAquPointCount.R +++ b/tests/testthat/test_joinAquPointCount.R @@ -2,7 +2,6 @@ ### POC: Madaline Ritter, ritterm1@BattelleEcology.org - ### Read in test data testList <- readRDS(testthat::test_path("testdata", "joinAquPointCount_testData_202307.rds")) testPoint <- testList$apc_pointTransect @@ -151,7 +150,7 @@ testthat::test_that(desc = "Table inputs NA when required", { testthat::expect_error(object = joinAquPointCount(inputDataList = testList, inputPoint = testPoint), - regexp = "When 'inputDataList' is supplied all table input arguments must be NA") + regexp = "When 'inputDataList' is supplied, all table input arguments must be NA.") }) @@ -160,7 +159,7 @@ testthat::test_that(desc = "Table inputs NA when required", { testthat::test_that(desc = "Table inputs are data frames when required", { testthat::expect_error(object = joinAquPointCount(inputPoint = testPoint, - inputPerTax = testPerTax), + inputTaxProc = testTaxProc), regexp = "Data frames must be supplied for table inputs if 'inputDataList' is missing") }) From 22a152b7a8542ccbbc1b9308328d0598ac09a4b9 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Mon, 16 Jun 2025 16:06:32 -0700 Subject: [PATCH 09/40] testing --- R/joinAquClipHarvest.R | 512 +++++++++++++++++------ R/joinAquPointCount.R | 14 +- tests/testthat/test_joinAquClipHarvest.R | 22 +- 3 files changed, 389 insertions(+), 159 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 037de39..e95d298 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -50,20 +50,23 @@ joinAquClipHarvest <- function(inputDataList, - inputBio = NA, - inputClip = NA, - inputTaxProc = NA, - inputMorph = NA) { + inputBio = NA, + inputClip = NA, + inputTaxProc = NA, + inputMorph = NA) { ### Test that user has supplied arguments as required by function #### ### Verify user-supplied inputDataList object contains correct data if not NA if (!missing(inputDataList)) { - # Check that input is a list if (!inherits(inputDataList, "list")) { - stop(glue::glue("Argument 'inputDataList' must be a list object from neonUtilities::loadByProduct(); - supplied input object is {class(inputDataList)}")) + stop( + glue::glue( + "Argument 'inputDataList' must be a list object from neonUtilities::loadByProduct(); + supplied input object is {class(inputDataList)}" + ) + ) } # Check that required tables within list match expected names @@ -71,9 +74,13 @@ joinAquClipHarvest <- function(inputDataList, # Determine dataType or stop with appropriate message if (length(setdiff(listExpNames, names(inputDataList))) > 0) { - stop(glue::glue("Required tables missing from 'inputDataList':", - '{paste(setdiff(listExpNames, names(inputDataList)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required tables missing from 'inputDataList':", + '{paste(setdiff(listExpNames, names(inputDataList)), collapse = ", ")}', + .sep = " " + ) + ) } } else { @@ -83,17 +90,19 @@ joinAquClipHarvest <- function(inputDataList, ### Verify table inputs are NA if inputDataList is supplied - if (inherits(inputDataList, "list") & (!is.logical(inputBio) | !is.logical(inputClip) | !is.logical(inputTaxProc) | !is.logical(inputMorph))) { - stop("When 'inputDataList' is supplied all table input arguments must be NA") + if (!is.null(inputDataList)) { + if (!isTRUE(is.na(inputBio)) || !isTRUE(is.na(inputClip)) || + !isTRUE(is.na(inputTaxProc)) || !isTRUE(is.na(inputMorph))) { + stop("When 'inputDataList' is supplied, all table input arguments must be NA.") + } } ### Verify all table inputs are data frames if inputDataList is NA - if (is.null(inputDataList) & - (!inherits(inputBio, "data.frame") | !inherits(inputClip, "data.frame") - # | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame") - )) { - + if (is.null(inputDataList) & + ( + !is.data.frame(inputBio) || !is.data.frame(inputClip) + )) { stop("Data frames must be supplied for table inputs if 'inputDataList' is missing") } @@ -101,14 +110,20 @@ joinAquClipHarvest <- function(inputDataList, ### Conditionally define input tables #### if (inherits(inputDataList, "list")) { - apBio <- inputDataList$apl_biomass apClip <- inputDataList$apl_clipHarvest - apTaxProc <- inputDataList$apl_taxonomyProcessed - apMorph <- inputDataList$apc_morphospecies - - } else { + if (!is.null(inputDataList$apl_taxonomyProcessed)) { + apTaxProc <- inputDataList$apl_taxonomyProcessed + } else{ + apTaxProc <- NA + } + if (!is.null(inputDataList$apc_morphospecies)) { + apMorph <- inputDataList$apc_morphospecies + } else{ + apMorph <- NA + } + } else { apBio <- inputBio apClip <- inputClip apTaxProc <- inputTaxProc @@ -122,13 +137,41 @@ joinAquClipHarvest <- function(inputDataList, ### Verify 'apBio' table contains required data # Check for required columns - # bioExpCols <- c("sampleID", "taxonID", "scientificName", "morphospeciesID") - bioExpCols <- c("sampleID", "taxonID", "scientificName", "morphospeciesID", "identifiedDate", "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "division", "class", "order", "family", "genus", "section", "specificEpithet", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "remarks", "identifiedBy", "uid") + bioExpCols <- c( + "sampleID", + "taxonID", + "scientificName", + "morphospeciesID", + "identifiedDate", + "sampleCondition", + "identificationHistoryID", + "dataQF", + "publicationDate", + "release", + "division", + "class", + "order", + "family", + "genus", + "section", + "specificEpithet", + "scientificNameAuthorship", + "identificationQualifier", + "identificationReferences", + "remarks", + "identifiedBy", + "uid" + ) if (length(setdiff(bioExpCols, colnames(apBio))) > 0) { - stop(glue::glue("Required columns missing from 'inputBio':", '{paste(setdiff(bioExpCols, colnames(apBio)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputBio':", + '{paste(setdiff(bioExpCols, colnames(apBio)), collapse = ", ")}', + .sep = " " + ) + ) } # Check for data @@ -140,11 +183,29 @@ joinAquClipHarvest <- function(inputDataList, ### Verify 'apClip' table contains required data # Check for required columns - clipExpCols <- c("namedLocation", "eventID", "boutNumber", "fieldID", "benthicArea", "domainID", "siteID", "startDate", "collectDate", "fieldIDCode", "recordedBy", "remarks") + clipExpCols <- c( + "namedLocation", + "eventID", + "boutNumber", + "fieldID", + "benthicArea", + "domainID", + "siteID", + "startDate", + "collectDate", + "fieldIDCode", + "recordedBy", + "remarks" + ) if (length(setdiff(clipExpCols, colnames(apClip))) > 0) { - stop(glue::glue("Required columns missing from 'inputClip':", '{paste(setdiff(clipExpCols, colnames(apClip)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputClip':", + '{paste(setdiff(clipExpCols, colnames(apClip)), collapse = ", ")}', + .sep = " " + ) + ) } # Check for data @@ -153,43 +214,97 @@ joinAquClipHarvest <- function(inputDataList, } # Check for bout 2 data - if (nrow(apClip%>%filter(boutNumber == '2')) == 0){ - stop(glue::glue("The input data does not contain any bout 2 records. No taxonomy data to join.")) + if (nrow(apClip %>% filter(boutNumber == '2')) == 0) { + stop( + glue::glue( + "The input data does not contain any bout 2 records. No taxonomy data to join." + ) + ) } ### Verify 'apTaxProc' table contains required data if data exists - taxProcExpCols <- c("sampleID", "taxonID", "identifiedDate", "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", "release", "division", "class", "order", "family", "genus", "section", "specificEpithet", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", "remarks", "identifiedBy", "morphospeciesID", "uid", "domainID", "siteID", "namedLocation", "collectDate", "sampleCode") + taxProcExpCols <- c( + "sampleID", + "taxonID", + "identifiedDate", + "sampleCondition", + "identificationHistoryID", + "dataQF", + "publicationDate", + "release", + "division", + "class", + "order", + "family", + "genus", + "section", + "specificEpithet", + "scientificNameAuthorship", + "identificationQualifier", + "identificationReferences", + "remarks", + "identifiedBy", + "morphospeciesID", + "uid", + "domainID", + "siteID", + "namedLocation", + "collectDate", + "sampleCode" + ) # Check for data - if(exists("apTaxProc")){ - if (is.null(apTaxProc) | nrow(apTaxProc) == 0) { - message(glue::glue("Warning: Table 'inputTaxProc' has no data. Join will not include processed taxonomy data.")) + if (is.data.frame(apTaxProc)) { + if(nrow(apTaxProc) == 0) { + message( + glue::glue( + "Warning: Table 'inputTaxProc' has no data. Join will not include processed taxonomy data." + ) + ) } else { - # Check for required columns if data exists if (length(setdiff(taxProcExpCols, colnames(apTaxProc))) > 0) { - stop(glue::glue("Required columns missing from 'inputTaxProc':", '{paste(setdiff(taxProcExpCols, colnames(apTaxProc)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputTaxProc':", + '{paste(setdiff(taxProcExpCols, colnames(apTaxProc)), collapse = ", ")}', + .sep = " " + ) + ) } } } ### Verify 'apMorph' table contains required data if data exists - morphExpCols <- c("morphospeciesID", "taxonID", "scientificName", "identificationQualifier","identificationReferences", "identifiedBy", "dataQF" ) + morphExpCols <- c( + "morphospeciesID", + "taxonID", + "scientificName", + "identificationQualifier", + "identificationReferences", + "identifiedBy", + "dataQF" + ) # Check for data - if(exists("apMorph")){ - if(is.null(apMorph) | nrow(apMorph) == 0){ - message("Warning: Table 'inputMorph' has no data. Joined output does not include identifications from the morphospecies table.") + if (is.data.frame(apMorph)){ + if(nrow(apMorph) == 0) { + message( + "Warning: Table 'inputMorph' has no data. Joined output does not include identifications from the morphospecies table." + ) } else { - # Check for required columns if data exists if (length(setdiff(morphExpCols, colnames(apMorph))) > 0) { - stop(glue::glue("Required columns missing from 'inputMorph':", '{paste(setdiff(morphExpCols, colnames(apMorph)), collapse = ", ")}', - .sep = " ")) + stop( + glue::glue( + "Required columns missing from 'inputMorph':", + '{paste(setdiff(morphExpCols, colnames(apMorph)), collapse = ", ")}', + .sep = " " + ) + ) } } } @@ -201,38 +316,72 @@ joinAquClipHarvest <- function(inputDataList, if (!is.null(apTaxProc) && nrow(apTaxProc) > 0) { # message("Join taxonomyProcessed taxonomic identifications.") # Select needed columns from apTaxProc - apTaxProc <- apTaxProc %>% - dplyr::select(-"uid", -"domainID", -"siteID", -"namedLocation", -"collectDate", -"morphospeciesID", -"sampleCode") + apTaxProc <- apTaxProc %>% + dplyr::select( + -"uid", + -"domainID", + -"siteID", + -"namedLocation", + -"collectDate", + -"morphospeciesID", + -"sampleCode" + ) # Update expert taxonomist identifications - apJoin1 <- apBio %>% - dplyr::left_join(apTaxProc, by = "sampleID", suffix = c("_bio", "_taxProc"), relationship = "many-to-many") %>% + apJoin1 <- apBio %>% + dplyr::left_join( + apTaxProc, + by = "sampleID", + suffix = c("_bio", "_taxProc"), + relationship = "many-to-many" + ) %>% dplyr::mutate( - identifiedDate = dplyr::case_when( !is.na(.data$taxonID_taxProc) ~ .data$identifiedDate_taxProc, - TRUE ~ .data$identifiedDate_bio), + TRUE ~ .data$identifiedDate_bio + ), sampleCondition = dplyr::case_when( - !is.na(.data$sampleCondition_bio)&!is.na(.data$sampleCondition_taxProc)~paste0("biomass ",.data$sampleCondition_bio," | taxonProcessed ",.data$sampleCondition_taxProc), - !is.na(.data$sampleCondition_bio)&is.na(.data$sampleCondition_taxProc)~paste0("biomass ",.data$sampleCondition_bio), - is.na(.data$sampleCondition_bio)&!is.na(.data$sampleCondition_taxProc)~paste0("taxonProcessed ",.data$sampleCondition_taxProc), - TRUE ~ NA), + !is.na(.data$sampleCondition_bio) & + !is.na(.data$sampleCondition_taxProc) ~ paste0( + "biomass ", + .data$sampleCondition_bio, + " | taxonProcessed ", + .data$sampleCondition_taxProc + ),!is.na(.data$sampleCondition_bio) & + is.na(.data$sampleCondition_taxProc) ~ paste0("biomass ", .data$sampleCondition_bio), + is.na(.data$sampleCondition_bio) & + !is.na(.data$sampleCondition_taxProc) ~ paste0("taxonProcessed ", .data$sampleCondition_taxProc), + TRUE ~ NA + ), taxonIDSourceTable = dplyr::case_when( - !is.na(.data$taxonID_taxProc)~"apl_taxonomyProcessed", - is.na(.data$taxonID_taxProc)&!is.na(.data$taxonID_bio)~"apl_biomass", - TRUE~NA), + !is.na(.data$taxonID_taxProc) ~ "apl_taxonomyProcessed", + is.na(.data$taxonID_taxProc) & + !is.na(.data$taxonID_bio) ~ "apl_biomass", + TRUE ~ NA + ), tempTaxonID = dplyr::if_else(!is.na(taxonID_taxProc), taxonID_taxProc, taxonID_bio), - scientificName = dplyr::if_else(!is.na(taxonID_taxProc), scientificName_taxProc, scientificName_bio), + scientificName = dplyr::if_else( + !is.na(taxonID_taxProc), + scientificName_taxProc, + scientificName_bio + ), identificationHistoryID = dplyr::case_when( - !is.na(.data$identificationHistoryID_bio)&!is.na(.data$identificationHistoryID_taxProc)~paste0(.data$identificationHistoryID_bio," | ",.data$identificationHistoryID_taxProc), - is.na(.data$identificationHistoryID_taxProc)&!is.na(.data$identificationHistoryID_bio)~.data$identificationHistoryID_bio, - !is.na(.data$identificationHistoryID_taxProc)&is.na(.data$identificationHistoryID_bio)~.data$identificationHistoryID_taxProc, - TRUE~NA), + !is.na(.data$identificationHistoryID_bio) & + !is.na(.data$identificationHistoryID_taxProc) ~ paste0( + .data$identificationHistoryID_bio, + " | ", + .data$identificationHistoryID_taxProc + ), + is.na(.data$identificationHistoryID_taxProc) & + !is.na(.data$identificationHistoryID_bio) ~ .data$identificationHistoryID_bio,!is.na(.data$identificationHistoryID_taxProc) & + is.na(.data$identificationHistoryID_bio) ~ .data$identificationHistoryID_taxProc, + TRUE ~ NA + ), biomassDataQF = dataQF_bio, taxProcessedDataQF = dataQF_taxProc, @@ -247,114 +396,207 @@ joinAquClipHarvest <- function(inputDataList, division = dplyr::if_else(!is.na(taxonID_taxProc), division_taxProc, division_bio), class = dplyr::if_else(!is.na(taxonID_taxProc), class_taxProc, class_bio), order = dplyr::if_else(!is.na(taxonID_taxProc), order_taxProc, order_bio), - family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_bio), - genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_bio), + family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_bio), + genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_bio), section = dplyr::if_else(!is.na(taxonID_taxProc), section_taxProc, section_bio), - specificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), specificEpithet_taxProc, specificEpithet_bio), - # infraspecificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), infraspecificEpithet_taxProc, infraspecificEpithet_bio), + specificEpithet = dplyr::if_else( + !is.na(taxonID_taxProc), + specificEpithet_taxProc, + specificEpithet_bio + ), + # infraspecificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), infraspecificEpithet_taxProc, infraspecificEpithet_bio), # variety = dplyr::if_else(!is.na(taxonID_taxProc), variety_taxProc, variety_bio), - # form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_bio), - scientificNameAuthorship = dplyr::if_else(!is.na(taxonID_taxProc), scientificNameAuthorship_taxProc, scientificNameAuthorship_bio), - identificationQualifier = dplyr::if_else(!is.na(taxonID_taxProc), identificationQualifier_taxProc, identificationQualifier_bio), - identificationReferences = dplyr::if_else(!is.na(taxonID_taxProc), identificationReferences_taxProc, identificationReferences_bio), - taxonRank = dplyr::if_else(!is.na(taxonID_taxProc), taxonRank_taxProc, taxonRank_bio), + # form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_bio), + scientificNameAuthorship = dplyr::if_else( + !is.na(taxonID_taxProc), + scientificNameAuthorship_taxProc, + scientificNameAuthorship_bio + ), + identificationQualifier = dplyr::if_else( + !is.na(taxonID_taxProc), + identificationQualifier_taxProc, + identificationQualifier_bio + ), + identificationReferences = dplyr::if_else( + !is.na(taxonID_taxProc), + identificationReferences_taxProc, + identificationReferences_bio + ), + taxonRank = dplyr::if_else( + !is.na(taxonID_taxProc), + taxonRank_taxProc, + taxonRank_bio + ), remarks = dplyr::case_when( - !is.na(.data$remarks_bio)&!is.na(.data$remarks_taxProc) ~ paste0("biomass remarks - ", .data$remarks_bio, " | taxonProcessed remarks - ", .data$remarks_taxProc ), - is.na(.data$remarks_taxProc)&!is.na(.data$remarks_bio) ~ paste0("biomass remarks - ", .data$remarks_bio), - !is.na(.data$remarks_taxProc)&is.na(.data$remarks_bio) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), - TRUE ~ NA), + !is.na(.data$remarks_bio) & + !is.na(.data$remarks_taxProc) ~ paste0( + "biomass remarks - ", + .data$remarks_bio, + " | taxonProcessed remarks - ", + .data$remarks_taxProc + ), + is.na(.data$remarks_taxProc) & + !is.na(.data$remarks_bio) ~ paste0("biomass remarks - ", .data$remarks_bio),!is.na(.data$remarks_taxProc) & + is.na(.data$remarks_bio) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), + TRUE ~ NA + ), - identifiedBy = dplyr::if_else(!is.na(taxonID_taxProc), identifiedBy_taxProc, identifiedBy_bio), - identifiedDate = dplyr::if_else(!is.na(identifiedDate_taxProc), identifiedDate_taxProc, identifiedDate_bio) + identifiedBy = dplyr::if_else( + !is.na(taxonID_taxProc), + identifiedBy_taxProc, + identifiedBy_bio + ), + identifiedDate = dplyr::if_else( + !is.na(identifiedDate_taxProc), + identifiedDate_taxProc, + identifiedDate_bio + ) - ) %>% - select(-matches("_taxProc"), - -matches("_bio"), - -"targetTaxaPresent", -"uid") + ) %>% + select(-matches("_taxProc"),-matches("_bio"),-"targetTaxaPresent", + -"uid") } else { # message("No data joined from apl_taxonomyProcessed table.") # rename columns if no taxProc join - apJoin1 <- apBio %>% - mutate(tempTaxonID = taxonID, - remarks = dplyr::if_else(is.na(remarks), NA, paste0("biomass remarks - ", remarks)), - perTaxonRelease = release, - taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apl_biomass"), - perTaxonDataQF = dataQF, - perTaxonPublicationDate = publicationDate) %>% - select(-"taxonID", -"release", -"dataQF", -"publicationDate", -"uid") - } + apJoin1 <- apBio %>% + mutate( + tempTaxonID = taxonID, + remarks = dplyr::if_else(is.na(remarks), NA, paste0("biomass remarks - ", remarks)), + perTaxonRelease = release, + taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apl_biomass"), + perTaxonDataQF = dataQF, + perTaxonPublicationDate = publicationDate + ) %>% + select(-"taxonID", + -"release", + -"dataQF", + -"publicationDate", + -"uid") + } ### Join apJoin1 and apMorph tables # Select needed columns from apMorph - if(!is.null(apMorph) && nrow(apMorph) > 0){ + if (!is.null(apMorph) && nrow(apMorph) > 0) { # message("Join morphospecies taxonomic identifications.") apMorph <- apMorph %>% - dplyr::select("taxonID", - "scientificName", - "morphospeciesID", - "identificationQualifier", - "identificationReferences", - "identifiedBy", - # "identifiedDate", - "dataQF" + dplyr::select( + "taxonID", + "scientificName", + "morphospeciesID", + "identificationQualifier", + "identificationReferences", + "identifiedBy", + # "identifiedDate", + "dataQF" ) # Update morphospecies taxon identifications apJoin2 <- apJoin1 %>% + dplyr::mutate(morphospeciesID = dplyr::if_else( + !is.na(morphospeciesID), + paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), + morphospeciesID + )) %>% + dplyr::left_join(apMorph, + by = "morphospeciesID", + suffix = c("_bio", "_morph")) %>% + # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% dplyr::mutate( - morphospeciesID = dplyr::if_else(!is.na(morphospeciesID), paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), morphospeciesID) - ) %>% - dplyr::left_join(apMorph, by = "morphospeciesID", suffix = c("_bio", "_morph")) %>% - # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% - dplyr::mutate( - taxonIDSourceTable = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), "apc_morphospecies", taxonIDSourceTable), - acceptedTaxonID = dplyr::if_else(!is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), taxonID, tempTaxonID), - scientificName = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), scientificName_morph, scientificName_bio), - identificationQualifier = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationQualifier_morph, identificationQualifier_bio), - identificationReferences = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identificationReferences_morph, identificationReferences_bio), - identifiedBy = dplyr::if_else( !is.na(taxonID) & tempTaxonID %in% c('2PLANT', 'UNKALG'), identifiedBy_morph, identifiedBy_bio), - identifiedDate = NA, #not currently in pub table + taxonIDSourceTable = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + "apc_morphospecies", + taxonIDSourceTable + ), + acceptedTaxonID = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + taxonID, + tempTaxonID + ), + scientificName = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + scientificName_morph, + scientificName_bio + ), + identificationQualifier = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + identificationQualifier_morph, + identificationQualifier_bio + ), + identificationReferences = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + identificationReferences_morph, + identificationReferences_bio + ), + identifiedBy = dplyr::if_else( + !is.na(taxonID) & + tempTaxonID %in% c('2PLANT', 'UNKALG'), + identifiedBy_morph, + identifiedBy_bio + ), + identifiedDate = NA, + #not currently in pub table morphospeciesDataQF = dataQF - ) %>% - dplyr::select( - -"taxonID", -"tempTaxonID", -"dataQF", - -matches("_morph"), - -matches("_bio") - ) + ) %>% + dplyr::select(-"taxonID", + -"tempTaxonID", + -"dataQF",-matches("_morph"),-matches("_bio")) } else { # message("No data joined from apc_morphospecies table.") - apJoin2 <- apJoin1 %>% - mutate(acceptedTaxonID = tempTaxonID) %>% - select(-"tempTaxonID") + apJoin2 <- apJoin1 %>% + mutate(acceptedTaxonID = tempTaxonID) %>% + select(-"tempTaxonID") } - ### Join apClip and apBio tables + ### Join apClip and apBio tables # Update morphospecies taxon identifications joinClipHarvest <- apClip %>% - dplyr::select(-"benthicArea", -"namedLocation", -"domainID", -"siteID", -"startDate", -"collectDate", -"fieldIDCode" ) %>% - dplyr::left_join(apJoin2, by = "fieldID", suffix = c("_clip", "_bio")) %>% + dplyr::select( + -"benthicArea", + -"namedLocation", + -"domainID", + -"siteID", + -"startDate", + -"collectDate", + -"fieldIDCode" + ) %>% + dplyr::left_join(apJoin2, by = "fieldID", suffix = c("_clip", "_bio")) %>% dplyr::mutate( remarks = dplyr::case_when( - !is.na(.data$remarks_bio)&!is.na(.data$remarks_clip) ~ paste0("clipHarvest remarks - ", .data$remarks_clip, " | ", .data$remarks_bio), - !is.na(.data$remarks_bio)&is.na(.data$remarks_clip) ~ .data$remarks_bio, - is.na(.data$remarks_bio)&!is.na(.data$remarks_clip) ~ paste0("clipHarvest remarks - ", .data$remarks_clip), - TRUE ~ NA), - recordedBy = dplyr::if_else(!is.na(.data$recordedBy_clip), .data$recordedBy_clip, .data$recordedBy_bio), + !is.na(.data$remarks_bio) & + !is.na(.data$remarks_clip) ~ paste0( + "clipHarvest remarks - ", + .data$remarks_clip, + " | ", + .data$remarks_bio + ),!is.na(.data$remarks_bio) & + is.na(.data$remarks_clip) ~ .data$remarks_bio, + is.na(.data$remarks_bio) & + !is.na(.data$remarks_clip) ~ paste0("clipHarvest remarks - ", .data$remarks_clip), + TRUE ~ NA + ), + recordedBy = dplyr::if_else( + !is.na(.data$recordedBy_clip), + .data$recordedBy_clip, + .data$recordedBy_bio + ), clipDataQF = .data$dataQF, clipPublicationDate = publicationDate, clipRelease = release - ) %>% - dplyr::select( - -"dataQF", -"publicationDate", -"release", - -matches("_bio"), - -matches("_clip") - ) + ) %>% + dplyr::select(-"dataQF", + -"publicationDate", + -"release",-matches("_bio"),-matches("_clip")) # Filter out bout 1 and 3 data diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 602d611..d16adf3 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -86,22 +86,11 @@ joinAquPointCount <- function(inputDataList, } # end missing conditional - # message(paste('inputDataList= ', inputDataList)) ### Verify table inputs are NA if inputDataList is supplied - # any_table_supplied <- !isTRUE(is.na(inputPoint)) || !isTRUE(is.na(inputPerTax)) || - # !isTRUE(is.na(inputTaxProc)) || !isTRUE(is.na(inputMorph)) - - # if (!missing(inputDataList) && !isTRUE(is.na(inputDataList)) && any_table_supplied) { - # stop("Provide either 'inputDataList' OR individual table inputs, not both.") - # } - # if (inherits(inputDataList, "list") & (!is.logical(inputPoint) | !is.logical(inputPerTax) | !is.logical(inputTaxProc) | !is.logical(inputMorph))) { - # stop("When 'inputDataList' is supplied all table input arguments must be NA") - # } if (!is.null(inputDataList)) { if (!isTRUE(is.na(inputPoint)) || !isTRUE(is.na(inputPerTax)) || - !isTRUE(is.na(inputTaxProc)) || - !isTRUE(is.na(inputMorph))) { + !isTRUE(is.na(inputTaxProc)) || !isTRUE(is.na(inputMorph))) { stop("When 'inputDataList' is supplied, all table input arguments must be NA.") } } @@ -111,7 +100,6 @@ joinAquPointCount <- function(inputDataList, if (is.null(inputDataList) & ( !is.data.frame(inputPoint) || !is.data.frame(inputPerTax) - # | !inherits(inputTaxProc, "data.frame") | !inherits(inputMorph, "data.frame") )) { stop("Data frames must be supplied for table inputs if 'inputDataList' is missing") diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R index ef0c383..4973fc7 100644 --- a/tests/testthat/test_joinAquClipHarvest.R +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -128,7 +128,7 @@ testthat::test_that(desc = "Output data frame row number table input", { testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$uid == '1bcced55-6162-4cc1-b91b-63edb4422f7f')]), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'PRLA.20230703.AP1.P6')]), expected = "apl_taxonomyProcessed") }) @@ -154,7 +154,7 @@ testthat::test_that(desc = "Output data frame source: biomass", { expected = "apl_biomass") testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'CRAM.20230720.AP1.P2')]), - expected = "ISTE5") + expected = "DRADA") }) @@ -178,8 +178,8 @@ testthat::test_that(desc = "Required tables present in 'inputDataList' input", { testthat::test_that(desc = "Table inputs NA when required", { testthat::expect_error(object = joinAquClipHarvest(inputDataList = testList, - inputPoint = testPoint), - regexp = "When 'inputDataList' is supplied all table input arguments must be NA") + inputBio = testBio), + regexp = "When 'inputDataList' is supplied, all table input arguments must be NA") }) @@ -187,7 +187,7 @@ testthat::test_that(desc = "Table inputs NA when required", { ### Tests: Generate expected errors with table inputs #### testthat::test_that(desc = "Table inputs are data frames when required", { - testthat::expect_error(object = joinAquClipHarvest(inputPoint = testPoint, + testthat::expect_error(object = joinAquClipHarvest(inputMorph = testMorph, inputBio = testBio), regexp = "Data frames must be supplied for table inputs if 'inputDataList' is missing") }) @@ -240,10 +240,10 @@ testthat::test_that(desc = "Table 'inputClip' missing data", { testthat::test_that(desc = "Table 'inputTaxProc' missing column", { testthat::expect_error(object = joinAquClipHarvest(inputTaxProc = testTaxProc %>% - dplyr::select(-acceptedTaxonID), - inputPoint = testPoint, - inputPerTax = testPerTax), - regexp = "Required columns missing from 'inputTaxProc': acceptedTaxonID") + dplyr::select(-taxonID), + inputBio = testBio, + inputClip = testClip), + regexp = "Required columns missing from 'inputTaxProc': taxonID") }) @@ -254,8 +254,8 @@ testthat::test_that(desc = "Table 'inputMorph' missing column", { testthat::expect_error(object = joinAquClipHarvest(inputMorph = testMorph %>% dplyr::select(-taxonID), - inputPoint = testPoint, - inputPerTax = testPerTax), + inputBio = testBio, + inputClip = testClip), regexp = "Required columns missing from 'inputMorph': taxonID") }) From cf9004af6f3e8294e2bef00cc02c8be7c46aec1f Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 07:54:46 -0700 Subject: [PATCH 10/40] Update joinAquClipHarvest.R --- R/joinAquClipHarvest.R | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index e95d298..6084df1 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -20,11 +20,11 @@ #' #' @param inputMorph The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] #' -#' @return A table containing clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent == 'Y'. +#' @return A table containing bout 2 clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent = 'Y' and an identification has been published.. #' #' @examples #' \dontrun{ -#' # Obtain NEON Aquatic Plant Point Count data +#' # Obtain NEON Aquatic Plant Clip Harvest data #' apl <- neonUtilities::loadByProduct( #' dpID = "DP1.20066.001", #' site = "all", @@ -34,7 +34,7 @@ #' check.size = FALSE #' ) #' -#' # Join downloaded root data +#' # Join downloaded clip harvest data #' df <- neonPlants::joinAquClipHarvest( #' inputDataList = apl, #' inputBio = NA, @@ -309,11 +309,11 @@ joinAquClipHarvest <- function(inputDataList, } } - ### Verify that 'apClip' table contains bout 2 data + ### Join apBio and apTaxProc tables using sampleID - if (!is.null(apTaxProc) && nrow(apTaxProc) > 0) { + if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { # message("Join taxonomyProcessed taxonomic identifications.") # Select needed columns from apTaxProc apTaxProc <- apTaxProc %>% @@ -458,7 +458,7 @@ joinAquClipHarvest <- function(inputDataList, -"uid") } else { - # message("No data joined from apl_taxonomyProcessed table.") + message("No data joined from apl_taxonomyProcessed table.") # rename columns if no taxProc join apJoin1 <- apBio %>% mutate( @@ -479,7 +479,7 @@ joinAquClipHarvest <- function(inputDataList, ### Join apJoin1 and apMorph tables # Select needed columns from apMorph - if (!is.null(apMorph) && nrow(apMorph) > 0) { + if (is.data.frame(apMorph) && nrow(apMorph) > 0) { # message("Join morphospecies taxonomic identifications.") apMorph <- apMorph %>% dplyr::select( @@ -550,7 +550,7 @@ joinAquClipHarvest <- function(inputDataList, -"tempTaxonID", -"dataQF",-matches("_morph"),-matches("_bio")) } else { - # message("No data joined from apc_morphospecies table.") + message("No data joined from apc_morphospecies table.") apJoin2 <- apJoin1 %>% mutate(acceptedTaxonID = tempTaxonID) %>% select(-"tempTaxonID") @@ -602,7 +602,6 @@ joinAquClipHarvest <- function(inputDataList, # Filter out bout 1 and 3 data joinClipHarvest <- joinClipHarvest %>% filter(boutNumber == '2') - return(joinClipHarvest) } #function closer From 229bcc3e49b4e3a39f0555d217ccd4eb34ffee3b Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 08:03:30 -0700 Subject: [PATCH 11/40] Update joinAquClipHarvest.Rd --- man/joinAquClipHarvest.Rd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/joinAquClipHarvest.Rd b/man/joinAquClipHarvest.Rd index 02e466a..91c33b1 100644 --- a/man/joinAquClipHarvest.Rd +++ b/man/joinAquClipHarvest.Rd @@ -24,7 +24,7 @@ joinAquClipHarvest( \item{inputMorph}{The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} } \value{ -A table containing clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent == 'Y'. +A table containing bout 2 clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent = 'Y' and an identification has been published.. } \description{ Join the 'apl_clipHarvest', 'apl_biomass', 'apl_taxonomyProcessed' and 'apc_morphospecies' tables to generate a single table that contains clip harvest data with taxonomic identifications for each sampleID. Data inputs are NEON Aquatic Plant Bryophyte Macroalgae Clip Harvest (DP1.20066.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. @@ -38,7 +38,7 @@ In the joined output table, the 'acceptedTaxonID' and associated taxonomic field } \examples{ \dontrun{ -# Obtain NEON Aquatic Plant Point Count data +# Obtain NEON Aquatic Plant Clip Harvest data apl <- neonUtilities::loadByProduct( dpID = "DP1.20066.001", site = "all", @@ -48,7 +48,7 @@ tabl = "all", check.size = FALSE ) -# Join downloaded root data +# Join downloaded clip harvest data df <- neonPlants::joinAquClipHarvest( inputDataList = apl, inputBio = NA, From 207e67df076e2c0c9abd05c664c8121c7729903c Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 08:15:11 -0700 Subject: [PATCH 12/40] cleaning --- R/joinAquClipHarvest.R | 4 ++-- R/joinAquPointCount.R | 6 ++--- tests/testthat/test_joinAquClipHarvest.R | 28 ------------------------ 3 files changed, 5 insertions(+), 33 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 6084df1..ba0ed8c 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -214,7 +214,7 @@ joinAquClipHarvest <- function(inputDataList, } # Check for bout 2 data - if (nrow(apClip %>% filter(boutNumber == '2')) == 0) { + if (nrow(apClip %>% dplyr::filter(.data$boutNumber == '2')) == 0) { stop( glue::glue( "The input data does not contain any bout 2 records. No taxonomy data to join." @@ -600,7 +600,7 @@ joinAquClipHarvest <- function(inputDataList, # Filter out bout 1 and 3 data - joinClipHarvest <- joinClipHarvest %>% filter(boutNumber == '2') + joinClipHarvest <- joinClipHarvest %>% dplyr::filter(.data$boutNumber == '2') return(joinClipHarvest) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index d16adf3..43c0f40 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -453,14 +453,14 @@ joinAquPointCount <- function(inputDataList, ) ) %>% - select(-matches("_taxProc"),-matches("_perTax"),-"targetTaxaPresent", + dplyr::select(-matches("_taxProc"),-matches("_perTax"),-"targetTaxaPresent", -"uid") } else { message("No data joined from apc_taxonomyProcessed table.") # rename columns if no taxProc join apJoin1 <- apPerTax %>% - mutate( + dplyr::mutate( tempTaxonID = taxonID, remarks = dplyr::if_else(is.na(remarks), NA, paste0("perTaxon remarks - ", remarks)), perTaxonRelease = release, @@ -468,7 +468,7 @@ joinAquPointCount <- function(inputDataList, perTaxonDataQF = dataQF, perTaxonPublicationDate = publicationDate ) %>% - select(-"taxonID", + dplyr::select(-"taxonID", -"release", -"dataQF", -"publicationDate", diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R index 4973fc7..f912cfb 100644 --- a/tests/testthat/test_joinAquClipHarvest.R +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -1,36 +1,8 @@ ### Unit tests for joinAquClipHarvest function #### ### POC: Madaline Ritter, ritterm1@BattelleEcology.org -### Download initial test data - DELETE -# clip_testDat <- neonUtilities::loadByProduct( -# dpID="DP1.20066.001", -# check.size=F, -# startdate = '2023-07', -# enddate = '2023-07', -# site = "all", -# # site = "SYCA", -# package='expanded', -# include.provisional = T, -# release = "LATEST", -# token = Sys.getenv('NEON_PAT')) -# -# clip_testDat$apl_identificationHistory <- NULL -# clip_testDat$apl_taxonomyRaw <- NULL -# clip_testDat$categoricalCodes_20066 <- NULL -# clip_testDat$issueLog_20066 <- NULL -# clip_testDat$readme_20066 <- NULL -# clip_testDat$validation_20066 <- NULL -# clip_testDat$variables_20066 <- NULL -# -# View(clip_testDat$apc_morphospecies) -# View(clip_testDat$apl_biomass) -# View(clip_testDat$apl_clipHarvest) -# View(clip_testDat$apl_taxonomyProcessed) -# -# saveRDS(clip_testDat, "C:/Users/ritterm1/Documents/GitHub/a_neonPackages/neonPlants/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds") # testList <- readRDS("C:/Users/ritterm1/Documents/GitHub/a_neonPackages/neonPlants/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds") - ### Read in test data testList <- readRDS(testthat::test_path("testdata", "joinAquClipHarvest_testData_202307.rds")) testBio <- testList$apl_biomass From 12a89ea40ef79a8030260fcef2ec99d4645effc3 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 10:06:27 -0700 Subject: [PATCH 13/40] update --- R/joinAquClipHarvest.R | 168 ++++++++++++++++++------------------ R/joinAquPointCount.R | 187 ++++++++++++++++++++++------------------- 2 files changed, 183 insertions(+), 172 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index ba0ed8c..f1e535b 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -311,7 +311,7 @@ joinAquClipHarvest <- function(inputDataList, - ### Join apBio and apTaxProc tables using sampleID + ### Join apBio and apTaxProc tables using sampleID #### if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { # message("Join taxonomyProcessed taxonomic identifications.") @@ -362,12 +362,12 @@ joinAquClipHarvest <- function(inputDataList, TRUE ~ NA ), - tempTaxonID = dplyr::if_else(!is.na(taxonID_taxProc), taxonID_taxProc, taxonID_bio), + tempTaxonID = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$taxonID_taxProc, .data$taxonID_bio), scientificName = dplyr::if_else( - !is.na(taxonID_taxProc), - scientificName_taxProc, - scientificName_bio + !is.na(.data$taxonID_taxProc), + .data$scientificName_taxProc, + .data$scientificName_bio ), identificationHistoryID = dplyr::case_when( @@ -383,49 +383,49 @@ joinAquClipHarvest <- function(inputDataList, TRUE ~ NA ), - biomassDataQF = dataQF_bio, - taxProcessedDataQF = dataQF_taxProc, + biomassDataQF = .data$dataQF_bio, + taxProcessedDataQF = .data$dataQF_taxProc, - biomassPublicationDate = publicationDate_bio, - taxProcessedPublicationDate = publicationDate_taxProc, + biomassPublicationDate = .data$publicationDate_bio, + taxProcessedPublicationDate = .data$publicationDate_taxProc, - biomassRelease = release_bio, - taxProcessedRelease = release_taxProc, + biomassRelease = .data$release_bio, + taxProcessedRelease = .data$release_taxProc, # phylum = dplyr::if_else(!is.na(taxonID_taxProc), phylum_taxProc, phylum_bio), - division = dplyr::if_else(!is.na(taxonID_taxProc), division_taxProc, division_bio), - class = dplyr::if_else(!is.na(taxonID_taxProc), class_taxProc, class_bio), - order = dplyr::if_else(!is.na(taxonID_taxProc), order_taxProc, order_bio), - family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_bio), - genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_bio), - section = dplyr::if_else(!is.na(taxonID_taxProc), section_taxProc, section_bio), + division = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$division_taxProc, .data$division_bio), + class = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$class_taxProc, .data$class_bio), + order = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$order_taxProc, .data$order_bio), + family = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$family_taxProc, .data$family_bio), + genus = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$genus_taxProc, .data$genus_bio), + section = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$section_taxProc, .data$section_bio), specificEpithet = dplyr::if_else( - !is.na(taxonID_taxProc), - specificEpithet_taxProc, - specificEpithet_bio + !is.na(.data$taxonID_taxProc), + .data$specificEpithet_taxProc, + .data$specificEpithet_bio ), # infraspecificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), infraspecificEpithet_taxProc, infraspecificEpithet_bio), # variety = dplyr::if_else(!is.na(taxonID_taxProc), variety_taxProc, variety_bio), # form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_bio), scientificNameAuthorship = dplyr::if_else( - !is.na(taxonID_taxProc), - scientificNameAuthorship_taxProc, - scientificNameAuthorship_bio + !is.na(.data$taxonID_taxProc), + .data$scientificNameAuthorship_taxProc, + .data$scientificNameAuthorship_bio ), identificationQualifier = dplyr::if_else( - !is.na(taxonID_taxProc), - identificationQualifier_taxProc, - identificationQualifier_bio + !is.na(.data$taxonID_taxProc), + .data$identificationQualifier_taxProc, + .data$identificationQualifier_bio ), identificationReferences = dplyr::if_else( - !is.na(taxonID_taxProc), - identificationReferences_taxProc, - identificationReferences_bio + !is.na(.data$taxonID_taxProc), + .data$identificationReferences_taxProc, + .data$identificationReferences_bio ), taxonRank = dplyr::if_else( - !is.na(taxonID_taxProc), - taxonRank_taxProc, - taxonRank_bio + !is.na(.data$taxonID_taxProc), + .data$taxonRank_taxProc, + .data$taxonRank_bio ), remarks = dplyr::case_when( @@ -443,40 +443,40 @@ joinAquClipHarvest <- function(inputDataList, ), identifiedBy = dplyr::if_else( - !is.na(taxonID_taxProc), - identifiedBy_taxProc, - identifiedBy_bio + !is.na(.data$taxonID_taxProc), + .data$identifiedBy_taxProc, + .data$identifiedBy_bio ), identifiedDate = dplyr::if_else( - !is.na(identifiedDate_taxProc), - identifiedDate_taxProc, - identifiedDate_bio + !is.na(.data$identifiedDate_taxProc), + .data$identifiedDate_taxProc, + .data$identifiedDate_bio ) ) %>% - select(-matches("_taxProc"),-matches("_bio"),-"targetTaxaPresent", - -"uid") + dplyr::select(-dplyr::matches("_taxProc"),-dplyr::matches("_bio"), + -"targetTaxaPresent", -"uid") } else { message("No data joined from apl_taxonomyProcessed table.") # rename columns if no taxProc join apJoin1 <- apBio %>% - mutate( - tempTaxonID = taxonID, - remarks = dplyr::if_else(is.na(remarks), NA, paste0("biomass remarks - ", remarks)), - perTaxonRelease = release, - taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apl_biomass"), - perTaxonDataQF = dataQF, - perTaxonPublicationDate = publicationDate + dplyr::mutate( + tempTaxonID = .data$taxonID, + remarks = dplyr::if_else(is.na(.data$remarks), NA, paste0("biomass remarks - ", .data$remarks)), + perTaxonRelease = .data$release, + taxonIDSourceTable = dplyr::if_else(is.na(.data$taxonID), NA, "apl_biomass"), + perTaxonDataQF = .data$dataQF, + perTaxonPublicationDate = .data$publicationDate ) %>% - select(-"taxonID", + dplyr::select(-"taxonID", -"release", -"dataQF", -"publicationDate", -"uid") } - ### Join apJoin1 and apMorph tables + ### Join apJoin1 and apMorph tables #### # Select needed columns from apMorph if (is.data.frame(apMorph) && nrow(apMorph) > 0) { @@ -496,9 +496,9 @@ joinAquClipHarvest <- function(inputDataList, # Update morphospecies taxon identifications apJoin2 <- apJoin1 %>% dplyr::mutate(morphospeciesID = dplyr::if_else( - !is.na(morphospeciesID), - paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), - morphospeciesID + !is.na(.data$morphospeciesID), + paste0(.data$morphospeciesID, ".", substr(.data$collectDate, 1, 4)), + .data$morphospeciesID )) %>% dplyr::left_join(apMorph, by = "morphospeciesID", @@ -506,58 +506,58 @@ joinAquClipHarvest <- function(inputDataList, # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% dplyr::mutate( taxonIDSourceTable = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), "apc_morphospecies", - taxonIDSourceTable + .data$taxonIDSourceTable ), acceptedTaxonID = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - taxonID, - tempTaxonID + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$taxonID, + .data$tempTaxonID ), scientificName = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - scientificName_morph, - scientificName_bio + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$scientificName_morph, + .data$scientificName_bio ), identificationQualifier = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - identificationQualifier_morph, - identificationQualifier_bio + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$identificationQualifier_morph, + .data$identificationQualifier_bio ), identificationReferences = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - identificationReferences_morph, - identificationReferences_bio + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$identificationReferences_morph, + .data$identificationReferences_bio ), identifiedBy = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - identifiedBy_morph, - identifiedBy_bio + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$identifiedBy_morph, + .data$identifiedBy_bio ), identifiedDate = NA, #not currently in pub table - morphospeciesDataQF = dataQF + morphospeciesDataQF = .data$dataQF ) %>% dplyr::select(-"taxonID", -"tempTaxonID", - -"dataQF",-matches("_morph"),-matches("_bio")) + -"dataQF",-dplyr::matches("_morph"),-dplyr::matches("_bio")) } else { message("No data joined from apc_morphospecies table.") apJoin2 <- apJoin1 %>% - mutate(acceptedTaxonID = tempTaxonID) %>% - select(-"tempTaxonID") + dplyr::mutate(acceptedTaxonID = .data$tempTaxonID) %>% + dplyr::select(-"tempTaxonID") } - ### Join apClip and apBio tables + ### Join apClip and apBio tables #### # Update morphospecies taxon identifications joinClipHarvest <- apClip %>% @@ -591,15 +591,15 @@ joinAquClipHarvest <- function(inputDataList, .data$recordedBy_bio ), clipDataQF = .data$dataQF, - clipPublicationDate = publicationDate, - clipRelease = release + clipPublicationDate = .data$publicationDate, + clipRelease = .data$release ) %>% dplyr::select(-"dataQF", -"publicationDate", - -"release",-matches("_bio"),-matches("_clip")) + -"release",-dplyr::matches("_bio"),-dplyr::matches("_clip")) - # Filter out bout 1 and 3 data + ### Filter out bout 1 and 3 data #### joinClipHarvest <- joinClipHarvest %>% dplyr::filter(.data$boutNumber == '2') return(joinClipHarvest) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 43c0f40..c3c0598 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -319,7 +319,7 @@ joinAquPointCount <- function(inputDataList, -"collectDate", -"morphospeciesID" ) %>% - dplyr::rename(taxonID = acceptedTaxonID) + dplyr::rename(taxonID = .data$acceptedTaxonID) # Update expert taxonomist identifications apJoin1 <- apPerTax %>% @@ -350,14 +350,14 @@ joinAquPointCount <- function(inputDataList, TRUE ~ NA ), tempTaxonID = dplyr::if_else( - !is.na(taxonID_taxProc), - taxonID_taxProc, - taxonID_perTax + !is.na(.data$taxonID_taxProc), + .data$taxonID_taxProc, + .data$taxonID_perTax ), scientificName = dplyr::if_else( - !is.na(taxonID_taxProc), - scientificName_taxProc, - scientificName_perTax + !is.na(.data$taxonID_taxProc), + .data$scientificName_taxProc, + .data$scientificName_perTax ), identificationHistoryID = dplyr::case_when( !is.na(.data$identificationHistoryID_perTax) & @@ -371,62 +371,73 @@ joinAquPointCount <- function(inputDataList, is.na(.data$identificationHistoryID_perTax) ~ .data$identificationHistoryID_taxProc, TRUE ~ NA ), - perTaxonDataQF = dataQF_perTax, - taxProcessedDataQF = dataQF_taxProc, - perTaxonPublicationDate = publicationDate_perTax, - taxProcessedPublicationDate = publicationDate_taxProc, - taxProcessedRelease = release_taxProc, - perTaxonRelease = release_perTax, - phylum = dplyr::if_else(!is.na(taxonID_taxProc), phylum_taxProc, phylum_perTax), + perTaxonDataQF = .data$dataQF_perTax, + taxProcessedDataQF = .data$dataQF_taxProc, + perTaxonPublicationDate = .data$publicationDate_perTax, + taxProcessedPublicationDate = .data$publicationDate_taxProc, + taxProcessedRelease = .data$release_taxProc, + perTaxonRelease = .data$release_perTax, + phylum = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$phylum_taxProc, .data$phylum_perTax), division = dplyr::if_else( - !is.na(taxonID_taxProc), - division_taxProc, - division_perTax + !is.na(.data$taxonID_taxProc), + .data$division_taxProc, + .data$division_perTax ), - class = dplyr::if_else(!is.na(taxonID_taxProc), class_taxProc, class_perTax), - order = dplyr::if_else(!is.na(taxonID_taxProc), order_taxProc, order_perTax), - family = dplyr::if_else(!is.na(taxonID_taxProc), family_taxProc, family_perTax), - genus = dplyr::if_else(!is.na(taxonID_taxProc), genus_taxProc, genus_perTax), + class = dplyr::if_else( + !is.na(.data$taxonID_taxProc), + .data$class_taxProc, + .data$class_perTax), + order = dplyr::if_else(!is.na(.data$taxonID_taxProc), + .data$order_taxProc, + .data$order_perTax), + family = dplyr::if_else(!is.na(.data$taxonID_taxProc), + .data$family_taxProc, + .data$family_perTax), + genus = dplyr::if_else(!is.na(.data$taxonID_taxProc), + .data$genus_taxProc, + .data$genus_perTax), section = dplyr::if_else( - !is.na(taxonID_taxProc), - section_taxProc, - section_perTax + !is.na(.data$taxonID_taxProc), + .data$section_taxProc, + .data$section_perTax ), specificEpithet = dplyr::if_else( - !is.na(taxonID_taxProc), - specificEpithet_taxProc, - specificEpithet_perTax + !is.na(.data$taxonID_taxProc), + .data$specificEpithet_taxProc, + .data$specificEpithet_perTax ), infraspecificEpithet = dplyr::if_else( - !is.na(taxonID_taxProc), - infraspecificEpithet_taxProc, - infraspecificEpithet_perTax + !is.na(.data$taxonID_taxProc), + .data$infraspecificEpithet_taxProc, + .data$infraspecificEpithet_perTax ), variety = dplyr::if_else( - !is.na(taxonID_taxProc), - variety_taxProc, - variety_perTax + !is.na(.data$taxonID_taxProc), + .data$variety_taxProc, + .data$variety_perTax ), - form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_perTax), + form = dplyr::if_else(!is.na(.data$taxonID_taxProc), + .data$form_taxProc, + .data$form_perTax), scientificNameAuthorship = dplyr::if_else( - !is.na(taxonID_taxProc), - scientificNameAuthorship_taxProc, - scientificNameAuthorship_perTax + !is.na(.data$taxonID_taxProc), + .data$scientificNameAuthorship_taxProc, + .data$scientificNameAuthorship_perTax ), identificationQualifier = dplyr::if_else( - !is.na(taxonID_taxProc), - identificationQualifier_taxProc, - identificationQualifier_perTax + !is.na(.data$taxonID_taxProc), + .data$identificationQualifier_taxProc, + .data$identificationQualifier_perTax ), identificationReferences = dplyr::if_else( - !is.na(taxonID_taxProc), - identificationReferences_taxProc, - identificationReferences_perTax + !is.na(.data$taxonID_taxProc), + .data$identificationReferences_taxProc, + .data$identificationReferences_perTax ), taxonRank = dplyr::if_else( - !is.na(taxonID_taxProc), - taxonRank_taxProc, - taxonRank_perTax + !is.na(.data$taxonID_taxProc), + .data$taxonRank_taxProc, + .data$taxonRank_perTax ), remarks = dplyr::case_when( !is.na(.data$remarks_perTax) & @@ -442,18 +453,18 @@ joinAquPointCount <- function(inputDataList, TRUE ~ NA ), identifiedBy = dplyr::if_else( - !is.na(taxonID_taxProc), - identifiedBy_taxProc, - identifiedBy_perTax + !is.na(.data$taxonID_taxProc), + .data$identifiedBy_taxProc, + .data$identifiedBy_perTax ), identifiedDate = dplyr::if_else( - !is.na(identifiedDate_taxProc), - identifiedDate_taxProc, - identifiedDate_perTax + !is.na(.data$identifiedDate_taxProc), + .data$identifiedDate_taxProc, + .data$identifiedDate_perTax ) ) %>% - dplyr::select(-matches("_taxProc"),-matches("_perTax"),-"targetTaxaPresent", + dplyr::select(-dplyr::matches("_taxProc"),-dplyr::matches("_perTax"),-"targetTaxaPresent", -"uid") } else { @@ -461,12 +472,12 @@ joinAquPointCount <- function(inputDataList, # rename columns if no taxProc join apJoin1 <- apPerTax %>% dplyr::mutate( - tempTaxonID = taxonID, - remarks = dplyr::if_else(is.na(remarks), NA, paste0("perTaxon remarks - ", remarks)), - perTaxonRelease = release, - taxonIDSourceTable = dplyr::if_else(is.na(taxonID), NA, "apc_perTaxon"), - perTaxonDataQF = dataQF, - perTaxonPublicationDate = publicationDate + tempTaxonID = .data$taxonID, + remarks = dplyr::if_else(is.na(.data$remarks), NA, paste0("perTaxon remarks - ", .data$remarks)), + perTaxonRelease = .data$release, + taxonIDSourceTable = dplyr::if_else(is.na(.data$taxonID), NA, "apc_perTaxon"), + perTaxonDataQF = .data$dataQF, + perTaxonPublicationDate = .data$publicationDate ) %>% dplyr::select(-"taxonID", -"release", @@ -475,7 +486,7 @@ joinAquPointCount <- function(inputDataList, -"uid") } - ### Join apJoin1 and apMorph tables + ### Join apJoin1 and apMorph tables #### # Select needed columns from apMorph if (is.data.frame(apMorph) && nrow(apMorph) > 0) { @@ -495,9 +506,9 @@ joinAquPointCount <- function(inputDataList, # Update morphospecies taxon identifications apJoin2 <- apJoin1 %>% dplyr::mutate(morphospeciesID = dplyr::if_else( - !is.na(morphospeciesID), - paste0(morphospeciesID, ".", substr(collectDate, 1, 4)), - morphospeciesID + !is.na(.data$morphospeciesID), + paste0(.data$morphospeciesID, ".", substr(.data$collectDate, 1, 4)), + .data$morphospeciesID )) %>% dplyr::left_join(apMorph, by = "morphospeciesID", @@ -505,54 +516,54 @@ joinAquPointCount <- function(inputDataList, # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% dplyr::mutate( taxonIDSourceTable = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), "apc_morphospecies", - taxonIDSourceTable + .data$taxonIDSourceTable ), acceptedTaxonID = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - taxonID, - tempTaxonID + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$taxonID, + .data$tempTaxonID ), scientificName = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - scientificName_morph, - scientificName_perTax + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$scientificName_morph, + .data$scientificName_perTax ), identificationQualifier = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - identificationQualifier_morph, - identificationQualifier_perTax + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$identificationQualifier_morph, + .data$identificationQualifier_perTax ), identificationReferences = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - identificationReferences_morph, - identificationReferences_perTax + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$identificationReferences_morph, + .data$identificationReferences_perTax ), identifiedBy = dplyr::if_else( - !is.na(taxonID) & - tempTaxonID %in% c('2PLANT', 'UNKALG'), - identifiedBy_morph, - identifiedBy_perTax + !is.na(.data$taxonID) & + .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$identifiedBy_morph, + .data$identifiedBy_perTax ), identifiedDate = NA #not currently in pub table #dataQF = ifelse(!is.na(taxonID), )#either that may be relevant? ) %>% dplyr::select(-"taxonID", - -"tempTaxonID",-matches("_morph"),-matches("_perTax")) + -"tempTaxonID",-dplyr::matches("_morph"),-dplyr::matches("_perTax")) } else { message("No data joined from apc_morphospecies table.") apJoin2 <- apJoin1 } - ### Join apPoint and apPerTax tables + ### Join apPoint and apPerTax tables #### # Update morphospecies taxon identifications joinPointCounts <- apPoint %>% From ab85436044da51d58427707afd10262245669f8c Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 10:31:17 -0700 Subject: [PATCH 14/40] Update joinAquPointCount.R --- R/joinAquPointCount.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index c3c0598..8d7db61 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -34,7 +34,7 @@ #' check.size = FALSE #' ) #' -#' # Join downloaded root data +#' # Join downloaded point count data #' df <- neonPlants::joinAquPointCount( #' inputDataList = apc, #' inputPoint = NA, From e8b2737de893885a6c2d6b5b46dd056d8be03b92 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 11:51:13 -0700 Subject: [PATCH 15/40] add license --- R/joinAquClipHarvest.R | 5 ++++- R/joinAquPointCount.R | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index f1e535b..36f1d66 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -19,9 +19,12 @@ #' @param inputTaxProc The 'apl_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] #' #' @param inputMorph The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] -#' +#' #' @return A table containing bout 2 clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent = 'Y' and an identification has been published.. #' +#' @references +#' License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 +#' #' @examples #' \dontrun{ #' # Obtain NEON Aquatic Plant Clip Harvest data diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 8d7db61..171b1f1 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -22,6 +22,9 @@ #' #' @return A table containing point transect data with all associated taxonomic information for each point where targetTaxaPresent == 'Y'. #' +#' @references +#' License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 +#' #' @examples #' \dontrun{ #' # Obtain NEON Aquatic Plant Point Count data @@ -319,7 +322,7 @@ joinAquPointCount <- function(inputDataList, -"collectDate", -"morphospeciesID" ) %>% - dplyr::rename(taxonID = .data$acceptedTaxonID) + dplyr::rename(taxonID = "acceptedTaxonID") # Update expert taxonomist identifications apJoin1 <- apPerTax %>% @@ -559,7 +562,10 @@ joinAquPointCount <- function(inputDataList, -"tempTaxonID",-dplyr::matches("_morph"),-dplyr::matches("_perTax")) } else { message("No data joined from apc_morphospecies table.") - apJoin2 <- apJoin1 + + apJoin2 <- apJoin1 %>% + dplyr::mutate(acceptedTaxonID = .data$tempTaxonID) %>% + dplyr::select(-"tempTaxonID") } From 728c746eb106181f0ce62ffdb34bc90bf94145bd Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 14:16:31 -0700 Subject: [PATCH 16/40] add percentCover function --- NAMESPACE | 1 + R/estimateAquPercentCover.R | 174 +++++++++++++++++++++++++++++++++ man/estimateAquPercentCover.Rd | 74 ++++++++++++++ man/joinAquClipHarvest.Rd | 3 + man/joinAquPointCount.Rd | 5 +- 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 R/estimateAquPercentCover.R create mode 100644 man/estimateAquPercentCover.Rd diff --git a/NAMESPACE b/NAMESPACE index cc25c2c..5658f7b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,7 @@ # Generated by roxygen2: do not edit by hand export("%>%") +export(estimateAquPercentCover) export(estimatePheDurationByTag) export(estimatePheTransByTag) export(joinAquClipHarvest) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R new file mode 100644 index 0000000..eac2ca4 --- /dev/null +++ b/R/estimateAquPercentCover.R @@ -0,0 +1,174 @@ +#' @title Estimate NEON aquatic plant, bryophyte, lichen, and macroalgae percent cover in wadeable streams +#' +#' @author Madaline Ritter \email{ritterm1@battelleecology.org} \cr +#' +#' @description Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. The calculateAquPercentCover() function aggregates the occurrence data from the Aquatic Plant Point Count data product to return estimates of percent cover at the scale of each transect. +#' +#' @details Input data may be provided either as a list generated from the neonUtilities::laodByProduct() function or as individual tables. However, if both list and table inputs are provided at the same time the function will error. +#' +#' Percent cover is calculated using the equation from Bowden et al. 2006: +#' +#' \deqn{percentCover_i = (N_i / N_t) * 100} +#' +#' Where: +#' +#' - \eqn{N_i} is the number of observed points in a transect that match class type “i” (i.., a particular taxonID) +#' - \eqn{N_t} is the total number of points observed in the transect +#' +#' Note: This calculation can generate percent cover values >100% if there is vertical stacking of plants. +#' +#' @param inputDataList A list object comprised of Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Count tables (DP1.20072.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. [list] +#' +#' @param inputPoint The 'apc_pointTransect' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputPerTax The 'apc_perTaxon' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputTaxProc The 'apc_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @param inputMorph The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] +#' +#' @return A data frame that estimates percent cover for each species observed on a given aquatic plant transect. Description of columns can be found in the Data Product User Guide. +#' +#' @references +#' License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 +#' +#' @examples +#' \dontrun{ +#' # Obtain NEON Aquatic Plant Point Count data +#' apc <- neonUtilities::loadByProduct( +#' dpID = "DP1.20072.001", +#' site = "all", +#' startdate = "2018-07", +#' enddate = "2018-08", +#' tabl = "all", +#' check.size = FALSE +#' ) +#' +#' # Calculate percent cover for downloaded data +#' df <- neonPlants::estimateAquPercentCover( +#' inputDataList = apc, +#' inputPoint = NA, +#' inputPerTax = NA, +#' inputTaxProc = NA, +#' inputMorph = NA +#' ) +#' } +#' @export estimateAquPercentCover + + + + +estimateAquPercentCover <- function(inputDataList, + inputPoint = NA, + inputPerTax = NA, + inputTaxProc = NA, + inputMorph = NA) { + + ### Join taxonomy data #### + + if(!missing(inputDataList)){ + + joinPointCounts <- neonPlants::joinAquPointCount(inputDataList = inputDataList) + # joinPointCounts <- joinAquPointCount(inputDataList = apc) + + + } else { + + joinPointCounts <- neonPlants::joinAquPointCount(inputPoint = inputPoint, + inputPerTax = inputPerTax, + inputTaxProc = inputTaxProc, + inputMorph = inputMorph) + } + + + ### Calculate Percent Cover #### + + # filter to only observed points: + # joinPointCounts <- joinPointCounts %>% filter(targetTaxaPresent == "Y") + + # Calculate percent cover of plants/macroalgae/etc + # percent_cover <- joinPointCounts %>% + # dplyr::group_by(.data$namedLocation, .data$collectDate) %>% + # dplyr::mutate(total_points = dplyr::n()) %>% + # # dplyr::filter(targetTaxaPresent == 'Y') %>% + # dplyr::group_by(.data$namedLocation, .data$collectDate, .data$acceptedTaxonID, .data$scientificName) %>% + # dplyr::reframe( + # tax_count = dplyr::n(), + # total_points = dplyr::first(total_points), + # percent_cover = round( (100 * tax_count / total_points), 2) + # ) + + + # Calculate total points per group (siteID + collectDate) + total_points_df <- joinPointCounts %>% + dplyr::group_by(.data$siteID, .data$collectDate) %>% + dplyr::summarise(total_points = dplyr::n(), .groups = "drop") + + # Percent cover by substrate + cover_substrate <- joinPointCounts %>% + dplyr::filter(.data$targetTaxaPresent == "N") %>% + dplyr::group_by(.data$siteID, .data$collectDate, .data$namedLocation, .data$substrate) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% + dplyr::left_join(.data$total_points_df, by = c("siteID", "collectDate")) %>% + dplyr::mutate( + percent_cover = round(100 * .data$count / .data$total_points, 2), + type = "substrate", + substrateOrTaxonID = .data$substrate + ) %>% + dplyr::select("siteID", "collectDate", "namedLocation", "substrateOrTaxonID", "percent_cover", "type") + + # Percent cover by taxon + cover_taxon <- joinPointCounts %>% + dplyr::filter(.data$targetTaxaPresent == "Y") %>% + dplyr::group_by(.data$siteID, .data$collectDate, .data$namedLocation, .data$acceptedTaxonID, .data$scientificName) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% + dplyr::left_join(.data$total_points_df, by = c("siteID", "collectDate")) %>% + dplyr::mutate( + percent_cover = round(100 * .data$count / .data$total_points, 2), + type = "taxon", + substrateOrTaxonID = .data$acceptedTaxonID + ) %>% + dplyr::select("siteID", "collectDate", "namedLocation", "substrateOrTaxonID", "scientificName", "percent_cover", "type") + + # Combine results + cover_substrate$scientificName <- NA_character_ + percent_cover <- dplyr::bind_rows(cover_substrate, cover_taxon) + + + ### Plot Percent Cover by Site/Date #### + + # # Create a unique ID for each siteID + collectDate + # percent_cover$plotID <- paste(percent_cover$siteID, substr(percent_cover$collectDate, 1, 10), sep = "_") + # + # # Generate plots + # plot_ids <- unique(percent_cover$plotID) + # + # plot_grid <- function(plot_id) { + # ggplot(subset(percent_cover, plotID == plot_id), + # aes(x = namedLocation, y = percent_cover, fill = substrateOrTaxonID)) + + # geom_bar(stat = "identity", position = "stack") + + # labs( + # title = paste("Percent Cover for", gsub("_", " on ", plot_id)), + # x = "Named Location", + # y = "Percent Cover", + # fill = "Substrate/Taxon" + # ) + + # theme_minimal() + + # theme(axis.text.x = element_text(angle = 45, hjust = 1)) + # } + # + # plot_list <- lapply(plot_ids, plot_grid) + # names(plot_list) <- plot_ids + # + # # Print all plots + # for (i in seq_along(plot_list)) { + # if (!is.null(plot_list[[i]])) { + # print(plot_list[[i]]) + # } + # } + + return(percent_cover) + +} #function closer + + \ No newline at end of file diff --git a/man/estimateAquPercentCover.Rd b/man/estimateAquPercentCover.Rd new file mode 100644 index 0000000..bb929fb --- /dev/null +++ b/man/estimateAquPercentCover.Rd @@ -0,0 +1,74 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/estimateAquPercentCover.R +\name{estimateAquPercentCover} +\alias{estimateAquPercentCover} +\title{Estimate NEON aquatic plant, bryophyte, lichen, and macroalgae percent cover in wadeable streams} +\usage{ +estimateAquPercentCover( + inputDataList, + inputPoint = NA, + inputPerTax = NA, + inputTaxProc = NA, + inputMorph = NA +) +} +\arguments{ +\item{inputDataList}{A list object comprised of Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Count tables (DP1.20072.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. \link{list}} + +\item{inputPoint}{The 'apc_pointTransect' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputPerTax}{The 'apc_perTaxon' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputTaxProc}{The 'apc_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{inputMorph}{The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} +} +\value{ +A data frame that estimates percent cover for each species observed on a given aquatic plant transect. Description of columns can be found in the Data Product User Guide. +} +\description{ +Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. The calculateAquPercentCover() function aggregates the occurrence data from the Aquatic Plant Point Count data product to return estimates of percent cover at the scale of each transect. +} +\details{ +Input data may be provided either as a list generated from the neonUtilities::laodByProduct() function or as individual tables. However, if both list and table inputs are provided at the same time the function will error. + +Percent cover is calculated using the equation from Bowden et al. 2006: + +\deqn{percentCover_i = (N_i / N_t) * 100} + +Where: +\itemize{ +\item \eqn{N_i} is the number of observed points in a transect that match class type “i” (i.., a particular taxonID) +\item \eqn{N_t} is the total number of points observed in the transect +} + +Note: This calculation can generate percent cover values >100\% if there is vertical stacking of plants. +} +\examples{ +\dontrun{ +# Obtain NEON Aquatic Plant Point Count data +apc <- neonUtilities::loadByProduct( +dpID = "DP1.20072.001", +site = "all", +startdate = "2018-07", +enddate = "2018-08", +tabl = "all", +check.size = FALSE +) + +# Calculate percent cover for downloaded data +df <- neonPlants::estimateAquPercentCover( +inputDataList = apc, +inputPoint = NA, +inputPerTax = NA, +inputTaxProc = NA, +inputMorph = NA +) +} +} +\references{ +License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 +} +\author{ +Madaline Ritter \email{ritterm1@battelleecology.org} \cr +} diff --git a/man/joinAquClipHarvest.Rd b/man/joinAquClipHarvest.Rd index 91c33b1..5dedf8f 100644 --- a/man/joinAquClipHarvest.Rd +++ b/man/joinAquClipHarvest.Rd @@ -59,6 +59,9 @@ inputMorph = NA } +} +\references{ +License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 } \author{ Madaline Ritter \email{ritterm1@battelleecology.org} \cr diff --git a/man/joinAquPointCount.Rd b/man/joinAquPointCount.Rd index c868a27..1722861 100644 --- a/man/joinAquPointCount.Rd +++ b/man/joinAquPointCount.Rd @@ -48,7 +48,7 @@ tabl = "all", check.size = FALSE ) -# Join downloaded root data +# Join downloaded point count data df <- neonPlants::joinAquPointCount( inputDataList = apc, inputPoint = NA, @@ -59,6 +59,9 @@ inputMorph = NA } +} +\references{ +License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 } \author{ Madaline Ritter \email{ritterm1@battelleecology.org} \cr From 856e9cd216a3de022f48239e324e4b34da9edf5f Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 17 Jun 2025 14:34:22 -0700 Subject: [PATCH 17/40] Update estimateAquPercentCover.R --- R/estimateAquPercentCover.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index eac2ca4..15ac608 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -12,7 +12,7 @@ #' #' Where: #' -#' - \eqn{N_i} is the number of observed points in a transect that match class type “i” (i.., a particular taxonID) +#' - \eqn{N_i} is the number of observed points in a transect that match class type “i” (i.e., a particular taxonID or substrate) #' - \eqn{N_t} is the total number of points observed in the transect #' #' Note: This calculation can generate percent cover values >100% if there is vertical stacking of plants. From 01b5747361fc09ebbc3608f20e5af5b0c1fd0c5e Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 18 Jun 2025 15:54:42 -0700 Subject: [PATCH 18/40] percent cover updates --- R/estimateAquPercentCover.R | 162 ++++++++++++++++++++++----------- man/estimateAquPercentCover.Rd | 18 +++- 2 files changed, 124 insertions(+), 56 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index 15ac608..def8086 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -2,7 +2,7 @@ #' #' @author Madaline Ritter \email{ritterm1@battelleecology.org} \cr #' -#' @description Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. The calculateAquPercentCover() function aggregates the occurrence data from the Aquatic Plant Point Count data product to return estimates of percent cover at the scale of each transect. +#' @description Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. The estimateAquPercentCover() function joins taxonomy information across point count tables and aggregates occurrence data to estimate percent cover at the transect level. #' #' @details Input data may be provided either as a list generated from the neonUtilities::laodByProduct() function or as individual tables. However, if both list and table inputs are provided at the same time the function will error. #' @@ -27,7 +27,13 @@ #' #' @param inputMorph The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] #' -#' @return A data frame that estimates percent cover for each species observed on a given aquatic plant transect. Description of columns can be found in the Data Product User Guide. +#' @param barPlots If TRUE, will produce a list of plots, one for each site/date in the data provided. +#' +#' @return Two tables are produced containing point count summary data. The first "percentCover" table contains estimated percent cover for each observed species and/or substrate class on aquatic plant transects. This table includes a 'type' column indicating whether the estimate corresponds to a taxonID or a substrate class, and a 'substrateOrTaxonID' column which provides the corresponding identifier. +#' +#' The second "transectMetrics" table contains summary information including the length, habitatType, and total number of points sampled at each transect. +#' +#' If barPlots = TRUE, a list containing plots of the data will also be produced. #' #' @references #' License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 @@ -50,7 +56,8 @@ #' inputPoint = NA, #' inputPerTax = NA, #' inputTaxProc = NA, -#' inputMorph = NA +#' inputMorph = NA, +#' barPlots = F #' ) #' } #' @export estimateAquPercentCover @@ -59,10 +66,11 @@ estimateAquPercentCover <- function(inputDataList, - inputPoint = NA, - inputPerTax = NA, - inputTaxProc = NA, - inputMorph = NA) { + inputPoint = NA, + inputPerTax = NA, + inputTaxProc = NA, + inputMorph = NA, + barPlots = FALSE) { ### Join taxonomy data #### @@ -80,6 +88,8 @@ estimateAquPercentCover <- function(inputDataList, inputMorph = inputMorph) } + ### Remove SI Records #### + joinPointCounts <- joinPointCounts %>% dplyr::filter(is.na(.data$samplingImpractical)) ### Calculate Percent Cover #### @@ -99,75 +109,125 @@ estimateAquPercentCover <- function(inputDataList, # ) - # Calculate total points per group (siteID + collectDate) + # Calculate total number of sampling points per group (siteID + collectDate) + # total_points_df <- joinPointCounts %>% + # dplyr::group_by(.data$siteID, .data$collectDate) %>% + # dplyr::summarise(total_points = dplyr::n(), .groups = "drop") + total_points_df <- joinPointCounts %>% - dplyr::group_by(.data$siteID, .data$collectDate) %>% + dplyr::distinct(.data$siteID, .data$namedLocation, .data$collectDate, .data$pointNumber) %>% + dplyr::group_by(.data$siteID, .data$namedLocation, .data$collectDate) %>% dplyr::summarise(total_points = dplyr::n(), .groups = "drop") + # Percent cover by substrate cover_substrate <- joinPointCounts %>% dplyr::filter(.data$targetTaxaPresent == "N") %>% dplyr::group_by(.data$siteID, .data$collectDate, .data$namedLocation, .data$substrate) %>% dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% - dplyr::left_join(.data$total_points_df, by = c("siteID", "collectDate")) %>% + dplyr::left_join(total_points_df, by = c("siteID", "namedLocation", "collectDate")) %>% dplyr::mutate( percent_cover = round(100 * .data$count / .data$total_points, 2), type = "substrate", substrateOrTaxonID = .data$substrate ) %>% - dplyr::select("siteID", "collectDate", "namedLocation", "substrateOrTaxonID", "percent_cover", "type") + dplyr::select("siteID", "collectDate", "namedLocation", "type", "substrateOrTaxonID", "percent_cover") # Percent cover by taxon cover_taxon <- joinPointCounts %>% dplyr::filter(.data$targetTaxaPresent == "Y") %>% - dplyr::group_by(.data$siteID, .data$collectDate, .data$namedLocation, .data$acceptedTaxonID, .data$scientificName) %>% + dplyr::group_by(.data$siteID, .data$collectDate, .data$namedLocation, .data$acceptedTaxonID) %>% #, .data$scientificName dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% - dplyr::left_join(.data$total_points_df, by = c("siteID", "collectDate")) %>% + dplyr::left_join(total_points_df, by = c("siteID", "namedLocation", "collectDate")) %>% dplyr::mutate( percent_cover = round(100 * .data$count / .data$total_points, 2), type = "taxon", substrateOrTaxonID = .data$acceptedTaxonID ) %>% - dplyr::select("siteID", "collectDate", "namedLocation", "substrateOrTaxonID", "scientificName", "percent_cover", "type") + dplyr::select("siteID", "collectDate", "namedLocation", "type", "substrateOrTaxonID", "percent_cover") #"scientificName", # Combine results - cover_substrate$scientificName <- NA_character_ - percent_cover <- dplyr::bind_rows(cover_substrate, cover_taxon) - - - ### Plot Percent Cover by Site/Date #### - - # # Create a unique ID for each siteID + collectDate - # percent_cover$plotID <- paste(percent_cover$siteID, substr(percent_cover$collectDate, 1, 10), sep = "_") - # - # # Generate plots - # plot_ids <- unique(percent_cover$plotID) - # - # plot_grid <- function(plot_id) { - # ggplot(subset(percent_cover, plotID == plot_id), - # aes(x = namedLocation, y = percent_cover, fill = substrateOrTaxonID)) + - # geom_bar(stat = "identity", position = "stack") + - # labs( - # title = paste("Percent Cover for", gsub("_", " on ", plot_id)), - # x = "Named Location", - # y = "Percent Cover", - # fill = "Substrate/Taxon" - # ) + - # theme_minimal() + - # theme(axis.text.x = element_text(angle = 45, hjust = 1)) - # } - # - # plot_list <- lapply(plot_ids, plot_grid) - # names(plot_list) <- plot_ids - # - # # Print all plots - # for (i in seq_along(plot_list)) { - # if (!is.null(plot_list[[i]])) { - # print(plot_list[[i]]) - # } - # } - - return(percent_cover) + # cover_substrate$scientificName <- NA_character_ + percent_cover <- dplyr::bind_rows(cover_substrate, cover_taxon) %>% + dplyr::arrange(.data$collectDate, .data$namedLocation) + + + + ### Calculate Transect metrics #### + transect_metrics <- joinPointCounts %>% + dplyr::distinct(.data$domainID, .data$siteID, .data$namedLocation, .data$collectDate, .data$boutNumber, .data$habitatType, .data$pointNumber, .data$transectDistance) %>% + dplyr::group_by(.data$domainID, .data$siteID, .data$namedLocation, .data$collectDate, .data$boutNumber, .data$habitatType) %>% + dplyr::summarise(transectMax = max(.data$transectDistance), + transectMin = min(.data$transectDistance), + total_points = dplyr::n(),.groups = "drop") %>% + dplyr::mutate(transectLength_m = .data$transectMax - .data$transectMin) %>% + dplyr::select("domainID", "siteID", "namedLocation", "collectDate", "boutNumber", "habitatType", "transectLength_m", "total_points") + + + returnList <- list(percentCover=percent_cover, transectMetrics=transect_metrics) + + + ### Optionally Plot Percent Cover by Site/Date #### + if(barPlots){ + + # Create a unique ID for each siteID + collectDate + percent_cover$boutID <- paste(percent_cover$siteID, substr(percent_cover$collectDate, 1, 10), sep = "_") + + plot_ids <- unique(percent_cover$boutID) + + # Create a consistent color palette + + # Separate substrate and taxon IDs + substrate_ids <- unique(cover_substrate$substrateOrTaxonID) + taxon_ids <- unique(cover_taxon$substrateOrTaxonID) + + # Assign greyscale colors to substrates + greys <- grDevices::gray.colors(length(substrate_ids), start = 0.3, end = 0.8) + + # Assign colorful palette to taxa + taxon_colors <- RColorBrewer::brewer.pal(min(length(taxon_ids), 8), "Set2") + if (length(taxon_ids) > 8) { + extra_colors <- grDevices::colors()[grep('gr(a|e)y', grDevices::colors(), invert = TRUE)] + taxon_colors <- c(taxon_colors, sample(extra_colors, length(taxon_ids) - 8)) + } + + # Combine into one palette + color_palette <- c(stats::setNames(greys, substrate_ids), stats::setNames(taxon_colors, taxon_ids)) + + + # Plotting function + plot_grid <- function(plot_id) { + ggplot2::ggplot(subset(percent_cover, boutID == plot_id), + ggplot2::aes(x = namedLocation, y = percent_cover, fill = substrateOrTaxonID)) + + ggplot2::geom_bar(stat = "identity", position = "stack") + + ggplot2::scale_fill_manual(values = color_palette) + # Apply consistent colors + labs( + # title = paste("Percent Cover for", gsub("_", " on ", plot_id)), + title = gsub(" ", " on ", plot_id), + # x = "Named Location", + y = "Percent Cover", + fill = "Substrate/Taxon" + ) + + ggplot2::theme_minimal() + + ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) + } + + plot_list <- lapply(plot_ids, plot_grid) + names(plot_list) <- plot_ids + + # Bind df and plots + returnList$plot_list <- plot_list + + # Print all plots + # for (i in seq_along(df$plot_list)) { + # if (!is.null(df$plot_list[[i]])) { + # print(df$plot_list[[i]]) + # } + # } + + } + + return(returnList) } #function closer diff --git a/man/estimateAquPercentCover.Rd b/man/estimateAquPercentCover.Rd index bb929fb..e7133e2 100644 --- a/man/estimateAquPercentCover.Rd +++ b/man/estimateAquPercentCover.Rd @@ -9,7 +9,8 @@ estimateAquPercentCover( inputPoint = NA, inputPerTax = NA, inputTaxProc = NA, - inputMorph = NA + inputMorph = NA, + barPlots = FALSE ) } \arguments{ @@ -22,12 +23,18 @@ estimateAquPercentCover( \item{inputTaxProc}{The 'apc_taxonomyProcessed' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} \item{inputMorph}{The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} + +\item{barPlots}{If TRUE, will produce a list of plots, one for each site/date in the data provided.} } \value{ -A data frame that estimates percent cover for each species observed on a given aquatic plant transect. Description of columns can be found in the Data Product User Guide. +Two tables are produced containing point count summary data. The first "percentCover" table contains estimated percent cover for each observed species and/or substrate class on aquatic plant transects. This table includes a 'type' column indicating whether the estimate corresponds to a taxonID or a substrate class, and a 'substrateOrTaxonID' column which provides the corresponding identifier. + +The second "transectMetrics" table contains summary information including the length, habitatType, and total number of points sampled at each transect. + +If barPlots = TRUE, a list containing plots of the data will also be produced. } \description{ -Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. The calculateAquPercentCover() function aggregates the occurrence data from the Aquatic Plant Point Count data product to return estimates of percent cover at the scale of each transect. +Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. The estimateAquPercentCover() function joins taxonomy information across point count tables and aggregates occurrence data to estimate percent cover at the transect level. } \details{ Input data may be provided either as a list generated from the neonUtilities::laodByProduct() function or as individual tables. However, if both list and table inputs are provided at the same time the function will error. @@ -38,7 +45,7 @@ Percent cover is calculated using the equation from Bowden et al. 2006: Where: \itemize{ -\item \eqn{N_i} is the number of observed points in a transect that match class type “i” (i.., a particular taxonID) +\item \eqn{N_i} is the number of observed points in a transect that match class type “i” (i.e., a particular taxonID or substrate) \item \eqn{N_t} is the total number of points observed in the transect } @@ -62,7 +69,8 @@ inputDataList = apc, inputPoint = NA, inputPerTax = NA, inputTaxProc = NA, -inputMorph = NA +inputMorph = NA, +barPlots = F ) } } From 0c42231650b6cc455ee027b9fede736ec72dbeeb Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 9 Jul 2025 10:22:31 -0700 Subject: [PATCH 19/40] create pct cover test --- R/estimateAquPercentCover.R | 2 +- R/joinAquClipHarvest.R | 261 +++++++++--------- R/joinAquPointCount.R | 230 ++++++--------- tests/testthat/test_estimateAquPercentCover.R | 132 +++++++++ ...ateAquPercentCover_testData_D04_202306.rds | Bin 0 -> 19294 bytes 5 files changed, 350 insertions(+), 275 deletions(-) create mode 100644 tests/testthat/test_estimateAquPercentCover.R create mode 100644 tests/testthat/testdata/estimateAquPercentCover_testData_D04_202306.rds diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index def8086..79c886d 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -51,7 +51,7 @@ #' ) #' #' # Calculate percent cover for downloaded data -#' df <- neonPlants::estimateAquPercentCover( +#' list <- neonPlants::estimateAquPercentCover( #' inputDataList = apc, #' inputPoint = NA, #' inputPerTax = NA, diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 36f1d66..8b3bb1d 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -330,6 +330,14 @@ joinAquClipHarvest <- function(inputDataList, -"sampleCode" ) + # Columns conditionally replaced with taxProc data + join1_cols <- c( + "division", "class", "order", "family", + "genus", "section", "specificEpithet", + "scientificNameAuthorship", "identificationQualifier", "identificationReferences", + "taxonRank", "identifiedBy", "identifiedDate" + ) + # Update expert taxonomist identifications apJoin1 <- apBio %>% dplyr::left_join( @@ -339,33 +347,26 @@ joinAquClipHarvest <- function(inputDataList, relationship = "many-to-many" ) %>% dplyr::mutate( - identifiedDate = dplyr::case_when( - !is.na(.data$taxonID_taxProc) ~ .data$identifiedDate_taxProc, - TRUE ~ .data$identifiedDate_bio - ), sampleCondition = dplyr::case_when( - !is.na(.data$sampleCondition_bio) & - !is.na(.data$sampleCondition_taxProc) ~ paste0( - "biomass ", - .data$sampleCondition_bio, - " | taxonProcessed ", - .data$sampleCondition_taxProc - ),!is.na(.data$sampleCondition_bio) & - is.na(.data$sampleCondition_taxProc) ~ paste0("biomass ", .data$sampleCondition_bio), - is.na(.data$sampleCondition_bio) & - !is.na(.data$sampleCondition_taxProc) ~ paste0("taxonProcessed ", .data$sampleCondition_taxProc), + !is.na(.data$sampleCondition_bio) & !is.na(.data$sampleCondition_taxProc) ~ + paste0("biomass ", .data$sampleCondition_bio," | taxonProcessed ", .data$sampleCondition_taxProc), + !is.na(.data$sampleCondition_bio) & is.na(.data$sampleCondition_taxProc) ~ + paste0("biomass ", .data$sampleCondition_bio), + is.na(.data$sampleCondition_bio) & !is.na(.data$sampleCondition_taxProc) ~ + paste0("taxonProcessed ", .data$sampleCondition_taxProc), TRUE ~ NA ), taxonIDSourceTable = dplyr::case_when( !is.na(.data$taxonID_taxProc) ~ "apl_taxonomyProcessed", - is.na(.data$taxonID_taxProc) & - !is.na(.data$taxonID_bio) ~ "apl_biomass", + is.na(.data$taxonID_taxProc) & !is.na(.data$taxonID_bio) ~ "apl_biomass", TRUE ~ NA ), - tempTaxonID = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$taxonID_taxProc, .data$taxonID_bio), + tempTaxonID = dplyr::if_else( + !is.na(.data$taxonID_taxProc), .data$taxonID_taxProc, .data$taxonID_bio + ), scientificName = dplyr::if_else( !is.na(.data$taxonID_taxProc), @@ -374,91 +375,47 @@ joinAquClipHarvest <- function(inputDataList, ), identificationHistoryID = dplyr::case_when( - !is.na(.data$identificationHistoryID_bio) & - !is.na(.data$identificationHistoryID_taxProc) ~ paste0( - .data$identificationHistoryID_bio, - " | ", - .data$identificationHistoryID_taxProc - ), - is.na(.data$identificationHistoryID_taxProc) & - !is.na(.data$identificationHistoryID_bio) ~ .data$identificationHistoryID_bio,!is.na(.data$identificationHistoryID_taxProc) & - is.na(.data$identificationHistoryID_bio) ~ .data$identificationHistoryID_taxProc, + !is.na(.data$identificationHistoryID_bio) & !is.na(.data$identificationHistoryID_taxProc) ~ + paste0(.data$identificationHistoryID_bio," | ",.data$identificationHistoryID_taxProc), + is.na(.data$identificationHistoryID_taxProc) & !is.na(.data$identificationHistoryID_bio) ~ + .data$identificationHistoryID_bio, + !is.na(.data$identificationHistoryID_taxProc) & is.na(.data$identificationHistoryID_bio) ~ + .data$identificationHistoryID_taxProc, TRUE ~ NA ), biomassDataQF = .data$dataQF_bio, taxProcessedDataQF = .data$dataQF_taxProc, - biomassPublicationDate = .data$publicationDate_bio, taxProcessedPublicationDate = .data$publicationDate_taxProc, - biomassRelease = .data$release_bio, taxProcessedRelease = .data$release_taxProc, - # phylum = dplyr::if_else(!is.na(taxonID_taxProc), phylum_taxProc, phylum_bio), - division = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$division_taxProc, .data$division_bio), - class = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$class_taxProc, .data$class_bio), - order = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$order_taxProc, .data$order_bio), - family = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$family_taxProc, .data$family_bio), - genus = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$genus_taxProc, .data$genus_bio), - section = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$section_taxProc, .data$section_bio), - specificEpithet = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$specificEpithet_taxProc, - .data$specificEpithet_bio - ), - # infraspecificEpithet = dplyr::if_else(!is.na(taxonID_taxProc), infraspecificEpithet_taxProc, infraspecificEpithet_bio), - # variety = dplyr::if_else(!is.na(taxonID_taxProc), variety_taxProc, variety_bio), - # form = dplyr::if_else(!is.na(taxonID_taxProc), form_taxProc, form_bio), - scientificNameAuthorship = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$scientificNameAuthorship_taxProc, - .data$scientificNameAuthorship_bio - ), - identificationQualifier = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$identificationQualifier_taxProc, - .data$identificationQualifier_bio - ), - identificationReferences = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$identificationReferences_taxProc, - .data$identificationReferences_bio - ), - taxonRank = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$taxonRank_taxProc, - .data$taxonRank_bio - ), - remarks = dplyr::case_when( - !is.na(.data$remarks_bio) & - !is.na(.data$remarks_taxProc) ~ paste0( - "biomass remarks - ", - .data$remarks_bio, - " | taxonProcessed remarks - ", - .data$remarks_taxProc - ), - is.na(.data$remarks_taxProc) & - !is.na(.data$remarks_bio) ~ paste0("biomass remarks - ", .data$remarks_bio),!is.na(.data$remarks_taxProc) & - is.na(.data$remarks_bio) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), + !is.na(.data$remarks_bio) & !is.na(.data$remarks_taxProc) ~ + paste0( "biomass remarks - ", .data$remarks_bio, " | taxonProcessed remarks - ", .data$remarks_taxProc), + is.na(.data$remarks_taxProc) & !is.na(.data$remarks_bio) ~ + paste0("biomass remarks - ", .data$remarks_bio), + !is.na(.data$remarks_taxProc) & is.na(.data$remarks_bio) ~ + paste0("taxonProcessed remarks - ", .data$remarks_taxProc), TRUE ~ NA - ), - - identifiedBy = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$identifiedBy_taxProc, - .data$identifiedBy_bio - ), - identifiedDate = dplyr::if_else( - !is.na(.data$identifiedDate_taxProc), - .data$identifiedDate_taxProc, - .data$identifiedDate_bio ) - - ) %>% - dplyr::select(-dplyr::matches("_taxProc"),-dplyr::matches("_bio"), - -"targetTaxaPresent", -"uid") + ) + + for (col in join1_cols) { + taxProc_col <- paste0(col, "_taxProc") + bio_col <- paste0(col, "_bio") + apJoin1[[col]] <- dplyr::if_else( + !is.na(apJoin1$taxonID_taxProc), + if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA_character_, + if (bio_col %in% names(apJoin1)) apJoin1[[bio_col]] else NA_character_ + ) + } + + apJoin1 <- apJoin1 %>% + dplyr::select(-"uid", -"targetTaxaPresent", + -dplyr::matches("_taxProc"),-dplyr::matches("_bio")) + } else { message("No data joined from apl_taxonomyProcessed table.") @@ -479,6 +436,10 @@ joinAquClipHarvest <- function(inputDataList, -"uid") } + + + + ### Join apJoin1 and apMorph tables #### # Select needed columns from apMorph @@ -492,9 +453,22 @@ joinAquClipHarvest <- function(inputDataList, "identificationQualifier", "identificationReferences", "identifiedBy", - # "identifiedDate", - "dataQF" - ) + "morphospeciesResolvedDate", + # "phylum", + # "division", + # "class", + # "order", + # "family", + # "genus", + # "section", + # "specificEpithet", + # "infraspecificEpithet", + # "variety", + # "form", + # "taxonRank" + # "dataQF" + )%>% + dplyr::rename(identifiedDate="morphospeciesResolvedDate") # Update morphospecies taxon identifications apJoin2 <- apJoin1 %>% @@ -506,63 +480,82 @@ joinAquClipHarvest <- function(inputDataList, dplyr::left_join(apMorph, by = "morphospeciesID", suffix = c("_bio", "_morph")) %>% - # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% dplyr::mutate( taxonIDSourceTable = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - "apc_morphospecies", - .data$taxonIDSourceTable - ), + !is.na(.data$taxonID) & .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + "apc_morphospecies", .data$taxonIDSourceTable), acceptedTaxonID = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$taxonID, - .data$tempTaxonID - ), - scientificName = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$scientificName_morph, - .data$scientificName_bio - ), - identificationQualifier = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$identificationQualifier_morph, - .data$identificationQualifier_bio - ), - identificationReferences = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$identificationReferences_morph, - .data$identificationReferences_bio - ), - identifiedBy = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$identifiedBy_morph, - .data$identifiedBy_bio - ), - identifiedDate = NA, - #not currently in pub table + !is.na(.data$taxonID) & .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + .data$taxonID, .data$tempTaxonID), + # scientificName = dplyr::if_else( + # !is.na(.data$taxonID) & + # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + # .data$scientificName_morph, + # .data$scientificName_bio + # ), + # identificationQualifier = dplyr::if_else( + # !is.na(.data$taxonID) & + # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + # .data$identificationQualifier_morph, + # .data$identificationQualifier_bio + # ), + # identificationReferences = dplyr::if_else( + # !is.na(.data$taxonID) & + # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + # .data$identificationReferences_morph, + # .data$identificationReferences_bio + # ), + # identifiedBy = dplyr::if_else( + # !is.na(.data$taxonID) & + # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), + # .data$identifiedBy_morph, + # .data$identifiedBy_bio + # ), + # identifiedDate = NA, + # #not currently in pub table morphospeciesDataQF = .data$dataQF - ) %>% - dplyr::select(-"taxonID", - -"tempTaxonID", - -"dataQF",-dplyr::matches("_morph"),-dplyr::matches("_bio")) + ) + + # Columns conditionally replaced with morph data + join2_cols <- c( + "scientificName", "identificationQualifier", "identificationReferences", + "identifiedBy", "identifiedDate" + # , "phylum", "division", "class", "order", + # "family", "genus", "section", "specificEpithet", "infraspecificEpithet", + # "variety", "form", "taxonRank" + ) + + for (col in join2_cols) { + morph_col <- paste0(col, "_morph") + bio_col <- paste0(col, "_bio") + apJoin2[[col]] <- dplyr::if_else( + !is.na(apJoin2$taxonID) & apJoin2$tempTaxonID %in% c("2PLANT", "UNKALG"), + if (morph_col %in% names(apJoin2)) apJoin2[[morph_col]] else NA_character_, + if (bio_col %in% names(apJoin2)) apJoin2[[bio_col]] else NA_character_ + ) + } + + apJoin2 <- apJoin2 %>% + dplyr::select( + -"taxonID", -"tempTaxonID", -"dataQF", + -dplyr::matches("_morph"),-dplyr::matches("_bio")) + + } else { message("No data joined from apc_morphospecies table.") + apJoin2 <- apJoin1 %>% dplyr::mutate(acceptedTaxonID = .data$tempTaxonID) %>% dplyr::select(-"tempTaxonID") } + + + ### Join apClip and apBio tables #### - # Update morphospecies taxon identifications joinClipHarvest <- apClip %>% dplyr::select( -"benthicArea", diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 171b1f1..3046572 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -31,9 +31,10 @@ #' apc <- neonUtilities::loadByProduct( #' dpID = "DP1.20072.001", #' site = "all", -#' startdate = "2018-07", -#' enddate = "2018-08", +#' startdate = "2018-03", +#' enddate = "2018-05", #' tabl = "all", +#' package = 'expanded', #' check.size = FALSE #' ) #' @@ -307,9 +308,10 @@ joinAquPointCount <- function(inputDataList, } + + + ### Join apPerTax and apTaxProc tables #### - # message(paste('apTaxProc = ', apTaxProc)) - is.data.frame(apTaxProc) && nrow(apTaxProc) > 0 if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { # Select needed columns from apTaxProc @@ -324,6 +326,15 @@ joinAquPointCount <- function(inputDataList, ) %>% dplyr::rename(taxonID = "acceptedTaxonID") + # Columns conditionally replaced with taxProc data + join1_cols <- c( + "scientificName", + "phylum", "division", "class", "order", + "family", "genus", "section", "specificEpithet", "infraspecificEpithet", + "variety", "form", "scientificNameAuthorship", "identificationQualifier", "identificationReferences", + "taxonRank", "identifiedBy", "identifiedDate" + ) + # Update expert taxonomist identifications apJoin1 <- apPerTax %>% dplyr::left_join( @@ -357,11 +368,6 @@ joinAquPointCount <- function(inputDataList, .data$taxonID_taxProc, .data$taxonID_perTax ), - scientificName = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$scientificName_taxProc, - .data$scientificName_perTax - ), identificationHistoryID = dplyr::case_when( !is.na(.data$identificationHistoryID_perTax) & !is.na(.data$identificationHistoryID_taxProc) ~ paste0( @@ -380,99 +386,35 @@ joinAquPointCount <- function(inputDataList, taxProcessedPublicationDate = .data$publicationDate_taxProc, taxProcessedRelease = .data$release_taxProc, perTaxonRelease = .data$release_perTax, - phylum = dplyr::if_else(!is.na(.data$taxonID_taxProc), .data$phylum_taxProc, .data$phylum_perTax), - division = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$division_taxProc, - .data$division_perTax - ), - class = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$class_taxProc, - .data$class_perTax), - order = dplyr::if_else(!is.na(.data$taxonID_taxProc), - .data$order_taxProc, - .data$order_perTax), - family = dplyr::if_else(!is.na(.data$taxonID_taxProc), - .data$family_taxProc, - .data$family_perTax), - genus = dplyr::if_else(!is.na(.data$taxonID_taxProc), - .data$genus_taxProc, - .data$genus_perTax), - section = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$section_taxProc, - .data$section_perTax - ), - specificEpithet = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$specificEpithet_taxProc, - .data$specificEpithet_perTax - ), - infraspecificEpithet = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$infraspecificEpithet_taxProc, - .data$infraspecificEpithet_perTax - ), - variety = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$variety_taxProc, - .data$variety_perTax - ), - form = dplyr::if_else(!is.na(.data$taxonID_taxProc), - .data$form_taxProc, - .data$form_perTax), - scientificNameAuthorship = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$scientificNameAuthorship_taxProc, - .data$scientificNameAuthorship_perTax - ), - identificationQualifier = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$identificationQualifier_taxProc, - .data$identificationQualifier_perTax - ), - identificationReferences = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$identificationReferences_taxProc, - .data$identificationReferences_perTax - ), - taxonRank = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$taxonRank_taxProc, - .data$taxonRank_perTax - ), remarks = dplyr::case_when( - !is.na(.data$remarks_perTax) & - !is.na(.data$remarks_taxProc) ~ paste0( - "perTaxon remarks - ", - .data$remarks_perTax, - " | taxonProcessed remarks - ", - .data$remarks_taxProc - ), + !is.na(.data$remarks_perTax) & !is.na(.data$remarks_taxProc) ~ paste0( + "perTaxon remarks - ", .data$remarks_perTax, " | taxonProcessed remarks - ",.data$remarks_taxProc + ), is.na(.data$remarks_taxProc) & !is.na(.data$remarks_perTax) ~ paste0("perTaxon remarks - ", .data$remarks_perTax),!is.na(.data$remarks_taxProc) & is.na(.data$remarks_perTax) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), TRUE ~ NA - ), - identifiedBy = dplyr::if_else( - !is.na(.data$taxonID_taxProc), - .data$identifiedBy_taxProc, - .data$identifiedBy_perTax - ), - identifiedDate = dplyr::if_else( - !is.na(.data$identifiedDate_taxProc), - .data$identifiedDate_taxProc, - .data$identifiedDate_perTax ) - - ) %>% - dplyr::select(-dplyr::matches("_taxProc"),-dplyr::matches("_perTax"),-"targetTaxaPresent", - -"uid") + ) + + for (col in join1_cols) { + taxProc_col <- paste0(col, "_taxProc") + perTax_col <- paste0(col, "_perTax") + apJoin1[[col]] <- dplyr::if_else( + !is.na(apJoin1$taxonID_taxProc), + if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA_character_, + if (perTax_col %in% names(apJoin1)) apJoin1[[perTax_col]] else NA_character_ + ) + } + + apJoin1 <- apJoin1 %>% + dplyr::select(-"uid", -"targetTaxaPresent", + -dplyr::matches("_taxProc"),-dplyr::matches("_perTax")) + } else { message("No data joined from apc_taxonomyProcessed table.") - # rename columns if no taxProc join + # rename columns if no taxProc join apJoin1 <- apPerTax %>% dplyr::mutate( tempTaxonID = .data$taxonID, @@ -489,11 +431,14 @@ joinAquPointCount <- function(inputDataList, -"uid") } + + + + ### Join apJoin1 and apMorph tables #### # Select needed columns from apMorph if (is.data.frame(apMorph) && nrow(apMorph) > 0) { - # message("Join morphospecies taxonomic identifications.") apMorph <- apMorph %>% dplyr::select( "taxonID", @@ -502,11 +447,23 @@ joinAquPointCount <- function(inputDataList, "identificationQualifier", "identificationReferences", "identifiedBy", - # "identifiedDate", - "dataQF" - ) + "morphospeciesResolvedDate", + # "phylum", + # "division", + # "class", + # "order", + # "family", + # "genus", + # "section", + # "specificEpithet", + # "infraspecificEpithet", + # "variety", + # "form", + # "taxonRank" + # "dataQF" + ) %>% + dplyr::rename(identifiedDate="morphospeciesResolvedDate") - # Update morphospecies taxon identifications apJoin2 <- apJoin1 %>% dplyr::mutate(morphospeciesID = dplyr::if_else( !is.na(.data$morphospeciesID), @@ -516,50 +473,41 @@ joinAquPointCount <- function(inputDataList, dplyr::left_join(apMorph, by = "morphospeciesID", suffix = c("_perTax", "_morph")) %>% - # filter(!is.na(taxonID) & acceptedTaxonID %in% c('2PLANT', 'UNKALG')) %>% dplyr::mutate( taxonIDSourceTable = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - "apc_morphospecies", - .data$taxonIDSourceTable - ), + !is.na(.data$taxonID) & .data$tempTaxonID %in% c("2PLANT", "UNKALG"), + "apc_morphospecies", .data$taxonIDSourceTable), acceptedTaxonID = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$taxonID, - .data$tempTaxonID - ), - scientificName = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$scientificName_morph, - .data$scientificName_perTax - ), - identificationQualifier = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$identificationQualifier_morph, - .data$identificationQualifier_perTax - ), - identificationReferences = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$identificationReferences_morph, - .data$identificationReferences_perTax - ), - identifiedBy = dplyr::if_else( - !is.na(.data$taxonID) & - .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - .data$identifiedBy_morph, - .data$identifiedBy_perTax - ), - identifiedDate = NA #not currently in pub table - #dataQF = ifelse(!is.na(taxonID), )#either that may be relevant? - - ) %>% - dplyr::select(-"taxonID", - -"tempTaxonID",-dplyr::matches("_morph"),-dplyr::matches("_perTax")) + !is.na(.data$taxonID) & .data$tempTaxonID %in% c("2PLANT", "UNKALG"), + .data$taxonID, .data$tempTaxonID), + morphospeciesDataQF = .data$dataQF + ) + + # Columns conditionally replaced with morph data + join2_cols <- c( + "scientificName", "identificationQualifier", "identificationReferences", + "identifiedBy", "identifiedDate" + # , "phylum", "division", "class", "order", + # "family", "genus", "section", "specificEpithet", "infraspecificEpithet", + # "variety", "form", "taxonRank" + ) + + for (col in join2_cols) { + morph_col <- paste0(col, "_morph") + perTax_col <- paste0(col, "_perTax") + apJoin2[[col]] <- dplyr::if_else( + !is.na(apJoin2$taxonID) & apJoin2$tempTaxonID %in% c("2PLANT", "UNKALG"), + if (morph_col %in% names(apJoin2)) apJoin2[[morph_col]] else NA_character_, + if (perTax_col %in% names(apJoin2)) apJoin2[[perTax_col]] else NA_character_ + ) + } + + apJoin2 <- apJoin2 %>% + dplyr::select( + -"taxonID", -"tempTaxonID", -"dataQF", + -dplyr::matches("_morph"),-dplyr::matches("_perTax")) + + } else { message("No data joined from apc_morphospecies table.") @@ -569,9 +517,11 @@ joinAquPointCount <- function(inputDataList, } + + + ### Join apPoint and apPerTax tables #### - # Update morphospecies taxon identifications joinPointCounts <- apPoint %>% dplyr::left_join( apJoin2, diff --git a/tests/testthat/test_estimateAquPercentCover.R b/tests/testthat/test_estimateAquPercentCover.R new file mode 100644 index 0000000..b1654e9 --- /dev/null +++ b/tests/testthat/test_estimateAquPercentCover.R @@ -0,0 +1,132 @@ +### Unit tests for estimateAquPercentCover function #### +### POC: Madaline Ritter, ritterm1@BattelleEcology.org + +# # retrieve test data +# ap <- neonUtilities::loadByProduct( +# dpID="DP1.20072.001", #APL-clip: DP1.20066.001, APC: DP1.20072.001 +# check.size=F, +# startdate = '2023-06', +# enddate = '2023-06', +# site = c("CUPE", "GUIL"), +# include.provisional = T, +# release = "LATEST", +# token = Sys.getenv('NEON_PAT')) +# +# ap$categoricalCodes_20072 <- NULL +# ap$issueLog_20072 <- NULL +# ap$readme_20072 <- NULL +# ap$validation_20072 <- NULL +# ap$variables_20072 <- NULL +# +# saveRDS(ap, "C:/Users/ritterm1/Documents/GitHub/a_neonPackages/neonPlants/tests/testthat/testdata/estimateAquPercentCover_testData_D04_202306.rds") + + +### Read in test data #### +testList <- readRDS(testthat::test_path("testdata", "estimateAquPercentCover_testData_D04_202306.rds")) +testPoint <- testList$apc_pointTransect +testPerTax <- testList$apc_perTaxon +testTaxProc <- testList$apc_taxonomyProcessed + + + +## Test: Function generates expected output type #### +# Test list input +testthat::test_that(desc = "Output type list input", { + + testthat::expect_type(object = estimateAquPercentCover(inputDataList = testList), + type = "list") +}) + +# Test table input +testthat::test_that(desc = "Output type table input", { + + testthat::expect_type(object = estimateAquPercentCover(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc), + type = "list") +}) + + +### Test: Function generates expected output class #### +# Test list input +testthat::test_that(desc = "Output class list input", + { + desc = estimateAquPercentCover(inputDataList = testList) + + testthat::expect_s3_class(desc, class = "list") + testthat::expect_s3_class(desc[1], class = "data.frame") + testthat::expect_s3_class(desc[2], class = "data.frame") +}) + +# Test table input +testthat::test_that(desc = "Output class table input", { + + desc = estimateAquPercentCover(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc) + + testthat::expect_s3_class(desc, class = "list") + testthat::expect_s3_class(desc[1], class = "data.frame") + testthat::expect_s3_class(desc[2], class = "data.frame") +}) + + +### Test: Function generates data frame with expected dimensions using test data #### +## Test list input +# Check expected dimensions of output df 1 +testthat::test_that(desc = "Output percentCover df dimensions list input", { + + out = estimateAquPercentCover(inputDataList = testList) + + testthat::expect_identical(object = nrow(out[[1]]), + expected = as.integer(106)) + + testthat::expect_identical(object = ncol(out[[1]]), + expected = as.integer(6)) +}) + +# Check expected dimensions of output df 2 +testthat::test_that(desc = "Output transectMetrics df dimensions list input", { + + out = estimateAquPercentCover(inputDataList = testList) + + testthat::expect_identical(object = nrow(out[[2]]), + expected = as.integer(20)) + + testthat::expect_identical(object = ncol(out[[2]]), + expected = as.integer(8)) +}) + +## Test table inputs +# Check expected dimensions of output df 1 +testthat::test_that(desc = "Output percentCover df dimensions list input", { + + out = estimateAquPercentCover(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc) + + testthat::expect_identical(object = nrow(out[[1]]), + expected = as.integer(106)) + + testthat::expect_identical(object = ncol(out[[1]]), + expected = as.integer(6)) +}) + +# Check expected dimensions of output df 2 +testthat::test_that(desc = "Output transectMetrics df dimensions list input", { + + out = estimateAquPercentCover(inputPoint = testPoint, + inputPerTax = testPerTax, + inputTaxProc = testTaxProc) + + testthat::expect_identical(object = nrow(out[[2]]), + expected = as.integer(20)) + + testthat::expect_identical(object = ncol(out[[2]]), + expected = as.integer(8)) +}) + + + + + diff --git a/tests/testthat/testdata/estimateAquPercentCover_testData_D04_202306.rds b/tests/testthat/testdata/estimateAquPercentCover_testData_D04_202306.rds new file mode 100644 index 0000000000000000000000000000000000000000..2197da19b1b30880475b5e064cd81e1f7d021963 GIT binary patch literal 19294 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 literal 0 HcmV?d00001 From 34bab83d6902a24f7441a00d39eb2cea127dc8c4 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 9 Jul 2025 11:22:30 -0700 Subject: [PATCH 20/40] update aqu tests --- man/estimateAquPercentCover.Rd | 2 +- man/joinAquPointCount.Rd | 5 +- tests/testthat/test_estimateAquPercentCover.R | 83 +++++++++++++++++-- tests/testthat/test_joinAquPointCount.R | 6 +- 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/man/estimateAquPercentCover.Rd b/man/estimateAquPercentCover.Rd index e7133e2..b65fb36 100644 --- a/man/estimateAquPercentCover.Rd +++ b/man/estimateAquPercentCover.Rd @@ -64,7 +64,7 @@ check.size = FALSE ) # Calculate percent cover for downloaded data -df <- neonPlants::estimateAquPercentCover( +list <- neonPlants::estimateAquPercentCover( inputDataList = apc, inputPoint = NA, inputPerTax = NA, diff --git a/man/joinAquPointCount.Rd b/man/joinAquPointCount.Rd index 1722861..d821757 100644 --- a/man/joinAquPointCount.Rd +++ b/man/joinAquPointCount.Rd @@ -42,9 +42,10 @@ If a single sample in 'apc_taxonomyProcessed' contains multiple macroalgae speci apc <- neonUtilities::loadByProduct( dpID = "DP1.20072.001", site = "all", -startdate = "2018-07", -enddate = "2018-08", +startdate = "2018-03", +enddate = "2018-05", tabl = "all", +package = 'expanded', check.size = FALSE ) diff --git a/tests/testthat/test_estimateAquPercentCover.R b/tests/testthat/test_estimateAquPercentCover.R index b1654e9..2d496a3 100644 --- a/tests/testthat/test_estimateAquPercentCover.R +++ b/tests/testthat/test_estimateAquPercentCover.R @@ -52,10 +52,9 @@ testthat::test_that(desc = "Output type table input", { testthat::test_that(desc = "Output class list input", { desc = estimateAquPercentCover(inputDataList = testList) - - testthat::expect_s3_class(desc, class = "list") - testthat::expect_s3_class(desc[1], class = "data.frame") - testthat::expect_s3_class(desc[2], class = "data.frame") + + testthat::expect_s3_class(desc[[1]], class = "data.frame") + testthat::expect_s3_class(desc[[2]], class = "data.frame") }) # Test table input @@ -65,9 +64,8 @@ testthat::test_that(desc = "Output class table input", { inputPerTax = testPerTax, inputTaxProc = testTaxProc) - testthat::expect_s3_class(desc, class = "list") - testthat::expect_s3_class(desc[1], class = "data.frame") - testthat::expect_s3_class(desc[2], class = "data.frame") + testthat::expect_s3_class(desc[[1]], class = "data.frame") + testthat::expect_s3_class(desc[[2]], class = "data.frame") }) @@ -125,6 +123,77 @@ testthat::test_that(desc = "Output transectMetrics df dimensions list input", { testthat::expect_identical(object = ncol(out[[2]]), expected = as.integer(8)) }) + + +### Test: Generates expected data using test data #### +## Test percentCover output +# Check sum of all percent_cover estimates +testthat::test_that(desc = "Output data frame percent cover sum", { + + out = estimateAquPercentCover(inputDataList = testList) + + testthat::expect_identical( + object = sum(out$percentCover$percent_cover), + expected = 1970) +}) + +# Check taxa percent_cover estimates +testthat::test_that(desc = "Output data frame percent cover taxa sum", { + + out = estimateAquPercentCover(inputDataList = testList) + + testthat::expect_identical( + object = sum(out$percentCover$percent_cover[out$percentCover$type == 'taxon']), + expected = 260) +}) + + +## Test transectMetrics output +# Check sum of all transectLengths +testthat::test_that(desc = "Output data frame transect length sum", { + + out = estimateAquPercentCover(inputDataList = testList) + + testthat::expect_identical( + object = round(sum(out$transectMetrics$transectLength_m), 2), + expected = 105.57) +}) + +# Check unique habitat types +testthat::test_that(desc = "Output data frame unique habitat types", { + + out = estimateAquPercentCover(inputDataList = testList) + + testthat::expect_identical( + object = unique(out$transectMetrics$habitatType), + expected = c("riffle", "run", "pool")) +}) + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/testthat/test_joinAquPointCount.R b/tests/testthat/test_joinAquPointCount.R index 643f73f..d60db6a 100644 --- a/tests/testthat/test_joinAquPointCount.R +++ b/tests/testthat/test_joinAquPointCount.R @@ -2,7 +2,7 @@ ### POC: Madaline Ritter, ritterm1@BattelleEcology.org -### Read in test data +### Read in test data #### testList <- readRDS(testthat::test_path("testdata", "joinAquPointCount_testData_202307.rds")) testPoint <- testList$apc_pointTransect testPerTax <- testList$apc_perTaxon @@ -93,7 +93,7 @@ testthat::test_that(desc = "Output data frame row number table input", { -### Test: Function generates data frame with expected dimensions using test data #### +### Test: Generates expected data using test data #### ## Test dataframe output # Check 'acceptedTaxonID' is pulled from apc_taxonomyProcessed if taxProc data exists testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { @@ -104,7 +104,7 @@ testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { }) -# Check 'acceptedTaxonID' is pulled from apc_morphospecies if identification is in morphospecies table +# Check 'acceptedTaxonID' is pulled from apc_morphospecies if identification is in morphospecies table and not apc_taxonomyProcessed testthat::test_that(desc = "Output data frame source: apc_morphospecies", { outDF <- joinAquPointCount(inputDataList = testList) From 2ba0ad02b98e448aabcd2fe27064cb3df040d3fb Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 9 Jul 2025 13:52:57 -0700 Subject: [PATCH 21/40] Update estimateAquPercentCover.R --- R/estimateAquPercentCover.R | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index 79c886d..940a984 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -15,7 +15,7 @@ #' - \eqn{N_i} is the number of observed points in a transect that match class type “i” (i.e., a particular taxonID or substrate) #' - \eqn{N_t} is the total number of points observed in the transect #' -#' Note: This calculation can generate percent cover values >100% if there is vertical stacking of plants. +#' Note: This calculation can generate percent cover values >100% if there is vertical stacking of plants, or values <100% if 'targetTaxaPresent' is unknown. #' #' @param inputDataList A list object comprised of Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Count tables (DP1.20072.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. [list] #' @@ -29,11 +29,11 @@ #' #' @param barPlots If TRUE, will produce a list of plots, one for each site/date in the data provided. #' -#' @return Two tables are produced containing point count summary data. The first "percentCover" table contains estimated percent cover for each observed species and/or substrate class on aquatic plant transects. This table includes a 'type' column indicating whether the estimate corresponds to a taxonID or a substrate class, and a 'substrateOrTaxonID' column which provides the corresponding identifier. +#' @return Two tables are produced containing point count summary data. The first "percentCover" table contains estimated percent cover for each observed species and/or substrate class on aquatic plant transects. This table includes a 'type' column indicating whether the estimate corresponds to a taxon or substrate class, and a 'substrateOrTaxonID' column which provides the corresponding taxonID or substrate identifier. #' #' The second "transectMetrics" table contains summary information including the length, habitatType, and total number of points sampled at each transect. #' -#' If barPlots = TRUE, a list containing plots of the data will also be produced. +#' If barPlots = TRUE, a list containing plots for each site x date combination will also be produced. #' #' @references #' License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 @@ -92,28 +92,8 @@ estimateAquPercentCover <- function(inputDataList, joinPointCounts <- joinPointCounts %>% dplyr::filter(is.na(.data$samplingImpractical)) ### Calculate Percent Cover #### - - # filter to only observed points: - # joinPointCounts <- joinPointCounts %>% filter(targetTaxaPresent == "Y") - - # Calculate percent cover of plants/macroalgae/etc - # percent_cover <- joinPointCounts %>% - # dplyr::group_by(.data$namedLocation, .data$collectDate) %>% - # dplyr::mutate(total_points = dplyr::n()) %>% - # # dplyr::filter(targetTaxaPresent == 'Y') %>% - # dplyr::group_by(.data$namedLocation, .data$collectDate, .data$acceptedTaxonID, .data$scientificName) %>% - # dplyr::reframe( - # tax_count = dplyr::n(), - # total_points = dplyr::first(total_points), - # percent_cover = round( (100 * tax_count / total_points), 2) - # ) - - # Calculate total number of sampling points per group (siteID + collectDate) - # total_points_df <- joinPointCounts %>% - # dplyr::group_by(.data$siteID, .data$collectDate) %>% - # dplyr::summarise(total_points = dplyr::n(), .groups = "drop") - + # Calculate total number of sampling points per transect total_points_df <- joinPointCounts %>% dplyr::distinct(.data$siteID, .data$namedLocation, .data$collectDate, .data$pointNumber) %>% dplyr::group_by(.data$siteID, .data$namedLocation, .data$collectDate) %>% @@ -147,7 +127,6 @@ estimateAquPercentCover <- function(inputDataList, dplyr::select("siteID", "collectDate", "namedLocation", "type", "substrateOrTaxonID", "percent_cover") #"scientificName", # Combine results - # cover_substrate$scientificName <- NA_character_ percent_cover <- dplyr::bind_rows(cover_substrate, cover_taxon) %>% dplyr::arrange(.data$collectDate, .data$namedLocation) From 84b42338cf6b2c961b137d71369b37d71b3cc98c Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 11 Jul 2025 13:31:21 -0700 Subject: [PATCH 22/40] Update estimateAquPercentCover.R --- R/estimateAquPercentCover.R | 68 +++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index 940a984..df83c03 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -151,7 +151,41 @@ estimateAquPercentCover <- function(inputDataList, # Create a unique ID for each siteID + collectDate percent_cover$boutID <- paste(percent_cover$siteID, substr(percent_cover$collectDate, 1, 10), sep = "_") - + + # Simplify transectID and ensure consistent ordering + percent_cover$transectID <- stringr::str_extract(percent_cover$namedLocation, "(?<=transect\\.)\\w+") + percent_cover$transectID <- stringr::str_replace(percent_cover$transectID, "^0+", "") #strip leading zeros + + percent_cover <- percent_cover %>% + dplyr::mutate( + transectID = as.character(transectID), + transect_num = as.numeric(stringr::str_extract(transectID, "\\d+")), + transect_suffix = stringr::str_extract(transectID, "[a-zA-Z]*") + ) + + ordered_ids <- percent_cover %>% + dplyr::distinct(transectID, transect_num, transect_suffix) %>% + dplyr::arrange(transect_num, transect_suffix) %>% + dplyr::pull(transectID) + + percent_cover <- percent_cover %>% + dplyr::mutate(transectID = factor(transectID, levels = ordered_ids)) + + # Create custom order for plotting + substrate_ids <- percent_cover %>% + dplyr::filter(type == "substrate") %>% + dplyr::pull(substrateOrTaxonID) %>% + unique() + + taxon_ids <- percent_cover %>% + dplyr::filter(type == "taxon") %>% + dplyr::pull(substrateOrTaxonID) %>% + unique() + + stack_order <- c(substrate_ids, taxon_ids) + percent_cover$substrateOrTaxonID <- factor(percent_cover$substrateOrTaxonID, levels = stack_order) + + plot_ids <- unique(percent_cover$boutID) # Create a consistent color palette @@ -176,19 +210,19 @@ estimateAquPercentCover <- function(inputDataList, # Plotting function plot_grid <- function(plot_id) { - ggplot2::ggplot(subset(percent_cover, boutID == plot_id), - ggplot2::aes(x = namedLocation, y = percent_cover, fill = substrateOrTaxonID)) + - ggplot2::geom_bar(stat = "identity", position = "stack") + - ggplot2::scale_fill_manual(values = color_palette) + # Apply consistent colors - labs( - # title = paste("Percent Cover for", gsub("_", " on ", plot_id)), - title = gsub(" ", " on ", plot_id), - # x = "Named Location", - y = "Percent Cover", - fill = "Substrate/Taxon" - ) + - ggplot2::theme_minimal() + - ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) + ggplotly( + ggplot2::ggplot(subset(percent_cover, boutID == plot_id), + ggplot2::aes(x = transectID, y = percent_cover, fill = substrateOrTaxonID)) + + ggplot2::geom_bar(stat = "identity", position = "stack") + + ggplot2::scale_fill_manual(values = color_palette) + # Apply consistent colors + ggplot2::labs( + title = gsub(" ", " on ", plot_id), + x = "Transect Number", + y = "Percent Cover", + fill = "Substrate/Taxon" + ) + + ggplot2::theme_minimal() + ) } plot_list <- lapply(plot_ids, plot_grid) @@ -198,9 +232,9 @@ estimateAquPercentCover <- function(inputDataList, returnList$plot_list <- plot_list # Print all plots - # for (i in seq_along(df$plot_list)) { - # if (!is.null(df$plot_list[[i]])) { - # print(df$plot_list[[i]]) + # for (i in seq_along(test$plot_list)) { + # if (!is.null(test$plot_list[[i]])) { + # print(test$plot_list[[i]]) # } # } From b0099fa9480f55b37f87a8bcc8d2909759d38304 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 30 Jul 2025 13:21:31 -0700 Subject: [PATCH 23/40] minor fix --- R/estimateAquPercentCover.R | 3 ++- R/joinAquClipHarvest.R | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index df83c03..ad19c2e 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -57,8 +57,9 @@ #' inputPerTax = NA, #' inputTaxProc = NA, #' inputMorph = NA, -#' barPlots = F +#' barPlots = FALSE #' ) +#' #' } #' @export estimateAquPercentCover diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 8b3bb1d..4f58c4d 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -466,7 +466,7 @@ joinAquClipHarvest <- function(inputDataList, # "variety", # "form", # "taxonRank" - # "dataQF" + "dataQF" )%>% dplyr::rename(identifiedDate="morphospeciesResolvedDate") From b4e8a928f5d940f0812b6dbbaa1efabc0ea6eef8 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Mon, 4 Aug 2025 09:59:14 -0700 Subject: [PATCH 24/40] Update joinAquPointCount.R --- R/joinAquPointCount.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 3046572..88a4073 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -460,7 +460,7 @@ joinAquPointCount <- function(inputDataList, # "variety", # "form", # "taxonRank" - # "dataQF" + "dataQF" ) %>% dplyr::rename(identifiedDate="morphospeciesResolvedDate") From 98db7f1f29d5dbb1b03fb3da5660d8556cf1a223 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 8 Aug 2025 14:41:56 -0700 Subject: [PATCH 25/40] cleaning up --- R/estimateAquPercentCover.R | 53 +++++++++++++++++++++++-------------- R/joinAquClipHarvest.R | 8 +++--- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index ad19c2e..a6b045b 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -80,25 +80,25 @@ estimateAquPercentCover <- function(inputDataList, joinPointCounts <- neonPlants::joinAquPointCount(inputDataList = inputDataList) # joinPointCounts <- joinAquPointCount(inputDataList = apc) - + } else { joinPointCounts <- neonPlants::joinAquPointCount(inputPoint = inputPoint, - inputPerTax = inputPerTax, - inputTaxProc = inputTaxProc, - inputMorph = inputMorph) + inputPerTax = inputPerTax, + inputTaxProc = inputTaxProc, + inputMorph = inputMorph) } ### Remove SI Records #### joinPointCounts <- joinPointCounts %>% dplyr::filter(is.na(.data$samplingImpractical)) ### Calculate Percent Cover #### - + # Calculate total number of sampling points per transect total_points_df <- joinPointCounts %>% dplyr::distinct(.data$siteID, .data$namedLocation, .data$collectDate, .data$pointNumber) %>% dplyr::group_by(.data$siteID, .data$namedLocation, .data$collectDate) %>% - dplyr::summarise(total_points = dplyr::n(), .groups = "drop") + dplyr::summarise(totalPoints = dplyr::n(), .groups = "drop") # Percent cover by substrate @@ -108,7 +108,7 @@ estimateAquPercentCover <- function(inputDataList, dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% dplyr::left_join(total_points_df, by = c("siteID", "namedLocation", "collectDate")) %>% dplyr::mutate( - percent_cover = round(100 * .data$count / .data$total_points, 2), + percent_cover = round(100 * .data$count / .data$totalPoints, 2), type = "substrate", substrateOrTaxonID = .data$substrate ) %>% @@ -116,14 +116,23 @@ estimateAquPercentCover <- function(inputDataList, # Percent cover by taxon cover_taxon <- joinPointCounts %>% - dplyr::filter(.data$targetTaxaPresent == "Y") %>% - dplyr::group_by(.data$siteID, .data$collectDate, .data$namedLocation, .data$acceptedTaxonID) %>% #, .data$scientificName + dplyr::filter(.data$targetTaxaPresent != "N") %>% # "Y" and "U" + dplyr::group_by(.data$siteID, .data$collectDate, .data$namedLocation, .data$acceptedTaxonID, .data$aquaticPlantType, .data$targetTaxaPresent) %>% #, .data$scientificName dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% dplyr::left_join(total_points_df, by = c("siteID", "namedLocation", "collectDate")) %>% dplyr::mutate( - percent_cover = round(100 * .data$count / .data$total_points, 2), - type = "taxon", - substrateOrTaxonID = .data$acceptedTaxonID + percent_cover = round(100 * .data$count / .data$totalPoints, 2), + # type = "taxon", + type = aquaticPlantType, + substrateOrTaxonID = .data$acceptedTaxonID, + type = case_when( + targetTaxaPresent == 'U' & is.na(type) ~ 'unknown', + TRUE ~ type + ), + substrateOrTaxonID = case_when( + type == 'unknown' ~ 'UNKNOWN', + TRUE ~ substrateOrTaxonID + ) ) %>% dplyr::select("siteID", "collectDate", "namedLocation", "type", "substrateOrTaxonID", "percent_cover") #"scientificName", @@ -134,15 +143,19 @@ estimateAquPercentCover <- function(inputDataList, ### Calculate Transect metrics #### + transect_metrics <- joinPointCounts %>% - dplyr::distinct(.data$domainID, .data$siteID, .data$namedLocation, .data$collectDate, .data$boutNumber, .data$habitatType, .data$pointNumber, .data$transectDistance) %>% + dplyr::distinct(.data$domainID, .data$siteID, .data$namedLocation, .data$collectDate, .data$boutNumber, .data$habitatType, .data$pointNumber, .data$transectDistance, .data$targetTaxaPresent) %>% dplyr::group_by(.data$domainID, .data$siteID, .data$namedLocation, .data$collectDate, .data$boutNumber, .data$habitatType) %>% - dplyr::summarise(transectMax = max(.data$transectDistance), - transectMin = min(.data$transectDistance), - total_points = dplyr::n(),.groups = "drop") %>% + dplyr::summarise( + transectMax = max(.data$transectDistance), + transectMin = min(.data$transectDistance), + totalPoints = dplyr::n(), + pointsWithTaxaPresent = sum(.data$targetTaxaPresent == "Y"), + .groups = "drop" + ) %>% dplyr::mutate(transectLength_m = .data$transectMax - .data$transectMin) %>% - dplyr::select("domainID", "siteID", "namedLocation", "collectDate", "boutNumber", "habitatType", "transectLength_m", "total_points") - + dplyr::select("domainID", "siteID", "namedLocation", "collectDate", "boutNumber", "habitatType", "transectLength_m", "totalPoints", "pointsWithTaxaPresent") returnList <- list(percentCover=percent_cover, transectMetrics=transect_metrics) @@ -174,12 +187,12 @@ estimateAquPercentCover <- function(inputDataList, # Create custom order for plotting substrate_ids <- percent_cover %>% - dplyr::filter(type == "substrate") %>% + dplyr::filter(type == "substrate" | type == 'unknown') %>% dplyr::pull(substrateOrTaxonID) %>% unique() taxon_ids <- percent_cover %>% - dplyr::filter(type == "taxon") %>% + dplyr::filter(type == "macroalgae" | type == "plant") %>% dplyr::pull(substrateOrTaxonID) %>% unique() diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 4f58c4d..e91d53a 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -407,8 +407,8 @@ joinAquClipHarvest <- function(inputDataList, bio_col <- paste0(col, "_bio") apJoin1[[col]] <- dplyr::if_else( !is.na(apJoin1$taxonID_taxProc), - if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA_character_, - if (bio_col %in% names(apJoin1)) apJoin1[[bio_col]] else NA_character_ + if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA, + if (bio_col %in% names(apJoin1)) apJoin1[[bio_col]] else NA ) } @@ -531,8 +531,8 @@ joinAquClipHarvest <- function(inputDataList, bio_col <- paste0(col, "_bio") apJoin2[[col]] <- dplyr::if_else( !is.na(apJoin2$taxonID) & apJoin2$tempTaxonID %in% c("2PLANT", "UNKALG"), - if (morph_col %in% names(apJoin2)) apJoin2[[morph_col]] else NA_character_, - if (bio_col %in% names(apJoin2)) apJoin2[[bio_col]] else NA_character_ + if (morph_col %in% names(apJoin2)) apJoin2[[morph_col]] else NA, + if (bio_col %in% names(apJoin2)) apJoin2[[bio_col]] else NA ) } From b5b27bd2589de493b79c1397d58b79b429291a2b Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 20 Aug 2025 13:49:34 -0700 Subject: [PATCH 26/40] cleaning up functions --- R/estimateAquPercentCover.R | 36 +++---- R/joinAquClipHarvest.R | 187 +++++++++------------------------ man/estimateAquPercentCover.Rd | 9 +- 3 files changed, 73 insertions(+), 159 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index a6b045b..31fe253 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -123,15 +123,15 @@ estimateAquPercentCover <- function(inputDataList, dplyr::mutate( percent_cover = round(100 * .data$count / .data$totalPoints, 2), # type = "taxon", - type = aquaticPlantType, + type = .data$aquaticPlantType, substrateOrTaxonID = .data$acceptedTaxonID, - type = case_when( + type = dplyr::case_when( targetTaxaPresent == 'U' & is.na(type) ~ 'unknown', TRUE ~ type ), - substrateOrTaxonID = case_when( + substrateOrTaxonID = dplyr::case_when( type == 'unknown' ~ 'UNKNOWN', - TRUE ~ substrateOrTaxonID + TRUE ~ .data$substrateOrTaxonID ) ) %>% dplyr::select("siteID", "collectDate", "namedLocation", "type", "substrateOrTaxonID", "percent_cover") #"scientificName", @@ -172,28 +172,28 @@ estimateAquPercentCover <- function(inputDataList, percent_cover <- percent_cover %>% dplyr::mutate( - transectID = as.character(transectID), - transect_num = as.numeric(stringr::str_extract(transectID, "\\d+")), - transect_suffix = stringr::str_extract(transectID, "[a-zA-Z]*") + transectID = as.character(.data$transectID), + transect_num = as.numeric(stringr::str_extract(.data$transectID, "\\d+")), + transect_suffix = stringr::str_extract(.data$transectID, "[a-zA-Z]*") ) ordered_ids <- percent_cover %>% - dplyr::distinct(transectID, transect_num, transect_suffix) %>% - dplyr::arrange(transect_num, transect_suffix) %>% - dplyr::pull(transectID) + dplyr::distinct(.data$transectID, .data$transect_num, .data$transect_suffix) %>% + dplyr::arrange(.data$transect_num, .data$transect_suffix) %>% + dplyr::pull(.data$transectID) percent_cover <- percent_cover %>% - dplyr::mutate(transectID = factor(transectID, levels = ordered_ids)) + dplyr::mutate(transectID = factor(.data$transectID, levels = ordered_ids)) # Create custom order for plotting substrate_ids <- percent_cover %>% - dplyr::filter(type == "substrate" | type == 'unknown') %>% - dplyr::pull(substrateOrTaxonID) %>% + dplyr::filter(.data$type == "substrate" | .data$type == 'unknown') %>% + dplyr::pull(.data$substrateOrTaxonID) %>% unique() taxon_ids <- percent_cover %>% - dplyr::filter(type == "macroalgae" | type == "plant") %>% - dplyr::pull(substrateOrTaxonID) %>% + dplyr::filter(.data$type == "macroalgae" | .data$type == "plant") %>% + dplyr::pull(.data$substrateOrTaxonID) %>% unique() stack_order <- c(substrate_ids, taxon_ids) @@ -224,9 +224,9 @@ estimateAquPercentCover <- function(inputDataList, # Plotting function plot_grid <- function(plot_id) { - ggplotly( - ggplot2::ggplot(subset(percent_cover, boutID == plot_id), - ggplot2::aes(x = transectID, y = percent_cover, fill = substrateOrTaxonID)) + + plotly::ggplotly( + ggplot2::ggplot(subset(percent_cover, 'boutID' == plot_id), + ggplot2::aes(x = 'transectID', y = percent_cover, fill = 'substrateOrTaxonID')) + ggplot2::geom_bar(stat = "identity", position = "stack") + ggplot2::scale_fill_manual(values = color_palette) + # Apply consistent colors ggplot2::labs( diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index e91d53a..cf775a2 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -62,6 +62,7 @@ joinAquClipHarvest <- function(inputDataList, ### Verify user-supplied inputDataList object contains correct data if not NA if (!missing(inputDataList)) { + # Check that input is a list if (!inherits(inputDataList, "list")) { stop( @@ -141,29 +142,11 @@ joinAquClipHarvest <- function(inputDataList, ### Verify 'apBio' table contains required data # Check for required columns bioExpCols <- c( - "sampleID", - "taxonID", - "scientificName", - "morphospeciesID", - "identifiedDate", - "sampleCondition", - "identificationHistoryID", - "dataQF", - "publicationDate", - "release", - "division", - "class", - "order", - "family", - "genus", - "section", - "specificEpithet", - "scientificNameAuthorship", - "identificationQualifier", - "identificationReferences", - "remarks", - "identifiedBy", - "uid" + "sampleID", "taxonID", "scientificName", "morphospeciesID", "identifiedDate", + "sampleCondition", "identificationHistoryID", "dataQF", "publicationDate", + "release", "division", "class", "order", "family", "genus", "section", + "specificEpithet", "scientificNameAuthorship", "identificationQualifier", + "identificationReferences", "remarks", "identifiedBy", "uid" ) @@ -187,18 +170,8 @@ joinAquClipHarvest <- function(inputDataList, ### Verify 'apClip' table contains required data # Check for required columns clipExpCols <- c( - "namedLocation", - "eventID", - "boutNumber", - "fieldID", - "benthicArea", - "domainID", - "siteID", - "startDate", - "collectDate", - "fieldIDCode", - "recordedBy", - "remarks" + "namedLocation", "eventID", "boutNumber", "fieldID", "benthicArea", "domainID", + "siteID", "startDate", "collectDate", "fieldIDCode", "recordedBy", "remarks" ) if (length(setdiff(clipExpCols, colnames(apClip))) > 0) { @@ -228,33 +201,12 @@ joinAquClipHarvest <- function(inputDataList, ### Verify 'apTaxProc' table contains required data if data exists taxProcExpCols <- c( - "sampleID", - "taxonID", - "identifiedDate", - "sampleCondition", - "identificationHistoryID", - "dataQF", - "publicationDate", - "release", - "division", - "class", - "order", - "family", - "genus", - "section", - "specificEpithet", - "scientificNameAuthorship", - "identificationQualifier", - "identificationReferences", - "remarks", - "identifiedBy", - "morphospeciesID", - "uid", - "domainID", - "siteID", - "namedLocation", - "collectDate", - "sampleCode" + "sampleID", "taxonID", "identifiedDate", "sampleCondition", + "identificationHistoryID", "dataQF", "publicationDate", "release", + "division", "class", "order", "family", "genus", "section", "specificEpithet", + "scientificNameAuthorship", "identificationQualifier", + "identificationReferences", "remarks", "identifiedBy", "morphospeciesID", + "uid", "domainID", "siteID", "namedLocation", "collectDate", "sampleCode" ) # Check for data @@ -282,13 +234,8 @@ joinAquClipHarvest <- function(inputDataList, ### Verify 'apMorph' table contains required data if data exists morphExpCols <- c( - "morphospeciesID", - "taxonID", - "scientificName", - "identificationQualifier", - "identificationReferences", - "identifiedBy", - "dataQF" + "morphospeciesID", "taxonID", "scientificName", "identificationQualifier", + "identificationReferences", "identifiedBy", "dataQF" ) @@ -317,7 +264,7 @@ joinAquClipHarvest <- function(inputDataList, ### Join apBio and apTaxProc tables using sampleID #### if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { - # message("Join taxonomyProcessed taxonomic identifications.") + # Select needed columns from apTaxProc apTaxProc <- apTaxProc %>% dplyr::select( @@ -328,7 +275,8 @@ joinAquClipHarvest <- function(inputDataList, -"collectDate", -"morphospeciesID", -"sampleCode" - ) + ) #%>% + # dplyr::mutate(identifiedDate = as.character(identifiedDate)) #biomass identifiedDate is character, not date # Columns conditionally replaced with taxProc data join1_cols <- c( @@ -402,16 +350,24 @@ joinAquClipHarvest <- function(inputDataList, ) ) + # for (col in join1_cols) { + # taxProc_col <- paste0(col, "_taxProc") + # bio_col <- paste0(col, "_bio") + # apJoin1[[col]] <- dplyr::if_else( + # !is.na(apJoin1$taxonID_taxProc), + # if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA_character_, + # if (bio_col %in% names(apJoin1)) apJoin1[[bio_col]] else NA_character_ + # ) + # } for (col in join1_cols) { taxProc_col <- paste0(col, "_taxProc") bio_col <- paste0(col, "_bio") apJoin1[[col]] <- dplyr::if_else( !is.na(apJoin1$taxonID_taxProc), - if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA, - if (bio_col %in% names(apJoin1)) apJoin1[[bio_col]] else NA + if (taxProc_col %in% names(apJoin1)) as.character(apJoin1[[taxProc_col]]) else NA_character_, + if (bio_col %in% names(apJoin1)) as.character(apJoin1[[bio_col]]) else NA_character_ ) } - apJoin1 <- apJoin1 %>% dplyr::select(-"uid", -"targetTaxaPresent", -dplyr::matches("_taxProc"),-dplyr::matches("_bio")) @@ -447,80 +403,42 @@ joinAquClipHarvest <- function(inputDataList, # message("Join morphospecies taxonomic identifications.") apMorph <- apMorph %>% dplyr::select( - "taxonID", - "scientificName", - "morphospeciesID", - "identificationQualifier", - "identificationReferences", - "identifiedBy", - "morphospeciesResolvedDate", - # "phylum", - # "division", - # "class", - # "order", - # "family", - # "genus", - # "section", - # "specificEpithet", - # "infraspecificEpithet", - # "variety", - # "form", - # "taxonRank" + "taxonID", "scientificName", "morphospeciesID", "identificationQualifier", + "identificationReferences", "identifiedBy", "morphospeciesResolvedDate", + ## Uncomment next two lines once morph table has been updated + # "phylum", "division", "class", "order", "family", "genus", "section", + # "specificEpithet", "infraspecificEpithet", "variety", "form", "taxonRank", "dataQF" )%>% dplyr::rename(identifiedDate="morphospeciesResolvedDate") # Update morphospecies taxon identifications apJoin2 <- apJoin1 %>% - dplyr::mutate(morphospeciesID = dplyr::if_else( - !is.na(.data$morphospeciesID), - paste0(.data$morphospeciesID, ".", substr(.data$collectDate, 1, 4)), - .data$morphospeciesID - )) %>% - dplyr::left_join(apMorph, - by = "morphospeciesID", - suffix = c("_bio", "_morph")) %>% + dplyr::mutate( + morphospeciesID = dplyr::if_else( + !is.na(.data$morphospeciesID), + paste0(.data$morphospeciesID, ".", substr(.data$collectDate, 1, 4)), + .data$morphospeciesID + ) + ) %>% + dplyr::left_join(apMorph, by="morphospeciesID", suffix=c("_bio","_morph")) %>% dplyr::mutate( taxonIDSourceTable = dplyr::if_else( !is.na(.data$taxonID) & .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), "apc_morphospecies", .data$taxonIDSourceTable), + acceptedTaxonID = dplyr::if_else( !is.na(.data$taxonID) & .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), .data$taxonID, .data$tempTaxonID), - # scientificName = dplyr::if_else( - # !is.na(.data$taxonID) & - # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - # .data$scientificName_morph, - # .data$scientificName_bio - # ), - # identificationQualifier = dplyr::if_else( - # !is.na(.data$taxonID) & - # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - # .data$identificationQualifier_morph, - # .data$identificationQualifier_bio - # ), - # identificationReferences = dplyr::if_else( - # !is.na(.data$taxonID) & - # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - # .data$identificationReferences_morph, - # .data$identificationReferences_bio - # ), - # identifiedBy = dplyr::if_else( - # !is.na(.data$taxonID) & - # .data$tempTaxonID %in% c('2PLANT', 'UNKALG'), - # .data$identifiedBy_morph, - # .data$identifiedBy_bio - # ), - # identifiedDate = NA, - # #not currently in pub table - morphospeciesDataQF = .data$dataQF + morphospeciesDataQF = .data$dataQF ) # Columns conditionally replaced with morph data join2_cols <- c( "scientificName", "identificationQualifier", "identificationReferences", "identifiedBy", "identifiedDate" + ## Uncomment next two lines once morph table has been updated # , "phylum", "division", "class", "order", # "family", "genus", "section", "specificEpithet", "infraspecificEpithet", # "variety", "form", "taxonRank" @@ -531,8 +449,8 @@ joinAquClipHarvest <- function(inputDataList, bio_col <- paste0(col, "_bio") apJoin2[[col]] <- dplyr::if_else( !is.na(apJoin2$taxonID) & apJoin2$tempTaxonID %in% c("2PLANT", "UNKALG"), - if (morph_col %in% names(apJoin2)) apJoin2[[morph_col]] else NA, - if (bio_col %in% names(apJoin2)) apJoin2[[bio_col]] else NA + if (morph_col %in% names(apJoin2)) as.character(apJoin2[[morph_col]]) else NA_character_, + if (bio_col %in% names(apJoin2)) as.character(apJoin2[[bio_col]]) else NA_character_ ) } @@ -558,13 +476,8 @@ joinAquClipHarvest <- function(inputDataList, joinClipHarvest <- apClip %>% dplyr::select( - -"benthicArea", - -"namedLocation", - -"domainID", - -"siteID", - -"startDate", - -"collectDate", - -"fieldIDCode" + -"benthicArea", -"namedLocation", -"domainID", -"siteID", + -"startDate", -"collectDate", -"fieldIDCode" ) %>% dplyr::left_join(apJoin2, by = "fieldID", suffix = c("_clip", "_bio")) %>% dplyr::mutate( diff --git a/man/estimateAquPercentCover.Rd b/man/estimateAquPercentCover.Rd index b65fb36..6b71831 100644 --- a/man/estimateAquPercentCover.Rd +++ b/man/estimateAquPercentCover.Rd @@ -27,11 +27,11 @@ estimateAquPercentCover( \item{barPlots}{If TRUE, will produce a list of plots, one for each site/date in the data provided.} } \value{ -Two tables are produced containing point count summary data. The first "percentCover" table contains estimated percent cover for each observed species and/or substrate class on aquatic plant transects. This table includes a 'type' column indicating whether the estimate corresponds to a taxonID or a substrate class, and a 'substrateOrTaxonID' column which provides the corresponding identifier. +Two tables are produced containing point count summary data. The first "percentCover" table contains estimated percent cover for each observed species and/or substrate class on aquatic plant transects. This table includes a 'type' column indicating whether the estimate corresponds to a taxon or substrate class, and a 'substrateOrTaxonID' column which provides the corresponding taxonID or substrate identifier. The second "transectMetrics" table contains summary information including the length, habitatType, and total number of points sampled at each transect. -If barPlots = TRUE, a list containing plots of the data will also be produced. +If barPlots = TRUE, a list containing plots for each site x date combination will also be produced. } \description{ Data inputs are NEON Aquatic Plant, Bryophyte, Lichen, and Macroalgae Point Counts in Wadeable Streams (DP1.20072.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. The estimateAquPercentCover() function joins taxonomy information across point count tables and aggregates occurrence data to estimate percent cover at the transect level. @@ -49,7 +49,7 @@ Where: \item \eqn{N_t} is the total number of points observed in the transect } -Note: This calculation can generate percent cover values >100\% if there is vertical stacking of plants. +Note: This calculation can generate percent cover values >100\% if there is vertical stacking of plants, or values <100\% if 'targetTaxaPresent' is unknown. } \examples{ \dontrun{ @@ -70,8 +70,9 @@ inputPoint = NA, inputPerTax = NA, inputTaxProc = NA, inputMorph = NA, -barPlots = F +barPlots = FALSE ) + } } \references{ From 20970db90954b27673760a9b4a38300c30b7a88d Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 20 Aug 2025 13:49:53 -0700 Subject: [PATCH 27/40] add dependencies --- DESCRIPTION | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index bb194a2..7a98d66 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -47,4 +47,7 @@ Imports: magrittr, rlang, stringr, - tidyr + tidyr, + RColorBrewer, + ggplot2, + plotly From bad699c6fc222520421d4118fab031ac75a5a2f0 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 20 Aug 2025 13:50:19 -0700 Subject: [PATCH 28/40] update tests --- tests/testthat/test_estimateAquPercentCover.R | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/testthat/test_estimateAquPercentCover.R b/tests/testthat/test_estimateAquPercentCover.R index 2d496a3..e7ca67e 100644 --- a/tests/testthat/test_estimateAquPercentCover.R +++ b/tests/testthat/test_estimateAquPercentCover.R @@ -19,7 +19,7 @@ # ap$variables_20072 <- NULL # # saveRDS(ap, "C:/Users/ritterm1/Documents/GitHub/a_neonPackages/neonPlants/tests/testthat/testdata/estimateAquPercentCover_testData_D04_202306.rds") - +# testList <- readRDS("C:/Users/ritterm1/Documents/GitHub/a_neonPackages/neonPlants/tests/testthat/testdata/estimateAquPercentCover_testData_D04_202306.rds") ### Read in test data #### testList <- readRDS(testthat::test_path("testdata", "estimateAquPercentCover_testData_D04_202306.rds")) @@ -77,7 +77,7 @@ testthat::test_that(desc = "Output percentCover df dimensions list input", { out = estimateAquPercentCover(inputDataList = testList) testthat::expect_identical(object = nrow(out[[1]]), - expected = as.integer(106)) + expected = as.integer(109)) testthat::expect_identical(object = ncol(out[[1]]), expected = as.integer(6)) @@ -92,7 +92,7 @@ testthat::test_that(desc = "Output transectMetrics df dimensions list input", { expected = as.integer(20)) testthat::expect_identical(object = ncol(out[[2]]), - expected = as.integer(8)) + expected = as.integer(9)) }) ## Test table inputs @@ -104,7 +104,7 @@ testthat::test_that(desc = "Output percentCover df dimensions list input", { inputTaxProc = testTaxProc) testthat::expect_identical(object = nrow(out[[1]]), - expected = as.integer(106)) + expected = as.integer(109)) testthat::expect_identical(object = ncol(out[[1]]), expected = as.integer(6)) @@ -121,7 +121,7 @@ testthat::test_that(desc = "Output transectMetrics df dimensions list input", { expected = as.integer(20)) testthat::expect_identical(object = ncol(out[[2]]), - expected = as.integer(8)) + expected = as.integer(9)) }) @@ -134,7 +134,7 @@ testthat::test_that(desc = "Output data frame percent cover sum", { testthat::expect_identical( object = sum(out$percentCover$percent_cover), - expected = 1970) + expected = 2050) }) # Check taxa percent_cover estimates @@ -143,8 +143,16 @@ testthat::test_that(desc = "Output data frame percent cover taxa sum", { out = estimateAquPercentCover(inputDataList = testList) testthat::expect_identical( - object = sum(out$percentCover$percent_cover[out$percentCover$type == 'taxon']), - expected = 260) + object = sum(out$percentCover$percent_cover[out$percentCover$type == 'macroalgae']), + expected = 200) + + testthat::expect_identical( + object = sum(out$percentCover$percent_cover[out$percentCover$type == 'plant']), + expected = 60) + + testthat::expect_identical( + object = sum(out$percentCover$percent_cover[out$percentCover$type == 'unknown']), + expected = 80) }) From 14f26d9691fd7fb20015b7e8dca7a0f534bb0429 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 20 Aug 2025 14:32:13 -0700 Subject: [PATCH 29/40] Update joinAquPointCount.R --- R/joinAquPointCount.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 88a4073..e719d17 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -402,8 +402,8 @@ joinAquPointCount <- function(inputDataList, perTax_col <- paste0(col, "_perTax") apJoin1[[col]] <- dplyr::if_else( !is.na(apJoin1$taxonID_taxProc), - if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA_character_, - if (perTax_col %in% names(apJoin1)) apJoin1[[perTax_col]] else NA_character_ + if (taxProc_col %in% names(apJoin1)) as.character(apJoin1[[taxProc_col]]) else NA_character_, + if (perTax_col %in% names(apJoin1)) as.character(apJoin1[[perTax_col]]) else NA_character_ ) } @@ -497,8 +497,8 @@ joinAquPointCount <- function(inputDataList, perTax_col <- paste0(col, "_perTax") apJoin2[[col]] <- dplyr::if_else( !is.na(apJoin2$taxonID) & apJoin2$tempTaxonID %in% c("2PLANT", "UNKALG"), - if (morph_col %in% names(apJoin2)) apJoin2[[morph_col]] else NA_character_, - if (perTax_col %in% names(apJoin2)) apJoin2[[perTax_col]] else NA_character_ + if (morph_col %in% names(apJoin2)) as.character(apJoin2[[morph_col]]) else NA_character_, + if (perTax_col %in% names(apJoin2)) as.character(apJoin2[[perTax_col]]) else NA_character_ ) } From 299b7c1c74ee4aaf77f0eb00a4548c69463add77 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 20 Aug 2025 14:36:38 -0700 Subject: [PATCH 30/40] Update joinAquPointCount.R --- R/joinAquPointCount.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index e719d17..5da08e8 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -549,7 +549,8 @@ joinAquPointCount <- function(inputDataList, !is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point), TRUE ~ NA ) - ) + ) %>% + dplyr::select(-"remarks_perTax", -"remarks_point") return(joinPointCounts) From a76423e08ed209bad3e90733f01da94fe50a43e7 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 20 Aug 2025 14:43:29 -0700 Subject: [PATCH 31/40] Update estimateAquPercentCover.R --- R/estimateAquPercentCover.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index 31fe253..b98af88 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -225,8 +225,8 @@ estimateAquPercentCover <- function(inputDataList, # Plotting function plot_grid <- function(plot_id) { plotly::ggplotly( - ggplot2::ggplot(subset(percent_cover, 'boutID' == plot_id), - ggplot2::aes(x = 'transectID', y = percent_cover, fill = 'substrateOrTaxonID')) + + ggplot2::ggplot(subset(percent_cover, boutID == plot_id), + ggplot2::aes(x = transectID, y = percent_cover, fill = substrateOrTaxonID)) + ggplot2::geom_bar(stat = "identity", position = "stack") + ggplot2::scale_fill_manual(values = color_palette) + # Apply consistent colors ggplot2::labs( From b3e3d19e73ea541b87b7f65d2d5ad5bf4e536f62 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Wed, 20 Aug 2025 14:47:46 -0700 Subject: [PATCH 32/40] Update test_joinAquPointCount.R --- tests/testthat/test_joinAquPointCount.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test_joinAquPointCount.R b/tests/testthat/test_joinAquPointCount.R index d60db6a..b786067 100644 --- a/tests/testthat/test_joinAquPointCount.R +++ b/tests/testthat/test_joinAquPointCount.R @@ -66,7 +66,7 @@ testthat::test_that(desc = "Output data frame row number list input", { testthat::test_that(desc = "Output data frame column number list input", { testthat::expect_identical(object = ncol(joinAquPointCount(inputDataList = testList)), - expected = as.integer(75)) + expected = as.integer(73)) }) @@ -88,7 +88,7 @@ testthat::test_that(desc = "Output data frame row number table input", { inputPerTax = testPerTax, inputTaxProc = testTaxProc, inputMorph = testMorph)), - expected = as.integer(75)) + expected = as.integer(73)) }) From d9fc8bd955ce9dbbfa7444a928227f4bfc48be56 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 22 Aug 2025 07:09:38 -0700 Subject: [PATCH 33/40] format dates --- R/joinAquClipHarvest.R | 12 ++++++++++++ R/joinAquPointCount.R | 13 +++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index cf775a2..3a24166 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -511,6 +511,18 @@ joinAquClipHarvest <- function(inputDataList, ### Filter out bout 1 and 3 data #### joinClipHarvest <- joinClipHarvest %>% dplyr::filter(.data$boutNumber == '2') + ### Correct data types for date fields #### + joinClipHarvest$collectDate <- as.POSIXct(joinClipHarvest$collectDate, + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinClipHarvest$processingDate <- as.Date(joinClipHarvest$processingDate) + joinClipHarvest$startDate <- as.POSIXct(joinClipHarvest$startDate, + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinClipHarvest$biomassPublicationDate <- as.POSIXct(joinClipHarvest$biomassPublicationDate, format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinClipHarvest$taxProcessedPublicationDate <- as.POSIXct(joinClipHarvest$taxProcessedPublicationDate, format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinClipHarvest$identifiedDate <- as.Date(joinClipHarvest$identifiedDate) + joinClipHarvest$clipPublicationDate <- as.POSIXct(joinClipHarvest$clipPublicationDate, format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + + return(joinClipHarvest) } #function closer diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 5da08e8..1ea95f7 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -523,6 +523,7 @@ joinAquPointCount <- function(inputDataList, ### Join apPoint and apPerTax tables #### joinPointCounts <- apPoint %>% + dplyr::rename(pointPublicationDate = "publicationDate") %>% dplyr::left_join( apJoin2, by = c( @@ -548,10 +549,18 @@ joinAquPointCount <- function(inputDataList, is.na(.data$remarks_perTax) & !is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point), TRUE ~ NA - ) - ) %>% + ) ) %>% dplyr::select(-"remarks_perTax", -"remarks_point") + ### Correct data types for date fields #### + joinPointCounts$collectDate <- as.POSIXct(joinPointCounts$collectDate, + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinPointCounts$pointPublicationDate <- as.POSIXct(joinPointCounts$pointPublicationDate, + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinPointCounts$perTaxonPublicationDate <- as.POSIXct(joinPointCounts$perTaxonPublicationDate, + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinPointCounts$identifiedDate <- as.Date(joinPointCounts$identifiedDate) + return(joinPointCounts) } #function closer From bc429255cc84624b025c4525736ae70d369b69e0 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 22 Aug 2025 07:36:36 -0700 Subject: [PATCH 34/40] update date format --- R/joinAquClipHarvest.R | 25 +++++++++++++++++-------- R/joinAquPointCount.R | 30 ++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 3a24166..0f77024 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -511,16 +511,25 @@ joinAquClipHarvest <- function(inputDataList, ### Filter out bout 1 and 3 data #### joinClipHarvest <- joinClipHarvest %>% dplyr::filter(.data$boutNumber == '2') - ### Correct data types for date fields #### - joinClipHarvest$collectDate <- as.POSIXct(joinClipHarvest$collectDate, - format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + ### Re-format date columns #### joinClipHarvest$processingDate <- as.Date(joinClipHarvest$processingDate) - joinClipHarvest$startDate <- as.POSIXct(joinClipHarvest$startDate, - format = "%Y-%m-%dT%H:%MZ", tz = "UTC") - joinClipHarvest$biomassPublicationDate <- as.POSIXct(joinClipHarvest$biomassPublicationDate, format = "%Y-%m-%dT%H:%MZ", tz = "UTC") - joinClipHarvest$taxProcessedPublicationDate <- as.POSIXct(joinClipHarvest$taxProcessedPublicationDate, format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinClipHarvest$identifiedDate <- as.Date(joinClipHarvest$identifiedDate) - joinClipHarvest$clipPublicationDate <- as.POSIXct(joinClipHarvest$clipPublicationDate, format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + + joinClipHarvest$collectDate <- as.POSIXct(joinClipHarvest$collectDate, + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + + joinClipHarvest$startDate <- as.POSIXct(joinClipHarvest$startDate, + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + + joinClipHarvest$biomassPublicationDate <- as.POSIXct(joinClipHarvest$biomassPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + + joinClipHarvest$taxProcessedPublicationDate <- as.POSIXct(joinClipHarvest$taxProcessedPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + + joinClipHarvest$clipPublicationDate <- as.POSIXct(joinClipHarvest$clipPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") return(joinClipHarvest) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index 1ea95f7..d51a4a5 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -349,12 +349,12 @@ joinAquPointCount <- function(inputDataList, !is.na(.data$sampleCondition_taxProc) ~ paste0( "perTaxon ", .data$sampleCondition_perTax, - " | taxonProcessed ", + " | taxProcessed ", .data$sampleCondition_taxProc ),!is.na(.data$sampleCondition_perTax) & is.na(.data$sampleCondition_taxProc) ~ paste0("perTaxon ", .data$sampleCondition_perTax), is.na(.data$sampleCondition_perTax) & - !is.na(.data$sampleCondition_taxProc) ~ paste0("taxonProcessed ", .data$sampleCondition_taxProc), + !is.na(.data$sampleCondition_taxProc) ~ paste0("taxProcessed ", .data$sampleCondition_taxProc), TRUE ~ NA ), taxonIDSourceTable = dplyr::case_when( @@ -388,11 +388,11 @@ joinAquPointCount <- function(inputDataList, perTaxonRelease = .data$release_perTax, remarks = dplyr::case_when( !is.na(.data$remarks_perTax) & !is.na(.data$remarks_taxProc) ~ paste0( - "perTaxon remarks - ", .data$remarks_perTax, " | taxonProcessed remarks - ",.data$remarks_taxProc + "perTaxon remarks - ", .data$remarks_perTax, " | taxProcessed remarks - ",.data$remarks_taxProc ), is.na(.data$remarks_taxProc) & !is.na(.data$remarks_perTax) ~ paste0("perTaxon remarks - ", .data$remarks_perTax),!is.na(.data$remarks_taxProc) & - is.na(.data$remarks_perTax) ~ paste0("taxonProcessed remarks - ", .data$remarks_taxProc), + is.na(.data$remarks_perTax) ~ paste0("taxProcessed remarks - ", .data$remarks_taxProc), TRUE ~ NA ) ) @@ -523,7 +523,10 @@ joinAquPointCount <- function(inputDataList, ### Join apPoint and apPerTax tables #### joinPointCounts <- apPoint %>% - dplyr::rename(pointPublicationDate = "publicationDate") %>% + dplyr::rename( + pointPublicationDate = "publicationDate", + pointDataQF = "dataQF" + ) %>% dplyr::left_join( apJoin2, by = c( @@ -552,14 +555,21 @@ joinAquPointCount <- function(inputDataList, ) ) %>% dplyr::select(-"remarks_perTax", -"remarks_point") - ### Correct data types for date fields #### + ### Re-format date columns #### + joinPointCounts$identifiedDate <- as.Date(joinPointCounts$identifiedDate) + joinPointCounts$collectDate <- as.POSIXct(joinPointCounts$collectDate, - format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + joinPointCounts$pointPublicationDate <- as.POSIXct(joinPointCounts$pointPublicationDate, - format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + joinPointCounts$perTaxonPublicationDate <- as.POSIXct(joinPointCounts$perTaxonPublicationDate, - format = "%Y-%m-%dT%H:%MZ", tz = "UTC") - joinPointCounts$identifiedDate <- as.Date(joinPointCounts$identifiedDate) + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + + joinPointCounts$taxProcessedPublicationDate <- as.POSIXct(joinPointCounts$taxProcessedPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + return(joinPointCounts) From 9f1d09a554e2e7279388d330e4dcd97cbfba55d8 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 22 Aug 2025 08:34:11 -0700 Subject: [PATCH 35/40] cleanup --- R/joinAquClipHarvest.R | 24 ++++++++---------------- R/joinAquPointCount.R | 10 +++++++--- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 0f77024..39f01d8 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -298,11 +298,11 @@ joinAquClipHarvest <- function(inputDataList, sampleCondition = dplyr::case_when( !is.na(.data$sampleCondition_bio) & !is.na(.data$sampleCondition_taxProc) ~ - paste0("biomass ", .data$sampleCondition_bio," | taxonProcessed ", .data$sampleCondition_taxProc), + paste0("biomass ", .data$sampleCondition_bio," | taxProcessed ", .data$sampleCondition_taxProc), !is.na(.data$sampleCondition_bio) & is.na(.data$sampleCondition_taxProc) ~ paste0("biomass ", .data$sampleCondition_bio), is.na(.data$sampleCondition_bio) & !is.na(.data$sampleCondition_taxProc) ~ - paste0("taxonProcessed ", .data$sampleCondition_taxProc), + paste0("taxProcessed ", .data$sampleCondition_taxProc), TRUE ~ NA ), @@ -341,24 +341,15 @@ joinAquClipHarvest <- function(inputDataList, remarks = dplyr::case_when( !is.na(.data$remarks_bio) & !is.na(.data$remarks_taxProc) ~ - paste0( "biomass remarks - ", .data$remarks_bio, " | taxonProcessed remarks - ", .data$remarks_taxProc), + paste0( "biomass remarks - ", .data$remarks_bio, " | taxProcessed remarks - ", .data$remarks_taxProc), is.na(.data$remarks_taxProc) & !is.na(.data$remarks_bio) ~ paste0("biomass remarks - ", .data$remarks_bio), !is.na(.data$remarks_taxProc) & is.na(.data$remarks_bio) ~ - paste0("taxonProcessed remarks - ", .data$remarks_taxProc), + paste0("taxProcessed remarks - ", .data$remarks_taxProc), TRUE ~ NA ) ) - # for (col in join1_cols) { - # taxProc_col <- paste0(col, "_taxProc") - # bio_col <- paste0(col, "_bio") - # apJoin1[[col]] <- dplyr::if_else( - # !is.na(apJoin1$taxonID_taxProc), - # if (taxProc_col %in% names(apJoin1)) apJoin1[[taxProc_col]] else NA_character_, - # if (bio_col %in% names(apJoin1)) apJoin1[[bio_col]] else NA_character_ - # ) - # } for (col in join1_cols) { taxProc_col <- paste0(col, "_taxProc") bio_col <- paste0(col, "_bio") @@ -525,12 +516,13 @@ joinAquClipHarvest <- function(inputDataList, joinClipHarvest$biomassPublicationDate <- as.POSIXct(joinClipHarvest$biomassPublicationDate, format = "%Y%m%dT%H%M%SZ", tz = "UTC") - joinClipHarvest$taxProcessedPublicationDate <- as.POSIXct(joinClipHarvest$taxProcessedPublicationDate, - format = "%Y%m%dT%H%M%SZ", tz = "UTC") - joinClipHarvest$clipPublicationDate <- as.POSIXct(joinClipHarvest$clipPublicationDate, format = "%Y%m%dT%H%M%SZ", tz = "UTC") + if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0){ + joinClipHarvest$taxProcessedPublicationDate <- as.POSIXct(joinClipHarvest$taxProcessedPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + } return(joinClipHarvest) diff --git a/R/joinAquPointCount.R b/R/joinAquPointCount.R index d51a4a5..5dd49df 100644 --- a/R/joinAquPointCount.R +++ b/R/joinAquPointCount.R @@ -525,6 +525,7 @@ joinAquPointCount <- function(inputDataList, joinPointCounts <- apPoint %>% dplyr::rename( pointPublicationDate = "publicationDate", + pointRelease = "release", pointDataQF = "dataQF" ) %>% dplyr::left_join( @@ -552,9 +553,10 @@ joinAquPointCount <- function(inputDataList, is.na(.data$remarks_perTax) & !is.na(.data$remarks_point) ~ paste0("pointTransect remarks - ", .data$remarks_point), TRUE ~ NA - ) ) %>% + )) %>% dplyr::select(-"remarks_perTax", -"remarks_point") + ### Re-format date columns #### joinPointCounts$identifiedDate <- as.Date(joinPointCounts$identifiedDate) @@ -567,8 +569,10 @@ joinAquPointCount <- function(inputDataList, joinPointCounts$perTaxonPublicationDate <- as.POSIXct(joinPointCounts$perTaxonPublicationDate, format = "%Y%m%dT%H%M%SZ", tz = "UTC") - joinPointCounts$taxProcessedPublicationDate <- as.POSIXct(joinPointCounts$taxProcessedPublicationDate, - format = "%Y%m%dT%H%M%SZ", tz = "UTC") + if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { + joinPointCounts$taxProcessedPublicationDate <- as.POSIXct(joinPointCounts$taxProcessedPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + } return(joinPointCounts) From f0708459100804be97eea06217479c95d0bf6257 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Fri, 22 Aug 2025 08:57:55 -0700 Subject: [PATCH 36/40] address plot notes --- R/estimateAquPercentCover.R | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/R/estimateAquPercentCover.R b/R/estimateAquPercentCover.R index b98af88..41e322c 100644 --- a/R/estimateAquPercentCover.R +++ b/R/estimateAquPercentCover.R @@ -225,8 +225,13 @@ estimateAquPercentCover <- function(inputDataList, # Plotting function plot_grid <- function(plot_id) { plotly::ggplotly( - ggplot2::ggplot(subset(percent_cover, boutID == plot_id), - ggplot2::aes(x = transectID, y = percent_cover, fill = substrateOrTaxonID)) + + ggplot2::ggplot( + # subset(percent_cover, .data$boutID == plot_id), + percent_cover[percent_cover$boutID == plot_id, ], + ggplot2::aes( + x = .data$transectID, + y = .data$percent_cover, + fill = .data$substrateOrTaxonID)) + ggplot2::geom_bar(stat = "identity", position = "stack") + ggplot2::scale_fill_manual(values = color_palette) + # Apply consistent colors ggplot2::labs( @@ -244,13 +249,6 @@ estimateAquPercentCover <- function(inputDataList, # Bind df and plots returnList$plot_list <- plot_list - - # Print all plots - # for (i in seq_along(test$plot_list)) { - # if (!is.null(test$plot_list[[i]])) { - # print(test$plot_list[[i]]) - # } - # } } From 7e496ece6953357852d8fe52e7b8a47dfefb124c Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 2 Sep 2025 11:39:43 -0700 Subject: [PATCH 37/40] subset test data --- tests/testthat/test_joinAquClipHarvest.R | 35 ++++++++--- tests/testthat/test_joinAquPointCount.R | 56 +++++++++++------- .../joinAquClipHarvest_testData_202307.rds | Bin 43948 -> 4272 bytes .../joinAquPointCount_testData_202307.rds | Bin 125492 -> 9640 bytes 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R index f912cfb..ab0f895 100644 --- a/tests/testthat/test_joinAquClipHarvest.R +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -59,7 +59,7 @@ testthat::test_that(desc = "Output class table input", { testthat::test_that(desc = "Output data frame row number list input", { testthat::expect_identical(object = nrow(joinAquClipHarvest(inputDataList = testList)), - expected = as.integer(324)) + expected = as.integer(9)) }) @@ -79,7 +79,7 @@ testthat::test_that(desc = "Output data frame row number table input", { inputClip = testClip, inputTaxProc = testTaxProc, inputMorph = testMorph)), - expected = as.integer(324)) + expected = as.integer(9)) }) # Check expected column number of output @@ -100,8 +100,10 @@ testthat::test_that(desc = "Output data frame row number table input", { testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'PRLA.20230703.AP1.P6')]), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), expected = "apl_taxonomyProcessed") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), + expected = "NEONDREX309000") }) @@ -111,10 +113,13 @@ testthat::test_that(desc = "Output data frame source: apc_morphospecies", { outDF <- joinAquClipHarvest(inputDataList = testList) testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP1.Q2')]), expected = "apc_morphospecies") - - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.AP1.Q2')]), expected = "LURE2") + + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'FLNT.20230724.AP2.P3')]), + expected = "apc_morphospecies") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'FLNT.20230724.AP2.P3')]), + expected = "SEAP") }) @@ -122,11 +127,25 @@ testthat::test_that(desc = "Output data frame source: apc_morphospecies", { testthat::test_that(desc = "Output data frame source: biomass", { outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'CRAM.20230720.AP1.P2')]), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'FLNT.20230724.MACROALGAE1.P1')]), expected = "apl_biomass") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'FLNT.20230724.MACROALGAE1.P1')]), + expected = "UNKALG") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'CRAM.20230720.AP1.P2')]), - expected = "DRADA") + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + expected = "apl_biomass") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + expected = "UNKALG") + + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP3.Q2')]), + expected = "apl_biomass") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.AP3.Q2')]), + expected = "RIFL4") + + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP2.Q2')]), + expected = "apl_biomass") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.AP2.Q2')]), + expected = "LERI6") }) diff --git a/tests/testthat/test_joinAquPointCount.R b/tests/testthat/test_joinAquPointCount.R index b786067..f02485b 100644 --- a/tests/testthat/test_joinAquPointCount.R +++ b/tests/testthat/test_joinAquPointCount.R @@ -4,6 +4,7 @@ ### Read in test data #### testList <- readRDS(testthat::test_path("testdata", "joinAquPointCount_testData_202307.rds")) + testPoint <- testList$apc_pointTransect testPerTax <- testList$apc_perTaxon testTaxProc <- testList$apc_taxonomyProcessed @@ -58,7 +59,7 @@ testthat::test_that(desc = "Output class table input", { testthat::test_that(desc = "Output data frame row number list input", { testthat::expect_identical(object = nrow(joinAquPointCount(inputDataList = testList)), - expected = as.integer(3114)) + expected = as.integer(145)) }) @@ -78,7 +79,7 @@ testthat::test_that(desc = "Output data frame row number table input", { inputPerTax = testPerTax, inputTaxProc = testTaxProc, inputMorph = testMorph)), - expected = as.integer(3114)) + expected = as.integer(145)) }) # Check expected column number of output @@ -95,12 +96,39 @@ testthat::test_that(desc = "Output data frame row number table input", { ### Test: Generates expected data using test data #### ## Test dataframe output -# Check 'acceptedTaxonID' is pulled from apc_taxonomyProcessed if taxProc data exists +# Check 'acceptedTaxonID' is pulled from apc_perTaxon if identification is not in morphospecies or taxProcessed tables +testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { + + outDF <- joinAquPointCount(inputDataList = testList) + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'HOPB.20230727.AP2.1.T1')]), + expected = "apc_perTaxon") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'HOPB.20230727.AP2.1.T1')]), + expected = "PLLE3") + + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'POSE.20230718.AP16.1.T7')]), + expected = "apc_perTaxon") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'POSE.20230718.AP16.1.T7')]), + expected = "2PLANT") +}) + +# Check 'acceptedTaxonID' is pulled from apc_taxonomyProcessed when expertTaxonomyRequired = 'Y' testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { outDF <- joinAquPointCount(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$uid == '1bc5392f-a567-4b6d-83b4-55ca74457ecd')]), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'KING.20230719.MACROALGAE17.T5')]), + expected = "apc_taxonomyProcessed") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'KING.20230719.MACROALGAE17.T5')]), + expected = c("NEONDREX1220001", "NEONDREX444000")) + + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'WLOU.20230703.AP19.1.T7')]), + expected = "apc_taxonomyProcessed") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'WLOU.20230703.AP19.1.T7')]), + expected = "HYOC5") + + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'REDB.20230719.AP19.1.T4')]), expected = "apc_taxonomyProcessed") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'REDB.20230719.AP19.1.T4')]), + expected = "EQAR") }) @@ -108,27 +136,14 @@ testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { testthat::test_that(desc = "Output data frame source: apc_morphospecies", { outDF <- joinAquPointCount(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'KING.20230719.AP1.1.T1')]), + testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'KING.20230719.AP11.1.T1')]), expected = "apc_morphospecies") - - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'KING.20230719.AP1.1.T1')]), + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'KING.20230719.AP11.1.T1')]), expected = "NAOF") }) -# Check 'acceptedTaxonID' is pulled from apc_perTaxon if identification is not in morphospecies or taxProcessed tables -testthat::test_that(desc = "Output data frame source: perTaxon", { - - outDF <- joinAquPointCount(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'POSE.20230718.AP16.1.T7')]), - expected = "apc_perTaxon") - - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'POSE.20230718.AP16.1.T7')]), - expected = "2PLANT") -}) - - ### Tests: Generate expected errors for 'inputDataList' #### # Test 'inputDataList' is a list @@ -229,6 +244,3 @@ testthat::test_that(desc = "Table 'inputMorph' missing column", { inputPerTax = testPerTax), regexp = "Required columns missing from 'inputMorph': taxonID") }) - - - diff --git a/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds b/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds index ba5ff4050f64f73739b6f763aa3a369a28f66f47..eae091e2396952b789372b1f869d1a9857a797c5 100644 GIT binary patch literal 4272 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 l-1vs}5dcd>Pr!EW`>tt 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 literal 125492 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 WwwGy>!Y<)E<6VYtnctGwj{FaI^EtNw From 135baed074ba7dd33291863701f61c437ae92031 Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 9 Sep 2025 13:47:03 -0700 Subject: [PATCH 38/40] handle biomass dups --- R/joinAquClipHarvest.R | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index 39f01d8..ad4aac4 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -10,6 +10,8 @@ #' #' In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. #' +#' A single sampleID in 'apl_biomass' may correspond to more than one taxa in 'apl_taxonomyProcessed'. When tables are joined, the taxon with the greatest 'algalParameterValue' in 'apl_taxonomyProcessed' will be listed as the 'acceptedTaxonID' and a new field, 'additionalTaxa', is included in the output and includes all other taxa associated with the sampleID. If more than one taxon shares the same max 'algalParameterValue', the first row in the input table is returned as the 'acceptedTaxonID'. Detailed taxonomic information for any additionalTaxa, including algalParameterValues, can be found in the input apl_taxonomyProcessed table. +#' #' @param inputDataList A list object comprised of Aquatic Plant Bryophyte Macroalgae Clip Harvest tables (DP1.20066.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. [list] #' #' @param inputBio The 'apl_biomass' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] @@ -51,7 +53,6 @@ #' @export joinAquClipHarvest - joinAquClipHarvest <- function(inputDataList, inputBio = NA, inputClip = NA, @@ -275,9 +276,24 @@ joinAquClipHarvest <- function(inputDataList, -"collectDate", -"morphospeciesID", -"sampleCode" - ) #%>% + ) %>% + dplyr::mutate(algalParameterValue = as.numeric(algalParameterValue)) # dplyr::mutate(identifiedDate = as.character(identifiedDate)) #biomass identifiedDate is character, not date + # Preprocess apTaxProc to determine primary/additional taxa per sampleID + apTaxProc_main <- apTaxProc %>% + group_by(sampleID) %>% + slice_max(order_by = algalParameterValue, n = 1, with_ties = FALSE) %>% #select first row if max algalParameterValue is shared between more than one sampleID + ungroup() + + apTaxProc_additional <- apTaxProc %>% + group_by(sampleID) %>% + arrange(desc(algalParameterValue)) %>% + summarise(additionalTaxa = if(length(taxonID) > 1) paste(taxonID[-1], collapse = "|") else NA_character_) + + apTaxProc_main <- apTaxProc_main %>% + left_join(apTaxProc_additional, by = "sampleID") + # Columns conditionally replaced with taxProc data join1_cols <- c( "division", "class", "order", "family", @@ -289,10 +305,10 @@ joinAquClipHarvest <- function(inputDataList, # Update expert taxonomist identifications apJoin1 <- apBio %>% dplyr::left_join( - apTaxProc, + apTaxProc_main, by = "sampleID", - suffix = c("_bio", "_taxProc"), - relationship = "many-to-many" + suffix = c("_bio", "_taxProc")#, + # relationship = "many-to-many" ) %>% dplyr::mutate( From b36a8790a4aab12282fb3e99405c46436c15ea9a Mon Sep 17 00:00:00 2001 From: Ritterm Date: Tue, 9 Sep 2025 15:14:31 -0700 Subject: [PATCH 39/40] update apl join tests --- R/joinAquClipHarvest.R | 24 ++++---- man/joinAquClipHarvest.Rd | 2 + tests/testthat/test_joinAquClipHarvest.R | 57 ++++++++++++++++-- .../joinAquClipHarvest_testData_202307.rds | Bin 4272 -> 5059 bytes 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index ad4aac4..c980c5f 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -277,23 +277,23 @@ joinAquClipHarvest <- function(inputDataList, -"morphospeciesID", -"sampleCode" ) %>% - dplyr::mutate(algalParameterValue = as.numeric(algalParameterValue)) + dplyr::mutate(algalParameterValue = as.numeric(.data$algalParameterValue)) # dplyr::mutate(identifiedDate = as.character(identifiedDate)) #biomass identifiedDate is character, not date - # Preprocess apTaxProc to determine primary/additional taxa per sampleID + # Preprocess apTaxProc to determine primary taxa per sampleID apTaxProc_main <- apTaxProc %>% - group_by(sampleID) %>% - slice_max(order_by = algalParameterValue, n = 1, with_ties = FALSE) %>% #select first row if max algalParameterValue is shared between more than one sampleID - ungroup() + dplyr::group_by(.data$sampleID) %>% + dplyr::slice_max(order_by = .data$algalParameterValue, n = 1, with_ties = FALSE) %>% #select first row if max algalParameterValue is shared between more than one sampleID + dplyr::ungroup() apTaxProc_additional <- apTaxProc %>% - group_by(sampleID) %>% - arrange(desc(algalParameterValue)) %>% - summarise(additionalTaxa = if(length(taxonID) > 1) paste(taxonID[-1], collapse = "|") else NA_character_) - - apTaxProc_main <- apTaxProc_main %>% - left_join(apTaxProc_additional, by = "sampleID") - + dplyr::group_by(.data$sampleID) %>% + dplyr::arrange(dplyr::desc(.data$algalParameterValue)) %>% + dplyr::summarise(additionalTaxa = if(length(.data$taxonID) > 1) paste(.data$taxonID[-1], collapse = "|") else NA_character_) + + apTaxProc_main <- apTaxProc_main %>% + dplyr::left_join(apTaxProc_additional, by = "sampleID") + # Columns conditionally replaced with taxProc data join1_cols <- c( "division", "class", "order", "family", diff --git a/man/joinAquClipHarvest.Rd b/man/joinAquClipHarvest.Rd index 5dedf8f..6b1a3ce 100644 --- a/man/joinAquClipHarvest.Rd +++ b/man/joinAquClipHarvest.Rd @@ -35,6 +35,8 @@ Input data may be provided either as a list or as individual tables. However, if Only data from bout 2 (midsummer sampling) is returned in the joined output table, as other bouts do not include taxonomy data. If the input does not include any bout 2 data, the function will error out. In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. + +A single sampleID in 'apl_biomass' may correspond to more than one taxa in 'apl_taxonomyProcessed'. When tables are joined, the taxon with the greatest 'algalParameterValue' in 'apl_taxonomyProcessed' will be listed as the 'acceptedTaxonID' and a new field, 'additionalTaxa', is included in the output and includes all other taxa associated with the sampleID. If more than one taxon shares the same max 'algalParameterValue', the first row in the input table is returned as the 'acceptedTaxonID'. Detailed taxonomic information for any additionalTaxa, including algalParameterValues, can be found in the input apl_taxonomyProcessed table. } \examples{ \dontrun{ diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R index ab0f895..05eca75 100644 --- a/tests/testthat/test_joinAquClipHarvest.R +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -67,7 +67,7 @@ testthat::test_that(desc = "Output data frame row number list input", { testthat::test_that(desc = "Output data frame column number list input", { testthat::expect_identical(object = ncol(joinAquClipHarvest(inputDataList = testList)), - expected = as.integer(103)) + expected = as.integer(104)) }) @@ -89,7 +89,7 @@ testthat::test_that(desc = "Output data frame row number table input", { inputClip = testClip, inputTaxProc = testTaxProc, inputMorph = testMorph)), - expected = as.integer(103)) + expected = as.integer(104)) }) @@ -133,9 +133,9 @@ testthat::test_that(desc = "Output data frame source: biomass", { expected = "UNKALG") testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), - expected = "apl_biomass") + expected = "apl_taxonomyProcessed") testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), - expected = "UNKALG") + expected = "NEONDREX1220001") testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP3.Q2')]), expected = "apl_biomass") @@ -149,6 +149,55 @@ testthat::test_that(desc = "Output data frame source: biomass", { }) +### Test: Function generates data frame with correct taxonomic IDs using test data #### +## Test dataframe output +# Check tax info is correct when sampleID has >1 taxonID in apl_taxonomyProcessed and max algalParameterValue is unique +testthat::test_that(desc = "Output taxonomy correct: multiple taxa per sampleID, single max algalParamValue", { + + outDF <- joinAquClipHarvest(inputDataList = testList) + + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + expected = "NEONDREX1220001") + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), + expected = "NEONDREX309000") + + testthat::expect_identical(object = unique(outDF$additionalTaxa[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + expected = "NEONDREX885004|AUDSP|NEONDREX920001|NITELLASP") + testthat::expect_identical(object = unique(outDF$additionalTaxa[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), + expected = NA_character_) + +}) + +# Check tax info is correct when sampleID has >1 taxonID in apl_taxonomyProcessed and max algalParameterValue is unique +testthat::test_that(desc = "Output additional taxa correct: multiple taxa per sampleID, many max algalParamValue", { + + # modify test data + testList2 <- testList + testList2$apl_taxonomyProcessed <- testList2$apl_taxonomyProcessed %>% dplyr::filter(algalParameterValue != 5) + outDF <- joinAquClipHarvest(inputDataList = testList2) + + testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + expected = "NEONDREX885004") + + testthat::expect_identical(object = unique(outDF$additionalTaxa[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + expected = "AUDSP|NEONDREX920001|NITELLASP") +}) + + +# Check 'acceptedTaxonID' is empty when only 1 taxonID exists per sampleID in apl_taxonomyProcessed +testthat::test_that(desc = "Output additional taxa correct: single taxon per sampleID", { + + # modify test data + testList3 <- testList + testList3$apl_taxonomyProcessed <- testList3$apl_taxonomyProcessed %>% dplyr::filter(siteID == 'BLUE') + outDF <- joinAquClipHarvest(inputDataList = testList3) + + testthat::expect_identical(object = unique(outDF$additionalTaxa), + expected = NA_character_) + +}) + + ### Tests: Generate expected errors for 'inputDataList' #### # Test 'inputDataList' is a list diff --git a/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds b/tests/testthat/testdata/joinAquClipHarvest_testData_202307.rds index eae091e2396952b789372b1f869d1a9857a797c5..fdead8020e6020c0773c89375dda063c9a59bd09 100644 GIT binary patch literal 5059 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 Z3TB@jE+t>?bZ%^X_v_rs_wjrFqY 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 Date: Thu, 11 Sep 2025 10:08:12 -0700 Subject: [PATCH 40/40] refine joinClipHarvest output --- DESCRIPTION | 1 + R/joinAquClipHarvest.R | 163 +++++++++++++++-------- man/joinAquClipHarvest.Rd | 16 +-- tests/testthat/test_joinAquClipHarvest.R | 162 +++++++++++++++------- 4 files changed, 229 insertions(+), 113 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7a98d66..7c5eb04 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -48,6 +48,7 @@ Imports: rlang, stringr, tidyr, + tidyselect, RColorBrewer, ggplot2, plotly diff --git a/R/joinAquClipHarvest.R b/R/joinAquClipHarvest.R index c980c5f..bbac5a3 100644 --- a/R/joinAquClipHarvest.R +++ b/R/joinAquClipHarvest.R @@ -1,16 +1,14 @@ -#' @title Join NEON aquatic plant clip harvest data into a single table with merged taxonomic identifications +#' @title Join taxonomic identifications in NEON aquatic plant clip harvest data to field and biomass tables #' @author Madaline Ritter \email{ritterm1@battelleecology.org} \cr -#' @description Join the 'apl_clipHarvest', 'apl_biomass', 'apl_taxonomyProcessed' and 'apc_morphospecies' tables to generate a single table that contains clip harvest data with taxonomic identifications for each sampleID. Data inputs are NEON Aquatic Plant Bryophyte Macroalgae Clip Harvest (DP1.20066.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. +#' @description Join the 'apl_clipHarvest', 'apl_biomass', 'apl_taxonomyProcessed' and 'apc_morphospecies' tables to generate two joined output tables that contain clip harvest data with merged taxonomic identifications. Data inputs are NEON Aquatic Plant Bryophyte Macroalgae Clip Harvest (DP1.20066.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. #' #' @details Input data may be provided either as a list or as individual tables. However, if both list and table inputs are provided at the same time the function will error out. For table joining to be successful, inputs must contain data from the same site x month combination(s) for all tables. #' -#' Only data from bout 2 (midsummer sampling) is returned in the joined output table, as other bouts do not include taxonomy data. If the input does not include any bout 2 data, the function will error out. +#' Only data from bout 2 (midsummer sampling) is returned in the joined output tables, as other bouts do not include taxonomy data. If the input does not include any bout 2 data, the function will error out. #' -#' In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. -#' -#' A single sampleID in 'apl_biomass' may correspond to more than one taxa in 'apl_taxonomyProcessed'. When tables are joined, the taxon with the greatest 'algalParameterValue' in 'apl_taxonomyProcessed' will be listed as the 'acceptedTaxonID' and a new field, 'additionalTaxa', is included in the output and includes all other taxa associated with the sampleID. If more than one taxon shares the same max 'algalParameterValue', the first row in the input table is returned as the 'acceptedTaxonID'. Detailed taxonomic information for any additionalTaxa, including algalParameterValues, can be found in the input apl_taxonomyProcessed table. +#' In the joined output tables, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. #' #' @param inputDataList A list object comprised of Aquatic Plant Bryophyte Macroalgae Clip Harvest tables (DP1.20066.001) downloaded using the neonUtilities::loadByProduct() function. If list input is provided, the table input arguments must all be NA; similarly, if list input is missing, table inputs must be provided. [list] #' @@ -22,7 +20,9 @@ #' #' @param inputMorph The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. [data.frame] #' -#' @return A table containing bout 2 clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent = 'Y' and an identification has been published.. +#' @return Two tables are produced containing joined clip harvest data. The first "joinedBiomass" table contains one row per sampleID in 'apl_biomass'. A single sampleID in 'apl_biomass' may correspond to more than one taxa in 'apl_taxonomyProcessed'. When tables are joined, the taxon with the greatest 'algalParameterValue' in 'apl_taxonomyProcessed' will be listed as the 'acceptedTaxonID' and a new field, 'additionalTaxa', appears in the output and includes all other taxa associated with the sampleID. If more than one taxon shares the same max 'algalParameterValue', the first row in the input table is returned as the 'acceptedTaxonID'. Detailed taxonomic information for any additionalTaxa can be found in the input 'apl_taxonomyProcessed' table. +#' +#' The second "fieldTaxonomy" table joins taxonomic identifications across tables to 'apl_clipHarvest' by field ID. Joining may result in one or many unique rows per sampleID. #' #' @references #' License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 @@ -40,7 +40,7 @@ #' ) #' #' # Join downloaded clip harvest data -#' df <- neonPlants::joinAquClipHarvest( +#' list <- neonPlants::joinAquClipHarvest( #' inputDataList = apl, #' inputBio = NA, #' inputClip = NA, @@ -53,6 +53,7 @@ #' @export joinAquClipHarvest + joinAquClipHarvest <- function(inputDataList, inputBio = NA, inputClip = NA, @@ -276,24 +277,9 @@ joinAquClipHarvest <- function(inputDataList, -"collectDate", -"morphospeciesID", -"sampleCode" - ) %>% - dplyr::mutate(algalParameterValue = as.numeric(.data$algalParameterValue)) + ) #%>% # dplyr::mutate(identifiedDate = as.character(identifiedDate)) #biomass identifiedDate is character, not date - # Preprocess apTaxProc to determine primary taxa per sampleID - apTaxProc_main <- apTaxProc %>% - dplyr::group_by(.data$sampleID) %>% - dplyr::slice_max(order_by = .data$algalParameterValue, n = 1, with_ties = FALSE) %>% #select first row if max algalParameterValue is shared between more than one sampleID - dplyr::ungroup() - - apTaxProc_additional <- apTaxProc %>% - dplyr::group_by(.data$sampleID) %>% - dplyr::arrange(dplyr::desc(.data$algalParameterValue)) %>% - dplyr::summarise(additionalTaxa = if(length(.data$taxonID) > 1) paste(.data$taxonID[-1], collapse = "|") else NA_character_) - - apTaxProc_main <- apTaxProc_main %>% - dplyr::left_join(apTaxProc_additional, by = "sampleID") - # Columns conditionally replaced with taxProc data join1_cols <- c( "division", "class", "order", "family", @@ -305,10 +291,10 @@ joinAquClipHarvest <- function(inputDataList, # Update expert taxonomist identifications apJoin1 <- apBio %>% dplyr::left_join( - apTaxProc_main, + apTaxProc, by = "sampleID", - suffix = c("_bio", "_taxProc")#, - # relationship = "many-to-many" + suffix = c("_bio", "_taxProc"), + relationship = "many-to-many" ) %>% dplyr::mutate( @@ -381,16 +367,17 @@ joinAquClipHarvest <- function(inputDataList, } else { - message("No data joined from apl_taxonomyProcessed table.") + message("Output tables do not include identifications from the expert taxonomists.\nProvide the 'apl_taxonomyProcessed' table to join expert identifications.") # rename columns if no taxProc join apJoin1 <- apBio %>% dplyr::mutate( + startDate = NA, tempTaxonID = .data$taxonID, remarks = dplyr::if_else(is.na(.data$remarks), NA, paste0("biomass remarks - ", .data$remarks)), - perTaxonRelease = .data$release, + biomassRelease = .data$release, taxonIDSourceTable = dplyr::if_else(is.na(.data$taxonID), NA, "apl_biomass"), - perTaxonDataQF = .data$dataQF, - perTaxonPublicationDate = .data$publicationDate + biomassDataQF = .data$dataQF, + biomassPublicationDate = .data$publicationDate ) %>% dplyr::select(-"taxonID", -"release", @@ -400,9 +387,6 @@ joinAquClipHarvest <- function(inputDataList, } - - - ### Join apJoin1 and apMorph tables #### # Select needed columns from apMorph @@ -481,12 +465,12 @@ joinAquClipHarvest <- function(inputDataList, ### Join apClip and apBio tables #### - joinClipHarvest <- apClip %>% - dplyr::select( - -"benthicArea", -"namedLocation", -"domainID", -"siteID", - -"startDate", -"collectDate", -"fieldIDCode" - ) %>% - dplyr::left_join(apJoin2, by = "fieldID", suffix = c("_clip", "_bio")) %>% + finalJoin <- apClip %>% + dplyr::left_join( + apJoin2 %>% dplyr::select( + -"benthicArea", -"namedLocation", -"domainID", -"siteID", + -"startDate", -"collectDate", -"fieldIDCode"), # + by = "fieldID", suffix = c("_clip", "_bio")) %>% dplyr::mutate( remarks = dplyr::case_when( !is.na(.data$remarks_bio) & @@ -516,30 +500,97 @@ joinAquClipHarvest <- function(inputDataList, ### Filter out bout 1 and 3 data #### - joinClipHarvest <- joinClipHarvest %>% dplyr::filter(.data$boutNumber == '2') + finalJoin <- finalJoin %>% dplyr::filter(.data$boutNumber == '2') - ### Re-format date columns #### - joinClipHarvest$processingDate <- as.Date(joinClipHarvest$processingDate) - joinClipHarvest$identifiedDate <- as.Date(joinClipHarvest$identifiedDate) + ### Re-format date columns #### - joinClipHarvest$collectDate <- as.POSIXct(joinClipHarvest$collectDate, - format = "%Y-%m-%dT%H:%MZ", tz = "UTC") + finalJoin$processingDate <- as.Date(finalJoin$processingDate) + finalJoin$identifiedDate <- as.Date(finalJoin$identifiedDate) + finalJoin$collectDate <- as.POSIXct(finalJoin$collectDate, + format = "%Y-%m-%d %H:%M:%S", tz = "UTC") + finalJoin$startDate <- as.POSIXct(finalJoin$startDate, + format = "%Y-%m-%d %H:%M:%S", tz = "UTC") + finalJoin$biomassPublicationDate <- as.POSIXct(finalJoin$biomassPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + finalJoin$clipPublicationDate <- as.POSIXct(finalJoin$clipPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0){ + finalJoin$taxProcessedPublicationDate <- as.POSIXct(finalJoin$taxProcessedPublicationDate, + format = "%Y%m%dT%H%M%SZ", tz = "UTC") + } - joinClipHarvest$startDate <- as.POSIXct(joinClipHarvest$startDate, - format = "%Y-%m-%dT%H:%MZ", tz = "UTC") - joinClipHarvest$biomassPublicationDate <- as.POSIXct(joinClipHarvest$biomassPublicationDate, - format = "%Y%m%dT%H%M%SZ", tz = "UTC") + ### Create joinedBiomass output table #### + if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { + joinedBiomass <- finalJoin %>% + dplyr::filter(.data$targetTaxaPresent=='Y') %>% #only include records where taxa present + dplyr::group_by(.data$sampleID) %>% + # Arrange so the highest algalParameterValue comes first + dplyr::arrange(dplyr::desc(.data$algalParameterValue), .by_group = TRUE) %>% + # Create new column with all acceptedTaxonIDs except the first + dplyr::mutate( + additionalTaxa = { + unique_taxa <- unique(.data$acceptedTaxonID) + if (length(unique_taxa) > 1) { + other_taxa <- .data$acceptedTaxonID[-1] # all but the first + other_taxa <- other_taxa[!is.na(other_taxa)] # remove NAs + if (length(other_taxa) > 0) paste(other_taxa, collapse = "|") + else NA_character_ + } else { + NA_character_ + } + } + ) %>% + # Keep only the first row per sampleID + dplyr::slice(1) %>% + dplyr::ungroup() %>% + dplyr::relocate('additionalTaxa', .after = 'acceptedTaxonID') + } else { + joinedBiomass <- finalJoin %>% + dplyr::filter(.data$targetTaxaPresent=='Y') %>% #only include records where taxa present + # Create empty column for acceptedTaxonID + dplyr::mutate( + additionalTaxa = NA_character_ + ) %>% + dplyr::relocate('additionalTaxa', .after = 'acceptedTaxonID') + } - joinClipHarvest$clipPublicationDate <- as.POSIXct(joinClipHarvest$clipPublicationDate, - format = "%Y%m%dT%H%M%SZ", tz = "UTC") - if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0){ - joinClipHarvest$taxProcessedPublicationDate <- as.POSIXct(joinClipHarvest$taxProcessedPublicationDate, - format = "%Y%m%dT%H%M%SZ", tz = "UTC") + ### Create fieldTaxonomy output table #### + if (is.data.frame(apTaxProc) && nrow(apTaxProc) > 0) { + fieldTaxCols <- setdiff( + c(names(apClip), "fieldID", "sampleID", "sampleCode", "sampleCondition", + "acceptedTaxonID", "scientificName", "scientificNameAuthorship", + "identificationQualifier", "identificationHistoryID", "identificationReferences", + "identifiedBy", "identifiedDate", "taxonIDSourceTable", + "algalParameter", "algalParameterValue", "algalParameterUnit", "testMethod", "method", + "subspecies", "variety", "subvariety", "form", "subform", "speciesGroup", + "taxonDatabaseName", "taxonDatabaseID", + "division", "class", "order", "family", "genus", "section", "specificEpithet", + "taxonRank","clipDataQF", "taxProcessedDataQF", "clipPublicationDate", "taxProcessedPublicationDate"), + c("release", "dataQF", "publicationDate") + ) + } else { + fieldTaxCols <- setdiff( + c(names(apClip), "fieldID", "sampleID", "sampleCode", "sampleCondition", + "acceptedTaxonID", "scientificName", "scientificNameAuthorship", + "identificationQualifier", "identificationHistoryID", "identificationReferences", + "identifiedBy", "identifiedDate", "taxonIDSourceTable", + "division", "class", "order", "family", "genus", "section", "specificEpithet", + "taxonRank","clipDataQF", "clipPublicationDate"), + c("release", "dataQF", "publicationDate") + ) } - return(joinClipHarvest) + fieldTaxonomy <- finalJoin %>% + dplyr::select(tidyselect::all_of(fieldTaxCols)) + + ### Create final output list #### + + joinClipHarvest <- list('joinedBiomass' = joinedBiomass, 'fieldTaxonomy' = fieldTaxonomy) + + return(joinClipHarvest) + } #function closer diff --git a/man/joinAquClipHarvest.Rd b/man/joinAquClipHarvest.Rd index 6b1a3ce..be52945 100644 --- a/man/joinAquClipHarvest.Rd +++ b/man/joinAquClipHarvest.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/joinAquClipHarvest.R \name{joinAquClipHarvest} \alias{joinAquClipHarvest} -\title{Join NEON aquatic plant clip harvest data into a single table with merged taxonomic identifications} +\title{Join taxonomic identifications in NEON aquatic plant clip harvest data to field and biomass tables} \usage{ joinAquClipHarvest( inputDataList, @@ -24,19 +24,19 @@ joinAquClipHarvest( \item{inputMorph}{The 'apc_morphospecies' table for the site x month combination(s) of interest (defaults to NA). If table input is provided, the 'inputDataList' argument must be missing. \link{data.frame}} } \value{ -A table containing bout 2 clip harvest data with all associated taxonomic information for each apl_clipHarvest record where targetTaxaPresent = 'Y' and an identification has been published.. +Two tables are produced containing joined clip harvest data. The first "joinedBiomass" table contains one row per sampleID in 'apl_biomass'. A single sampleID in 'apl_biomass' may correspond to more than one taxa in 'apl_taxonomyProcessed'. When tables are joined, the taxon with the greatest 'algalParameterValue' in 'apl_taxonomyProcessed' will be listed as the 'acceptedTaxonID' and a new field, 'additionalTaxa', appears in the output and includes all other taxa associated with the sampleID. If more than one taxon shares the same max 'algalParameterValue', the first row in the input table is returned as the 'acceptedTaxonID'. Detailed taxonomic information for any additionalTaxa can be found in the input 'apl_taxonomyProcessed' table. + +The second "fieldTaxonomy" table joins taxonomic identifications across tables to 'apl_clipHarvest' by field ID. Joining may result in one or many unique rows per sampleID. } \description{ -Join the 'apl_clipHarvest', 'apl_biomass', 'apl_taxonomyProcessed' and 'apc_morphospecies' tables to generate a single table that contains clip harvest data with taxonomic identifications for each sampleID. Data inputs are NEON Aquatic Plant Bryophyte Macroalgae Clip Harvest (DP1.20066.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. +Join the 'apl_clipHarvest', 'apl_biomass', 'apl_taxonomyProcessed' and 'apc_morphospecies' tables to generate two joined output tables that contain clip harvest data with merged taxonomic identifications. Data inputs are NEON Aquatic Plant Bryophyte Macroalgae Clip Harvest (DP1.20066.001) in list format retrieved using the neonUtilities::loadByProduct() function (preferred), data tables downloaded from the NEON Data Portal, or input data tables with an equivalent structure and representing the same site x month combinations. } \details{ Input data may be provided either as a list or as individual tables. However, if both list and table inputs are provided at the same time the function will error out. For table joining to be successful, inputs must contain data from the same site x month combination(s) for all tables. -Only data from bout 2 (midsummer sampling) is returned in the joined output table, as other bouts do not include taxonomy data. If the input does not include any bout 2 data, the function will error out. - -In the joined output table, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. +Only data from bout 2 (midsummer sampling) is returned in the joined output tables, as other bouts do not include taxonomy data. If the input does not include any bout 2 data, the function will error out. -A single sampleID in 'apl_biomass' may correspond to more than one taxa in 'apl_taxonomyProcessed'. When tables are joined, the taxon with the greatest 'algalParameterValue' in 'apl_taxonomyProcessed' will be listed as the 'acceptedTaxonID' and a new field, 'additionalTaxa', is included in the output and includes all other taxa associated with the sampleID. If more than one taxon shares the same max 'algalParameterValue', the first row in the input table is returned as the 'acceptedTaxonID'. Detailed taxonomic information for any additionalTaxa, including algalParameterValues, can be found in the input apl_taxonomyProcessed table. +In the joined output tables, the 'acceptedTaxonID' and associated taxonomic fields are populated from the first available identification in the following order: 'apl_taxonomyProcessed', 'apl_biomass', or 'apc_morphospecies'. For samples identified both in the field and by an expert taxonomist, the expert identification is retained in the output. A new field, 'taxonIDSourceTable', is included in the output and indicates the source table for each sample's identification. } \examples{ \dontrun{ @@ -51,7 +51,7 @@ check.size = FALSE ) # Join downloaded clip harvest data -df <- neonPlants::joinAquClipHarvest( +list <- neonPlants::joinAquClipHarvest( inputDataList = apl, inputBio = NA, inputClip = NA, diff --git a/tests/testthat/test_joinAquClipHarvest.R b/tests/testthat/test_joinAquClipHarvest.R index 05eca75..f5497cb 100644 --- a/tests/testthat/test_joinAquClipHarvest.R +++ b/tests/testthat/test_joinAquClipHarvest.R @@ -37,37 +37,50 @@ testthat::test_that(desc = "Output type table input", { # Test list input testthat::test_that(desc = "Output class list input", { - testthat::expect_s3_class(object = joinAquClipHarvest(inputDataList = testList), - class = "data.frame") + desc = joinAquClipHarvest(inputDataList = testList) + + testthat::expect_s3_class(desc[[1]], class = "data.frame") + testthat::expect_s3_class(desc[[2]], class = "data.frame") }) # Test table input testthat::test_that(desc = "Output class table input", { - testthat::expect_s3_class(object = joinAquClipHarvest(inputBio = testBio, - inputClip = testClip, - inputTaxProc = testTaxProc, - inputMorph = testMorph), - class = "data.frame") + desc = joinAquClipHarvest(inputBio = testBio, + inputClip = testClip, + inputTaxProc = testTaxProc, + inputMorph = testMorph) + + testthat::expect_s3_class(desc[[1]], class = "data.frame") + testthat::expect_s3_class(desc[[2]], class = "data.frame") }) -### Test: Function generates data frame with expected dimensions using test data #### +### Test: Function generates data frames with expected dimensions using test data #### ## Test list input # Check expected row number of output testthat::test_that(desc = "Output data frame row number list input", { - testthat::expect_identical(object = nrow(joinAquClipHarvest(inputDataList = testList)), - expected = as.integer(9)) + out <- joinAquClipHarvest(inputDataList = testList) + + testthat::expect_identical(object = nrow(out$joinedBiomass), + expected = as.integer(7)) + testthat::expect_identical(object = nrow(out$fieldTaxonomy), + expected = as.integer(13)) }) # Check expected column number of output testthat::test_that(desc = "Output data frame column number list input", { - testthat::expect_identical(object = ncol(joinAquClipHarvest(inputDataList = testList)), + out <- joinAquClipHarvest(inputDataList = testList) + + testthat::expect_identical(object = ncol(out$joinedBiomass), expected = as.integer(104)) + + testthat::expect_identical(object = ncol(out$fieldTaxonomy), + expected = as.integer(76)) }) @@ -75,34 +88,44 @@ testthat::test_that(desc = "Output data frame column number list input", { # Check expected row number of output testthat::test_that(desc = "Output data frame row number table input", { - testthat::expect_identical(object = nrow(joinAquClipHarvest(inputBio = testBio, - inputClip = testClip, - inputTaxProc = testTaxProc, - inputMorph = testMorph)), - expected = as.integer(9)) + out <- joinAquClipHarvest(inputBio = testBio, + inputClip = testClip, + inputTaxProc = testTaxProc, + inputMorph = testMorph) + + testthat::expect_identical(object = nrow(out$joinedBiomass), + expected = as.integer(7)) + testthat::expect_identical(object = nrow(out$fieldTaxonomy), + expected = as.integer(13)) }) # Check expected column number of output testthat::test_that(desc = "Output data frame row number table input", { - testthat::expect_identical(object = ncol(joinAquClipHarvest(inputBio = testBio, - inputClip = testClip, - inputTaxProc = testTaxProc, - inputMorph = testMorph)), + out <- joinAquClipHarvest(inputBio = testBio, + inputClip = testClip, + inputTaxProc = testTaxProc, + inputMorph = testMorph) + + testthat::expect_identical(object = ncol(out$joinedBiomass), expected = as.integer(104)) + + testthat::expect_identical(object = ncol(out$fieldTaxonomy), + expected = as.integer(76)) }) -### Test: Function generates data frame with expected dimensions using test data #### +### Test: Function joins biomass data correctly using test data #### ## Test dataframe output # Check 'acceptedTaxonID' is pulled from apc_taxonomyProcessed if taxProc data exists testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { - outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), + out <- joinAquClipHarvest(inputDataList = testList) + + testthat::expect_identical(object = unique(out$joinedBiomass$taxonIDSourceTable[which(out$joinedBiomass$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), expected = "apl_taxonomyProcessed") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), expected = "NEONDREX309000") }) @@ -110,15 +133,16 @@ testthat::test_that(desc = "Output data frame source: taxonomyProcessed", { # Check 'acceptedTaxonID' is pulled from apc_morphospecies if identification is in morphospecies table testthat::test_that(desc = "Output data frame source: apc_morphospecies", { - outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP1.Q2')]), + out <- joinAquClipHarvest(inputDataList = testList) + + testthat::expect_identical(object = unique(out$joinedBiomass$taxonIDSourceTable[which(out$joinedBiomass$sampleID == 'BLUE.20230717.AP1.Q2')]), expected = "apc_morphospecies") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.AP1.Q2')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'BLUE.20230717.AP1.Q2')]), expected = "LURE2") - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'FLNT.20230724.AP2.P3')]), + testthat::expect_identical(object = unique(out$joinedBiomass$taxonIDSourceTable[which(out$joinedBiomass$sampleID == 'FLNT.20230724.AP2.P3')]), expected = "apc_morphospecies") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'FLNT.20230724.AP2.P3')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'FLNT.20230724.AP2.P3')]), expected = "SEAP") }) @@ -126,44 +150,45 @@ testthat::test_that(desc = "Output data frame source: apc_morphospecies", { # Check 'acceptedTaxonID' is pulled from apl_biomass if identification is not in morphospecies or taxProcessed tables testthat::test_that(desc = "Output data frame source: biomass", { - outDF <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'FLNT.20230724.MACROALGAE1.P1')]), + out <- joinAquClipHarvest(inputDataList = testList) + testthat::expect_identical(object = unique(out$joinedBiomass$taxonIDSourceTable[which(out$joinedBiomass$sampleID == 'FLNT.20230724.MACROALGAE1.P1')]), expected = "apl_biomass") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'FLNT.20230724.MACROALGAE1.P1')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'FLNT.20230724.MACROALGAE1.P1')]), expected = "UNKALG") - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + testthat::expect_identical(object = unique(out$joinedBiomass$taxonIDSourceTable[which(out$joinedBiomass$sampleID == 'TOOK.20230726.AP3.P6')]), expected = "apl_taxonomyProcessed") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'TOOK.20230726.AP3.P6')]), expected = "NEONDREX1220001") - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP3.Q2')]), + testthat::expect_identical(object = unique(out$joinedBiomass$taxonIDSourceTable[which(out$joinedBiomass$sampleID == 'BLUE.20230717.AP3.Q2')]), expected = "apl_biomass") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.AP3.Q2')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'BLUE.20230717.AP3.Q2')]), expected = "RIFL4") - testthat::expect_identical(object = unique(outDF$taxonIDSourceTable[which(outDF$sampleID == 'BLUE.20230717.AP2.Q2')]), + testthat::expect_identical(object = unique(out$joinedBiomass$taxonIDSourceTable[which(out$joinedBiomass$sampleID == 'BLUE.20230717.AP2.Q2')]), expected = "apl_biomass") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.AP2.Q2')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'BLUE.20230717.AP2.Q2')]), expected = "LERI6") }) -### Test: Function generates data frame with correct taxonomic IDs using test data #### + +### Test: Generate joinedBiomass dataframe with correct taxonomic IDs #### ## Test dataframe output # Check tax info is correct when sampleID has >1 taxonID in apl_taxonomyProcessed and max algalParameterValue is unique testthat::test_that(desc = "Output taxonomy correct: multiple taxa per sampleID, single max algalParamValue", { - outDF <- joinAquClipHarvest(inputDataList = testList) + out <- joinAquClipHarvest(inputDataList = testList) - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'TOOK.20230726.AP3.P6')]), expected = "NEONDREX1220001") - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), expected = "NEONDREX309000") - testthat::expect_identical(object = unique(outDF$additionalTaxa[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + testthat::expect_identical(object = unique(out$joinedBiomass$additionalTaxa[which(out$joinedBiomass$sampleID == 'TOOK.20230726.AP3.P6')]), expected = "NEONDREX885004|AUDSP|NEONDREX920001|NITELLASP") - testthat::expect_identical(object = unique(outDF$additionalTaxa[which(outDF$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), + testthat::expect_identical(object = unique(out$joinedBiomass$additionalTaxa[which(out$joinedBiomass$sampleID == 'BLUE.20230717.MACROALGAE1.Q8')]), expected = NA_character_) }) @@ -174,12 +199,12 @@ testthat::test_that(desc = "Output additional taxa correct: multiple taxa per sa # modify test data testList2 <- testList testList2$apl_taxonomyProcessed <- testList2$apl_taxonomyProcessed %>% dplyr::filter(algalParameterValue != 5) - outDF <- joinAquClipHarvest(inputDataList = testList2) + out <- joinAquClipHarvest(inputDataList = testList2) - testthat::expect_identical(object = unique(outDF$acceptedTaxonID[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + testthat::expect_identical(object = unique(out$joinedBiomass$acceptedTaxonID[which(out$joinedBiomass$sampleID == 'TOOK.20230726.AP3.P6')]), expected = "NEONDREX885004") - testthat::expect_identical(object = unique(outDF$additionalTaxa[which(outDF$sampleID == 'TOOK.20230726.AP3.P6')]), + testthat::expect_identical(object = unique(out$joinedBiomass$additionalTaxa[which(out$joinedBiomass$sampleID == 'TOOK.20230726.AP3.P6')]), expected = "AUDSP|NEONDREX920001|NITELLASP") }) @@ -190,15 +215,43 @@ testthat::test_that(desc = "Output additional taxa correct: single taxon per sam # modify test data testList3 <- testList testList3$apl_taxonomyProcessed <- testList3$apl_taxonomyProcessed %>% dplyr::filter(siteID == 'BLUE') - outDF <- joinAquClipHarvest(inputDataList = testList3) + out <- joinAquClipHarvest(inputDataList = testList3) - testthat::expect_identical(object = unique(outDF$additionalTaxa), + testthat::expect_identical(object = unique(out$joinedBiomass$additionalTaxa), expected = NA_character_) }) +### Test: Generate fieldTaxonomy dataframe with correct taxonomic IDs #### +## Test dataframe output +# Check each fieldID has correct number of associated taxa +testthat::test_that(desc = "Output fieldTaxonomy: multiple rows per fieldID", { + + out <- joinAquClipHarvest(inputDataList = testList) + + testthat::expect_identical(object = as.numeric(sum(out$fieldTaxonomy$fieldID == 'BLUE.20230717.QUADRAT.Q2', na.rm = TRUE)), + expected = 3) + testthat::expect_identical(object = as.numeric(sum(out$fieldTaxonomy$fieldID == 'TOOK.20230726.RAKE.P6', na.rm = TRUE)), + expected = 5) + +}) + +# Check each fieldID has correct joined taxonIDs +testthat::test_that(desc = "Output fieldTaxonomy: correct taxa per fieldID", { + + out <- joinAquClipHarvest(inputDataList = testList) + + testthat::expect_identical(object = unique(out$fieldTaxonomy$acceptedTaxonID[which(out$fieldTaxonomy$fieldID == 'BLUE.20230717.QUADRAT.Q2')]), + expected = c("RIFL4", "LERI6", "LURE2")) + testthat::expect_identical(object = unique(out$fieldTaxonomy$acceptedTaxonID[which(out$fieldTaxonomy$fieldID == 'TOOK.20230726.RAKE.P6')]), + expected = c("NEONDREX1220001", "NEONDREX885004", "AUDSP", "NEONDREX920001", "NITELLASP")) + +}) + + + ### Tests: Generate expected errors for 'inputDataList' #### # Test 'inputDataList' is a list testthat::test_that(desc = "Argument 'inputDataList' is list object", { @@ -300,4 +353,15 @@ testthat::test_that(desc = "Table 'inputMorph' missing column", { }) +### Test: Generate expected message when apl_taxProcessed isn't provided (works for inputDataList or inputMorph source) #### +# Test when inputMorph lacks required column +testthat::test_that(desc = "Message: expert tax data not provided", { + + testthat::expect_message(object = joinAquClipHarvest(inputMorph = testMorph, + inputBio = testBio, + inputClip = testClip), + regexp = "Output tables do not include identifications from the expert taxonomists.") +}) + +