From 3373d9a9b505e09dea8bbd80a201bfc1f9ac5aa3 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Sun, 18 Feb 2024 17:01:02 +0000 Subject: [PATCH 01/18] as.respeciate; sp_profile, sp_build_rsp_x updates --- .Rproj.user/shared/notebooks/paths | 4 +- DESCRIPTION | 6 +- NAMESPACE | 4 +- NEWS.md | 14 +- R/respeciate.generics.R | 82 ++++++- R/sp.R | 338 +++-------------------------- R/sp.build.R | 240 ++++++++++++++++++++ R/speciate.R | 67 ------ man/find_code.Rd | 32 --- man/respeciate-package.Rd | 2 +- man/respeciate.generics.Rd | 16 +- man/sp.Rd | 52 +---- man/sp.build.Rd | 54 +++++ 13 files changed, 435 insertions(+), 476 deletions(-) create mode 100644 R/sp.build.R delete mode 100644 R/speciate.R delete mode 100644 man/find_code.Rd create mode 100644 man/sp.build.Rd diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index aa92c1b..c1e879c 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -17,6 +17,7 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/respeciate.generics.R="54ECE8F1" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.R="58F2A9BE" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.average.R="0A1E36E4" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.build.R="FE2BA79A" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cluster.R="49C0F861" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cor.R="3F1DA8E5" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.info.R="D0E7AC03" @@ -26,11 +27,10 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.pls.R="EACFE9A4" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.rescale.R="D668C5B2" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.reshape.R="CC655C60" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/speciate.R="6C6E673A" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spq.R="3AD9ACC8" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spx.R="CA18044A" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sysdata.R="82103C52" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/xxx.R="3415FF44" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/README.Rmd="887EDA27" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/README.md="D46A00DB" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/man/spec.Rd="8472593F" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/man/respeciate.generics.Rd="2897F12C" diff --git a/DESCRIPTION b/DESCRIPTION index 8c3d69a..b8b4c65 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: respeciate Title: Speciation profiles for gases and aerosols -Version: 0.2.7 -Date: 2024-02-17 -Description: Acess to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for +Version: 0.3.0 +Date: 2024-02-18 +Description: Access to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) . Type: Package Authors@R: c( person(given = "Sergio", family = "Ibarra-Espinosa", role = c("aut", "cre"), diff --git a/NAMESPACE b/NAMESPACE index f7507c6..bb78c27 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,10 +1,11 @@ # Generated by roxygen2: do not edit by hand +S3method(as.respeciate,default) S3method(plot,respeciate) S3method(print,respeciate) S3method(print,rsp_pls) S3method(summary,respeciate) -export(find_code) +export(as.respeciate) export(pls_fit_species) export(pls_plot) export(pls_plot_profile) @@ -35,7 +36,6 @@ export(sp_rescale_profile) export(sp_rescale_species) export(sp_species_cor) export(sp_species_info) -export(spec) export(spq_gas) export(spq_other) export(spq_pm) diff --git a/NEWS.md b/NEWS.md index fcd9fba..a15018a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,19 @@ -# Version 0.0 - Release Notes +# Release Notes Version 0.3 + +* [0.3.0] + * released 2024-02-18 + * code and documentation refresh... + * added as.respeciate method; replacing related unexported code + * sp_profile, sp_build_rsp_x updates; both now use as.respeciate + +# Release Notes Version 0.2 * [0.2.7] * released 2024-02-17 * sp_plot_species update; species order forced, added reset.x * sp_match_profile update; fit methods for pd and sid (Belis) * sp_match_profile update; added sid variations - * pls_plot patch; forcing profile order + * pls_plot patch; forcing profile order 1 * [0.2.6] * released 2023-12-01 @@ -68,6 +76,8 @@ * updated date and version * The new code is in R:speciate.0.2.r +# Release Notes Version 0.1 + * [0.1.0] * released 2020-12-20 * Created respeciate diff --git a/R/respeciate.generics.R b/R/respeciate.generics.R index ba83e0f..83e53f5 100644 --- a/R/respeciate.generics.R +++ b/R/respeciate.generics.R @@ -7,6 +7,10 @@ # hard to keep style consistent when docs are in between # multiple functions +#' @description When supplied a \code{data.frame} or similar, +#' \code{\link{as.respeciate}} attempts to coerce it into a +#' \code{respeciate} objects. + #' @description When supplied a \code{respeciate} #' object or similar, \code{\link{print}} manages its appearance. #' @description When supplied a \code{respeciate} @@ -29,6 +33,70 @@ #' capacity... + + +################################# +# as.respeciate +################################# + + +# notes +################################## + +# currently only allows for data.frames and object that can be converted +# to data.frames using as.data.frames... + +# might also want to add setAs ???? + +#' @rdname respeciate.generics +#' @export + +as.respeciate <- function(x, ...) +{ + if (is.null(x)) + return(as.respeciate(list())) + UseMethod("as.respeciate") +} + +#' @rdname respeciate.generics +#' @method as.respeciate default +#' @export + +as.respeciate.default <- function(x, ...){ + + #setup + .xargs <- list(...) + + #try to make data.frame + .try <- try(as.data.frame(x), silent=TRUE) + if(class(.try)[1]=="try-error"){ + stop("as.respeciate> x needs to be data.frame or similar...", + sep="", call. = FALSE) + } + + #test structure + if(!"test.rsp" %in% names(.xargs) || .xargs$test.rsp){ + .test <- c("PROFILE_NAME", "PROFILE_CODE", "SPECIES_NAME", "SPECIES_ID", + ".value", "WEIGHT_PERCENT") + .test <- .test[!.test %in% names(.try)] + if(length(.test)>0){ + stop("as.respeciate> bad data structure, expected column(s) missing/unassigned:\n", + paste(.test, sep="", collapse = ", "), "\n", sep="", call.=FALSE) + } + if(any(is.na(.try$SPECIES_ID)) | any(is.na(.try$SPECIES_NAMES))){ + warning("as.respeciate> suspect species data, values missing:\n", + "(respeciate needs valid species entries)\n", + sep="", call.=FALSE) + } + } + + #output + class(.try) <- c("respeciate", class(.try)) + .try +} + + + #notes ################################## @@ -38,6 +106,7 @@ # with different print outputs? # only plot for class plot, etc??? + #' @rdname respeciate.generics #' @method print respeciate #' @export @@ -805,6 +874,12 @@ summary.respeciate <- #class builds ################################### +################################### +# hoping to drop this +# as.respeciate to supersede +################################### + + #rsp_build_respeciate.spcs <- # function(x, ...){ #build @@ -823,8 +898,11 @@ summary.respeciate <- rsp_build_respeciate <- function(x, ...){ - #build - class(x) <- c("respeciate", "data.frame") + x <- as.data.frame(x) + if("WEIGHT_PERCENT" %in% names(x)) { + x$.value <- x$WEIGHT_PERCENT + } + class(x) <- c("respeciate", class(x)) x } diff --git a/R/sp.R b/R/sp.R index 51c0027..de1c1f0 100644 --- a/R/sp.R +++ b/R/sp.R @@ -1,12 +1,10 @@ #' @name sp -#' @title sp_ functions -#' @aliases sp_profile sp_build_rsp_x +#' @title sp_profile +#' @aliases sp_profile -#' @description sp function to get profiles from the R (re)SPECIATE archive +#' @description sp function to get profile(s) from the R (re)SPECIATE archive -#' @description \code{\link{sp_profile}} extracts a -#' SPECIATE profile from the local (re)SPECIATE archive. #' @param code character, numeric or data.frame, the SPECIATE code #' of the required profile (EPA SPECIATE identifier PROFILE_CODE). This is #' typically one or concatenated character or numeric entries, but can also @@ -17,30 +15,10 @@ #' @param include.refs logical, (for \code{sp_profile} only) include profile #' reference information when getting the requested profile(s) from the #' archive, default \code{FALSE}. -#' @param x (for \code{sp_build}s only) A \code{data.frame} or similar (i.e. -#' something that can be converted to a \code{data.frame} using -#' \code{as.data.frame}) to be converted into a \code{respeciate} object for -#' comparison with SPECIATE profiles. -#' @param profile_name,profile_code (for \code{sp_build}s only; -#' \code{character}) The name of the column in \code{x} containing -#' profile name and code, respectively. If not already named according -#' to SPECIATE conventions, at least one of these will need to be assigned. -#' @param species_name,species_id (for \code{sp_build}s only; -#' \code{character}) The name of the column in \code{x} containing -#' species name and id, respectively. If not already named according -#' to SPECIATE conventions, at least one of these will need to be assigned. -#' @param value (for \code{sp_build}s only; \code{character}) The name -#' of the column in \code{x} containing measurement values. If not already -#' named according to SPECIATE conventions, this will need to be assigned. #' @return \code{sp_profile} returns a object of #' \code{respeciate} class, a \code{data.frame} containing a #' (re)SPECIATE profile. -#' -#' \code{sp_build}s attempt to build and return a (re)SPECIATE-like profile -#' that can be compared with with data in re(SPECIATE). -#' @note With \code{sp_profile}: -#' -#' The option \code{include.refs} adds profile source reference +#' @note The option \code{include.refs} adds profile source reference #' information to the returned \code{respeciate} data set. The default option #' is to not include these because some profiles have several associated #' references and including these replicates records, once per reference. @@ -48,17 +26,6 @@ #' own methods or code and include references in any profile build you may be #' biasing some analyses in favor of those multiple-reference profile unless #' you check and account such cases. -#' -#' With \code{sp_build}s: -#' -#' It is particularly IMPORTANT that you use EPA SPECIATE conventions when -#' assign species information if you want to compare your data with SPECIATE -#' profiles. Currently, working on option to improve on this (and very happy -#' to discuss if anyone has ideas), but current best suggestion is: (1) -#' identify the SPECIATE species code for all the species in your data set, -#' and (2) assign these as \code{species_id} when \code{sp_build}ing. The -#' function will then associate the \code{species_name}. -#' #' @references #' Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., #' Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. @@ -74,12 +41,10 @@ #to think about ####################### -#add functions??? +#add functions to build or add respeciate-like data of own, +# e.g. x matrices for pls modelling -## sp_build_profile to make a profile locally -## needs profile_name, profile_code -## species_name, species_id -## weight_percent (and possibly .value) +# (build functions started as separate script, sp.build.R) ## sp_import_profile to import a profile from an external source ## extension of above to import data from specific sources @@ -161,236 +126,36 @@ sp_profile <- function(code, ..., include.refs=FALSE) { ########################## # could test replacing some of this with sp_pad??? # IF sp_pad stays - df <- PROFILES[tolower(PROFILES$PROFILE_CODE) %in% tolower(code),] - df <- merge(df, SPECIES, by = "PROFILE_CODE", all.y=FALSE, all.x=TRUE, + dt <- PROFILES[tolower(PROFILES$PROFILE_CODE) %in% tolower(code),] + dt <- merge(dt, SPECIES, by = "PROFILE_CODE", all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) - df <- merge(df, SPECIES_PROPERTIES, by = "SPECIES_ID", all.y=FALSE, + dt <- merge(dt, SPECIES_PROPERTIES, by = "SPECIES_ID", all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) if(include.refs){ - df <- merge(df, PROFILE_REFERENCE, by = "PROFILE_CODE", all.y=FALSE, + dt <- merge(dt, PROFILE_REFERENCE, by = "PROFILE_CODE", all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) - df <- merge(df, REFERENCES, by = "REF_Code", all.y=FALSE, all.x=TRUE, + dt <- merge(dt, REFERENCES, by = "REF_Code", all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) } - df <- df[order(df$PROFILE_CODE, decreasing = FALSE),] + dt <- dt[order(dt$PROFILE_CODE, decreasing = FALSE),] - #build - #note: currently adding .value in rsp_build_respeciate - # could do it here? - # leaving there for now... because we would - # still have to do it there for self-build or - # imported profiles... - df <- rsp_build_respeciate(as.data.frame(df)) - return(df) -} - - - -############################## -# sp_build_rsp_x -############################## - -# notes -############################## - -# sp_build_rsp_x currently converts x as.data.frame(x) -# if tibble is loaded, any tibbles complicate things - -# BUT might want to revisit this because it looked like: -# the data structure was fine but -# print.respeciate was having problems... - -# BUT might be other problems I did not spot - -# BUT be nice if c("respeciate", class("tibble")) could be use... -# to retain the data type history -# and drop back to tibble rather than data.frame.... - - -#' @rdname sp -#' @export - -sp_build_rsp_x <- - function(x, profile_code, profile_name, - species_name, species_id, - value, ...){ - - - # light build for a rsp_x data object - # might need spec_mwt - - ########################### - # current build rules - ########################### - - # must be a data.frame or something that can be converted - # using as.data.frame(x) - - # profile and species columns must be character... - - # profile_name: if not there, if sent in call use, - # else if there use profile_code - # profile_code: if not there, if sent in call use, - # else if there use profile_name - # species_name: if not there, if sent in call use, - # else if there use use species_id to look-up - # if any missing, warn - # species_id: if not there, if sent in call use, - # else if there use species_name to look-up - # if any missing, warn - # .value: if not there, if sent in call use. - # (NEW/TESTING) else if there use WEIGHT_PERCENT - # WEIGHT_PERCENT:if not there, if sent in call use - # else if there use .value to look-up - - # don't build/error if any of these missing and end of build - - # redundant? - # currently not using ... - .x.args <- list(...) - - #adding the as.data.frame because - # code is not planning nicely with Dennis' tibbles - # if tibble is loaded before respeciate... - x <- as.data.frame(x) - - #rationalise this?... - # could they be else options when - # check for species and profile columns? - ################################ - # notes - # profile and species columns all need to character - # user could supply any thing and previously - # only applying as.character when making something new... - # else may at start then when making something new... - # (at end did not work for species if building one of species_name - # and species_id from other...) - # also - # do values need to be as.numeric??? - if("PROFILE_NAME" %in% names(x)){ - x$PROFILE_NAME <- as.character(x$PROFILE_NAME) - } - if("PROFILE_CODE" %in% names(x)){ - x$PROFILE_CODE <- as.character(x$PROFILE_CODE) - } - if("SPECIES_NAME" %in% names(x)){ - x$SPECIES_NAME <- as.character(x$SPECIES_NAME) - } - if("SPECIES_ID" %in% names(x)){ - x$SPECIES_ID <- as.character(x$SPECIES_ID) - } - - #if not there and sent in call - - #note: - #current making all BUT values, character class - if(!"PROFILE_NAME" %in% names(x) & (!missing(profile_name))){ - if(!profile_name %in% names(x)){ - stop("sp_build> '", as.character(profile_name)[1], - "' not in 'x'...", sep="", call. = FALSE) - } - x$PROFILE_NAME <- as.character(x[, profile_name]) - } - if(!"PROFILE_CODE" %in% names(x) & (!missing(profile_code))){ - if(!profile_code %in% names(x)){ - stop("sp_build> '", as.character(profile_code)[1], - "' not in 'x'...", sep="", call. = FALSE) - } - x$PROFILE_CODE <- as.character(x[, profile_code]) - } - if(!"SPECIES_NAME" %in% names(x) & (!missing(species_name))){ - if(!species_name %in% names(x)){ - stop("sp_build> '", as.character(species_name)[1], - "' not in 'x'...", sep="", call. = FALSE) - } - x$SPECIES_NAME <- as.character(x[, species_name]) - } - if(!"SPECIES_ID" %in% names(x) & (!missing(species_id))){ - if(!species_id %in% names(x)){ - stop("sp_build> '", as.character(species_id)[1], - "' not in 'x'...", sep="", call. = FALSE) - } - x$SPECIES_ID <- as.character(x[, species_id]) - } - if(!".value" %in% names(x)){ - if(missing(value)){ - if("WEIGHT_PERCENT" %in% names(x)){ - x$.value <- x[, "WEIGHT_PERCENT"] - } else { - stop("sp_build> 'value' not found for 'x'...", - sep="", call. = FALSE) - } - } else { - if(!value %in% names(x)){ - stop("sp_build> '", as.character(value)[1], - "' not in 'x'...", sep="", call. = FALSE) - } - } - x$.value <- x[, value] - } - ################# - #old - ################# - #if(!".value" %in% names(x) & (!missing(value))){ - # if(!value %in% names(x)){ - # stop("sp_build> '", as.character(value)[1], - # "' not in 'x'...", sep="", call. = FALSE) - # } - # x$.value <- x[, value] - #} - - #if still not there try to assign using what is there - - if("PROFILE_NAME" %in% names(x) & !"PROFILE_CODE" %in% names(x)){ - x$PROFILE_CODE <- x$PROFILE_NAME - } - if("PROFILE_CODE" %in% names(x) & !"PROFILE_NAME" %in% names(x)){ - x$PROFILE_NAME <- x$PROFILE_CODE - } - test <- c("SPECIES_NAME", "SPECIES_ID")[c("SPECIES_NAME", "SPECIES_ID") - %in% names(x)] - if(length(test)==1){ - #one there, other as look-up - .tmp <- data.table::as.data.table( - sysdata$SPECIES_PROPERTIES[c("SPECIES_NAME", "SPECIES_ID")] - ) - .tmp$SPECIES_NAME <- as.character(.tmp$SPECIES_NAME) - .tmp$SPECIES_ID <- as.character(.tmp$SPECIES_ID) - x <- merge(data.table::as.data.table(x), - data.table::as.data.table(.tmp), - all.x=TRUE, all.y=FALSE, allow.cartesian=TRUE) - x <- as.data.frame(x) - } - if(".value" %in% names(x) & !"WEIGHT_PERCENT" %in% names(x)){ - x$WEIGHT_PERCENT <- x$.value - } - - #test what we have - ######################## - - .test <- c("PROFILE_NAME", "PROFILE_CODE", "SPECIES_NAME", "SPECIES_ID", - ".value", "WEIGHT_PERCENT") - .test <- .test[!.test %in% names(x)] - if(length(.test)>0){ - stop("sp_build> bad data structure, expected column(s) missing/unassigned:\n", - paste(.test, sep="", collapse = ", "), "\n", sep="", call.=FALSE) - } - if(any(is.na(x$SPECIES_ID)) | any(is.na(x$SPECIES_NAMES))){ - warning("sp_build> suspect species data, values missing:\n", - "(respeciate needs valid species entries)\n", - sep="", call.=FALSE) - } - - #output - ###################### - - x <- as.data.frame(x) - class(x) <- c("rsp_x", "respeciate", "data.frame") - x + #add .value if weight_percent to copy... + x <- as.data.frame(dt) + if("WEIGHT_PERCENT" %in% names(x)) { + x$.value <- x$WEIGHT_PERCENT } + # note + ###################################### + #dropping generic unexported rsp_build_respeciate(x) + # replacing with as.respeciate + # could do similar elsewhere if not used widely elsewhere ??? + #output + rsp <- as.respeciate(x, test.rsp=FALSE) + return(rsp) +} @@ -399,54 +164,3 @@ sp_build_rsp_x <- - - -############################# -#unexported & previous code -############################# - -#sp_profile v 0.1 -#now unexported - -rsp_profile.old <- function(code) { - #handle numerics/characters - ####################### - #could replace code with ...??? - ###################### - if(class(code)[1] == "respeciate" && "PROFILE_CODE" %in% names(code)){ - code <- unique(code$PROFILE_CODE) - } - if(is.numeric(code)) code <- as.character(code) - if(!is.character(code)) stop("unexpected code class") - - PROFILES <- sysdata$PROFILES - SPECIES <- sysdata$SPECIES - SPECIES_PROPERTIES <- sysdata$SPECIES_PROPERTIES - PROFILE_REFERENCE <- sysdata$PROFILE_REFERENCE - REFERENCES <- sysdata$REFERENCES - - #handle multiple codes - ############################ - #replace previous lapply with a direct %in% - ## df <- lapply(code, function(x){ - ## df <- PROFILES[PROFILES$PROFILE_CODE == x, ] - ## ... - ## }) - ## df <- do.call(rbind, df) - #testing as sp_profile.2 - #faster with data.table - ############################ - df <- PROFILES[PROFILES$PROFILE_CODE %in% code,] - df <- merge(df, SPECIES, by = "PROFILE_CODE", all.y=FALSE, all.x=TRUE) - df <- merge(df, SPECIES_PROPERTIES, by = "SPECIES_ID", all.y=FALSE, all.x=TRUE) - df <- merge(df, PROFILE_REFERENCE, by = "PROFILE_CODE", all.y=FALSE, all.x=TRUE) - df <- merge(df, REFERENCES, by = "REF_Code", all.y=FALSE, all.x=TRUE) - df <- df[order(df$PROFILE_CODE, decreasing = FALSE),] -## }) - #build - df <- rsp_build_respeciate(df) - return(df) -} - - - diff --git a/R/sp.build.R b/R/sp.build.R new file mode 100644 index 0000000..474b607 --- /dev/null +++ b/R/sp.build.R @@ -0,0 +1,240 @@ +#' @name sp.build +#' @title sp_build functions +#' @aliases sp_build_rsp_x + + +#' @description sp function(s) to reconfigure data.frames (and similar +#' object classes) for use with data and functions in re(SPECIATE). + +#' @param x \code{data.frame} or similar (i.e. +#' something that can be coerced into a \code{data.frame} using +#' \code{as.data.frame}) to be converted into a \code{respeciate} object. +#' @param profile_name,profile_code (\code{character}) The name of the column +#' in \code{x} containing profile name and code records, respectively. If not +#' already named according to SPECIATE conventions, at least one of these will +#' need to be assigned. +#' @param species_name,species_id (\code{character}) The name of the column +#' in \code{x} containing species name and id records, respectively. If not +#' already named according to SPECIATE conventions, at least one of these will +#' need to be assigned. +#' @param value (\code{character}) The name of the column in \code{x} +#' containing measurement values. If not already named according to SPECIATE +#' conventions, this will need to be assigned. +#' @return \code{sp_build}s attempt to build and return a (re)SPECIATE-like +#' profile that can be compared with data in re(SPECIATE). +#' @note If you want to compare your data with profiles in the SPECIATE archive, +#' you need to use EPA SPECIATE conventions when assigning species names and +#' identifiers. Currently, we are working on options to improve on this (and +#' very happy to discuss if anyone has ideas), but current best suggestion is: +#' (1) identify the SPECIATE species code for each of the species in your data set, +#' and (2) assign these as \code{species_id} when \code{sp_build}ing. The +#' function will then associate the \code{species_name} from SPECIATE species +#' records. + +#NOTES +####################### + +#to think about +####################### + +## sp_build_profile to make a profile locally +## needs profile_name, profile_code +## species_name, species_id +## weight_percent (and possibly .value) + +############################## +# sp_build_rsp_x +############################## + +# notes +############################## + +# sp_build_rsp_x currently converts x as.data.frame(x) +# if tibble is loaded, any tibbles complicate things + +# BUT might want to revisit this because it looked like: +# the data structure was fine but +# print.respeciate was having problems... + +# BUT might be other problems I did not spot + +# BUT be nice if c("respeciate", class("tibble")) could be used... +# to retain the data type history +# and drop back to tibble rather than data.frame.... + + +#' @rdname sp.build +#' @export + +sp_build_rsp_x <- + function(x, profile_code, profile_name, + species_name, species_id, + value, ...){ + + + # light build for a rsp_x data object + # might need spec_mwt + + ########################### + # current build rules + ########################### + + # must be a data.frame or something that can be converted + # using as.data.frame(x) + + # profile and species columns must be character... + + # profile_name: if not there, if sent in call use, + # else if there use profile_code + # profile_code: if not there, if sent in call use, + # else if there use profile_name + # species_name: if not there, if sent in call use, + # else if there use use species_id to look-up + # if any missing, warn + # species_id: if not there, if sent in call use, + # else if there use species_name to look-up + # if any missing, warn + # .value: if not there, if sent in call use. + # (NEW/TESTING) else if there use WEIGHT_PERCENT + # WEIGHT_PERCENT:if not there, if sent in call use + # else if there use .value to look-up + + # don't build/error if any of these missing and end of build + + # redundant? + # currently not using ... + .x.args <- list(...) + + #adding the as.data.frame because + # code is not planning nicely with Dennis' tibbles + # if tibble is loaded before respeciate... + x <- as.data.frame(x) + + #rationalise this?... + # could they be else options when + # check for species and profile columns? + ################################ + # notes + # profile and species columns all need to character + # user could supply any thing and previously + # only applying as.character when making something new... + # else may at start then when making something new... + # (at end did not work for species if building one of species_name + # and species_id from other...) + # also + # do values need to be as.numeric??? + if("PROFILE_NAME" %in% names(x)){ + x$PROFILE_NAME <- as.character(x$PROFILE_NAME) + } + if("PROFILE_CODE" %in% names(x)){ + x$PROFILE_CODE <- as.character(x$PROFILE_CODE) + } + if("SPECIES_NAME" %in% names(x)){ + x$SPECIES_NAME <- as.character(x$SPECIES_NAME) + } + if("SPECIES_ID" %in% names(x)){ + x$SPECIES_ID <- as.character(x$SPECIES_ID) + } + + #if not there and sent in call + + #note: + #current making all BUT values, character class + if(!"PROFILE_NAME" %in% names(x) & (!missing(profile_name))){ + if(!profile_name %in% names(x)){ + stop("sp_build> '", as.character(profile_name)[1], + "' not in 'x'...", sep="", call. = FALSE) + } + x$PROFILE_NAME <- as.character(x[, profile_name]) + } + if(!"PROFILE_CODE" %in% names(x) & (!missing(profile_code))){ + if(!profile_code %in% names(x)){ + stop("sp_build> '", as.character(profile_code)[1], + "' not in 'x'...", sep="", call. = FALSE) + } + x$PROFILE_CODE <- as.character(x[, profile_code]) + } + if(!"SPECIES_NAME" %in% names(x) & (!missing(species_name))){ + if(!species_name %in% names(x)){ + stop("sp_build> '", as.character(species_name)[1], + "' not in 'x'...", sep="", call. = FALSE) + } + x$SPECIES_NAME <- as.character(x[, species_name]) + } + if(!"SPECIES_ID" %in% names(x) & (!missing(species_id))){ + if(!species_id %in% names(x)){ + stop("sp_build> '", as.character(species_id)[1], + "' not in 'x'...", sep="", call. = FALSE) + } + x$SPECIES_ID <- as.character(x[, species_id]) + } + if(!".value" %in% names(x)){ + if(missing(value)){ + if("WEIGHT_PERCENT" %in% names(x)){ + x$.value <- x[, "WEIGHT_PERCENT"] + } else { + stop("sp_build> 'value' not found for 'x'...", + sep="", call. = FALSE) + } + } else { + if(!value %in% names(x)){ + stop("sp_build> '", as.character(value)[1], + "' not in 'x'...", sep="", call. = FALSE) + } + } + x$.value <- x[, value] + } + ################# + #old + ################# + #if(!".value" %in% names(x) & (!missing(value))){ + # if(!value %in% names(x)){ + # stop("sp_build> '", as.character(value)[1], + # "' not in 'x'...", sep="", call. = FALSE) + # } + # x$.value <- x[, value] + #} + + #if still not there try to assign using what is there + + if("PROFILE_NAME" %in% names(x) & !"PROFILE_CODE" %in% names(x)){ + x$PROFILE_CODE <- x$PROFILE_NAME + } + if("PROFILE_CODE" %in% names(x) & !"PROFILE_NAME" %in% names(x)){ + x$PROFILE_NAME <- x$PROFILE_CODE + } + test <- c("SPECIES_NAME", "SPECIES_ID")[c("SPECIES_NAME", "SPECIES_ID") + %in% names(x)] + if(length(test)==1){ + #one there, other as look-up + .tmp <- data.table::as.data.table( + sysdata$SPECIES_PROPERTIES[c("SPECIES_NAME", "SPECIES_ID")] + ) + .tmp$SPECIES_NAME <- as.character(.tmp$SPECIES_NAME) + .tmp$SPECIES_ID <- as.character(.tmp$SPECIES_ID) + x <- merge(data.table::as.data.table(x), + data.table::as.data.table(.tmp), + all.x=TRUE, all.y=FALSE, allow.cartesian=TRUE) + x <- as.data.frame(x) + } + if(".value" %in% names(x) & !"WEIGHT_PERCENT" %in% names(x)){ + x$WEIGHT_PERCENT <- x$.value + } + + #pass via as.speciate to build rsp_x + # note: this replaces previous local testing + x <- as.respeciate(x, test.rsp=TRUE) + #slip in rsp_x tag + class(x) <- c("rsp_x", class(x)) + x + } + + + + + + + + + + diff --git a/R/speciate.R b/R/speciate.R deleted file mode 100644 index 3b4a764..0000000 --- a/R/speciate.R +++ /dev/null @@ -1,67 +0,0 @@ -#' Access to the SPECIATE 5.1 US/EPA Tool -#' -#' @description \code{\link{spec}} Return a speciate data.frame -#' -#' @param code Character, PROFILE CODE required by EPA/Speciate -#' @return a data.frame with full information for the desired code (PROFILE_CODE) -#' @export -#' @references -#' Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., -#' Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. -#' The development and uses of EPA SPECIATE database. -#' Atmospheric Pollution Research, 1(4), pp.196-206. -#' @examples \dontrun{ -#' code <- "8855" -#' x <- spec(code) -#' } -spec <- function(code) { - - PROFILES <- sysdata$PROFILES - SPECIES <- sysdata$SPECIES - SPECIES_PROPERTIES <- sysdata$SPECIES_PROPERTIES - PROFILE_REFERENCE <- sysdata$PROFILE_REFERENCE - REFERENCES <- sysdata$REFERENCES - - df <- PROFILES[PROFILES$PROFILE_CODE == code, ] - df <- merge(df, - SPECIES, - by = "PROFILE_CODE") - cat("Sum WEIGHT_PERCENT: ", - sum(as.numeric(as.character(df$WEIGHT_PERCENT)), na.rm = T), - "\n") - - df <- merge(df, SPECIES_PROPERTIES, by = "SPECIES_ID") - - df <- merge(df, PROFILE_REFERENCE, by = "PROFILE_CODE") - - df <- merge(df, REFERENCES, by = "REF_Code") - -return(df) - -} - - -#' Find PROFILE_CODE -#' -#' @description \code{\link{find_code}} Return a data.frame with profile codes -#' -#' @param profile Character, to search PROFILE CODE -#' @param by Character, to search code. eg: "Keywords", "PROFILE_NOTES", "PROFILE_TYPE" -#' or other name of PROFILES -#' @return a data.frame with with profile codes -#' @export -#' @references -#' Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., -#' Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. -#' The development and uses of EPA SPECIATE database. -#' Atmospheric Pollution Research, 1(4), pp.196-206. -#' @examples \dontrun{ -#' profile <- "Ethanol" -#' dt <- find_code(profile) -#' } -find_code <- function(profile, by = "Keywords") { - - PROFILES <- sysdata$PROFILES - - return(PROFILES[grep(profile, PROFILES[[by]]), ]) -} diff --git a/man/find_code.Rd b/man/find_code.Rd deleted file mode 100644 index 827dfdd..0000000 --- a/man/find_code.Rd +++ /dev/null @@ -1,32 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/speciate.R -\name{find_code} -\alias{find_code} -\title{Find PROFILE_CODE} -\usage{ -find_code(profile, by = "Keywords") -} -\arguments{ -\item{profile}{Character, to search PROFILE CODE} - -\item{by}{Character, to search code. eg: "Keywords", "PROFILE_NOTES", "PROFILE_TYPE" -or other name of PROFILES} -} -\value{ -a data.frame with with profile codes -} -\description{ -\code{\link{find_code}} Return a data.frame with profile codes -} -\examples{ -\dontrun{ -profile <- "Ethanol" -dt <- find_code(profile) -} -} -\references{ -Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., -Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. -The development and uses of EPA SPECIATE database. -Atmospheric Pollution Research, 1(4), pp.196-206. -} diff --git a/man/respeciate-package.Rd b/man/respeciate-package.Rd index 2dde598..b92bef7 100644 --- a/man/respeciate-package.Rd +++ b/man/respeciate-package.Rd @@ -8,7 +8,7 @@ \description{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} -Acess to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) \doi{10.5094/APR.2010.026}. +Access to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) \doi{10.5094/APR.2010.026}. } \seealso{ Useful links: diff --git a/man/respeciate.generics.Rd b/man/respeciate.generics.Rd index cd90be2..a37960e 100644 --- a/man/respeciate.generics.Rd +++ b/man/respeciate.generics.Rd @@ -2,12 +2,18 @@ % Please edit documentation in R/respeciate.generics.R \name{respeciate.generics} \alias{respeciate.generics} +\alias{as.respeciate} +\alias{as.respeciate.default} \alias{print.respeciate} \alias{print.rsp_pls} \alias{plot.respeciate} \alias{summary.respeciate} \title{respeciate.generics} \usage{ +as.respeciate(x, ...) + +\method{as.respeciate}{default}(x, ...) + \method{print}{respeciate}(x, n = NULL, ...) \method{print}{rsp_pls}(x, n = NULL, ...) @@ -20,17 +26,21 @@ \item{x}{the \code{respeciate} object to be printed, plotted, etc.} -\item{n}{when plotting or printing a multi-profile object, the -maximum number of profiles to report.} - \item{...}{any extra arguments, mostly ignored except by \code{plot} which passes them to \code{\link{sp_plot_profile}}.} +\item{n}{when plotting or printing a multi-profile object, the +maximum number of profiles to report.} + \item{object}{like \code{x} but for \code{summary}.} } \description{ \code{respeciate} object classes and generic functions. +When supplied a \code{data.frame} or similar, +\code{\link{as.respeciate}} attempts to coerce it into a +\code{respeciate} objects. + When supplied a \code{respeciate} object or similar, \code{\link{print}} manages its appearance. diff --git a/man/sp.Rd b/man/sp.Rd index 135b82c..69341b8 100644 --- a/man/sp.Rd +++ b/man/sp.Rd @@ -3,20 +3,9 @@ \name{sp} \alias{sp} \alias{sp_profile} -\alias{sp_build_rsp_x} -\title{sp_ functions} +\title{sp_profile} \usage{ sp_profile(code, ..., include.refs = FALSE) - -sp_build_rsp_x( - x, - profile_code, - profile_name, - species_name, - species_id, - value, - ... -) } \arguments{ \item{code}{character, numeric or data.frame, the SPECIATE code @@ -31,43 +20,16 @@ treats these as additional sources for \code{code}.} \item{include.refs}{logical, (for \code{sp_profile} only) include profile reference information when getting the requested profile(s) from the archive, default \code{FALSE}.} - -\item{x}{(for \code{sp_build}s only) A \code{data.frame} or similar (i.e. -something that can be converted to a \code{data.frame} using -\code{as.data.frame}) to be converted into a \code{respeciate} object for -comparison with SPECIATE profiles.} - -\item{profile_name, profile_code}{(for \code{sp_build}s only; -\code{character}) The name of the column in \code{x} containing -profile name and code, respectively. If not already named according -to SPECIATE conventions, at least one of these will need to be assigned.} - -\item{species_name, species_id}{(for \code{sp_build}s only; -\code{character}) The name of the column in \code{x} containing -species name and id, respectively. If not already named according -to SPECIATE conventions, at least one of these will need to be assigned.} - -\item{value}{(for \code{sp_build}s only; \code{character}) The name -of the column in \code{x} containing measurement values. If not already -named according to SPECIATE conventions, this will need to be assigned.} } \value{ \code{sp_profile} returns a object of \code{respeciate} class, a \code{data.frame} containing a (re)SPECIATE profile. - -\code{sp_build}s attempt to build and return a (re)SPECIATE-like profile -that can be compared with with data in re(SPECIATE). } \description{ -sp function to get profiles from the R (re)SPECIATE archive - -\code{\link{sp_profile}} extracts a -SPECIATE profile from the local (re)SPECIATE archive. +sp function to get profile(s) from the R (re)SPECIATE archive } \note{ -With \code{sp_profile}: - The option \code{include.refs} adds profile source reference information to the returned \code{respeciate} data set. The default option is to not include these because some profiles have several associated @@ -76,16 +38,6 @@ references and including these replicates records, once per reference. own methods or code and include references in any profile build you may be biasing some analyses in favor of those multiple-reference profile unless you check and account such cases. - -With \code{sp_build}s: - -It is particularly IMPORTANT that you use EPA SPECIATE conventions when -assign species information if you want to compare your data with SPECIATE -profiles. Currently, working on option to improve on this (and very happy -to discuss if anyone has ideas), but current best suggestion is: (1) -identify the SPECIATE species code for all the species in your data set, -and (2) assign these as \code{species_id} when \code{sp_build}ing. The -function will then associate the \code{species_name}. } \examples{ \dontrun{ diff --git a/man/sp.build.Rd b/man/sp.build.Rd new file mode 100644 index 0000000..06095e6 --- /dev/null +++ b/man/sp.build.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sp.build.R +\name{sp.build} +\alias{sp.build} +\alias{sp_build_rsp_x} +\title{sp_build functions} +\usage{ +sp_build_rsp_x( + x, + profile_code, + profile_name, + species_name, + species_id, + value, + ... +) +} +\arguments{ +\item{x}{\code{data.frame} or similar (i.e. +something that can be coerced into a \code{data.frame} using +\code{as.data.frame}) to be converted into a \code{respeciate} object.} + +\item{profile_name, profile_code}{(\code{character}) The name of the column +in \code{x} containing profile name and code records, respectively. If not +already named according to SPECIATE conventions, at least one of these will +need to be assigned.} + +\item{species_name, species_id}{(\code{character}) The name of the column +in \code{x} containing species name and id records, respectively. If not +already named according to SPECIATE conventions, at least one of these will +need to be assigned.} + +\item{value}{(\code{character}) The name of the column in \code{x} +containing measurement values. If not already named according to SPECIATE +conventions, this will need to be assigned.} +} +\value{ +\code{sp_build}s attempt to build and return a (re)SPECIATE-like +profile that can be compared with data in re(SPECIATE). +} +\description{ +sp function(s) to reconfigure data.frames (and similar +object classes) for use with data and functions in re(SPECIATE). +} +\note{ +If you want to compare your data with profiles in the SPECIATE archive, +you need to use EPA SPECIATE conventions when assigning species names and +identifiers. Currently, we are working on options to improve on this (and +very happy to discuss if anyone has ideas), but current best suggestion is: +(1) identify the SPECIATE species code for each of the species in your data set, +and (2) assign these as \code{species_id} when \code{sp_build}ing. The +function will then associate the \code{species_name} from SPECIATE species +records. +} From b663ffe2533476674bcbe997b8d9b6ee2f9f0bc6 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Sun, 18 Feb 2024 17:03:10 +0000 Subject: [PATCH 02/18] tidy docs --- man/spec.Rd | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 man/spec.Rd diff --git a/man/spec.Rd b/man/spec.Rd deleted file mode 100644 index a5069a3..0000000 --- a/man/spec.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/speciate.R -\name{spec} -\alias{spec} -\title{Access to the SPECIATE 5.1 US/EPA Tool} -\usage{ -spec(code) -} -\arguments{ -\item{code}{Character, PROFILE CODE required by EPA/Speciate} -} -\value{ -a data.frame with full information for the desired code (PROFILE_CODE) -} -\description{ -\code{\link{spec}} Return a speciate data.frame -} -\examples{ -\dontrun{ -code <- "8855" -x <- spec(code) -} -} -\references{ -Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., -Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. -The development and uses of EPA SPECIATE database. -Atmospheric Pollution Research, 1(4), pp.196-206. -} From 3970a131b4d73acaf34199aabc618def09b0fabd Mon Sep 17 00:00:00 2001 From: karlropkins Date: Wed, 28 Feb 2024 17:04:02 +0000 Subject: [PATCH 03/18] rsp_profile, rsp_dcast/melt --- .Rproj.user/shared/notebooks/paths | 8 +- DESCRIPTION | 2 +- NAMESPACE | 27 +-- NEWS.md | 5 +- R/respeciate.generics.R | 280 ++++++++++++++++++++++++-- R/{sp.R => rsp.R} | 64 +++--- R/{sp.build.R => rsp.build.R} | 25 ++- R/{sp.info.R => rsp.info.R} | 68 +++---- R/{sp.plot.R => rsp.plot.R} | 50 ++--- R/{sp.reshape.R => rsp.reshape.R} | 70 ++++--- R/sp.pad.R | 4 +- R/xxx.R | 24 +-- man/respeciate.generics.Rd | 4 +- man/rsp.Rd | 54 +++++ man/rsp.info.Rd | 63 ++++++ man/{sp.plot.Rd => rsp.plot.Rd} | 30 +-- man/{sp.reshape.Rd => rsp.reshape.Rd} | 46 ++--- man/sp.Rd | 52 ----- man/sp.build.Rd | 20 +- man/sp.info.Rd | 63 ------ 20 files changed, 619 insertions(+), 340 deletions(-) rename R/{sp.R => rsp.R} (72%) rename R/{sp.build.R => rsp.build.R} (93%) rename R/{sp.info.R => rsp.info.R} (70%) rename R/{sp.plot.R => rsp.plot.R} (93%) rename R/{sp.reshape.R => rsp.reshape.R} (85%) create mode 100644 man/rsp.Rd create mode 100644 man/rsp.info.Rd rename man/{sp.plot.Rd => rsp.plot.Rd} (75%) rename man/{sp.reshape.Rd => rsp.reshape.Rd} (66%) delete mode 100644 man/sp.Rd delete mode 100644 man/sp.info.Rd diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index c1e879c..88ed5f3 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -15,15 +15,15 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/NEWS.md="F6ED8BF4" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/respeciate-package.R="A43C9569" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/respeciate.generics.R="54ECE8F1" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.R="58F2A9BE" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.R="787EA0C5" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.build.R="5A264727" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.info.R="FD1BAD48" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.average.R="0A1E36E4" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.build.R="FE2BA79A" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cluster.R="49C0F861" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cor.R="3F1DA8E5" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.info.R="D0E7AC03" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.match.R="4326C1C3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.pad.R="4962C940" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.plot.R="561F2EAC" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.pls.R="EACFE9A4" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.rescale.R="D668C5B2" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.reshape.R="CC655C60" diff --git a/DESCRIPTION b/DESCRIPTION index b8b4c65..5514722 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: respeciate Title: Speciation profiles for gases and aerosols Version: 0.3.0 -Date: 2024-02-18 +Date: 2024-02-28 Description: Access to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) . Type: Package diff --git a/NAMESPACE b/NAMESPACE index bb78c27..e288c19 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,28 +14,29 @@ export(pls_rebuild) export(pls_refit_species) export(pls_report) export(pls_test) +export(rsp) +export(rsp_build_x) +export(rsp_dcast) +export(rsp_dcast_profile) +export(rsp_dcast_species) +export(rsp_find_profile) +export(rsp_find_species) +export(rsp_info) +export(rsp_melt_wide) +export(rsp_plot_profile) +export(rsp_plot_species) +export(rsp_profile) +export(rsp_profile_info) +export(rsp_species_info) export(sp_average_profile) -export(sp_build_rsp_x) -export(sp_dcast) -export(sp_dcast_profile) -export(sp_dcast_species) -export(sp_find_profile) -export(sp_find_species) -export(sp_info) export(sp_match_profile) -export(sp_melt_wide) export(sp_pad) -export(sp_plot_profile) -export(sp_plot_species) export(sp_pls_profile) -export(sp_profile) export(sp_profile_distance) -export(sp_profile_info) export(sp_rescale) export(sp_rescale_profile) export(sp_rescale_species) export(sp_species_cor) -export(sp_species_info) export(spq_gas) export(spq_other) export(spq_pm) diff --git a/NEWS.md b/NEWS.md index a15018a..3b0503f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,10 +1,13 @@ # Release Notes Version 0.3 * [0.3.0] - * released 2024-02-18 + * released 2024-02-28 * code and documentation refresh... * added as.respeciate method; replacing related unexported code * sp_profile, sp_build_rsp_x updates; both now use as.respeciate + * sp_profile to rsp_profile; sp_dcast/melt to rsp_dcast/melt; both as + part of object class rebuild + * # Release Notes Version 0.2 diff --git a/R/respeciate.generics.R b/R/respeciate.generics.R index 83e53f5..bfbdf8d 100644 --- a/R/respeciate.generics.R +++ b/R/respeciate.generics.R @@ -9,7 +9,7 @@ #' @description When supplied a \code{data.frame} or similar, #' \code{\link{as.respeciate}} attempts to coerce it into a -#' \code{respeciate} objects. +#' \code{respeciate} object. #' @description When supplied a \code{respeciate} #' object or similar, \code{\link{print}} manages its appearance. @@ -111,24 +111,155 @@ as.respeciate.default <- function(x, ...){ #' @method print respeciate #' @export -print.respeciate <- function(x, n = NULL, ...){ - test <- rsp_test_respeciate(x, level = 2, silent = TRUE) + +print.respeciate <- + function(x, n=6, ...){ + + ################################ + #new general respeciate print method + .tmp <- getOption("width") + .x <- x + + #species info + if(class(.x)[1] == "rsp_si"){ + .y <- unique(.x$SPECIES_ID) + report <- paste("respeciate species list:", + length(.y), "\n", + "[NO PROFILES]", "\n", sep="") + if(length(.y)>0){ + yy <- if(length(.y)>n) {.y[1:n]} else {.y} + for(i in yy){ + .m1 <- paste(" (", "ID ", i, ") ", + subset(.x, SPECIES_ID == i)$SPECIES_NAME[1], + "\n", sep="") + if(nchar(.m1)>.tmp){ + .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") + } + report <- paste(report, .m1, sep="") + } + } + } + + #profile info + if(class(x)[1] == "rsp_pi"){ + .y <- unique(x$PROFILE_CODE) + report <- paste("respeciate profile list: ", + length(.y), "\n", + "[NO SPECIES]", "\n", sep="") + if(length(.y)>0){ + yy <- if(length(.y)>n) {.y[1:n]} else {.y} + for(i in yy){ + .m1 <- paste(" (", "CODE ", i, ") ", + subset(.x, PROFILE_CODE == i)$PROFILE_NAME[1], + "\n", sep="") + if(nchar(.m1)>.tmp){ + .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") + } + report <- paste(report, .m1, sep="") + } + } + } + + #################################### + #to do + #################################### + #handling for wide frames + #melt .x and carry on... + # could include a warning...? + # how to handle if profile_code is missing??? + # or profile_name ???? + # or species_name or species_id ??? + + + rsp.rep <- "respeciate profile(s):" + if(class(.x)[1] == "rsp_sw"){ + rsp.rep <- "respeciate (wide/species):" + .x <- rsp_melt_wide(.x, pad=FALSE, drop.nas = TRUE) + .x$SPECIES_ID <- .x$SPECIES_NAME + } + if(class(.x)[1] == "rsp_pw"){ + rsp.rep <- "respeciate (wide/profiles):" + .x <- rsp_melt_wide(.x, pad=FALSE, drop.nas = TRUE) + #.x$PROFILE_NAME <- .x$PROFILE_CODE + } + if(class(.x)[1] == "rsp_x"){ + rsp.rep <- "respeciate-like x:" + class(.x) <- class(.x)[class(.x) != "rsp_x"] + } + + #standard respeciate + if(class(.x)[1] == "respeciate"){ + .y <- unique(.x$PROFILE_CODE) + report <- paste(rsp.rep, " count ", + length(.y), "\n", sep="") + if(length(.y)>0){ + yy <- if(length(.y)>n) {.y[1:n]} else {.y} + for(i in yy){ + if("PROFILE_NAME" %in% names(.x)){ + i2 <- .x$PROFILE_NAME[.x$PROFILE_CODE==i][1] + } else { + i2 <- "[unknown]" + } + if("SPECIES_ID" %in% names(.x)){ + .spe <- length(unique(.x$SPECIES_ID[.x$PROFILE_CODE==i])) + } else { + .spe <- "0!" + } + .m1 <- paste(" ", i, " (", .spe, " species) ", + i2, "\n", sep="") + if(nchar(.m1)>.tmp){ + .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") + } + report <- paste(report, .m1, sep="") + } + } + } + + #cat output + if(length(.y)<1){ + cat(paste(report, + "empty (or bad?) respeciate object\n", + sep="")) + } else { + if(length(.y)>n){ + #rather no showing last...??? + report <- paste(report, + " > showing ", n, " of ", length(.y), + sep="") + } + cat(report) + } + + #return x (not .x) + return(invisible(x)) + } + + + + + + + + + +rsp_print.respeciate.old <- function(x, n = NULL, ...){ + test <- .rsp_test_respeciate(x, level = 2, silent = TRUE) if(test == "respeciate.profile.ref"){ if(is.null(n)){ n <- 100 } - return(rsp_print_respeciate_profile(x=x, n=n, ...)) + return(.rsp_print_respeciate_profile(x=x, n=n, ...)) } if(test == "respeciate.species.ref"){ if(is.null(n)){ n <- 10 } - return(rsp_print_respeciate_species(x=x, n=n, ...)) + return(.rsp_print_respeciate_species(x=x, n=n, ...)) } if(is.null(n)){ n <- 10 } - rsp_print_respeciate(x=x, n=n, ...) + .rsp_print_respeciate(x=x, n=n, ...) } ## rsp_print functions unexported @@ -200,7 +331,7 @@ print.rsp_pls <- function(x, n = NULL, ...){ #this is now sp_plot_profile plot.respeciate <- function(x, ...){ - sp_plot_profile(x, ...) + rsp_plot_profile(x, ...) } @@ -216,7 +347,7 @@ rsp_plot.respeciate.old <- #add .value if not there ## don't think .value works - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(x) ##test object type test <- rsp_test_respeciate(x, level=2, silent=TRUE) @@ -408,14 +539,14 @@ rsp_plot.respeciate.old <- #profile name to code option??? #species name to species id option??? -rsp_plot <- +.rsp_plot <- function(x, id, order=TRUE, log=FALSE, ...){ #setup ################## #add .value if not there - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(x) #others refs .x.args <- list(...) .sp.ord <- unique(x$SPECIES_ID) @@ -896,7 +1027,7 @@ summary.respeciate <- # x # } -rsp_build_respeciate <- +.rsp_build_respeciate <- function(x, ...){ x <- as.data.frame(x) if("WEIGHT_PERCENT" %in% names(x)) { @@ -923,7 +1054,7 @@ rsp_build_respeciate <- # i think rsp_test, then rsp_test.2 replaced # and code in plot.respeciate.old ??? -rsp_split_profile <- function(x){ +.rsp_split_profile <- function(x){ ref <- unique(x$PROFILE_CODE) lapply(ref, function(y) x[x$PROFILE_CODE==y,]) } @@ -953,8 +1084,127 @@ rsp_split_profile <- function(x){ # could make other respeciate print outputs # look like this? -rsp_print_respeciate <- +######################################### +######################################## +## thinking about making this main print.respeciate +######################################### +######################################### + +#then removing all the other print.respeciate related functions ??? + +.rsp_p2_ <- + function(x, n=6, ...){ + + ################################ + #new general respeciate print method + .tmp <- getOption("width") + .x <- x + + #species info + if(class(.x)[1] == "rsp_si"){ + .y <- unique(.x$SPECIES_ID) + report <- paste("respeciate species list:", + length(.y), "\n", + "[NO PROFILES]", "\n", sep="") + if(length(.y)>0){ + yy <- if(length(.y)>n) {.y[1:n]} else {.y} + for(i in yy){ + .m1 <- paste(" (", "ID ", i, ") ", + subset(.x, SPECIES_ID == i)$SPECIES_NAME[1], + "\n", sep="") + if(nchar(.m1)>.tmp){ + .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") + } + report <- paste(report, .m1, sep="") + } + } + } + + #profile info + if(class(x)[1] == "rsp_pi"){ + .y <- unique(x$PROFILE_CODE) + report <- paste("respeciate profile list: ", + length(.y), "\n", + "[NO SPECIES]", "\n", sep="") + if(length(.y)>0){ + yy <- if(length(.y)>n) {.y[1:n]} else {.y} + for(i in yy){ + .m1 <- paste(" (", "CODE ", i, ") ", + subset(.x, PROFILE_CODE == i)$PROFILE_NAME[1], + "\n", sep="") + if(nchar(.m1)>.tmp){ + .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") + } + report <- paste(report, .m1, sep="") + } + } + } + + #################################### + #to do + #################################### + #handling for wide frames + #melt .x and carry on... + # could include a warning...? + # how to handle if profile_code is missing??? + # or profile_name ???? + # or species_name or species_id ??? + + + + #standard respeciate + if(class(.x)[1] == "respeciate"){ + .y <- unique(.x$PROFILE_CODE) + report <- paste("respeciate profile(s): count ", + length(.y), "\n", sep="") + if(length(.y)>0){ + yy <- if(length(.y)>n) {.y[1:n]} else {.y} + for(i in yy){ + if("PROFILE_NAME" %in% names(.x)){ + i2 <- .x$PROFILE_NAME[.x$PROFILE_CODE==i][1] + } else { + i2 <- "[unknown]" + } + if("SPECIES_ID" %in% names(.x)){ + .spe <- length(unique(.x$SPECIES_ID[.x$PROFILE_CODE==i])) + } else { + .spe <- "0!" + } + .m1 <- paste(" ", i, " (", .spe, " species) ", + i2, "\n", sep="") + if(nchar(.m1)>.tmp){ + .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") + } + report <- paste(report, .m1, sep="") + } + } + } + + #cat output + if(length(.y)<1){ + cat(paste(report, + "empty (or bad?) respeciate object\n", + sep="")) + } else { + if(length(.y)>n){ + #rather no showing last...??? + report <- paste(report, + " > showing ", n, " of ", length(.y), + sep="") + } + cat(report) + } + + #return x (not .x) + return(invisible(x)) + } + + + + +.rsp_print_respeciate <- function(x, n=6, ...){ + ####################################### #profile_code is (I think) only term unique to a profile y <- unique(x$PROFILE_CODE) report <- paste("respeciate profile(s): count ", @@ -1001,7 +1251,7 @@ rsp_print_respeciate <- # #' @rdname respeciate.generics # #' @method print respeciate.ref # #' @export -rsp_print_respeciate_profile <- +.rsp_print_respeciate_profile <- function(x, n = 100, ...){ xx <- nrow(x) wi <- getOption("width") @@ -1029,7 +1279,7 @@ rsp_print_respeciate_profile <- # #' @rdname respeciate.generics # #' @method print respeciate.spcs # #' @export -rsp_print_respeciate_species <- +.rsp_print_respeciate_species <- function(x, n = 10, ...){ xx <- nrow(x) wi <- getOption("width") diff --git a/R/sp.R b/R/rsp.R similarity index 72% rename from R/sp.R rename to R/rsp.R index de1c1f0..f025696 100644 --- a/R/sp.R +++ b/R/rsp.R @@ -1,23 +1,24 @@ -#' @name sp -#' @title sp_profile -#' @aliases sp_profile - - -#' @description sp function to get profile(s) from the R (re)SPECIATE archive - -#' @param code character, numeric or data.frame, the SPECIATE code -#' of the required profile (EPA SPECIATE identifier PROFILE_CODE). This is -#' typically one or concatenated character or numeric entries, but can also -#' be a \code{respeciate} object or similar \code{data.frame} containing -#' the \code{code}s as a named \code{PROFILE_NAME} column. -#' @param ... additional arguments, ignored except by \code{sp_profile} which -#' treats these as additional sources for \code{code}. -#' @param include.refs logical, (for \code{sp_profile} only) include profile -#' reference information when getting the requested profile(s) from the -#' archive, default \code{FALSE}. -#' @return \code{sp_profile} returns a object of -#' \code{respeciate} class, a \code{data.frame} containing a -#' (re)SPECIATE profile. +#' @name rsp +#' @title rsp_profile +#' @aliases rsp rsp_profile + + +#' @description Getting profile(s) from the R (re)SPECIATE archive + +#' @param ... The function assumes all inputs (except \code{include.refs}) +#' are \code{SPECIES_CODE}s (the unique descriptor the EPA assigns to all +#' profiles in SPECIATE) or sources of profile information and requests these +#' form the local (re)SPECIATE archive. Typically, simple +#' objects like character and numeric vectors, as assumed to profile codes and +#' composite data-types like \code{respeciate} objects or \code{data.frame}, +#' are assumed to contain a named \code{PROFILE_CODE} column. All potential +#' profile codes are requested and unrecognized codes are ignored. +#' @param include.refs logical, if profile reference information should be +#' included when extracting the requested profile(s) from the archive, default +#' \code{FALSE}. +#' @return \code{rsp_profile} or the short-hand \code{rsp} return an object of +#' \code{respeciate} class, a \code{data.frame} containing one or more profile +#' from the local (re)SPECIATE archive. #' @note The option \code{include.refs} adds profile source reference #' information to the returned \code{respeciate} data set. The default option #' is to not include these because some profiles have several associated @@ -32,27 +33,33 @@ #' The development and uses of EPA SPECIATE database. #' Atmospheric Pollution Research, 1(4), pp.196-206. #' @examples \dontrun{ -#' x <- sp_profile(c(8833, 8850)) +#' x <- rsp_profile(8833, 8850) #' plot(x)} #NOTES ####################### +# 0.3. notes +# went from sp_profile to rsp_profile (and rsp) +# dropped code argument +# using as.respeciate in generics to build rsp object + + #to think about ####################### #add functions to build or add respeciate-like data of own, # e.g. x matrices for pls modelling -# (build functions started as separate script, sp.build.R) +# (build functions started as separate script, rsp.build.R) -## sp_import_profile to import a profile from an external source +## rsp_import_profile to import a profile from an external source ## extension of above to import data from specific sources ## might be very code intensive..? ## local function to pad data using database??? -#' @rdname sp +#' @rdname rsp #' @export ## (now importing via xxx.r) ## #' @import data.table @@ -75,7 +82,7 @@ # based on previous sp_profile but using data.table # (0.1 version currently unexported sp_profile.old) -sp_profile <- function(code, ..., include.refs=FALSE) { +rsp_profile <- function(..., include.refs=FALSE) { # code currently handles: # respeciate.ref, data.frames containing profile_code, @@ -86,7 +93,7 @@ sp_profile <- function(code, ..., include.refs=FALSE) { # but would need to think about options # if any in ... were data.frames ###################### - .try <- lapply(list(code, ...), function(.code){ + .try <- lapply(list(...), function(.code){ if(is.data.frame(.code) && "PROFILE_CODE" %in% names(.code)){ .code <- unique(.code$PROFILE_CODE) } @@ -94,7 +101,7 @@ sp_profile <- function(code, ..., include.refs=FALSE) { .code <- as.character(.code) } if(!is.character(.code)) { - warning("unexpected 'code' object found and ignored", + warning("RSP> unexpected 'PROFILE_CODE' source found and ignored", call.=FALSE) .code <- NULL } @@ -157,6 +164,9 @@ sp_profile <- function(code, ..., include.refs=FALSE) { return(rsp) } +#' @rdname rsp +#' @export +rsp <- function(...) { rsp_profile(...) } diff --git a/R/sp.build.R b/R/rsp.build.R similarity index 93% rename from R/sp.build.R rename to R/rsp.build.R index 474b607..b6d8aae 100644 --- a/R/sp.build.R +++ b/R/rsp.build.R @@ -1,9 +1,9 @@ -#' @name sp.build -#' @title sp_build functions -#' @aliases sp_build_rsp_x +#' @name rsp.build +#' @title Building respeciate-like Objects +#' @aliases rsp_build_x -#' @description sp function(s) to reconfigure data.frames (and similar +#' @description rsp function(s) to reconfigure data.frames (and similar #' object classes) for use with data and functions in re(SPECIATE). #' @param x \code{data.frame} or similar (i.e. @@ -20,14 +20,14 @@ #' @param value (\code{character}) The name of the column in \code{x} #' containing measurement values. If not already named according to SPECIATE #' conventions, this will need to be assigned. -#' @return \code{sp_build}s attempt to build and return a (re)SPECIATE-like -#' profile that can be compared with data in re(SPECIATE). +#' @return \code{rsp_build}s attempt to build and return a (re)SPECIATE-like +#' object that can be compared with data from re(SPECIATE). #' @note If you want to compare your data with profiles in the SPECIATE archive, #' you need to use EPA SPECIATE conventions when assigning species names and #' identifiers. Currently, we are working on options to improve on this (and #' very happy to discuss if anyone has ideas), but current best suggestion is: #' (1) identify the SPECIATE species code for each of the species in your data set, -#' and (2) assign these as \code{species_id} when \code{sp_build}ing. The +#' and (2) assign these as \code{species_id} when \code{rsp_build}ing. The #' function will then associate the \code{species_name} from SPECIATE species #' records. @@ -49,7 +49,12 @@ # notes ############################## -# sp_build_rsp_x currently converts x as.data.frame(x) +# 0.3. notes +# went from sp_build_rsp_x to rsp_build_x +# using as.respeciate and adding rsp_x + + +# rsp_build_x currently converts x as.data.frame(x) # if tibble is loaded, any tibbles complicate things # BUT might want to revisit this because it looked like: @@ -66,7 +71,7 @@ #' @rdname sp.build #' @export -sp_build_rsp_x <- +rsp_build_x <- function(x, profile_code, profile_name, species_name, species_id, value, ...){ @@ -225,7 +230,7 @@ sp_build_rsp_x <- # note: this replaces previous local testing x <- as.respeciate(x, test.rsp=TRUE) #slip in rsp_x tag - class(x) <- c("rsp_x", class(x)) + class(x) <- unique(c("rsp_x", class(x))) x } diff --git a/R/sp.info.R b/R/rsp.info.R similarity index 70% rename from R/sp.info.R rename to R/rsp.info.R index 58cef31..671e9b2 100644 --- a/R/sp.info.R +++ b/R/rsp.info.R @@ -1,15 +1,13 @@ -#' @name sp.info +#' @name rsp.info #' @title re(SPECIATE) information -#' @aliases sp_info sp_profile_info sp_species_info sp_find_profile -#' sp_find_species +#' @aliases rsp_info rsp_profile_info rsp_species_info rsp_find_profile +#' rsp_find_species ########################### #keep think about the names ########################### -# sp_profile_info used to be sp_find_profile -# sp_species_info used to be sp_find_species -# both sp_find_ functions are currently sp_info_ wrappers -# should remove at some point??? +# rsp_profile_info/ rsp_find_profile +# rsp_species_info/ rsp_find_species ######################### #to think about @@ -20,36 +18,36 @@ #' @description Functions that provide (re)SPECIATE #' source information. -#' \code{sp_info} generates a brief version report for the currently installed +#' \code{rsp_info} generates a brief version report for the currently installed #' (re)SPECIATE data sets. -#' \code{sp_profile_info} searches the currently installed (re)SPECIATE +#' \code{rsp_profile_info} searches the currently installed (re)SPECIATE #' data sets for profile records. -#' \code{sp_species_info} searches the currently installed (re)SPECIATE +#' \code{rsp_species_info} searches the currently installed (re)SPECIATE #' data sets for species records. #' @param ... character(s), any search term(s) to use when searching #' the local (re)SPECIATE archive for relevant records using -#' \code{sp_profile_info} or \code{sp_species_info}. +#' \code{rsp_profile_info} or \code{rsp_species_info}. #' @param by character, the section of the archive to -#' search, by default \code{'keywords'} for \code{sp_profile_info} and +#' search, by default \code{'keywords'} for \code{rsp_profile_info} and #' \code{'species_names'} for \code{sp_species_info}. #' @param partial logical, if \code{TRUE} (default) -#' \code{sp_profile_info} or \code{sp_profile_info} use partial matching. +#' \code{rsp_profile_info} or \code{rsp_profile_info} use partial matching. -#' @return \code{sp_info} provides a brief version information report on the +#' @return \code{rsp_info} provides a brief version information report on the #' currently installed (re)SPECIATE archive. -#' @return \code{sp_profile_info} returns a \code{data.frame} of +#' @return \code{rsp_profile_info} returns a \code{data.frame} of #' profile information, as a \code{respeciate} object. -#' \code{sp_species_info} returns a \code{data.frame} of +#' \code{rsp_species_info} returns a \code{data.frame} of #' species information as a \code{respeciate} object. #' @examples \dontrun{ #' profile <- "Ethanol" -#' pr <- sp_find_profile(profile) +#' pr <- rsp_find_profile(profile) #' pr #' #' species <- "Ethanol" -#' sp <- sp_find_species(species) +#' sp <- rsp_find_species(species) #' sp} #' @@ -58,7 +56,7 @@ ####################### -#sp_info +#rsp_info ####################### # tidy output??? @@ -70,10 +68,10 @@ # this is not currently catchable!!!! # a <- sp_info() #a = NULL -#' @rdname sp.info +#' @rdname rsp.info #' @export -sp_info <- function() { +rsp_info <- function() { #extract profile info from archive .ver <- "source: SPECIATE 5.2\n\t[in (re)SPECIATE since 0.2.0]" .ver <- paste(.ver, "\n\t[now (re)SPECIATE ", packageVersion("respeciate"), "]", sep="") @@ -83,10 +81,10 @@ sp_info <- function() { } -#' @rdname sp.info +#' @rdname rsp.info #' @export -sp_profile_info <- function(..., by = "keywords", partial = TRUE) { +rsp_profile_info <- function(..., by = "keywords", partial = TRUE) { #extract profile info from archive out <- sysdata$PROFILES terms <- c(...) @@ -112,7 +110,7 @@ sp_profile_info <- function(..., by = "keywords", partial = TRUE) { species <- sysdata$SPECIES ref <- out$PROFILE_CODE for(ti in terms){ - ans <- sp_find_species(ti, by=by, partial=partial) + ans <- rsp_species_info(ti, by=by, partial=partial) terms <- species$PROFILE_CODE[species$SPECIES_ID %in% ans$SPECIES_ID] ref <- ref[ref %in% terms] } @@ -129,23 +127,24 @@ sp_profile_info <- function(..., by = "keywords", partial = TRUE) { } } } - out <- rsp_build_respeciate(out) + out <- .rsp_build_respeciate(out) + class(out) <- unique(c("rsp_pi", class(out))) return(out) } -#' @rdname sp.info +#' @rdname rsp.info #' @export #wrapper for above -sp_find_profile <- function(...){ - sp_profile_info(...) +rsp_find_profile <- function(...){ + rsp_profile_info(...) } -#' @rdname sp.info +#' @rdname rsp.info #' @export -sp_species_info <- function(..., by = "species_name", partial = TRUE) { +rsp_species_info <- function(..., by = "species_name", partial = TRUE) { #extract species info from archive out <- sysdata$SPECIES_PROPERTIES terms <- c(...) @@ -160,15 +159,16 @@ sp_species_info <- function(..., by = "species_name", partial = TRUE) { } } #out <- PROFILES[grep(term, PROFILES[[by]], ignore.case = TRUE), ] - out <- rsp_build_respeciate(out) + out <- .rsp_build_respeciate(out) + class(out) <- unique(c("rsp_si", class(out))) return(out) } -#' @rdname sp.info +#' @rdname rsp.info #' @export #wrapper for above -sp_find_species <- function(...){ - sp_species_info(...) +rsp_find_species <- function(...){ + rsp_species_info(...) } diff --git a/R/sp.plot.R b/R/rsp.plot.R similarity index 93% rename from R/sp.plot.R rename to R/rsp.plot.R index d555fcf..c70b518 100644 --- a/R/sp.plot.R +++ b/R/rsp.plot.R @@ -1,18 +1,18 @@ -#' @name sp.plot +#' @name rsp.plot #' @title plotting (re)SPECIATE profiles -#' @aliases sp_plot_profile sp_plot_species +#' @aliases rsp_plot_profile rsp_plot_species #' @description General plots for \code{respeciate} objects. -#' @description \code{sp_plot} functions generate plots for supplied +#' @description \code{rsp_plot} functions generate plots for supplied #' (re)SPECIATE data sets. -#' @param x A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. #' @param id numeric, the indices of profiles or species to use when -#' plotting with \code{sp_plot_profile} or \code{sp_plot_species}, -#' respectively. For example, \code{sp_plot_profile(x, id=1:6)} plots -#' first 6 profiles in \code{respeciate} object \code{x}. -#' @param multi.profile character, how \code{sp_plot_profile} should +#' plotting with \code{rsp_plot_profile} or \code{rsp_plot_species}, +#' respectively. For example, \code{rsp_plot_profile(rsp, id=1:6)} plots +#' first 6 profiles in \code{respeciate} object \code{rsp}. +#' @param multi.profile character, how \code{rsp_plot_profile} should #' handle multiple profiles, e.g. 'group' or 'panel' (default #' group). #' @param order logical, order the species in the @@ -43,14 +43,14 @@ #functions -# sp_plot_profile -# sp_plot_species +# rsp_plot_profile +# rsp_plot_species -# plot.respeciate is wrapper for sp_plot_profile +# plot.respeciate is wrapper for rsp_plot_profile #uses unexported code -# rsp_plot_fix -# rsp_yscale.component.log10 (currently in sp.pls.r) +# .rsp_plot_fix +# .rsp_yscale.component.log10 (currently in sp.pls.r) @@ -97,7 +97,7 @@ #sp_plot_profile ################################### -#' @rdname sp.plot +#' @rdname rsp.plot #' @export # now done in xxx.r @@ -107,12 +107,12 @@ #moved this to lattice for paneling option ############################ -#using rsp_plot_fix for warning/handling for +#using .rsp_plot_fix for warning/handling for # duplicate species in profiles (handling merge/mean) # duplicated profile names (handling make unique) ############################# -#using rsp_test_profile +#using .rsp_test_profile # when ordering... #see in code notes about jobs @@ -126,11 +126,12 @@ # what is x, how is it formatted, etc # then same for y, groups and cond... -sp_plot_profile <- function(x, id, multi.profile = "group", +rsp_plot_profile <- function(rsp, id, multi.profile = "group", order=TRUE, log=FALSE, ..., silent=FALSE){ #setup + x <- rsp ## this needs sorting... .x.args <- list(...) #currently not even trying to stack logs... @@ -177,7 +178,7 @@ sp_plot_profile <- function(x, id, multi.profile = "group", #check for duplicates, etc... #tidy naming etc... - x <- rsp_plot_fix(x, silent=silent, ...) + x <- .rsp_plot_fix(x, silent=silent, ...) ##test something to plot if(nrow(x)==0){ @@ -207,7 +208,7 @@ sp_plot_profile <- function(x, id, multi.profile = "group", ################################ test <- x test$PROFILE_CODE <- ".default" - test <- rsp_test_profile(test) + test <- .rsp_test_profile(test) #previous barplot had bedside if("stack" %in% names(.x.args) && .x.args$stack){ test <- test[order(test$.total, decreasing = TRUE),] @@ -315,7 +316,7 @@ sp_plot_profile <- function(x, id, multi.profile = "group", #like to track border as well as col... #want to add box behind legend when key in plot... if("groups" %in% names(p1.ls)){ - .tmp <- list(space="right", + .tmp <- list(space="top", #title="Legends", rectangles=list(col=rep(p1.ls$col, length.out=length(profile))), @@ -335,7 +336,7 @@ sp_plot_profile <- function(x, id, multi.profile = "group", #sp_plot_species ################################### -#' @rdname sp.plot +#' @rdname rsp.plot #' @export #in development @@ -343,11 +344,12 @@ sp_plot_profile <- function(x, id, multi.profile = "group", #lot taken straight from sp_plot_profile #so lots of redundancy -sp_plot_species <- function(x, id, multi.species = "group", +rsp_plot_species <- function(rsp, id, multi.species = "group", order = FALSE, log = FALSE, ..., silent = FALSE){ #setup + x <- rsp ## this needs sorting... .x.args <- list(...) ###################################### @@ -404,7 +406,7 @@ sp_plot_species <- function(x, id, multi.species = "group", #check for duplicates, etc... #tidy naming etc... - x <- rsp_plot_fix(x, silent=silent, ...) + x <- .rsp_plot_fix(x, silent=silent, ...) ##test something to plot if(nrow(x)==0){ @@ -449,7 +451,7 @@ sp_plot_species <- function(x, id, multi.species = "group", ################################ test <- x test$PROFILE_CODE <- ".default" - test <- rsp_test_profile(test) + test <- .rsp_test_profile(test) #previous barplot had bedside if("stack" %in% names(.x.args) && .x.args$stack){ test <- test[order(test$.total, decreasing = TRUE),] diff --git a/R/sp.reshape.R b/R/rsp.reshape.R similarity index 85% rename from R/sp.reshape.R rename to R/rsp.reshape.R index 490f4d3..b4dc9ff 100644 --- a/R/sp.reshape.R +++ b/R/rsp.reshape.R @@ -1,21 +1,21 @@ -#' @name sp.reshape +#' @name rsp.reshape #' @title (re)SPECIATE profile reshaping functions -#' @aliases sp_dcast sp_dcast_profile sp_dcast_species sp_melt_wide +#' @aliases rsp_dcast rsp_dcast_profile rsp_dcast_species rsp_melt_wide #' @description Functions for reshaping (re)SPECIATE profiles -#' @description \code{sp_dcast} and \code{sp_melt_wide} reshape supplied -#' (re)SPECIATE profile(s). \code{sp_dcast} converts these from their supplied +#' @description \code{rsp_dcast} and \code{rsp_melt_wide} reshape supplied +#' (re)SPECIATE profile(s). \code{rsp_dcast} converts these from their supplied #' long form to a widened form, \code{dcast}ing the data set by either species #' or profiles depending on the \code{widen} setting applied. -#' \code{sp_dcast_profile} and \code{sp_dcast_species} are wrappers for these -#' options. \code{sp_melt_wide} attempts to return a previously widened data +#' \code{rsp_dcast_profile} and \code{rsp_dcast_species} are wrappers for these +#' options. \code{rsp_melt_wide} attempts to return a previously widened data #' set to the original long form. -#' @param x A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) -#' profiles in standard long form or widened form for -#' \code{\link{sp_dcast}} and \code{\link{sp_melt_wide}}, respectively. -#' @param widen character, when widening \code{x} with -#' \code{\link{sp_dcast}}, the data type to \code{dcast}, +#' @param rsp A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' profiles in standard long form or widened form using +#' \code{\link{rsp_dcast}} and \code{\link{rsp_melt_wide}}, respectively. +#' @param widen character, when widening \code{rsp} with +#' \code{\link{rsp_dcast}}, the data type to \code{dcast}, #' currently \code{'species'} (default) or \code{'profile'}. See Note. #' @param pad logical or character, when \code{melt}ing a previously widened #' data set, should output be re-populated with species and/or profile @@ -30,14 +30,14 @@ #' attempt account for that when working with standard re(SPECIATE) #' profiles. It is, however, sometimes useful to check first, e.g. when #' building profiles yourself. -#' @return \code{sp_dcast} returns the wide form of the supplied -#' \code{respeciate} profile. \code{sp_melt_wide} +#' @return \code{rsp_dcast} returns the wide form of the supplied +#' \code{respeciate} profile. \code{rsp_melt_wide} #' returns the (standard) long form of a previously widened profile. #' @note Conventional long-to-wide reshaping of data, or \code{dcast}ing, can #' be slow and memory inefficient. So, \code{respeciate} uses the #' \code{\link[data.table:dcast]{data.table::dcast}} -#' method. The \code{sp_dcast_species} method, +#' method. The \code{rsp_dcast_species} method, #' applied using \code{widen='species'}, is effectively: #' #' \code{dcast(..., PROFILE_CODE+PROFILE_NAME~SPECIES_NAME, value.var="WEIGHT_PERCENT")} @@ -56,7 +56,7 @@ #NOTE -#' @rdname sp.reshape +#' @rdname rsp.reshape #' @export ## now imports from xxx.r @@ -74,7 +74,7 @@ #long_to_wide reshape ###################### -sp_dcast <- function(x, widen = "species"){ +rsp_dcast <- function(rsp, widen = "species"){ #################### #see ?data.table::dcast for examples @@ -92,7 +92,7 @@ sp_dcast <- function(x, widen = "species"){ #adds .value if missing ## using .value rather the WEIGHT_PERCENT in case rescaled - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(rsp) #save class cls <- class(x) @@ -134,30 +134,34 @@ sp_dcast <- function(x, widen = "species"){ # could use an output arg??? as.is, data.frame, etc... out <- as.data.frame(out) - #class(out) <- cls + if(widen=="species"){ + class(out) <- c("rsp_sw", cls) + } else { + class(out) <- c("rsp_pw", cls) + } out } -#' @rdname sp.reshape +#' @rdname rsp.reshape #' @export -sp_dcast_profile <- function(x, widen = "profile"){ - sp_dcast(x=x, widen=widen) +rsp_dcast_profile <- function(rsp, widen = "profile"){ + rsp_dcast(rsp=rsp, widen=widen) } -#' @rdname sp.reshape +#' @rdname rsp.reshape #' @export -sp_dcast_species <- function(x, widen = "species"){ - sp_dcast(x=x, widen=widen) +rsp_dcast_species <- function(rsp=rsp, widen = "species"){ + rsp_dcast(rsp=rsp, widen=widen) } -#' @rdname sp.reshape +#' @rdname rsp.reshape #' @export ## now imports from xxx.r @@ -179,7 +183,7 @@ sp_dcast_species <- function(x, widen = "species"){ #wide_to_long reshape ###################### -sp_melt_wide <- function(x, pad = TRUE, drop.nas = TRUE){ +rsp_melt_wide <- function(rsp, pad = TRUE, drop.nas = TRUE){ #################### #see ?data.table::melt for examples @@ -187,7 +191,7 @@ sp_melt_wide <- function(x, pad = TRUE, drop.nas = TRUE){ #adds .value if missing ## using .value rather the WEIGHT_PERCENT in case rescaled - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(rsp) #save class cls <- class(x) @@ -207,7 +211,7 @@ sp_melt_wide <- function(x, pad = TRUE, drop.nas = TRUE){ .test.sp <- length(grep("PROFILE", .test)) .test.pr <- length(grep("SPECIES", .test)) if(.test.pr>0 & .test.sp>0){ - stop("sp_melt_wide halted; x already looks suspect.", call.=FALSE) + stop("rsp_melt_wide halted; x already looks suspect.", call.=FALSE) } .long <- "bad" if(.test.pr>0 & length(.test)==.test.pr){ @@ -219,7 +223,7 @@ sp_melt_wide <- function(x, pad = TRUE, drop.nas = TRUE){ .long <- "SPECIES_NAME" } if(.long=="bad"){ - stop("sp_melt_wide halted; x already looks suspect.", call.=FALSE) + stop("rsp_melt_wide halted; x already looks suspect.", call.=FALSE) } #should only be species.wide or profile.wide @@ -284,11 +288,13 @@ sp_melt_wide <- function(x, pad = TRUE, drop.nas = TRUE){ #if so, in else here?? } } - out <- as.data.frame(out) + #output + out <- as.data.frame(out) #need to rationalise outputs!!! - rsp_build_respeciate(out) - + #.rsp_build_respeciate(out) + class(out) <- cls[!cls %in% c("rsp_pw", "rsp_sw")] + out } diff --git a/R/sp.pad.R b/R/sp.pad.R index 97aeb84..5760b13 100644 --- a/R/sp.pad.R +++ b/R/sp.pad.R @@ -87,7 +87,7 @@ sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ #should argument within sp_pad be method?? #tidy x - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(x) #save class .cls <- class(x) out <- data.table::as.data.table(x) @@ -161,7 +161,7 @@ sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ # could return as input class # see notes out <- as.data.frame(out) - rsp_build_respeciate(out) + .rsp_build_respeciate(out) } diff --git a/R/xxx.R b/R/xxx.R index 66295d7..2029d5c 100644 --- a/R/xxx.R +++ b/R/xxx.R @@ -91,12 +91,12 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", -rsp_plot_fix <- function(x, silent = FALSE, ...){ +.rsp_plot_fix <- function(x, silent = FALSE, ...){ .x.args <- list(...) - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(x) ##test object type - test <- rsp_test_respeciate(x, level=2, silent=TRUE) + test <- .rsp_test_respeciate(x, level=2, silent=TRUE) if(test != "respeciate"){ if(test %in% c("respeciate.profile.ref", "respeciate.species.ref")){ stop("RSP> No plot method for respeciate.reference files.", @@ -108,20 +108,20 @@ rsp_plot_fix <- function(x, silent = FALSE, ...){ #don't stop - respeciate profile } #check for duplicates - x <- rsp_test_profile(x) + x <- .rsp_test_profile(x) if(any(x$.n>1) & !silent){ warning(paste("RSP> found duplicate species in profiles (merged and averaged...)", sep=""), call.=FALSE) } #shorten names for plotting - x$SPECIES_NAME <- rsp_tidy_species_name(x$SPECIES_NAME) + x$SPECIES_NAME <- .rsp_tidy_species_name(x$SPECIES_NAME) #################################### #issue profile names are not always unique #################################### test <- x test$SPECIES_ID <- ".default" - test <- rsp_test_profile(test) + test <- .rsp_test_profile(test) ################### #rep_test #can now replace this with data.table version @@ -167,7 +167,7 @@ rsp_plot_fix <- function(x, silent = FALSE, ...){ ## could also test for .value -rsp_test_respeciate <- function(x, level = 1, +.rsp_test_respeciate <- function(x, level = 1, silent = FALSE){ test <- class(x) out <- "bad" @@ -209,7 +209,7 @@ rsp_test_respeciate <- function(x, level = 1, ## enabled in plot.respeciate, sp_profile_rescale, sp_profile_dcast ## rsp_test_profile -rsp_tidy_profile <- function(x){ +.rsp_tidy_profile <- function(x){ #.value is local version of weight if(!".value" %in% names(x)){ x$.value <- x$WEIGHT_PERCENT @@ -237,7 +237,7 @@ rsp_tidy_profile <- function(x){ # option foreshorten any names longer than [n] characters??? # similar function to tidy profile names -rsp_tidy_species_name <- function(x){ +.rsp_tidy_species_name <- function(x){ #attempts shorten names by remove other versions #names seem to be in format a (or b || c) @@ -267,10 +267,10 @@ rsp_tidy_species_name <- function(x){ #file:///C:/Users/trakradmin/Downloads/datatable.pdf ##rsp_test_profile(aa) -rsp_test_profile <- function(x){ +.rsp_test_profile <- function(x){ #set up .value if not there - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(x) ####################################### #track and return original class? @@ -486,7 +486,7 @@ rsp_test_profile <- function(x){ -rsp_col_key <- function(key, cols, x, y = NULL, +.rsp_col_key <- function(key, cols, x, y = NULL, ticks, nticks, na.col = "grey", na.cex = 0.25, title = "", axes, bg, border, diff --git a/man/respeciate.generics.Rd b/man/respeciate.generics.Rd index a37960e..bc03961 100644 --- a/man/respeciate.generics.Rd +++ b/man/respeciate.generics.Rd @@ -14,7 +14,7 @@ as.respeciate(x, ...) \method{as.respeciate}{default}(x, ...) -\method{print}{respeciate}(x, n = NULL, ...) +\method{print}{respeciate}(x, n = 6, ...) \method{print}{rsp_pls}(x, n = NULL, ...) @@ -39,7 +39,7 @@ maximum number of profiles to report.} When supplied a \code{data.frame} or similar, \code{\link{as.respeciate}} attempts to coerce it into a -\code{respeciate} objects. +\code{respeciate} object. When supplied a \code{respeciate} object or similar, \code{\link{print}} manages its appearance. diff --git a/man/rsp.Rd b/man/rsp.Rd new file mode 100644 index 0000000..e0c1e78 --- /dev/null +++ b/man/rsp.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rsp.R +\name{rsp} +\alias{rsp} +\alias{rsp_profile} +\title{rsp_profile} +\usage{ +rsp_profile(..., include.refs = FALSE) + +rsp(...) +} +\arguments{ +\item{...}{The function assumes all inputs (except \code{include.refs}) +are \code{SPECIES_CODE}s (the unique descriptor the EPA assigns to all +profiles in SPECIATE) or sources of profile information and requests these +form the local (re)SPECIATE archive. Typically, simple +objects like character and numeric vectors, as assumed to profile codes and +composite data-types like \code{respeciate} objects or \code{data.frame}, +are assumed to contain a named \code{PROFILE_CODE} column. All potential +profile codes are requested and unrecognized codes are ignored.} + +\item{include.refs}{logical, if profile reference information should be +included when extracting the requested profile(s) from the archive, default +\code{FALSE}.} +} +\value{ +\code{rsp_profile} or the short-hand \code{rsp} return an object of +\code{respeciate} class, a \code{data.frame} containing one or more profile +from the local (re)SPECIATE archive. +} +\description{ +Getting profile(s) from the R (re)SPECIATE archive +} +\note{ +The option \code{include.refs} adds profile source reference +information to the returned \code{respeciate} data set. The default option +is to not include these because some profiles have several associated +references and including these replicates records, once per reference. +\code{respeciate} code is written to handle this but if you are developing +own methods or code and include references in any profile build you may be +biasing some analyses in favor of those multiple-reference profile unless +you check and account such cases. +} +\examples{ +\dontrun{ +x <- rsp_profile(8833, 8850) +plot(x)} +} +\references{ +Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., +Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. +The development and uses of EPA SPECIATE database. +Atmospheric Pollution Research, 1(4), pp.196-206. +} diff --git a/man/rsp.info.Rd b/man/rsp.info.Rd new file mode 100644 index 0000000..acf71fe --- /dev/null +++ b/man/rsp.info.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rsp.info.R +\name{rsp.info} +\alias{rsp.info} +\alias{rsp_info} +\alias{rsp_profile_info} +\alias{rsp_species_info} +\alias{rsp_find_profile} +\alias{rsp_find_species} +\title{re(SPECIATE) information} +\usage{ +rsp_info() + +rsp_profile_info(..., by = "keywords", partial = TRUE) + +rsp_find_profile(...) + +rsp_species_info(..., by = "species_name", partial = TRUE) + +rsp_find_species(...) +} +\arguments{ +\item{...}{character(s), any search term(s) to use when searching +the local (re)SPECIATE archive for relevant records using +\code{rsp_profile_info} or \code{rsp_species_info}.} + +\item{by}{character, the section of the archive to +search, by default \code{'keywords'} for \code{rsp_profile_info} and +\code{'species_names'} for \code{sp_species_info}.} + +\item{partial}{logical, if \code{TRUE} (default) +\code{rsp_profile_info} or \code{rsp_profile_info} use partial matching.} +} +\value{ +\code{rsp_info} provides a brief version information report on the +currently installed (re)SPECIATE archive. + +\code{rsp_profile_info} returns a \code{data.frame} of +profile information, as a \code{respeciate} object. +\code{rsp_species_info} returns a \code{data.frame} of +species information as a \code{respeciate} object. +} +\description{ +Functions that provide (re)SPECIATE +source information. +\code{rsp_info} generates a brief version report for the currently installed +(re)SPECIATE data sets. +\code{rsp_profile_info} searches the currently installed (re)SPECIATE +data sets for profile records. +\code{rsp_species_info} searches the currently installed (re)SPECIATE +data sets for species records. +} +\examples{ +\dontrun{ +profile <- "Ethanol" +pr <- rsp_find_profile(profile) +pr + +species <- "Ethanol" +sp <- rsp_find_species(species) +sp} + +} diff --git a/man/sp.plot.Rd b/man/rsp.plot.Rd similarity index 75% rename from man/sp.plot.Rd rename to man/rsp.plot.Rd index dfcecd3..fefe2c3 100644 --- a/man/sp.plot.Rd +++ b/man/rsp.plot.Rd @@ -1,13 +1,13 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.plot.R -\name{sp.plot} -\alias{sp.plot} -\alias{sp_plot_profile} -\alias{sp_plot_species} +% Please edit documentation in R/rsp.plot.R +\name{rsp.plot} +\alias{rsp.plot} +\alias{rsp_plot_profile} +\alias{rsp_plot_species} \title{plotting (re)SPECIATE profiles} \usage{ -sp_plot_profile( - x, +rsp_plot_profile( + rsp, id, multi.profile = "group", order = TRUE, @@ -16,8 +16,8 @@ sp_plot_profile( silent = FALSE ) -sp_plot_species( - x, +rsp_plot_species( + rsp, id, multi.species = "group", order = FALSE, @@ -27,15 +27,15 @@ sp_plot_species( ) } \arguments{ -\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +\item{rsp}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) profiles.} \item{id}{numeric, the indices of profiles or species to use when -plotting with \code{sp_plot_profile} or \code{sp_plot_species}, -respectively. For example, \code{sp_plot_profile(x, id=1:6)} plots -first 6 profiles in \code{respeciate} object \code{x}.} +plotting with \code{rsp_plot_profile} or \code{rsp_plot_species}, +respectively. For example, \code{rsp_plot_profile(rsp, id=1:6)} plots +first 6 profiles in \code{respeciate} object \code{rsp}.} -\item{multi.profile}{character, how \code{sp_plot_profile} should +\item{multi.profile}{character, how \code{rsp_plot_profile} should handle multiple profiles, e.g. 'group' or 'panel' (default group).} @@ -59,7 +59,7 @@ plotting functions.} \description{ General plots for \code{respeciate} objects. -\code{sp_plot} functions generate plots for supplied +\code{rsp_plot} functions generate plots for supplied (re)SPECIATE data sets. } \note{ diff --git a/man/sp.reshape.Rd b/man/rsp.reshape.Rd similarity index 66% rename from man/sp.reshape.Rd rename to man/rsp.reshape.Rd index 01f472f..3394e09 100644 --- a/man/sp.reshape.Rd +++ b/man/rsp.reshape.Rd @@ -1,28 +1,28 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.reshape.R -\name{sp.reshape} -\alias{sp.reshape} -\alias{sp_dcast} -\alias{sp_dcast_profile} -\alias{sp_dcast_species} -\alias{sp_melt_wide} +% Please edit documentation in R/rsp.reshape.R +\name{rsp.reshape} +\alias{rsp.reshape} +\alias{rsp_dcast} +\alias{rsp_dcast_profile} +\alias{rsp_dcast_species} +\alias{rsp_melt_wide} \title{(re)SPECIATE profile reshaping functions} \usage{ -sp_dcast(x, widen = "species") +rsp_dcast(rsp, widen = "species") -sp_dcast_profile(x, widen = "profile") +rsp_dcast_profile(rsp, widen = "profile") -sp_dcast_species(x, widen = "species") +rsp_dcast_species(rsp = rsp, widen = "species") -sp_melt_wide(x, pad = TRUE, drop.nas = TRUE) +rsp_melt_wide(rsp, pad = TRUE, drop.nas = TRUE) } \arguments{ -\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) -profiles in standard long form or widened form for -\code{\link{sp_dcast}} and \code{\link{sp_melt_wide}}, respectively.} +\item{rsp}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +profiles in standard long form or widened form using +\code{\link{rsp_dcast}} and \code{\link{rsp_melt_wide}}, respectively.} -\item{widen}{character, when widening \code{x} with -\code{\link{sp_dcast}}, the data type to \code{dcast}, +\item{widen}{character, when widening \code{rsp} with +\code{\link{rsp_dcast}}, the data type to \code{dcast}, currently \code{'species'} (default) or \code{'profile'}. See Note.} \item{pad}{logical or character, when \code{melt}ing a previously widened @@ -41,26 +41,26 @@ profiles. It is, however, sometimes useful to check first, e.g. when building profiles yourself.} } \value{ -\code{sp_dcast} returns the wide form of the supplied -\code{respeciate} profile. \code{sp_melt_wide} +\code{rsp_dcast} returns the wide form of the supplied +\code{respeciate} profile. \code{rsp_melt_wide} returns the (standard) long form of a previously widened profile. } \description{ Functions for reshaping (re)SPECIATE profiles -\code{sp_dcast} and \code{sp_melt_wide} reshape supplied -(re)SPECIATE profile(s). \code{sp_dcast} converts these from their supplied +\code{rsp_dcast} and \code{rsp_melt_wide} reshape supplied +(re)SPECIATE profile(s). \code{rsp_dcast} converts these from their supplied long form to a widened form, \code{dcast}ing the data set by either species or profiles depending on the \code{widen} setting applied. -\code{sp_dcast_profile} and \code{sp_dcast_species} are wrappers for these -options. \code{sp_melt_wide} attempts to return a previously widened data +\code{rsp_dcast_profile} and \code{rsp_dcast_species} are wrappers for these +options. \code{rsp_melt_wide} attempts to return a previously widened data set to the original long form. } \note{ Conventional long-to-wide reshaping of data, or \code{dcast}ing, can be slow and memory inefficient. So, \code{respeciate} uses the \code{\link[data.table:dcast]{data.table::dcast}} -method. The \code{sp_dcast_species} method, +method. The \code{rsp_dcast_species} method, applied using \code{widen='species'}, is effectively: \code{dcast(..., PROFILE_CODE+PROFILE_NAME~SPECIES_NAME, value.var="WEIGHT_PERCENT")} diff --git a/man/sp.Rd b/man/sp.Rd deleted file mode 100644 index 69341b8..0000000 --- a/man/sp.Rd +++ /dev/null @@ -1,52 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.R -\name{sp} -\alias{sp} -\alias{sp_profile} -\title{sp_profile} -\usage{ -sp_profile(code, ..., include.refs = FALSE) -} -\arguments{ -\item{code}{character, numeric or data.frame, the SPECIATE code -of the required profile (EPA SPECIATE identifier PROFILE_CODE). This is -typically one or concatenated character or numeric entries, but can also -be a \code{respeciate} object or similar \code{data.frame} containing -the \code{code}s as a named \code{PROFILE_NAME} column.} - -\item{...}{additional arguments, ignored except by \code{sp_profile} which -treats these as additional sources for \code{code}.} - -\item{include.refs}{logical, (for \code{sp_profile} only) include profile -reference information when getting the requested profile(s) from the -archive, default \code{FALSE}.} -} -\value{ -\code{sp_profile} returns a object of -\code{respeciate} class, a \code{data.frame} containing a -(re)SPECIATE profile. -} -\description{ -sp function to get profile(s) from the R (re)SPECIATE archive -} -\note{ -The option \code{include.refs} adds profile source reference -information to the returned \code{respeciate} data set. The default option -is to not include these because some profiles have several associated -references and including these replicates records, once per reference. -\code{respeciate} code is written to handle this but if you are developing -own methods or code and include references in any profile build you may be -biasing some analyses in favor of those multiple-reference profile unless -you check and account such cases. -} -\examples{ -\dontrun{ -x <- sp_profile(c(8833, 8850)) -plot(x)} -} -\references{ -Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., -Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. -The development and uses of EPA SPECIATE database. -Atmospheric Pollution Research, 1(4), pp.196-206. -} diff --git a/man/sp.build.Rd b/man/sp.build.Rd index 06095e6..135bd73 100644 --- a/man/sp.build.Rd +++ b/man/sp.build.Rd @@ -1,11 +1,11 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.build.R -\name{sp.build} -\alias{sp.build} -\alias{sp_build_rsp_x} -\title{sp_build functions} +% Please edit documentation in R/rsp.build.R +\name{rsp.build} +\alias{rsp.build} +\alias{rsp_build_x} +\title{Building respeciate-like Objects} \usage{ -sp_build_rsp_x( +rsp_build_x( x, profile_code, profile_name, @@ -35,11 +35,11 @@ containing measurement values. If not already named according to SPECIATE conventions, this will need to be assigned.} } \value{ -\code{sp_build}s attempt to build and return a (re)SPECIATE-like -profile that can be compared with data in re(SPECIATE). +\code{rsp_build}s attempt to build and return a (re)SPECIATE-like +object that can be compared with data from re(SPECIATE). } \description{ -sp function(s) to reconfigure data.frames (and similar +rsp function(s) to reconfigure data.frames (and similar object classes) for use with data and functions in re(SPECIATE). } \note{ @@ -48,7 +48,7 @@ you need to use EPA SPECIATE conventions when assigning species names and identifiers. Currently, we are working on options to improve on this (and very happy to discuss if anyone has ideas), but current best suggestion is: (1) identify the SPECIATE species code for each of the species in your data set, -and (2) assign these as \code{species_id} when \code{sp_build}ing. The +and (2) assign these as \code{species_id} when \code{rsp_build}ing. The function will then associate the \code{species_name} from SPECIATE species records. } diff --git a/man/sp.info.Rd b/man/sp.info.Rd deleted file mode 100644 index 8126699..0000000 --- a/man/sp.info.Rd +++ /dev/null @@ -1,63 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.info.R -\name{sp.info} -\alias{sp.info} -\alias{sp_info} -\alias{sp_profile_info} -\alias{sp_species_info} -\alias{sp_find_profile} -\alias{sp_find_species} -\title{re(SPECIATE) information} -\usage{ -sp_info() - -sp_profile_info(..., by = "keywords", partial = TRUE) - -sp_find_profile(...) - -sp_species_info(..., by = "species_name", partial = TRUE) - -sp_find_species(...) -} -\arguments{ -\item{...}{character(s), any search term(s) to use when searching -the local (re)SPECIATE archive for relevant records using -\code{sp_profile_info} or \code{sp_species_info}.} - -\item{by}{character, the section of the archive to -search, by default \code{'keywords'} for \code{sp_profile_info} and -\code{'species_names'} for \code{sp_species_info}.} - -\item{partial}{logical, if \code{TRUE} (default) -\code{sp_profile_info} or \code{sp_profile_info} use partial matching.} -} -\value{ -\code{sp_info} provides a brief version information report on the -currently installed (re)SPECIATE archive. - -\code{sp_profile_info} returns a \code{data.frame} of -profile information, as a \code{respeciate} object. -\code{sp_species_info} returns a \code{data.frame} of -species information as a \code{respeciate} object. -} -\description{ -Functions that provide (re)SPECIATE -source information. -\code{sp_info} generates a brief version report for the currently installed -(re)SPECIATE data sets. -\code{sp_profile_info} searches the currently installed (re)SPECIATE -data sets for profile records. -\code{sp_species_info} searches the currently installed (re)SPECIATE -data sets for species records. -} -\examples{ -\dontrun{ -profile <- "Ethanol" -pr <- sp_find_profile(profile) -pr - -species <- "Ethanol" -sp <- sp_find_species(species) -sp} - -} From 36eac441f17c8fdd2e2a9412bc1496e9f636b9c0 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Tue, 7 May 2024 16:46:16 +0100 Subject: [PATCH 04/18] rsp.pad update --- .Rproj.user/shared/notebooks/paths | 5 +- NAMESPACE | 2 +- R/respeciate.generics.R | 265 ++++------------------------- R/rsp.R | 2 +- R/{sp.pad.R => rsp.pad.R} | 64 ++++--- R/rsp.plot.R | 12 +- R/rsp.reshape.R | 89 ++++++---- man/rsp.pad.Rd | 40 +++++ man/rsp.reshape.Rd | 4 +- man/sp.pad.Rd | 40 ----- 10 files changed, 178 insertions(+), 345 deletions(-) rename R/{sp.pad.R => rsp.pad.R} (77%) create mode 100644 man/rsp.pad.Rd delete mode 100644 man/sp.pad.Rd diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 88ed5f3..5b921db 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -1,4 +1,5 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/ITS/projects/IPSOS/_______________latest/dts/banes/_techNote_IPSOSDefra_DTTrend_BANES_working_04.Rmd="EF13DF93" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/ITS/projects/Leeds_CPRPC/_working/climate_rough_01.Rmd="59DA471D" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/ITS/projects/NERC_TRANSISTION/events/Clean Air Networks Conference/naei estimate.Rmd="1BB48545" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/_isolateContribution&breakPointAnalysis_KR_20230824.R="F18B98A3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/_projects/_paper_01_IntroToRespeciate/MS Access Versions/speciate_5.2_0/test.R="FCE2E494" @@ -18,15 +19,15 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.R="787EA0C5" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.build.R="5A264727" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.info.R="FD1BAD48" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pad.R="FEC8C57D" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.reshape.R="94C8EF32" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.average.R="0A1E36E4" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cluster.R="49C0F861" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cor.R="3F1DA8E5" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.match.R="4326C1C3" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.pad.R="4962C940" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.pls.R="EACFE9A4" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.rescale.R="D668C5B2" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.reshape.R="CC655C60" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spq.R="3AD9ACC8" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spx.R="CA18044A" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sysdata.R="82103C52" diff --git a/NAMESPACE b/NAMESPACE index e288c19..8a2744f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -23,6 +23,7 @@ export(rsp_find_profile) export(rsp_find_species) export(rsp_info) export(rsp_melt_wide) +export(rsp_pad) export(rsp_plot_profile) export(rsp_plot_species) export(rsp_profile) @@ -30,7 +31,6 @@ export(rsp_profile_info) export(rsp_species_info) export(sp_average_profile) export(sp_match_profile) -export(sp_pad) export(sp_pls_profile) export(sp_profile_distance) export(sp_rescale) diff --git a/R/respeciate.generics.R b/R/respeciate.generics.R index bfbdf8d..8a4f7b8 100644 --- a/R/respeciate.generics.R +++ b/R/respeciate.generics.R @@ -3,7 +3,7 @@ #' @description \code{respeciate} object classes and generic functions. ######################## -#might move all the text to top +#might move all the @description to top # hard to keep style consistent when docs are in between # multiple functions @@ -33,6 +33,11 @@ #' capacity... +################################## +# using +################################## + +# TO DO... ################################# @@ -79,7 +84,7 @@ as.respeciate.default <- function(x, ...){ .test <- c("PROFILE_NAME", "PROFILE_CODE", "SPECIES_NAME", "SPECIES_ID", ".value", "WEIGHT_PERCENT") .test <- .test[!.test %in% names(.try)] - if(length(.test)>0){ + if(length(.test)>0){x stop("as.respeciate> bad data structure, expected column(s) missing/unassigned:\n", paste(.test, sep="", collapse = ", "), "\n", sep="", call.=FALSE) } @@ -91,7 +96,7 @@ as.respeciate.default <- function(x, ...){ } #output - class(.try) <- c("respeciate", class(.try)) + class(.try) <- unique(c("respeciate", class(.try))) .try } @@ -164,33 +169,29 @@ print.respeciate <- #to do #################################### #handling for wide frames - #melt .x and carry on... - # could include a warning...? - # how to handle if profile_code is missing??? - # or profile_name ???? - # or species_name or species_id ??? + # needs testing - rsp.rep <- "respeciate profile(s):" + rsp.rep <- "respeciate" if(class(.x)[1] == "rsp_sw"){ - rsp.rep <- "respeciate (wide/species):" + rsp.rep <- paste(rsp.rep, " (wide/species)", sep="") .x <- rsp_melt_wide(.x, pad=FALSE, drop.nas = TRUE) .x$SPECIES_ID <- .x$SPECIES_NAME } if(class(.x)[1] == "rsp_pw"){ - rsp.rep <- "respeciate (wide/profiles):" + rsp.rep <- paste(rsp.rep, " (wide/profile)", sep="") .x <- rsp_melt_wide(.x, pad=FALSE, drop.nas = TRUE) #.x$PROFILE_NAME <- .x$PROFILE_CODE } if(class(.x)[1] == "rsp_x"){ - rsp.rep <- "respeciate-like x:" + rsp.rep <- gsub("respeciate", "respeciate-like x", rsp.rep) class(.x) <- class(.x)[class(.x) != "rsp_x"] } #standard respeciate if(class(.x)[1] == "respeciate"){ .y <- unique(.x$PROFILE_CODE) - report <- paste(rsp.rep, " count ", + report <- paste(rsp.rep, ": count ", length(.y), "\n", sep="") if(length(.y)>0){ yy <- if(length(.y)>n) {.y[1:n]} else {.y} @@ -240,7 +241,11 @@ print.respeciate <- - +########################## +########################## +## DROP THIS ??? +########################## +########################## rsp_print.respeciate.old <- function(x, n = NULL, ...){ test <- .rsp_test_respeciate(x, level = 2, silent = TRUE) @@ -342,6 +347,12 @@ plot.respeciate <- function(x, ...){ #check below and then remove??? +########################## +########################## +## DROP THIS ??? +########################## +########################## + rsp_plot.respeciate.old <- function(x, n=NULL, id=NULL, order=TRUE, ...){ @@ -1084,225 +1095,6 @@ summary.respeciate <- # could make other respeciate print outputs # look like this? -######################################### -######################################## -## thinking about making this main print.respeciate -######################################### -######################################### - -#then removing all the other print.respeciate related functions ??? - -.rsp_p2_ <- - function(x, n=6, ...){ - - ################################ - #new general respeciate print method - .tmp <- getOption("width") - .x <- x - - #species info - if(class(.x)[1] == "rsp_si"){ - .y <- unique(.x$SPECIES_ID) - report <- paste("respeciate species list:", - length(.y), "\n", - "[NO PROFILES]", "\n", sep="") - if(length(.y)>0){ - yy <- if(length(.y)>n) {.y[1:n]} else {.y} - for(i in yy){ - .m1 <- paste(" (", "ID ", i, ") ", - subset(.x, SPECIES_ID == i)$SPECIES_NAME[1], - "\n", sep="") - if(nchar(.m1)>.tmp){ - .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") - } - report <- paste(report, .m1, sep="") - } - } - } - - #profile info - if(class(x)[1] == "rsp_pi"){ - .y <- unique(x$PROFILE_CODE) - report <- paste("respeciate profile list: ", - length(.y), "\n", - "[NO SPECIES]", "\n", sep="") - if(length(.y)>0){ - yy <- if(length(.y)>n) {.y[1:n]} else {.y} - for(i in yy){ - .m1 <- paste(" (", "CODE ", i, ") ", - subset(.x, PROFILE_CODE == i)$PROFILE_NAME[1], - "\n", sep="") - if(nchar(.m1)>.tmp){ - .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") - } - report <- paste(report, .m1, sep="") - } - } - } - - #################################### - #to do - #################################### - #handling for wide frames - #melt .x and carry on... - # could include a warning...? - # how to handle if profile_code is missing??? - # or profile_name ???? - # or species_name or species_id ??? - - - - #standard respeciate - if(class(.x)[1] == "respeciate"){ - .y <- unique(.x$PROFILE_CODE) - report <- paste("respeciate profile(s): count ", - length(.y), "\n", sep="") - if(length(.y)>0){ - yy <- if(length(.y)>n) {.y[1:n]} else {.y} - for(i in yy){ - if("PROFILE_NAME" %in% names(.x)){ - i2 <- .x$PROFILE_NAME[.x$PROFILE_CODE==i][1] - } else { - i2 <- "[unknown]" - } - if("SPECIES_ID" %in% names(.x)){ - .spe <- length(unique(.x$SPECIES_ID[.x$PROFILE_CODE==i])) - } else { - .spe <- "0!" - } - .m1 <- paste(" ", i, " (", .spe, " species) ", - i2, "\n", sep="") - if(nchar(.m1)>.tmp){ - .m1 <- paste(substring(.m1, 1, .tmp-3), "...\n") - } - report <- paste(report, .m1, sep="") - } - } - } - - #cat output - if(length(.y)<1){ - cat(paste(report, - "empty (or bad?) respeciate object\n", - sep="")) - } else { - if(length(.y)>n){ - #rather no showing last...??? - report <- paste(report, - " > showing ", n, " of ", length(.y), - sep="") - } - cat(report) - } - - #return x (not .x) - return(invisible(x)) - } - - - - -.rsp_print_respeciate <- - function(x, n=6, ...){ - ####################################### - #profile_code is (I think) only term unique to a profile - y <- unique(x$PROFILE_CODE) - report <- paste("respeciate profile(s): count ", - length(y), "\n", sep="") - if(length(y)==0){ - cat(paste(report, - "empty (or bad?) respeciate object\n", - sep="")) - return(invisible(x)) - } - .tmp <- getOption("width") - yy <- if(length(y)>n) {y[1:n]} else {y} - for(i in yy){ - if("PROFILE_NAME" %in% names(x)){ - i2 <- x$PROFILE_NAME[x$PROFILE_CODE==i][1] - } else { - i2 <- "[unknown]" - } - if("SPECIES_ID" %in% names(x)){ - .spe <- length(unique(x$SPECIES_ID[x$PROFILE_CODE==i])) - } else { - .spe <- "0!" - } - .msg <- paste(" ", i, " (", .spe, " species) ", - i2, "\n", sep="") - if(nchar(.msg)>.tmp){ - .msg <- paste(substring(.msg, 1, .tmp-3), "...\n") - } - report <- paste(report, .msg, sep="") - } - if(length(y)>n){ - report <- paste(report, " ... not showing last ", - length(y)-n, "\n", sep="") - } - cat(report, sep="") - invisible(x) - } - - -## #' @description When supplied a \code{respeciate.ref} -# #' object, \code{\link{print}} manages its appearance. -# #' @param x the \code{respeciate} or \code{respeciate.ref} -# #' object to be printed, plotted, etc. -# #' @rdname respeciate.generics -# #' @method print respeciate.ref -# #' @export -.rsp_print_respeciate_profile <- - function(x, n = 100, ...){ - xx <- nrow(x) - wi <- getOption("width") - #################################### - #use of cat might need rethinking? - #################################### - cat("respeciate profile reference\n") - if(n>xx){ - n <- xx - } - if(xx>n){ - report <- c(x$PROFILE_CODE[1:n], "...") - comment <- paste(" profiles [showing first ", n, - "]\n", sep = "") - } else { - report <- x$PROFILE_CODE - comment <- " profiles\n" - } - if(xx>0) cat(report, fill=wi) - cat(" > ", xx, comment, sep="") - invisible(x) - } - - -# #' @rdname respeciate.generics -# #' @method print respeciate.spcs -# #' @export -.rsp_print_respeciate_species <- - function(x, n = 10, ...){ - xx <- nrow(x) - wi <- getOption("width") - #################################### - #use of cat might need rethinking? - #################################### - cat("respeciate species reference\n") - if(n>xx){ - n <- xx - } - if(xx>n){ - report <- c(x$SPECIES_NAME[1:n], "...") - comment <- paste(" species [showing first ", n, - "]\n", sep="") - } else { - report <- x$SPECIES_NAME - comment <- " species\n" - } - if(xx>0) cat(report, fill=wi) - cat(" > ", xx, comment, sep="") - invisible(x) - } - @@ -1323,6 +1115,13 @@ summary.respeciate <- #now replacing previous plot.respeciate + +########################## +########################## +## DROP THIS ??? +########################## +########################## + plot.respeciate.old <- function(x, n=NULL, order=TRUE, ..., legend.text=NULL, diff --git a/R/rsp.R b/R/rsp.R index f025696..0872276 100644 --- a/R/rsp.R +++ b/R/rsp.R @@ -148,7 +148,7 @@ rsp_profile <- function(..., include.refs=FALSE) { #add .value if weight_percent to copy... x <- as.data.frame(dt) - if("WEIGHT_PERCENT" %in% names(x)) { + if("WEIGHT_PERCENT" %in% names(x) & !".value" %in% names(x)) { x$.value <- x$WEIGHT_PERCENT } diff --git a/R/sp.pad.R b/R/rsp.pad.R similarity index 77% rename from R/sp.pad.R rename to R/rsp.pad.R index 5760b13..5a29d13 100644 --- a/R/sp.pad.R +++ b/R/rsp.pad.R @@ -1,32 +1,32 @@ -#' @name sp.pad +#' @name rsp.pad #' @title (re)SPECIATE profile padding functions -#' @aliases sp_pad +#' @aliases rsp_pad #' @description Functions for padding \code{respeciate} objects. -#' @description \code{sp_pad} pads a supplied (re)SPECIATE profile data set +#' @description \code{rsp_pad} pads a supplied (re)SPECIATE profile data set #' with profile and species meta-data. -#' @param x A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. #' @param pad character, type of meta data padding, current options -#' \code{'profile'}, \code{'species'}, \code{'weight'}, \code{reference}, +#' \code{'profile'}, \code{'species'}, \code{'weight'}, \code{'reference'}, #' \code{'standard'} (default; all but \code{'reference'}), and \code{'all'} -#' ('all'). +#' (all). #' @param drop.nas logical, discard any rows where \code{WEIGHT_PERCENT} is #' \code{NA}, default \code{TRUE}. -#' @return \code{sp_pad} returns \code{x}, with requested additional profile -#' and species meta-data added as additional \code{data.frame} columns. -#' See Note. +#' @return \code{rsp_pad} returns supplied \code{respeciate} data set, with +#' requested additional profile and species meta-data added as additional +#' \code{data.frame} columns. See Note. #' @note Some data handling can remove (re)SPECIATE meta-data, -#' and \code{sp_pad}s provide a quick rebuild/repair. For example, -#' \code{\link{sp_dcast}}ing to a (by-species or by-profile) widened +#' and \code{rsp_pad}s provide a quick rebuild/repair. For example, +#' \code{\link{rsp_dcast}}ing to a (by-species or by-profile) widened #' form strips some meta-data, and padding is used as part of the -#' \code{\link{sp_melt_wide}} and padding is used to re-add this meta-data +#' \code{\link{rsp_melt_wide}} to re-add this meta-data #' when returning the data set to its standard long form. #NOTE -#' @rdname sp.pad +#' @rdname rsp.pad #' @export ## #' @import data.table # now done in xxx.r @@ -80,14 +80,14 @@ #x <- sp_pad(c) -sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ - +rsp_pad <- function(rsp, pad = "standard", drop.nas = TRUE){ #should pad allow TRUE/FALSE??? #should argument within sp_pad be method?? - #tidy x - x <- .rsp_tidy_profile(x) + #tidy rsp + x <- .rsp_tidy_profile(rsp) + #save class .cls <- class(x) out <- data.table::as.data.table(x) @@ -97,7 +97,7 @@ sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ PROFILES <- data.table::as.data.table(sysdata$PROFILES) .tmp <- intersect(names(out), names(PROFILES)) if(length(.tmp)>0){ - out <- merge(out, PROFILES, by = .tmp, all.y=FALSE, + out <- data.table::merge.data.table(out, PROFILES, by = .tmp, all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) } } @@ -105,25 +105,28 @@ sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ #species if(any(c("species", "standard", "all") %in% tolower(pad))){ SPECIES_PROPERTIES <- data.table::as.data.table(sysdata$SPECIES_PROPERTIES) + SPECIES_PROPERTIES$SPECIES_ID <- as.character(SPECIES_PROPERTIES$SPECIES_ID) .tmp <- intersect(names(out), names(SPECIES_PROPERTIES)) - print(.tmp) if(length(.tmp) >0){ - out <- merge(out, SPECIES_PROPERTIES, by = .tmp, all.y=FALSE, + out <- data.table::merge.data.table(out, SPECIES_PROPERTIES, by = .tmp, all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) } } #species weights + # might not want to add weights if rsp_x??? if(any(c("weight", "weights", "standard", "all") %in% tolower(pad))){ SPECIES <- data.table::as.data.table(sysdata$SPECIES) + SPECIES$SPECIES_ID <- as.character(SPECIES$SPECIES_ID) .tmp <- intersect(names(out), names(SPECIES)) if(length(.tmp) >0){ - out <- merge(out, SPECIES, by = .tmp, all.y=FALSE, + out <- data.table::merge.data.table(out, SPECIES, by = .tmp, all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) } } - - #return(out) + if(all(is.na(out$WEIGHT_PERCENT)) && ".value" %in% names(out)){ + out$WEIGHT_PERCENT <- out$.value + } #references if(any(c("reference", "references", "all") %in% tolower(pad))){ @@ -131,12 +134,12 @@ sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ REFERENCES <- data.table::as.data.table(sysdata$REFERENCES) .tmp <- intersect(names(out), names(PROFILE_REFERENCE)) if(length(.tmp) >0){ - out <- merge(out, PROFILE_REFERENCE, by = .tmp, all.y=FALSE, + out <- data.table::merge.data.table(out, PROFILE_REFERENCE, by = .tmp, all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) } .tmp <- intersect(names(out), names(REFERENCES)) if(length(.tmp) >0){ - out <- merge(out, REFERENCES, by = .tmp, all.y=FALSE, + out <- data.table::merge.data.table(out, REFERENCES, by = .tmp, all.y=FALSE, all.x=TRUE, allow.cartesian=TRUE) } } @@ -148,6 +151,7 @@ sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ # in the SPECIATE archive ################################################# #drop.nas. + if(drop.nas){ out <- out[!is.na(out$WEIGHT_PERCENT),] } @@ -161,7 +165,9 @@ sp_pad <- function(x, pad = "standard", drop.nas = TRUE){ # could return as input class # see notes out <- as.data.frame(out) - .rsp_build_respeciate(out) + #.rsp_build_respeciate(out) + class(out) <- .cls + out } @@ -225,6 +231,12 @@ sp_pad.old <- function(x, pad = "species", drop.nas = TRUE){ if(drop.nas){ out <- out[!is.na(out$WEIGHT_PERCENT),] } + ############################# + ##thinking about + ## removing all columns of just NAs... + # out[,which(unlist(lapply(out, function(x)!all(is.na(x))))), with=FALSE] + ## works if data.table object... + ############################ #not sure how to handle output... #see notes diff --git a/R/rsp.plot.R b/R/rsp.plot.R index c70b518..3fc431b 100644 --- a/R/rsp.plot.R +++ b/R/rsp.plot.R @@ -361,7 +361,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", ###################### #to do #document issues - stop("RSP> Sorry, currently not stacking logs.", + stop("RSP> Sorry, currently not stacking logs", call. = FALSE) } } @@ -465,6 +465,8 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", } x <- x[c(".value","PROFILE_CODE", "PROFILE_NAME", "SPECIES_NAME")] + #print(xx) + ################## #species trend line plot ################## @@ -472,7 +474,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", #dcast and melt to add in any missed entries as NAs #(to force trend line gaps) #not padding, obviously not dropping nas... - x <- sp_melt_wide(sp_dcast_species(x), pad=FALSE, drop.nas = FALSE) + x <- rsp_melt_wide(rsp_dcast_species(x), pad=FALSE, drop.nas = FALSE) ############################### #species handling @@ -595,11 +597,15 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", #could be issue here if user uses auto.key??? #like to track border as well as col... if("groups" %in% names(p1.ls)){ + #print(x$SPECIES_NAME) .tmp <- list(space="right", #title="Legends", lines=list(col=rep(p1.ls$col, length.out=length(species))), - text = list(levels(x$SPECIES_NAME), cex=0.7)) + ########################## + #text = list(levels(x$SPECIES_NAME), cex=0.7)) + text = list(species, cex=0.7)) + #changed from above because x$SPECIES_NAME p1.ls$key <- if("key" %in% names(p1.ls)){ modifyList(.tmp, p1.ls$key) } else { diff --git a/R/rsp.reshape.R b/R/rsp.reshape.R index b4dc9ff..7078f30 100644 --- a/R/rsp.reshape.R +++ b/R/rsp.reshape.R @@ -20,9 +20,9 @@ #' @param pad logical or character, when \code{melt}ing a previously widened #' data set, should output be re-populated with species and/or profile #' meta-data, discarded when widening. This is currently handled by -#' \code{\link{sp_pad}}. The default \code{TRUE} applies standard settings, +#' \code{\link{rsp_pad}}. The default \code{TRUE} applies standard settings, #' so does not include profile sources reference meta-data. (See -#' \code{\link{sp_pad}} for other options). +#' \code{\link{rsp_pad}} for other options). #' @param drop.nas logical, when \code{melt}ing a previously widened #' data set, should output be stripped of any rows containing empty #' weight/value columns. Because not all profile contains all species, the @@ -56,6 +56,18 @@ #NOTE +############################# +# these use +############################# +# .rsp_tidy_profile +# data.table::as.data.table +# data.table::dcast +# data.table::melt +# rsp_pad + + + + #' @rdname rsp.reshape #' @export @@ -206,12 +218,12 @@ rsp_melt_wide <- function(rsp, pad = TRUE, drop.nas = TRUE){ "WEIGHT_PERCENT", ".value") .test <- .test[.test %in% names(xx)] if(length(.test)>2){ - stop("sp_melt_wide halted; x already looks like a long profile.", call.=FALSE) + stop("RSP> melt halted; rsp already looks like a long profile.", call.=FALSE) } .test.sp <- length(grep("PROFILE", .test)) .test.pr <- length(grep("SPECIES", .test)) if(.test.pr>0 & .test.sp>0){ - stop("rsp_melt_wide halted; x already looks suspect.", call.=FALSE) + stop("RSP> melt halted; rsp looks looks suspect.", call.=FALSE) } .long <- "bad" if(.test.pr>0 & length(.test)==.test.pr){ @@ -223,7 +235,7 @@ rsp_melt_wide <- function(rsp, pad = TRUE, drop.nas = TRUE){ .long <- "SPECIES_NAME" } if(.long=="bad"){ - stop("rsp_melt_wide halted; x already looks suspect.", call.=FALSE) + stop("RSP> melt halted; rsp looks suspect.", call.=FALSE) } #should only be species.wide or profile.wide @@ -232,50 +244,51 @@ rsp_melt_wide <- function(rsp, pad = TRUE, drop.nas = TRUE){ out <- data.table::melt(xx, id.vars = .id.vars) names(out)[names(out)=="variable"] <- .long names(out)[names(out)=="value"] <- ".value" + if("SPECIES_ID" %in% names(out)){ + out$SPECIES_ID <- as.character(out$SPECIES_ID) + } + if("SPECIES_NAME" %in% names(out)){ + out$SPECIES_NAME <- as.character(out$SPECIES_NAME) + } + if("PROFILE_CODE" %in% names(out)){ + out$PROFILE_CODE <- as.character(out$PROFILE_CODE) + } #out$WEIGHT_PERCENT <- out$.value #merge if padding ##################### #might not be best way of doing it - #testing sp_pad as an alternative to previous remarked code??? - # first need to standardise method, decide where to drop.nas, - # finalise formals, decide best data.table methods, etc + # could pass other args to pad + # might need to think about the .value/WEIGHT_PERCENT handling if(is.logical(pad) && pad){ pad <- "standard" } if(is.character(pad)){ + out <- rsp_pad(out, pad, drop.nas) + #tidy bad profile_name + if(all(is.na(out$PROFILE_NAME)) && "PROFILE_CODE" %in% names(out)){ + out$PROFILE_NAME <- out$PROFILE_CODE + } + #tidy bad species_id + if(all(is.na(out$SPECIES_ID)) && "SPECIES_NAME" %in% names(out)){ + out$SPECIES_ID <- as.character(-as.numeric(factor(out$SPECIES_NAME))) + } - out <- sp_pad(out, pad) - -# PROFILES <- as.data.table(sysdata$PROFILES) -# SPECIES_PROPERTIES <- as.data.table(sysdata$SPECIES_PROPERTIES) -# if(.long=="PROFILE_CODE"){ -# out <- merge(out, PROFILES, by = .long, all.y=FALSE, -# all.x=TRUE, allow.cartesian=TRUE) -# .tmp <- intersect(names(out), names(SPECIES_PROPERTIES)) -# out <- merge(out, SPECIES_PROPERTIES, by = .tmp, all.y=FALSE, -# all.x=TRUE, allow.cartesian=TRUE) -# } else { -# #.long must be "SPECIES_NAME" -# out <- merge(out, SPECIES_PROPERTIES, by = .long, all.y=FALSE, -# all.x=TRUE, allow.cartesian=TRUE) -# .tmp <- intersect(names(out), names(PROFILES)) -# out <- merge(out, PROFILES, by = .tmp, all.y=FALSE, -# all.x=TRUE, allow.cartesian=TRUE) -# } -# #to get weight_percentage etc -# SPECIES <- as.data.table(sysdata$SPECIES) -# .tmp <- intersect(names(out), names(SPECIES)) -# print(.tmp) -# out <- merge(out, SPECIES, by = .tmp, all.y=FALSE, -# all.x=TRUE, allow.cartesian=TRUE) -# } else { -# #not great but... -# #if not padding WEIGHT_PERCENT has to be .value -# out$WEIGHT_PERCENT <- out$.value } + + ################################ + # could tidy structure here?? + ################################ + + # if weight_percent but not .value add .value + # if.value but not weight_percent add .value + # similar for profile_name/code and species_name/id + + # is that done in rsp_build_x ?? + + #drop.nas... if(drop.nas){ if(".value" %in% names(out)){ @@ -289,10 +302,12 @@ rsp_melt_wide <- function(rsp, pad = TRUE, drop.nas = TRUE){ } } + + #output - out <- as.data.frame(out) #need to rationalise outputs!!! #.rsp_build_respeciate(out) + out <- as.data.frame(out) class(out) <- cls[!cls %in% c("rsp_pw", "rsp_sw")] out } diff --git a/man/rsp.pad.Rd b/man/rsp.pad.Rd new file mode 100644 index 0000000..39b66c8 --- /dev/null +++ b/man/rsp.pad.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rsp.pad.R +\name{rsp.pad} +\alias{rsp.pad} +\alias{rsp_pad} +\title{(re)SPECIATE profile padding functions} +\usage{ +rsp_pad(rsp, pad = "standard", drop.nas = TRUE) +} +\arguments{ +\item{rsp}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +profiles.} + +\item{pad}{character, type of meta data padding, current options +\code{'profile'}, \code{'species'}, \code{'weight'}, \code{'reference'}, +\code{'standard'} (default; all but \code{'reference'}), and \code{'all'} +(all).} + +\item{drop.nas}{logical, discard any rows where \code{WEIGHT_PERCENT} is +\code{NA}, default \code{TRUE}.} +} +\value{ +\code{rsp_pad} returns supplied \code{respeciate} data set, with +requested additional profile and species meta-data added as additional +\code{data.frame} columns. See Note. +} +\description{ +Functions for padding \code{respeciate} objects. + +\code{rsp_pad} pads a supplied (re)SPECIATE profile data set +with profile and species meta-data. +} +\note{ +Some data handling can remove (re)SPECIATE meta-data, +and \code{rsp_pad}s provide a quick rebuild/repair. For example, +\code{\link{rsp_dcast}}ing to a (by-species or by-profile) widened +form strips some meta-data, and padding is used as part of the +\code{\link{rsp_melt_wide}} re-add this meta-data +when returning the data set to its standard long form. +} diff --git a/man/rsp.reshape.Rd b/man/rsp.reshape.Rd index 3394e09..831dc35 100644 --- a/man/rsp.reshape.Rd +++ b/man/rsp.reshape.Rd @@ -28,9 +28,9 @@ currently \code{'species'} (default) or \code{'profile'}. See Note.} \item{pad}{logical or character, when \code{melt}ing a previously widened data set, should output be re-populated with species and/or profile meta-data, discarded when widening. This is currently handled by -\code{\link{sp_pad}}. The default \code{TRUE} applies standard settings, +\code{\link{rsp_pad}}. The default \code{TRUE} applies standard settings, so does not include profile sources reference meta-data. (See -\code{\link{sp_pad}} for other options).} +\code{\link{rsp_pad}} for other options).} \item{drop.nas}{logical, when \code{melt}ing a previously widened data set, should output be stripped of any rows containing empty diff --git a/man/sp.pad.Rd b/man/sp.pad.Rd deleted file mode 100644 index ecdbb4e..0000000 --- a/man/sp.pad.Rd +++ /dev/null @@ -1,40 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.pad.R -\name{sp.pad} -\alias{sp.pad} -\alias{sp_pad} -\title{(re)SPECIATE profile padding functions} -\usage{ -sp_pad(x, pad = "standard", drop.nas = TRUE) -} -\arguments{ -\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) -profiles.} - -\item{pad}{character, type of meta data padding, current options -\code{'profile'}, \code{'species'}, \code{'weight'}, \code{reference}, -\code{'standard'} (default; all but \code{'reference'}), and \code{'all'} -('all').} - -\item{drop.nas}{logical, discard any rows where \code{WEIGHT_PERCENT} is -\code{NA}, default \code{TRUE}.} -} -\value{ -\code{sp_pad} returns \code{x}, with requested additional profile -and species meta-data added as additional \code{data.frame} columns. -See Note. -} -\description{ -Functions for padding \code{respeciate} objects. - -\code{sp_pad} pads a supplied (re)SPECIATE profile data set -with profile and species meta-data. -} -\note{ -Some data handling can remove (re)SPECIATE meta-data, -and \code{sp_pad}s provide a quick rebuild/repair. For example, -\code{\link{sp_dcast}}ing to a (by-species or by-profile) widened -form strips some meta-data, and padding is used as part of the -\code{\link{sp_melt_wide}} and padding is used to re-add this meta-data -when returning the data set to its standard long form. -} From f8068719a36861dd1c8188da9dbedf14a8303da0 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Tue, 7 May 2024 17:32:52 +0100 Subject: [PATCH 05/18] rsp.match update --- .Rproj.user/shared/notebooks/paths | 1 + NAMESPACE | 10 +-- R/{sp.average.R => rsp.average.R} | 24 +++---- R/{sp.match.R => rsp.match.R} | 96 +++++++++++++++------------ R/{sp.rescale.R => rsp.rescale.R} | 28 ++++---- man/{sp.average.Rd => rsp.average.Rd} | 20 +++--- man/{sp.match.Rd => rsp.match.Rd} | 48 +++++++------- man/rsp.pad.Rd | 2 +- man/{sp.rescale.Rd => rsp.rescale.Rd} | 25 ++++--- 9 files changed, 134 insertions(+), 120 deletions(-) rename R/{sp.average.R => rsp.average.R} (86%) rename R/{sp.match.R => rsp.match.R} (82%) rename R/{sp.rescale.R => rsp.rescale.R} (93%) rename man/{sp.average.Rd => rsp.average.Rd} (67%) rename man/{sp.match.Rd => rsp.match.Rd} (60%) rename man/{sp.rescale.Rd => rsp.rescale.Rd} (79%) diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 5b921db..912da29 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -17,6 +17,7 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/respeciate-package.R="A43C9569" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/respeciate.generics.R="54ECE8F1" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.R="787EA0C5" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.average.R="67ED42C3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.build.R="5A264727" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.info.R="FD1BAD48" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pad.R="FEC8C57D" diff --git a/NAMESPACE b/NAMESPACE index 8a2744f..4d47f54 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,6 +15,7 @@ export(pls_refit_species) export(pls_report) export(pls_test) export(rsp) +export(rsp_average_profile) export(rsp_build_x) export(rsp_dcast) export(rsp_dcast_profile) @@ -22,20 +23,19 @@ export(rsp_dcast_species) export(rsp_find_profile) export(rsp_find_species) export(rsp_info) +export(rsp_match_profile) export(rsp_melt_wide) export(rsp_pad) export(rsp_plot_profile) export(rsp_plot_species) export(rsp_profile) export(rsp_profile_info) +export(rsp_rescale) +export(rsp_rescale_profile) +export(rsp_rescale_species) export(rsp_species_info) -export(sp_average_profile) -export(sp_match_profile) export(sp_pls_profile) export(sp_profile_distance) -export(sp_rescale) -export(sp_rescale_profile) -export(sp_rescale_species) export(sp_species_cor) export(spq_gas) export(spq_other) diff --git a/R/sp.average.R b/R/rsp.average.R similarity index 86% rename from R/sp.average.R rename to R/rsp.average.R index 8e68d55..a43d1f6 100644 --- a/R/sp.average.R +++ b/R/rsp.average.R @@ -1,14 +1,14 @@ -#' @name sp.average -#' @title speciate data averaging functions -#' @aliases sp_average_profile +#' @name rsp.average +#' @title (re)SPECIATE data averaging functions +#' @aliases rsp_average_profile #' @description Functions to build composite (re)SPECIATE profiles -#' @description \code{sp_average_profile} generates an average composite +#' @description \code{rsp_average_profile} generates an average composite #' of a supplied multi-profile \code{respeciate} object. -#' @param x A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. #' @param code required character, the unique profile code to assign to the #' average profile. @@ -16,9 +16,9 @@ #' profile. If not supplied, this defaults to a collapsed list of the codes #' of all the profiles averaged. #' @param method numeric, the averaging method to apply: Currently only 1 (default) -#' \code{mean(x)}. +#' \code{mean(rsp)}. #' @param ... additional arguments, currently ignored -#' @return \code{sp_average_profile} returns a single profile average +#' @return \code{rsp_average_profile} returns a single profile average #' version of the supplied \code{respeciate} profile. #' @note In development function; arguments and outputs likely to be subject to #' change. @@ -29,7 +29,7 @@ #NOTE -#' @rdname sp.average +#' @rdname rsp.average #' @export ## #' @import data.table (in xxx.r) @@ -66,18 +66,18 @@ #test ########################### -#aa <- sp_profile(sp_find_profile("ae8", by="profile_type")) -#sp_average_profile(aa) +#aa <- rsp_profile(sp_find_profile("ae8", by="profile_type")) +#rsp_average_profile(aa) -sp_average_profile <- function(x, code = NULL, name = NULL, method = 1, +rsp_average_profile <- function(rsp, code = NULL, name = NULL, method = 1, ...){ ################################# #check x is a respeciate object?? #check it has .value - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(rsp) #save class to return as is.. # thinking about this diff --git a/R/sp.match.R b/R/rsp.match.R similarity index 82% rename from R/sp.match.R rename to R/rsp.match.R index d463994..76a98d2 100644 --- a/R/sp.match.R +++ b/R/rsp.match.R @@ -1,34 +1,34 @@ -#' @name sp.match +#' @name rsp.match #' @title Find nearest matches from reference set of profiles -#' @aliases sp_match_profile - -#' @description \code{sp_match_profile} compares a supplied species -#' (re)SPECIATE profile and a reference set of supplied profiles and -#' attempt to identify nearest matches on the basis of correlation -#' coefficient. -#' @param x A \code{respeciate} object or similar \code{data.frame} containing -#' a species profile to be compared with profiles in \code{ref}. If \code{x} +#' @aliases rsp_match_profile + +#' @description \code{rsp_match_profile} compares a supplied species +#' (re)SPECIATE profile (or similar data set) and a reference set of +#' supplied profiles and attempt to identify nearest matches on the +#' basis of similarity. +#' @param rsp A \code{respeciate} object or similar \code{data.frame} containing +#' a species profile to be compared with profiles in \code{ref}. If \code{rsp} #' contains more than one profile, these are averaged (using -#' \code{\link{sp_average_profile}}), and the average compared. +#' \code{\link{rsp_average_profile}}), and the average compared. #' @param ref A \code{respeciate} object, a \code{data.frame} containing a #' multiple species profiles, to be used as reference library when identifying -#' nearest matches for \code{x}. +#' nearest matches for \code{rsp}. #' @param matches Numeric (default 10), the maximum number of profile matches to #' report. #' @param rescale Numeric (default 5), the data scaling method to apply before -#' comparing \code{x} and profiles in \code{ref}: options 0 to 5 handled by -#' \code{\link{sp_rescale}}. +#' comparing \code{rsp} and profiles in \code{ref}: options 0 to 5 handled by +#' \code{\link{rsp_rescale}}. #' @param min.n \code{numeric} (default 8), the minimum number of paired #' species measurements in two profiles required for a match to be assessed. -#' See also \code{\link{sp_species_cor}}. +#' See also \code{\link{rsp_species_cor}}. #' @param method Character (default 'pd'), the similarity measure to use, current -#' options 'pd', the Pearson's Distance (1- Pearson's correlation coefficient), +#' options 'pd', the Pearson's Distance (1 - Pearson's correlation coefficient), #' or 'sid', the Standardized Identity Distance (See References). -#' @param test.x Logical (default FALSE). The match process self-tests by adding -#' \code{x} to \code{ref}, which should generate a perfect fit=0 score. Setting -#' \code{test.x} to \code{TRUE} retains this as an extra record. -#' @return \code{sp_match_profile} returns a fit report: a \code{data.frame} of -#' up to \code{n} fit reports for the nearest matches to \code{x} from the +#' @param test.rsp Logical (default FALSE). The match process self-tests by adding +#' \code{rsp} to \code{ref}, which should generate a perfect fit=0 score. Setting +#' \code{test.rsp} to \code{TRUE} retains this as an extra record. +#' @return \code{rsp_match_profile} returns a fit report: a \code{data.frame} of +#' up to \code{n} fit reports for the nearest matches to \code{rsp} from the #' reference profile data set, \code{ref}. #' @references Distance metrics are based on recommendations by Belis et al (2015) #' and as implemented in Mooibroek et al (2022): @@ -46,7 +46,7 @@ #NOTE -#' @rdname sp.match +#' @rdname rsp.match #' @export ###################### @@ -54,16 +54,25 @@ #find ref profile 'most similar to x ###################### -# the sp_dcast uses data.table -# sp_match uses rbindlist from data.table +############################ +############################ +##need to go through notes and code and tidy +## this is first goes at x->rsp +## will need tidy and rethink? +########################### +########################### + + +# the rsp_dcast uses data.table +# rsp_match uses rbindlist from data.table #in development #to do ######################### -##aa <- sp_profile(sp_find_profile("composite", by="profile_name")) -##sp_match_profile(sp_profile("41220C"), aa) +##aa <- rsp_profile(rsp_find_profile("composite", by="profile_name")) +##rsp_match_profile(rsp_profile("41220C"), aa) ##assuming 41220C exists ##NOTE sp_profile code is case sensitive @@ -79,11 +88,11 @@ # when (I guess) nothing there to compare... #default for ref -# using sp_profile(sp_find_profile("composite", by="profile_name")) +# using rsp_profile(rsp_find_profile("composite", by="profile_name")) # in example. #could add error if x is more than one profile -# could use sp_profile_mean when written if we want to force to one +# could use rsp_profile_mean when written if we want to force to one # one profile [?? nb: that function name not decided yet] #do we want an output option? @@ -91,8 +100,8 @@ #option to exclude test? from report -#how can users make x if not from (re)SPECIATE archive -# currently using rsp_ [code after this function] +#how can users make rsp if not from (re)SPECIATE archive +# currently using .rsp_ [code after this function] # to anonymise a speciate profile # suggestion: # identify needed columns, formats and names @@ -114,18 +123,18 @@ # could also do this earlier if min.bin set in formals # but might need to rethink n, min.bin, etc??? -sp_match_profile <- function(x, ref, matches=10, rescale=5, - min.n=8, method = "pd", test.x=FALSE){ +rsp_match_profile <- function(rsp, ref, matches=10, rescale=5, + min.n=8, method = "pd", test.rsp=FALSE){ ####################### #if ref missing ################## #to do - # using sp_profile(sp_find_profile("composite", by="profile_name")) + # using rsp_profile(rsp_find_profile("composite", by="profile_name")) # looked promising #add .value if not there - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(rsp) #tidy x for testing #.x.pr.cd <- as.character(x$PROFILE_CODE) @@ -136,9 +145,9 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, # might think about changing this in future if(length(unique(x$PROFILE_CODE))>1){ - x <- sp_average_profile(x, code = "test") + x <- rsp_average_profile(x, code = "test") } else { - x <- sp_average_profile(x, code = "test", + x <- rsp_average_profile(x, code = "test", name = paste("test>", x$PROFILE_NAME[1], sep="")) } @@ -152,7 +161,7 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, ############### #do test anyway ############### - #if(test.x){ + #if(test.rsp){ matches <- matches + 1 #} @@ -184,7 +193,7 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, # cols are there??? #.tmp <- data.table::as.data.table(sp_rescale_species(.tmp, method=rescale)) - .tmp <- data.table::as.data.table(sp_rescale_profile(.tmp, method=rescale)) + .tmp <- data.table::as.data.table(rsp_rescale_profile(.tmp, method=rescale)) ################### #keep species names and ids for renaming @@ -210,7 +219,7 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, # na.rm=TRUE, # value.var = ".value") - .tmp <- data.table::as.data.table(sp_dcast(.tmp, widen="profile")) + .tmp <- data.table::as.data.table(rsp_dcast(.tmp, widen="profile")) #nb: need the as.data.table() because sp_profile_dcast # currently returns data.frame @@ -359,7 +368,7 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, } if(!is.function(f)){ - stop("RSP> sp_match_profile 'method' unknown", call. = FALSE) + stop("RSP> rsp_match_profile 'method' unknown", call. = FALSE) } .out <- .tmp[, (.cols) := lapply(.SD, f), .SDcols = .cols] @@ -387,7 +396,7 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, if(length(.out)<1){ #see notes.... #sometimes this is because there are less than min.n species in the x profile - stop("sp_match_profile> No (", min.n, " point) matches for x", call. = FALSE) + stop("rsp_match_profile> No (", min.n, " point) matches for rsp", call. = FALSE) } .tmp <- names(.out) @@ -405,7 +414,7 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, row.names = 1:length(.out)) #conflicted!!! - if(!test.x){ + if(!test.rsp){ matches <- matches - 1 if("test" %in% x$PROFILE_CODE){ .out <- .out[tolower(.out$PROFILE_CODE)!="test",] @@ -428,8 +437,6 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, - - #need something to replace this that helps users build local profiles #basic build needs @@ -437,6 +444,9 @@ sp_match_profile <- function(x, ref, matches=10, rescale=5, # species_name and species_id # weight_percent (and possibly .value) +########################### +# think this can go because we now have rsp_build_x??? + rsp_ <- function(x){ .o <- sp_profile(x) .o$PROFILE_NAME <- paste("test", .o$PROFILE_NAME, sep=">") diff --git a/R/sp.rescale.R b/R/rsp.rescale.R similarity index 93% rename from R/sp.rescale.R rename to R/rsp.rescale.R index 743547e..69fe872 100644 --- a/R/sp.rescale.R +++ b/R/rsp.rescale.R @@ -1,12 +1,12 @@ -#' @name sp.rescale +#' @name rsp.rescale #' @title (re)SPECIATE profile rescaling functions #' @aliases sp_rescale sp_rescale_profile sp_rescale_species #' @description Functions for rescaling -#' @description \code{sp_rescale} rescales the percentage weight records in +#' @description \code{rsp_rescale} rescales the percentage weight records in #' a supplied (re)SPECIATE profile data set. This can be by profile or species -#' subsets, and \code{sp_rescale_profile} and \code{sp_rescale_species} provide +#' subsets, and \code{rsp_rescale_profile} and \code{rsp_rescale_species} provide #' short-cuts to these options. #' @param x A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. @@ -38,7 +38,7 @@ #NOTE -#' @rdname sp.rescale +#' @rdname rsp.rescale #' @export ## #' @import data.table # now done in xxx.r @@ -63,13 +63,13 @@ -sp_rescale <- function(x, method = 2, by = "species"){ +rsp_rescale <- function(rsp, method = 2, by = "species"){ ################################# #check x is a respeciate object?? #check it has .value - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(rsp) #save to return as is.. # thinking about this @@ -197,18 +197,18 @@ sp_rescale <- function(x, method = 2, by = "species"){ } -#' @rdname sp.rescale +#' @rdname rsp.rescale #' @export -sp_rescale_profile <- function(x, method = 1, by ="profile"){ - sp_rescale(x=x, method=method, by=by) +rsp_rescale_profile <- function(rsp, method = 1, by ="profile"){ + rsp_rescale(rsp=rsp, method=method, by=by) } -#' @rdname sp.rescale +#' @rdname rsp.rescale #' @export -sp_rescale_species <- function(x, method = 2, by ="species"){ - sp_rescale(x=x, method=method, by=by) +rsp_rescale_species <- function(rsp, method = 2, by ="species"){ + rsp_rescale(rsp=rsp, method=method, by=by) } @@ -233,13 +233,13 @@ sp_rescale_species <- function(x, method = 2, by ="species"){ # may need to think about additional local scaling # e.g. within in profile [species conc]/[sum of all species concs] -rsp_rescale_species <- function(x, method = 2){ +.rsp_rescale_species <- function(x, method = 2){ ################################# #check x is a respeciate object?? #check it has .value - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(x) #save to return as is.. # thinking about this diff --git a/man/sp.average.Rd b/man/rsp.average.Rd similarity index 67% rename from man/sp.average.Rd rename to man/rsp.average.Rd index a2a967c..acaaa5d 100644 --- a/man/sp.average.Rd +++ b/man/rsp.average.Rd @@ -1,14 +1,14 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.average.R -\name{sp.average} -\alias{sp.average} -\alias{sp_average_profile} -\title{speciate data averaging functions} +% Please edit documentation in R/rsp.average.R +\name{rsp.average} +\alias{rsp.average} +\alias{rsp_average_profile} +\title{(re)SPECIATE data averaging functions} \usage{ -sp_average_profile(x, code = NULL, name = NULL, method = 1, ...) +rsp_average_profile(rsp, code = NULL, name = NULL, method = 1, ...) } \arguments{ -\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +\item{rsp}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) profiles.} \item{code}{required character, the unique profile code to assign to the @@ -19,18 +19,18 @@ profile. If not supplied, this defaults to a collapsed list of the codes of all the profiles averaged.} \item{method}{numeric, the averaging method to apply: Currently only 1 (default) -\code{mean(x)}.} +\code{mean(rsp)}.} \item{...}{additional arguments, currently ignored} } \value{ -\code{sp_average_profile} returns a single profile average +\code{rsp_average_profile} returns a single profile average version of the supplied \code{respeciate} profile. } \description{ Functions to build composite (re)SPECIATE profiles -\code{sp_average_profile} generates an average composite +\code{rsp_average_profile} generates an average composite of a supplied multi-profile \code{respeciate} object. } \note{ diff --git a/man/sp.match.Rd b/man/rsp.match.Rd similarity index 60% rename from man/sp.match.Rd rename to man/rsp.match.Rd index b8b7fe8..bf7441b 100644 --- a/man/sp.match.Rd +++ b/man/rsp.match.Rd @@ -1,59 +1,59 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.match.R -\name{sp.match} -\alias{sp.match} -\alias{sp_match_profile} +% Please edit documentation in R/rsp.match.R +\name{rsp.match} +\alias{rsp.match} +\alias{rsp_match_profile} \title{Find nearest matches from reference set of profiles} \usage{ -sp_match_profile( - x, +rsp_match_profile( + rsp, ref, matches = 10, rescale = 5, min.n = 8, method = "pd", - test.x = FALSE + test.rsp = FALSE ) } \arguments{ -\item{x}{A \code{respeciate} object or similar \code{data.frame} containing -a species profile to be compared with profiles in \code{ref}. If \code{x} +\item{rsp}{A \code{respeciate} object or similar \code{data.frame} containing +a species profile to be compared with profiles in \code{ref}. If \code{rsp} contains more than one profile, these are averaged (using -\code{\link{sp_average_profile}}), and the average compared.} +\code{\link{rsp_average_profile}}), and the average compared.} \item{ref}{A \code{respeciate} object, a \code{data.frame} containing a multiple species profiles, to be used as reference library when identifying -nearest matches for \code{x}.} +nearest matches for \code{rsp}.} \item{matches}{Numeric (default 10), the maximum number of profile matches to report.} \item{rescale}{Numeric (default 5), the data scaling method to apply before -comparing \code{x} and profiles in \code{ref}: options 0 to 5 handled by -\code{\link{sp_rescale}}.} +comparing \code{rsp} and profiles in \code{ref}: options 0 to 5 handled by +\code{\link{rsp_rescale}}.} \item{min.n}{\code{numeric} (default 8), the minimum number of paired species measurements in two profiles required for a match to be assessed. -See also \code{\link{sp_species_cor}}.} +See also \code{\link{rsp_species_cor}}.} \item{method}{Character (default 'pd'), the similarity measure to use, current -options 'pd', the Pearson's Distance (1- Pearson's correlation coefficient), +options 'pd', the Pearson's Distance (1 - Pearson's correlation coefficient), or 'sid', the Standardized Identity Distance (See References).} -\item{test.x}{Logical (default FALSE). The match process self-tests by adding -\code{x} to \code{ref}, which should generate a perfect fit=0 score. Setting -\code{test.x} to \code{TRUE} retains this as an extra record.} +\item{test.rsp}{Logical (default FALSE). The match process self-tests by adding +\code{rsp} to \code{ref}, which should generate a perfect fit=0 score. Setting +\code{test.rsp} to \code{TRUE} retains this as an extra record.} } \value{ -\code{sp_match_profile} returns a fit report: a \code{data.frame} of -up to \code{n} fit reports for the nearest matches to \code{x} from the +\code{rsp_match_profile} returns a fit report: a \code{data.frame} of +up to \code{n} fit reports for the nearest matches to \code{rsp} from the reference profile data set, \code{ref}. } \description{ -\code{sp_match_profile} compares a supplied species -(re)SPECIATE profile and a reference set of supplied profiles and -attempt to identify nearest matches on the basis of correlation -coefficient. +\code{rsp_match_profile} compares a supplied species +(re)SPECIATE profile (or similar data set) and a reference set of +supplied profiles and attempt to identify nearest matches on the +basis of similarity. } \references{ Distance metrics are based on recommendations by Belis et al (2015) diff --git a/man/rsp.pad.Rd b/man/rsp.pad.Rd index 39b66c8..da4eeca 100644 --- a/man/rsp.pad.Rd +++ b/man/rsp.pad.Rd @@ -35,6 +35,6 @@ Some data handling can remove (re)SPECIATE meta-data, and \code{rsp_pad}s provide a quick rebuild/repair. For example, \code{\link{rsp_dcast}}ing to a (by-species or by-profile) widened form strips some meta-data, and padding is used as part of the -\code{\link{rsp_melt_wide}} re-add this meta-data +\code{\link{rsp_melt_wide}} to re-add this meta-data when returning the data set to its standard long form. } diff --git a/man/sp.rescale.Rd b/man/rsp.rescale.Rd similarity index 79% rename from man/sp.rescale.Rd rename to man/rsp.rescale.Rd index a04852a..ec19ef6 100644 --- a/man/sp.rescale.Rd +++ b/man/rsp.rescale.Rd @@ -1,22 +1,22 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.rescale.R -\name{sp.rescale} -\alias{sp.rescale} +% Please edit documentation in R/rsp.rescale.R +\name{rsp.rescale} +\alias{rsp.rescale} +\alias{rsp_rescale} \alias{sp_rescale} \alias{sp_rescale_profile} \alias{sp_rescale_species} +\alias{rsp_rescale_profile} +\alias{rsp_rescale_species} \title{(re)SPECIATE profile rescaling functions} \usage{ -sp_rescale(x, method = 2, by = "species") +rsp_rescale(rsp, method = 2, by = "species") -sp_rescale_profile(x, method = 1, by = "profile") +rsp_rescale_profile(rsp, method = 1, by = "profile") -sp_rescale_species(x, method = 2, by = "species") +rsp_rescale_species(rsp, method = 2, by = "species") } \arguments{ -\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) -profiles.} - \item{method}{numeric, the rescaling method to apply: 1 \code{x/total(x)}; 2 \code{x/mean(x)}; @@ -29,6 +29,9 @@ values.} \item{by}{character, when rescaling \code{x} with \code{\link{sp_rescale}}, the data type to group and rescale, currently \code{'species'} (default) or \code{'profile'}.} + +\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +profiles.} } \value{ \code{sp_rescale} and \code{sp_rescale} return the @@ -38,9 +41,9 @@ the requested method. See Note. \description{ Functions for rescaling -\code{sp_rescale} rescales the percentage weight records in +\code{rsp_rescale} rescales the percentage weight records in a supplied (re)SPECIATE profile data set. This can be by profile or species -subsets, and \code{sp_rescale_profile} and \code{sp_rescale_species} provide +subsets, and \code{rsp_rescale_profile} and \code{rsp_rescale_species} provide short-cuts to these options. } \note{ From eb7cf9b0d7c7dd02b68bc0d80b06a4bf569452df Mon Sep 17 00:00:00 2001 From: karlropkins Date: Tue, 7 May 2024 17:43:03 +0100 Subject: [PATCH 06/18] news update --- .Rproj.user/shared/notebooks/paths | 1 + DESCRIPTION | 2 +- NEWS.md | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 912da29..b2307d1 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -20,6 +20,7 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.average.R="67ED42C3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.build.R="5A264727" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.info.R="FD1BAD48" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.match.R="7AE83929" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pad.R="FEC8C57D" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.reshape.R="94C8EF32" diff --git a/DESCRIPTION b/DESCRIPTION index 5514722..bb352d5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: respeciate Title: Speciation profiles for gases and aerosols Version: 0.3.0 -Date: 2024-02-28 +Date: 2024-05-07 Description: Access to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) . Type: Package diff --git a/NEWS.md b/NEWS.md index 3b0503f..bd67476 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,13 +1,13 @@ # Release Notes Version 0.3 * [0.3.0] - * released 2024-02-28 + * released 2024-05-07 * code and documentation refresh... * added as.respeciate method; replacing related unexported code * sp_profile, sp_build_rsp_x updates; both now use as.respeciate * sp_profile to rsp_profile; sp_dcast/melt to rsp_dcast/melt; both as part of object class rebuild - * + * sp_pad to rsp_pad; sp_match_profile to rsp_match_profile; # Release Notes Version 0.2 From af086c7149b5baa85db576caba447dba9737c4ee Mon Sep 17 00:00:00 2001 From: karlropkins Date: Wed, 8 May 2024 10:53:04 +0100 Subject: [PATCH 07/18] spx to rsp_x; spq to rsp_q --- .Rproj.user/shared/notebooks/paths | 4 +- NAMESPACE | 20 +++--- NEWS.md | 3 +- R/rsp.q.R | 95 ++++++++++++++++++++++++++ R/{spx.R => rsp.x.R} | 104 +++++++++++++++++------------ R/spq.R | 89 ------------------------ R/xxx.R | 44 ++++++------ man/rsp.q.Rd | 52 +++++++++++++++ man/rsp.x.Rd | 55 +++++++++++++++ man/spq.Rd | 48 ------------- man/spx.Rd | 55 --------------- 11 files changed, 295 insertions(+), 274 deletions(-) create mode 100644 R/rsp.q.R rename R/{spx.R => rsp.x.R} (71%) delete mode 100644 R/spq.R create mode 100644 man/rsp.q.Rd create mode 100644 man/rsp.x.Rd delete mode 100644 man/spq.Rd delete mode 100644 man/spx.Rd diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index b2307d1..45dadb3 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -24,12 +24,10 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pad.R="FEC8C57D" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.reshape.R="94C8EF32" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.average.R="0A1E36E4" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp_x.R="3CCD538B" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cluster.R="49C0F861" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cor.R="3F1DA8E5" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.match.R="4326C1C3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.pls.R="EACFE9A4" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.rescale.R="D668C5B2" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spq.R="3AD9ACC8" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spx.R="CA18044A" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sysdata.R="82103C52" diff --git a/NAMESPACE b/NAMESPACE index 4d47f54..cc9cbaf 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,23 +30,23 @@ export(rsp_plot_profile) export(rsp_plot_species) export(rsp_profile) export(rsp_profile_info) +export(rsp_q_gas) +export(rsp_q_other) +export(rsp_q_pm) +export(rsp_q_pm.ae6) +export(rsp_q_pm.ae8) +export(rsp_q_pm.cr1) +export(rsp_q_pm.simplified) export(rsp_rescale) export(rsp_rescale_profile) export(rsp_rescale_species) export(rsp_species_info) +export(rsp_x_btex) +export(rsp_x_copy) +export(rsp_x_nalkane) export(sp_pls_profile) export(sp_profile_distance) export(sp_species_cor) -export(spq_gas) -export(spq_other) -export(spq_pm) -export(spq_pm.ae6) -export(spq_pm.ae8) -export(spq_pm.cr1) -export(spq_pm.simplified) -export(spx_btex) -export(spx_copy) -export(spx_n_alkane) importFrom(data.table,":=") importFrom(grDevices,as.graphicsAnnot) importFrom(grDevices,cm.colors) diff --git a/NEWS.md b/NEWS.md index bd67476..1cd25a5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,7 +7,8 @@ * sp_profile, sp_build_rsp_x updates; both now use as.respeciate * sp_profile to rsp_profile; sp_dcast/melt to rsp_dcast/melt; both as part of object class rebuild - * sp_pad to rsp_pad; sp_match_profile to rsp_match_profile; + * sp_pad to rsp_pad; sp_rescale to rsp_rescale; class rebuild + * sp_match_profile to rsp_match_profile; class rebuild # Release Notes Version 0.2 diff --git a/R/rsp.q.R b/R/rsp.q.R new file mode 100644 index 0000000..77f1b50 --- /dev/null +++ b/R/rsp.q.R @@ -0,0 +1,95 @@ +#' @name rsp.q +#' @title rsp_q_ provide quick access to common re(SPECIATE) sub-samples +#' @aliases rsp_q rsp_q_gas rsp_q_other rsp_q_pm rsp_q_pm.ae6 rsp_q_pm.ae8 +#' rsp_q_pm.cr1 rsp_q_pm.simplified + +#' @description \code{rsp_q_} functions are quick access wrappers to commonly +#' requested re(SPECIATE) sub-samples. +#' @return \code{rsp_q_} functions typically return a \code{respeciate} +#' \code{data.frame} of the requested profiles. +#' +#' For example: +#' +#' \code{rsp_q_gas()} returns all gaseous profiles in re(SPECIATE) +#' (\code{PROFILE_TYPE == 'GAS'}). +#' +#' \code{rsp_q_pm} returns all particulate matter (PM) profiles in re(SPECIATE) +#' not classified as a special PM type (\code{PROFILE_TYPE == 'PM'}). +#' +#' The special PM types are subsets profiles intended for special +#' applications, and these include \code{rsp_q_pm.ae6} (type \code{PM-AE6}), +#' \code{rsp_q_pm.ae8} (type \code{PM-AE8}), \code{rsp_q_pm.cr1} (type +#' \code{PM-CR1}), and \code{rsp_q_pm.simplified} (type \code{PM-Simplified}). +#' +#' \code{rsp_q_other} returns all profiles classified as other in re(SPECIATE) +#' (\code{PROFILE_TYPE == 'OTHER'}). +#' + + +############################# +#NOTES +############################ + +# might not be keeping these + +# should be a quicker way of doing this... +# maybe try going sysdata directly instead of using rsp_profile_info??? +# BUT might not be much a speed saving... + +#profile types +#GAS, OTHER, PM, PM-AE6 PM-AE8 PM-CR1 PM-Simplified + +# any others worth doing??? + +#' @rdname rsp.q +#' @export + +rsp_q_gas <- function(){ + rsp_profile(rsp_profile_info("gas", by = "profile_type", partial=FALSE)) +} + +#' @rdname rsp.q +#' @export + +rsp_q_other <- function(){ + rsp_profile(rsp_profile_info("other", by = "profile_type", partial=FALSE)) +} + +#' @rdname rsp.q +#' @export + +rsp_q_pm <- function(){ + rsp_profile(rsp_profile_info("pm", by = "profile_type", partial=FALSE)) +} + +#' @rdname rsp.q +#' @export + +rsp_q_pm.ae6 <- function(){ + rsp_profile(rsp_profile_info("pm-ae6", by = "profile_type", partial=FALSE)) +} + +#' @rdname rsp.q +#' @export + +rsp_q_pm.ae8 <- function(){ + rsp_profile(rsp_profile_info("pm-ae8", by = "profile_type", partial=FALSE)) +} + +#' @rdname rsp.q +#' @export + +rsp_q_pm.cr1 <- function(){ + rsp_profile(rsp_profile_info("pm-cr1", by = "profile_type", partial=FALSE)) +} + +#' @rdname rsp.q +#' @export + +rsp_q_pm.simplified <- function(){ + rsp_profile(rsp_profile_info("pm-simplified", by = "profile_type", partial=FALSE)) +} + + + + diff --git a/R/spx.R b/R/rsp.x.R similarity index 71% rename from R/spx.R rename to R/rsp.x.R index 7b9a76c..5f91fdc 100644 --- a/R/spx.R +++ b/R/rsp.x.R @@ -1,39 +1,40 @@ -#' @name spx -#' @title spx_ functions for grouping and subsetting -#' @aliases spx_ spx_copy spx_n_alkane spx_btex +#' @name rsp.x +#' @title rsp_x_ functions for grouping and subsetting re(SPECIATE) profiles +#' @aliases rsp_x rsp_x_copy rsp_x_nalkane rsp_x_btex +# still wondering if these should be rsp_cut_... -#' @description \code{spx_} functions generate a vector of assignment +#' @description \code{rsp_x_} functions generate a vector of assignment #' terms and can be used to subset or condition a supplied re(SPECIATE) #' \code{data.frame}. #' -#' Most commonly, the \code{spx_} functions accept a single input, a +#' Most commonly, the \code{rsp_x_} functions accept a single input, a #' re(SPECIATE) \code{data.frame} and return a logical vector of #' length \code{nrow(x)}, identifying species of interest as #' \code{TRUE}. So, for example, they can be used when #' \code{\link{subset}}ting in the form: #' -#' \code{subset(x, spx_n_alkane(x))} +#' \code{subset(rsp, rsp_x_nalkane(rsp))} #' -#' ... to extract just n-alkane records from a \code{respeciate} object -#' \code{x}. +#' ... to extract just n-alkane records from a supplied \code{respeciate} +#' object \code{rsp}. #' -#' However, some accept additional arguments. For example, \code{spx_copy} +#' However, some accept additional arguments. For example, \code{rsp_x_copy} #' also accepts a reference data set, \code{ref}, and a column identifier, -#' \code{by}, and tests \code{x$by \%in\% unique(ref$by)}. +#' \code{by}, and tests \code{rsp$by \%in\% unique(ref$by)}. #' -#' @param x a \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp a \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. -#' @param ref (\code{spx_copy} only) a second \code{respeciate} object, to -#' be used as reference when testing \code{x}. -#' @param by (\code{spx_copy} only) character, the name of the column -#' in \code{ref} to copy when testing \code{x}. -#' @return \code{spx_copy} outputs can be modified but, by default, it +#' @param ref (\code{rsp_x_copy} only) a second \code{respeciate} object, to +#' be used as reference when subsetting (or conditioning) \code{rsp}. +#' @param by (\code{rsp_x_copy} only) character, the name of the column +#' in \code{ref} to copy when subsetting (or conditioning) \code{rsp}. +#' @return \code{rsp_x_copy} outputs can be modified but, by default, it #' identifies all species in the supplied reference data set. #' -#' \code{spx_n_alkane} identifies C1 to C40 n-alkanes. +#' \code{rsp_x_nalkane} identifies (straight chain) C1 to C40 n-alkanes. #' -#' \code{spx_btex} identifies the BTEX group of aromatic hydrocarbons +#' \code{rsp_x_btex} identifies the BTEX group of aromatic hydrocarbons #' (benzene, toluene, ethyl benzene, and M-, O- and P-xylene). ############################# @@ -44,11 +45,10 @@ # but it did not seem to be slowing things down # and other approaches seems likely to get messy # really quick... +# tidyverse folks would argue against it... # others to do???? -# the BTEXs - doing/testing - # others to consider??? # PAHs different groups @@ -56,55 +56,70 @@ # elementals??? # monitoring network relevant subsets of species -# special cases??? +#do we need a strategy to rationalize multiple species names +# see rsp_x_nalkane where some species have two names in SPECIATE. -# spx_ref(x, ref, by="") -# where x is respeciate object, ref is a reference -# by is column in ref; case is x$by %in% unique(ref$by) +################################# +# rsp_x_copy +################################# -# could ref also be a vector of terms??? +# identify species in rsp that are in ref(erence) +# special cases??? +# rsp_x_ref(rsp, ref, by="") +# where rsp is respeciate object, ref is a reference +# by is column in ref; case is x$by %in% unique(ref$by) +# could ref also be a vector of terms??? -#' @rdname spx +#' @rdname rsp.x #' @export -spx_copy <- function(x, ref = NULL, by="species_id"){ +rsp_x_copy <- function(rsp, ref = NULL, by="species_id"){ #maybe warn??? if(is.null(ref)){ - ref <- x + ref <- rsp } - names(x) <- tolower(names(x)) + + names(rsp) <- tolower(names(rsp)) names(ref) <- tolower(names(ref)) .tmp <- unique(ref[, by]) - x[, by] %in% .tmp + rsp[, by] %in% .tmp } + ##################### -#spx_n_alkanes +#rsp_x_nalkanes ####################### +# identify only the n-alkanes in rsp... #source names # from https://en.wikipedia.org/wiki/List_of_straight-chain_alkanes # (might be duplicates) -# (some not using standard names) + +# some in SPECIATE may not standard names... +# need to check because I am not sure if standard names are international.. + +# some are just [alkane] rather than n-[alkane] +# but not sure if any are in as both [alkane] and n-[alkane] # could try smiles, molecular formula, cas numbers??? # should be one entry/species if they are unique??? #test ## a <- sysdata$SPECIES_PROPERTIES -## b <- subset(a, spx_n_alkane(a)) +## b <- subset(a, rsp_x_nalkane(a)) ## b[order(b$SPEC_MW),] -#' @rdname spx +#' @rdname rsp.x #' @export -spx_n_alkane <- function(x){ +rsp_x_nalkane <- function(rsp){ + #group x by is/isn't n-alkane - tolower(x$SPECIES_NAME) %in% c("methane", #C1 + tolower(rsp$SPECIES_NAME) %in% c("methane", #C1 "ethane", "propane", "n-butane", @@ -152,7 +167,7 @@ spx_n_alkane <- function(x){ ##################### -#spx_n_btex +#rsp_x_btex ####################### # Benzene, toluene, ethylbenzene, 3 xylenes isomers @@ -180,22 +195,23 @@ spx_n_alkane <- function(x){ # if several names for btex, might need to think about how to # sample (CAS? etc), merge and compare??? - - #tests ######################### ## a <- sysdata$SPECIES_PROPERTIES -## b <- subset(a, spx_btex(a)) +## b <- subset(a, rsp_x_btex(a)) ## b[order(b$SPEC_MW),] ## to do -#' @rdname spx +#' @rdname rsp.x #' @export -spx_btex <- function(x){ - #group x by is/isn't btex - tolower(x$SPECIES_NAME) %in% c( +rsp_x_btex <- function(rsp){ + + #identify species that is a btex + #might need to think about mixtures... + # for example all xylenes or all c2 benzenes, etc... + tolower(rsp$SPECIES_NAME) %in% c( #to test/check... "benzene", "toluene", diff --git a/R/spq.R b/R/spq.R deleted file mode 100644 index 7be81c1..0000000 --- a/R/spq.R +++ /dev/null @@ -1,89 +0,0 @@ -#' @name spq -#' @title spq_ quick access to common re(SPECIATE) sub-samples -#' @aliases spq_gas spq_other spq_pm spq_pm.ae6 spq_pm.ae8 spq_pm.cr1 -#' spq_pm.simplified - -#' @description \code{spq_} functions are quick access wrappers to commonly -#' requested re(SPECIATE) sub-samples. -#' @return \code{spq_} functions typically return a \code{respeciate} -#' \code{data.frame} of the requested profiles. -#' -#' For example: -#' -#' \code{sqr_gas} returns all gaseous profiles (\code{PROFILE_TYPE == 'GAS'}). -#' -#' \code{sqr_pm} returns all particulate matter (PM) profiles not classified -#' as a special PM type (\code{PROFILE_TYPE == 'PM'}). -#' -#' The special PM types are subsets profiles intended for special -#' applications, and these include \code{sqr_pm.ae6} (type \code{PM-AE6}), -#' \code{sqr_pm.ae8} (type \code{PM-AE8}), \code{sqr_pm.cr1} (type -#' \code{PM-CR1}), \code{sqr_pm.simplified} (type \code{PM-Simplified}) -#' and \code{sqr_other} (\code{PROFILE_TYPE == 'OTHER'}). -#' - - -############################# -#NOTE -############################ - -#might not be keeping these - -#profile types -#GAS, OTHER, PM, PM-AE6 PM-AE8 PM-CR1 PM-Simplified - -#spq_gas, - - -#' @rdname spq -#' @export - -spq_gas <- function(){ - sp_profile(sp_profile_info("gas", by = "profile_type", partial=FALSE)) -} - -#' @rdname spq -#' @export - -spq_other <- function(){ - sp_profile(sp_profile_info("other", by = "profile_type", partial=FALSE)) -} - -#' @rdname spq -#' @export - -spq_pm <- function(){ - sp_profile(sp_profile_info("pm", by = "profile_type", partial=FALSE)) -} - -#' @rdname spq -#' @export - -spq_pm.ae6 <- function(){ - sp_profile(sp_profile_info("pm-ae6", by = "profile_type", partial=FALSE)) -} - -#' @rdname spq -#' @export - -spq_pm.ae8 <- function(){ - sp_profile(sp_profile_info("pm-ae8", by = "profile_type", partial=FALSE)) -} - -#' @rdname spq -#' @export - -spq_pm.cr1 <- function(){ - sp_profile(sp_profile_info("pm-cr1", by = "profile_type", partial=FALSE)) -} - -#' @rdname spq -#' @export - -spq_pm.simplified <- function(){ - sp_profile(sp_profile_info("pm-simplified", by = "profile_type", partial=FALSE)) -} - - - - diff --git a/R/xxx.R b/R/xxx.R index 2029d5c..48caad1 100644 --- a/R/xxx.R +++ b/R/xxx.R @@ -1,11 +1,3 @@ -############################## -#setup code, misc code, -#testing code, etc -############################## - -#currently no hooks, etc... - - ##################### # to think about ##################### @@ -13,8 +5,7 @@ # standardise error messages, e.g. RSP> [function]: [issue] \n\t [fix]? # make respeciate object argument rsp rather than x -# that helps sp_plot..() but maybe not plot() - +# that helps sp_plot..() but not plot.respeciate() ##################### @@ -25,26 +16,30 @@ # xxx_test and its depends... # (not keeping unless we can get it to work better) +############################## +#setup code, misc code, +#testing code, etc +############################## +#currently no hooks, etc... + +#globals utils::globalVariables(c("sysdata", ".SD", "ans", "control", "PROFILE_CODE", "PROFILE_NAME", "PROFILE_TYPE", "SPECIES_ID", "SPECIES_NAME", "SPEC_MW", "WEIGHT_PERCENT", ".", ".value")) -######################## #to think about... -####################### # all @import here -# in case we have to move to data.table::as.data.table, etc... -# moving to data.table::as.data.table... +# moved to data.table::as.data.table in code... # #' @import data.table # data.table used by: # rsp_test_profile, -# sp_dcast_profile, and those that use dcast? -# sp_species_cor -# sp_profile_distance +# rsp_dcast_profile, and those that use dcast? +# sp_species_cor +# sp_profile_distance # and others??? # need to identify them @@ -61,6 +56,7 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", #' @importFrom grDevices cm.colors colorRampPalette as.graphicsAnnot #' dev.flush dev.hold heat.colors rainbow +# notes #might be able to drop legend? # check plot.respeciate @@ -70,26 +66,26 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", #common unexported ############################## +# suggesting standardizing naming .rsp_[function_description] -#rsp_plot_fix +#.rsp_plot_fix ######################### # general tidy function for data before plotting # merges duplicate species in profiles # makes profile names unique if duplicated # tidies species names for use in labelling +# warns about changes #used by ################### #plot.respeciate #sp_plot_profile -#uses +#used by #################### -#rsp_tidy_profile -#rsp_test_respeciate -#rsp_test_profile - - +#.rsp_tidy_profile +#.rsp_test_respeciate +#.rsp_test_profile .rsp_plot_fix <- function(x, silent = FALSE, ...){ diff --git a/man/rsp.q.Rd b/man/rsp.q.Rd new file mode 100644 index 0000000..4ac4fe0 --- /dev/null +++ b/man/rsp.q.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rsp.q.R +\name{rsp.q} +\alias{rsp.q} +\alias{rsp_q_gas} +\alias{rsp_q} +\alias{rsp_q_other} +\alias{rsp_q_pm} +\alias{rsp_q_pm.ae6} +\alias{rsp_q_pm.ae8} +\alias{rsp_q_pm.cr1} +\alias{rsp_q_pm.simplified} +\title{rsp_q_ provide quick access to common re(SPECIATE) sub-samples} +\usage{ +rsp_q_gas() + +rsp_q_other() + +rsp_q_pm() + +rsp_q_pm.ae6() + +rsp_q_pm.ae8() + +rsp_q_pm.cr1() + +rsp_q_pm.simplified() +} +\value{ +\code{rsp_q_} functions typically return a \code{respeciate} +\code{data.frame} of the requested profiles. + +For example: + +\code{rsp_q_gas()} returns all gaseous profiles in re(SPECIATE) +(\code{PROFILE_TYPE == 'GAS'}). + +\code{rsp_q_pm} returns all particulate matter (PM) profiles in re(SPECIATE) +not classified as a special PM type (\code{PROFILE_TYPE == 'PM'}). + +The special PM types are subsets profiles intended for special +applications, and these include \code{rsp_q_pm.ae6} (type \code{PM-AE6}), +\code{rsp_q_pm.ae8} (type \code{PM-AE8}), \code{rsp_q_pm.cr1} (type +\code{PM-CR1}), and \code{rsp_q_pm.simplified} (type \code{PM-Simplified}). + +\code{rsp_q_other} returns all profiles classified as other in re(SPECIATE) +(\code{PROFILE_TYPE == 'OTHER'}). +} +\description{ +\code{rsp_q_} functions are quick access wrappers to commonly +requested re(SPECIATE) sub-samples. +} diff --git a/man/rsp.x.Rd b/man/rsp.x.Rd new file mode 100644 index 0000000..5703f82 --- /dev/null +++ b/man/rsp.x.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rsp.x.R +\name{rsp.x} +\alias{rsp.x} +\alias{rsp_x_copy} +\alias{rsp_x} +\alias{rsp_x_nalkane} +\alias{rsp_x_btex} +\title{rsp_x_ functions for grouping and subsetting re(SPECIATE) profiles} +\usage{ +rsp_x_copy(rsp, ref = NULL, by = "species_id") + +rsp_x_nalkane(rsp) + +rsp_x_btex(rsp) +} +\arguments{ +\item{rsp}{a \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +profiles.} + +\item{ref}{(\code{rsp_x_copy} only) a second \code{respeciate} object, to +be used as reference when subsetting (or conditioning) \code{rsp}.} + +\item{by}{(\code{rsp_x_copy} only) character, the name of the column +in \code{ref} to copy when subsetting (or conditioning) \code{rsp}.} +} +\value{ +\code{rsp_x_copy} outputs can be modified but, by default, it +identifies all species in the supplied reference data set. + +\code{rsp_x_nalkane} identifies (straight chain) C1 to C40 n-alkanes. + +\code{rsp_x_btex} identifies the BTEX group of aromatic hydrocarbons +(benzene, toluene, ethyl benzene, and M-, O- and P-xylene). +} +\description{ +\code{rsp_x_} functions generate a vector of assignment +terms and can be used to subset or condition a supplied re(SPECIATE) +\code{data.frame}. + +Most commonly, the \code{rsp_x_} functions accept a single input, a +re(SPECIATE) \code{data.frame} and return a logical vector of +length \code{nrow(x)}, identifying species of interest as +\code{TRUE}. So, for example, they can be used when +\code{\link{subset}}ting in the form: + +\code{subset(rsp, rsp_x_nalkane(rsp))} + +... to extract just n-alkane records from a supplied \code{respeciate} +object \code{rsp}. + +However, some accept additional arguments. For example, \code{rsp_x_copy} +also accepts a reference data set, \code{ref}, and a column identifier, +\code{by}, and tests \code{rsp$by \%in\% unique(ref$by)}. +} diff --git a/man/spq.Rd b/man/spq.Rd deleted file mode 100644 index 61a4a3c..0000000 --- a/man/spq.Rd +++ /dev/null @@ -1,48 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/spq.R -\name{spq} -\alias{spq} -\alias{spq_gas} -\alias{spq_other} -\alias{spq_pm} -\alias{spq_pm.ae6} -\alias{spq_pm.ae8} -\alias{spq_pm.cr1} -\alias{spq_pm.simplified} -\title{spq_ quick access to common re(SPECIATE) sub-samples} -\usage{ -spq_gas() - -spq_other() - -spq_pm() - -spq_pm.ae6() - -spq_pm.ae8() - -spq_pm.cr1() - -spq_pm.simplified() -} -\value{ -\code{spq_} functions typically return a \code{respeciate} -\code{data.frame} of the requested profiles. - -For example: - -\code{sqr_gas} returns all gaseous profiles (\code{PROFILE_TYPE == 'GAS'}). - -\code{sqr_pm} returns all particulate matter (PM) profiles not classified -as a special PM type (\code{PROFILE_TYPE == 'PM'}). - -The special PM types are subsets profiles intended for special -applications, and these include \code{sqr_pm.ae6} (type \code{PM-AE6}), -\code{sqr_pm.ae8} (type \code{PM-AE8}), \code{sqr_pm.cr1} (type -\code{PM-CR1}), \code{sqr_pm.simplified} (type \code{PM-Simplified}) -and \code{sqr_other} (\code{PROFILE_TYPE == 'OTHER'}). -} -\description{ -\code{spq_} functions are quick access wrappers to commonly -requested re(SPECIATE) sub-samples. -} diff --git a/man/spx.Rd b/man/spx.Rd deleted file mode 100644 index f520964..0000000 --- a/man/spx.Rd +++ /dev/null @@ -1,55 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/spx.R -\name{spx} -\alias{spx} -\alias{spx_copy} -\alias{spx_} -\alias{spx_n_alkane} -\alias{spx_btex} -\title{spx_ functions for grouping and subsetting} -\usage{ -spx_copy(x, ref = NULL, by = "species_id") - -spx_n_alkane(x) - -spx_btex(x) -} -\arguments{ -\item{x}{a \code{respeciate} object, a \code{data.frame} of re(SPECIATE) -profiles.} - -\item{ref}{(\code{spx_copy} only) a second \code{respeciate} object, to -be used as reference when testing \code{x}.} - -\item{by}{(\code{spx_copy} only) character, the name of the column -in \code{ref} to copy when testing \code{x}.} -} -\value{ -\code{spx_copy} outputs can be modified but, by default, it -identifies all species in the supplied reference data set. - -\code{spx_n_alkane} identifies C1 to C40 n-alkanes. - -\code{spx_btex} identifies the BTEX group of aromatic hydrocarbons -(benzene, toluene, ethyl benzene, and M-, O- and P-xylene). -} -\description{ -\code{spx_} functions generate a vector of assignment -terms and can be used to subset or condition a supplied re(SPECIATE) -\code{data.frame}. - -Most commonly, the \code{spx_} functions accept a single input, a -re(SPECIATE) \code{data.frame} and return a logical vector of -length \code{nrow(x)}, identifying species of interest as -\code{TRUE}. So, for example, they can be used when -\code{\link{subset}}ting in the form: - -\code{subset(x, spx_n_alkane(x))} - -... to extract just n-alkane records from a \code{respeciate} object -\code{x}. - -However, some accept additional arguments. For example, \code{spx_copy} -also accepts a reference data set, \code{ref}, and a column identifier, -\code{by}, and tests \code{x$by \%in\% unique(ref$by)}. -} From a264e85e8fd8f472cbd571893f3600a62664b1cb Mon Sep 17 00:00:00 2001 From: karlropkins Date: Wed, 8 May 2024 13:09:53 +0100 Subject: [PATCH 08/18] sp_pls -> rsp_pls --- .Rproj.user/shared/notebooks/paths | 2 + NAMESPACE | 2 +- R/{sp.pls.R => rsp.pls.R} | 75 ++++++++++++++++++------------ man/{sp.pls.Rd => rsp.pls.Rd} | 20 ++++---- 4 files changed, 58 insertions(+), 41 deletions(-) rename R/{sp.pls.R => rsp.pls.R} (97%) rename man/{sp.pls.Rd => rsp.pls.Rd} (92%) diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 45dadb3..80260b1 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -23,7 +23,9 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.match.R="7AE83929" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pad.R="FEC8C57D" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.q.R="2721C15F" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.reshape.R="94C8EF32" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.x.R="4DA91187" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp_x.R="3CCD538B" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cluster.R="49C0F861" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cor.R="3F1DA8E5" diff --git a/NAMESPACE b/NAMESPACE index cc9cbaf..61ebaf6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,6 +28,7 @@ export(rsp_melt_wide) export(rsp_pad) export(rsp_plot_profile) export(rsp_plot_species) +export(rsp_pls_profile) export(rsp_profile) export(rsp_profile_info) export(rsp_q_gas) @@ -44,7 +45,6 @@ export(rsp_species_info) export(rsp_x_btex) export(rsp_x_copy) export(rsp_x_nalkane) -export(sp_pls_profile) export(sp_profile_distance) export(sp_species_cor) importFrom(data.table,":=") diff --git a/R/sp.pls.R b/R/rsp.pls.R similarity index 97% rename from R/sp.pls.R rename to R/rsp.pls.R index af012a5..a3f6941 100644 --- a/R/sp.pls.R +++ b/R/rsp.pls.R @@ -1,6 +1,6 @@ -#' @name sp.pls -#' @title (re)SPECIATE profile Positive Least Squares -#' @aliases sp_pls_profile pls_report pls_test pls_fit_species +#' @name rsp.pls +#' @title (re)SPECIATE profile Positive Least Squares models +#' @aliases rsp_pls_profile pls_report pls_test pls_fit_species #' pls_refit_species pls_rebuild pls_plot #' pls_plot_species pls_plot_profile @@ -8,7 +8,7 @@ #' (re)SPECIATE profiles #' @description -#' \code{sp_pls_profile} builds PSL models for supplied profile(s) using +#' \code{rsp_pls_profile} builds PSL models for supplied profile(s) using #' the \code{\link{nls}} function, the 'port' algorithm and a lower #' limit of zero for all model outputs to enforce the positive fits. The #' modeled profiles are typically from an external source, e.g. a @@ -16,15 +16,15 @@ #' profiles, here typically from (re)SPECIATE, to provide a measure of #' source apportionment based on the assumption that the profiles in the #' reference set are representative of the mix that make up the modeled -#' sample. The \code{pls_} functions work with \code{sp_pls_profile} +#' sample. The \code{pls_} functions work with \code{rsp_pls_profile} #' outputs, and are intended to be used when refining and analyzing #' these PLS models. -#' @param x A \code{respeciate} object, a \code{data.frame} of +#' @param rsp A \code{respeciate} object, a \code{data.frame} of #' profiles in standard long form, intended for PLS modelling. #' @param ref A \code{respeciate} object, a \code{data.frame} of #' profiles also in standard long form, used as the set of candidate -#' source profiles when fitting \code{x}. +#' source profiles when fitting \code{rsp}. #' @param power A numeric, an additional factor to be added to #' weightings when fitting the PLS model. This is applied in the form #' \code{weight^power}, and increasing this, increases the relative @@ -74,8 +74,8 @@ # The zero handling is a based on offset in plot(..., log="y", off.set) # but automatically estimated... -#' @return \code{sp_pls_profile} returns a list of nls models, one per -#' profile/measurement set in \code{x}. The \code{pls_} functions work with +#' @return \code{rsp_pls_profile} returns a list of nls models, one per +#' profile/measurement set in \code{rsp}. The \code{pls_} functions work with #' these outputs. \code{pls_report} generates a \code{data.frame} of #' model outputs, and is used of several of the other \code{pls_} #' functions. \code{pls_fit_species}, \code{pls_refit_species} and @@ -86,13 +86,13 @@ #' @note This implementation of PLS applies the following modeling constraints: #' -#' 1. It generates a model of \code{x} that is positively constrained linear +#' 1. It generates a model of \code{rsp} that is positively constrained linear #' product of the profiles in \code{ref}, so outputs can only be #' zero or more. Although the model is generated using \code{\link{nls}}, #' which is a Nonlinear Least Squares (NLS) model, the fitting term applied #' in this case is linear. #' -#' 2. The number of species in \code{x} must be more that the number of +#' 2. The number of species in \code{rsp} must be more that the number of #' profiles in \code{ref} to reduce the likelihood of over-fitting. #' #' @@ -109,13 +109,10 @@ ############################ ############################ -## sp_pls_profile +## rsp_pls_profile ############################ ############################ -#' @rdname sp.pls -#' @export - ## now importing locally where possible ## data.table::[function] ## #' @import data.table @@ -123,18 +120,36 @@ #This is version 2 #version 1 combined version2 and pls_report -#now separated because it simplified pls model reworking +#now separated because it simplified pls_ model reworking #currently keeping the function args # might not need to do this BUT -# model does not seem to be tracking them when ... +# model does not seem to be tracking them ... # check power handling is right -sp_pls_profile <- function(x, ref, +######################### +#think about ? +######################### + +# should first arg be rsp.x rather than x or rsp ??? + +# maybe get formula into docs ??? + +# maybe split this into rsp.pls and then separate pls. documents ??? + +#' @rdname rsp.pls +#' @export + +rsp_pls_profile <- function(rsp, ref, power = 1, ...){ + ################## + #quick tidy for now + ################## + x <- rsp + ################## #from rough code ################## @@ -142,7 +157,7 @@ sp_pls_profile <- function(x, ref, ######################## #only allowing profiles < species if(length(unique(ref$PROFILE_CODE)) >= length(unique(x$SPECIES_ID))){ - stop("sp_pls: #.need species > #.profiles, more species or less profiles?", + stop("rsp_pls: need n.species > n.profiles, more species or less profiles?", call. = FALSE) } @@ -156,7 +171,7 @@ sp_pls_profile <- function(x, ref, ## .xx <- respeciate:::rsp_tidy_profile(x) .xx <- lapply(.pr.cd, function(y){ .x <- x[x$PROFILE_CODE==y,] - .x <- sp_average_profile(.x, y, .x$PROFILE_NAME[1]) + .x <- rsp_average_profile(.x, y, .x$PROFILE_NAME[1]) .x }) .xx <- data.table::rbindlist(.xx) @@ -204,7 +219,7 @@ sp_pls_profile <- function(x, ref, .test <- try({ #need to try this because it does not always work .x <- as.data.frame(.xx[.xx$PROFILE_CODE==y,]) - .x <- sp_average_profile(.x, "test", "1_test") + .x <- rsp_average_profile(.x, "test", "1_test") #might not need one of this-and-same-above #might be better doing it here... @@ -213,7 +228,7 @@ sp_pls_profile <- function(x, ref, #could change this with rbindlist version?? .ref <- intersect(names(.x), names(.tmp)) .out <- rbind(.x[.ref], .tmp[.ref]) - .out <- sp_dcast_profile(.out) + .out <- rsp_dcast_profile(.out) #build formula and model args .tmp <- names(.out) @@ -309,7 +324,7 @@ sp_pls_profile <- function(x, ref, ############################# ############################# -#' @rdname sp.pls +#' @rdname rsp.pls #' @export ## now imports from xxx.r @@ -427,7 +442,7 @@ pls_report <- function(pls){ ############################# ############################# -#' @rdname sp.pls +#' @rdname rsp.pls #' @export ## now imports from xxx.r @@ -480,7 +495,7 @@ pls_test <- function(pls){ # pls_rebuild -#' @rdname sp.pls +#' @rdname rsp.pls #' @export pls_fit_species <- function(pls, species, power=1, @@ -509,7 +524,7 @@ pls_fit_species <- function(pls, species, power=1, -#' @rdname sp.pls +#' @rdname rsp.pls #' @export pls_refit_species <- function(pls, species, power=1, @@ -539,7 +554,7 @@ pls_refit_species <- function(pls, species, power=1, -#' @rdname sp.pls +#' @rdname rsp.pls #' @export @@ -1037,7 +1052,7 @@ pls_rebuild <- function(pls, species, power=1, #################################### -#' @rdname sp.pls +#' @rdname rsp.pls #' @export ## now imports via data.table:: @@ -1296,7 +1311,7 @@ pls_plot <- function (pls, n, type = 1, ...){ #################################### -#' @rdname sp.pls +#' @rdname rsp.pls #' @export ## now imports from xxx.r @@ -1517,7 +1532,7 @@ pls_plot_species <- function (pls, n, type = 1, ...) #################################### -#' @rdname sp.pls +#' @rdname rsp.pls #' @export ## now imports from xxx.r diff --git a/man/sp.pls.Rd b/man/rsp.pls.Rd similarity index 92% rename from man/sp.pls.Rd rename to man/rsp.pls.Rd index 1c9850c..52782ef 100644 --- a/man/sp.pls.Rd +++ b/man/rsp.pls.Rd @@ -1,8 +1,8 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.pls.R -\name{sp.pls} -\alias{sp.pls} -\alias{sp_pls_profile} +% Please edit documentation in R/rsp.pls.R +\name{rsp.pls} +\alias{rsp.pls} +\alias{rsp_pls_profile} \alias{pls_report} \alias{pls_test} \alias{pls_fit_species} @@ -11,9 +11,9 @@ \alias{pls_plot} \alias{pls_plot_species} \alias{pls_plot_profile} -\title{(re)SPECIATE profile Positive Least Squares} +\title{(re)SPECIATE profile Positive Least Squares models} \usage{ -sp_pls_profile(x, ref, power = 1, ...) +rsp_pls_profile(rsp, ref, power = 1, ...) pls_report(pls) @@ -56,12 +56,12 @@ pls_plot_species(pls, n, type = 1, ...) pls_plot_profile(pls, n, log = FALSE, ...) } \arguments{ -\item{x}{A \code{respeciate} object, a \code{data.frame} of +\item{rsp}{A \code{respeciate} object, a \code{data.frame} of profiles in standard long form, intended for PLS modelling.} \item{ref}{A \code{respeciate} object, a \code{data.frame} of profiles also in standard long form, used as the set of candidate -source profiles when fitting \code{x}.} +source profiles when fitting \code{rsp}.} \item{power}{A numeric, an additional factor to be added to weightings when fitting the PLS model. This is applied in the form @@ -131,7 +131,7 @@ apportionment studies. Functions for Positive Least Squares (PSL) fitting of (re)SPECIATE profiles -\code{sp_pls_profile} builds PSL models for supplied profile(s) using +\code{rsp_pls_profile} builds PSL models for supplied profile(s) using the \code{\link{nls}} function, the 'port' algorithm and a lower limit of zero for all model outputs to enforce the positive fits. The modeled profiles are typically from an external source, e.g. a @@ -139,7 +139,7 @@ measurement campaign, and are fit as a linear additive series of reference profiles, here typically from (re)SPECIATE, to provide a measure of source apportionment based on the assumption that the profiles in the reference set are representative of the mix that make up the modeled -sample. The \code{pls_} functions work with \code{sp_pls_profile} +sample. The \code{pls_} functions work with \code{rsp_pls_profile} outputs, and are intended to be used when refining and analyzing these PLS models. } From 35b013310e28d0bb7e64a5154261177eb509abfc Mon Sep 17 00:00:00 2001 From: karlropkins Date: Wed, 8 May 2024 14:09:11 +0100 Subject: [PATCH 09/18] sp_cor -> rsp_cor; sp_distance -> rsp_distance --- .Rproj.user/shared/notebooks/paths | 2 ++ NAMESPACE | 4 +-- R/{sp.cluster.R => rsp.cluster.R} | 38 +++++++++++++-------------- R/{sp.cor.R => rsp.cor.R} | 35 +++++++++++++++--------- R/xxx.R | 5 +++- man/{sp.cluster.Rd => rsp.cluster.Rd} | 22 ++++++++-------- man/{sp.cor.Rd => rsp.cor.Rd} | 22 ++++++++-------- man/rsp.pls.Rd | 8 +++--- 8 files changed, 74 insertions(+), 62 deletions(-) rename R/{sp.cluster.R => rsp.cluster.R} (91%) rename R/{sp.cor.R => rsp.cor.R} (90%) rename man/{sp.cluster.Rd => rsp.cluster.Rd} (50%) rename man/{sp.cor.Rd => rsp.cor.Rd} (84%) diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 80260b1..88ea646 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -19,10 +19,12 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.R="787EA0C5" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.average.R="67ED42C3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.build.R="5A264727" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.cor.R="DE099ED6" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.info.R="FD1BAD48" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.match.R="7AE83929" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pad.R="FEC8C57D" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pls.R="430E71B2" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.q.R="2721C15F" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.reshape.R="94C8EF32" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.x.R="4DA91187" diff --git a/NAMESPACE b/NAMESPACE index 61ebaf6..a6cc1b1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,9 +17,11 @@ export(pls_test) export(rsp) export(rsp_average_profile) export(rsp_build_x) +export(rsp_cor_species) export(rsp_dcast) export(rsp_dcast_profile) export(rsp_dcast_species) +export(rsp_distance_profile) export(rsp_find_profile) export(rsp_find_species) export(rsp_info) @@ -45,8 +47,6 @@ export(rsp_species_info) export(rsp_x_btex) export(rsp_x_copy) export(rsp_x_nalkane) -export(sp_profile_distance) -export(sp_species_cor) importFrom(data.table,":=") importFrom(grDevices,as.graphicsAnnot) importFrom(grDevices,cm.colors) diff --git a/R/sp.cluster.R b/R/rsp.cluster.R similarity index 91% rename from R/sp.cluster.R rename to R/rsp.cluster.R index 722ebbe..ff9f3b0 100644 --- a/R/sp.cluster.R +++ b/R/rsp.cluster.R @@ -1,20 +1,20 @@ -#' @name sp.cluster -#' @title sp_profile clustering -#' @aliases sp_profile_distance +#' @name rsp.cluster +#' @title (re)SPECIATE profile cluster analysis methods +#' @aliases rsp_distance_profile -#' @description sp_profile functions for studying similarities (or -#' dissimilarities) within multi-profile (re)SPECIATE data sets +#' @description (re)SPECIATE functions for studying similarities (or +#' dissimilarities) within (re)SPECIATE data sets -#' @description \code{\link{sp_profile_distance}} calculates the statistical distance +#' @description \code{\link{rsp_distance_profile}} calculates the statistical distance #' between re(SPECIATE) profiles, and clusters profiles according to nearness. -#' @param x A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. #' @param output Character vector, required function output: \code{'report'} the #' calculated distance matrix; \code{'plot'} a heat map of that distance #' matrix. #' @note Please note: function in development; structure and arguments may be #' subject to change. -#' @return Depending on the \code{output} option, \code{sp_profile_distance} returns +#' @return Depending on the \code{output} option, \code{sp_distance_profile} returns #' one or more of the following: the correlation matrix, a heat map of the #' correlation matrix. @@ -32,9 +32,6 @@ #NOTE -#' @rdname sp.cluster -#' @export - # using data.table for dcast # start build the code for the matching method @@ -45,33 +42,34 @@ # needs thinking through # needs options/formals -#output like in sp_species_cor +#output like in rsp_cor_species #also check through and consider other options in sp_profile_cor #currently tracking #think about how we handle too-big matrices, e.g. -# aa <- sp_profile(sp_find_profile("ae6", by="profile_type")) -# sp_profile_distance(aa) - +# aa <- rsp_profile(rsp_find_profile("ae6", by="profile_type")) +# rsp_distance_profile(aa) #test ###################### -#aa <- sp_profile(sp_find_profile("ae8", by="profile_type")) -#sp_profile_distance(aa) +#aa <- rsp_profile(rsp_find_profile("ae8", by="profile_type")) +#rsp_distance_profile(aa) +#' @rdname rsp.cluster +#' @export -sp_profile_distance <- function(x, output = c("plot", "report")){ +rsp_distance_profile <- function(rsp, output = c("plot", "report")){ #add .value if missing - x <- rsp_tidy_profile(x) + x <- .rsp_tidy_profile(rsp) # make by profile (rows) by species (columns) data.frame # move profile_code to row.names for heatmap - .x <- sp_dcast(x, widen = "species") + .x <- rsp_dcast(x, widen = "species") .tmp <- .x[-1:-2] row.names(.tmp) <- .x[,1] diff --git a/R/sp.cor.R b/R/rsp.cor.R similarity index 90% rename from R/sp.cor.R rename to R/rsp.cor.R index 8c14e9c..4e5559d 100644 --- a/R/sp.cor.R +++ b/R/rsp.cor.R @@ -1,13 +1,13 @@ -#' @name sp.cor +#' @name rsp.cor #' @title (re)SPECIATE Species Correlations -#' @aliases sp_species_cor +#' @aliases rsp_cor_species -#' @description sp_species functions for studying relationships between -#' species in multi-profile (re)SPECIATE data sets. +#' @description (re)SPECIATE functions for studying relationships between +#' species in (re)SPECIATE data sets. -#' @description \code{\link{sp_species_cor}} generates a by-species correlation +#' @description \code{\link{rsp_species_cor}} generates a by-species correlation #' matrix of the supplied (re)SPECIATE data sets. -#' @param x \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. #' @param min.n \code{numeric} (default 3), the minimum number of species measurements #' needed in a profile for the function to use it in correlation calculations. @@ -36,7 +36,7 @@ #' output. Options include: \code{'silent'} (default), to return the #' correlation matrix invisibly; \code{TRUE} to return the matrix #' (visibly); and, \code{FALSE} to not return it. -#' @return By default \code{sp_species_cor} invisibly returns the calculated +#' @return By default \code{rsp_cor_species} invisibly returns the calculated #' correlation matrix a plots it as a heat map, but arguments including #' \code{heatmap} and \code{report} can be used to modify function outputs. @@ -48,7 +48,7 @@ # PLUS when done maybe for make local function so same # can be used in other similar functions -#' @rdname sp.cor +#' @rdname rsp.cor #' @export # using data.table for dcast @@ -65,6 +65,10 @@ #to think about ######################### +#changed name from rsp_species_cor to rsp_cor_species +# rsp_species_cor +# then you could also have rsp_cor_profile + #speed up the correlation calculation # currently painful using for loop # can't use stats::cor on whole data set and include min.n option @@ -108,15 +112,20 @@ # The list options are not yet done... -#aa <- sp_profile(sp_find_profile("ae8", by="profile_type")) -#sp_species_cor(aa) +#aa <- rsp_profile(rsp_find_profile("ae8", by="profile_type")) +#rsp_cor_species(aa) + +#rsp_cor_species(rsp_q_pm.ae8()) -sp_species_cor <- function(x, min.n = 3, +rsp_cor_species <- function(rsp, min.n = 3, cols = c("#80FFFF", "#FFFFFF", "#FF80FF"), na.col = "#CFCFCF", heatmap.args = TRUE, key.args = TRUE, report = "silent"){ + + x <- rsp #quick fix for now + #if ref missing - .x <- sp_dcast(x, widen="species") + .x <- rsp_dcast(x, widen="species") #no point doing any have less than min.n values? .test <- apply(.x, 2, function(x) length(.x[!is.na(x)])) @@ -210,7 +219,7 @@ sp_species_cor <- function(x, min.n = 3, if(is.list(key.args)){ .k[names(key.args)] <- key.args } - do.call(rsp_col_key, .k) + do.call(.rsp_col_key, .k) } } diff --git a/R/xxx.R b/R/xxx.R index 48caad1..3a6fabf 100644 --- a/R/xxx.R +++ b/R/xxx.R @@ -480,7 +480,10 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", # or passing to another package/better home # (not sure how that fits with package remit) - +################################## +#used by: +################################## +# rsp_cor_species .rsp_col_key <- function(key, cols, x, y = NULL, ticks, nticks, diff --git a/man/sp.cluster.Rd b/man/rsp.cluster.Rd similarity index 50% rename from man/sp.cluster.Rd rename to man/rsp.cluster.Rd index 0e51b66..b4882e2 100644 --- a/man/sp.cluster.Rd +++ b/man/rsp.cluster.Rd @@ -1,14 +1,14 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.cluster.R -\name{sp.cluster} -\alias{sp.cluster} -\alias{sp_profile_distance} -\title{sp_profile clustering} +% Please edit documentation in R/rsp.cluster.R +\name{rsp.cluster} +\alias{rsp.cluster} +\alias{rsp_distance_profile} +\title{(re)SPECIATE profile cluster analysis methods} \usage{ -sp_profile_distance(x, output = c("plot", "report")) +rsp_distance_profile(rsp, output = c("plot", "report")) } \arguments{ -\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +\item{rsp}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) profiles.} \item{output}{Character vector, required function output: \code{'report'} the @@ -16,15 +16,15 @@ calculated distance matrix; \code{'plot'} a heat map of that distance matrix.} } \value{ -Depending on the \code{output} option, \code{sp_profile_distance} returns +Depending on the \code{output} option, \code{sp_distance_profile} returns one or more of the following: the correlation matrix, a heat map of the correlation matrix. } \description{ -sp_profile functions for studying similarities (or -dissimilarities) within multi-profile (re)SPECIATE data sets +(re)SPECIATE functions for studying similarities (or +dissimilarities) within (re)SPECIATE data sets -\code{\link{sp_profile_distance}} calculates the statistical distance +\code{\link{rsp_distance_profile}} calculates the statistical distance between re(SPECIATE) profiles, and clusters profiles according to nearness. } \note{ diff --git a/man/sp.cor.Rd b/man/rsp.cor.Rd similarity index 84% rename from man/sp.cor.Rd rename to man/rsp.cor.Rd index 046f5e3..ab01d24 100644 --- a/man/sp.cor.Rd +++ b/man/rsp.cor.Rd @@ -1,12 +1,12 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sp.cor.R -\name{sp.cor} -\alias{sp.cor} -\alias{sp_species_cor} +% Please edit documentation in R/rsp.cor.R +\name{rsp.cor} +\alias{rsp.cor} +\alias{rsp_cor_species} \title{(re)SPECIATE Species Correlations} \usage{ -sp_species_cor( - x, +rsp_cor_species( + rsp, min.n = 3, cols = c("#80FFFF", "#FFFFFF", "#FF80FF"), na.col = "#CFCFCF", @@ -16,7 +16,7 @@ sp_species_cor( ) } \arguments{ -\item{x}{\code{respeciate} object, a \code{data.frame} of re(SPECIATE) +\item{rsp}{\code{respeciate} object, a \code{data.frame} of re(SPECIATE) profiles.} \item{min.n}{\code{numeric} (default 3), the minimum number of species measurements @@ -53,14 +53,14 @@ correlation matrix invisibly; \code{TRUE} to return the matrix (visibly); and, \code{FALSE} to not return it.} } \value{ -By default \code{sp_species_cor} invisibly returns the calculated +By default \code{rsp_cor_species} invisibly returns the calculated correlation matrix a plots it as a heat map, but arguments including \code{heatmap} and \code{report} can be used to modify function outputs. } \description{ -sp_species functions for studying relationships between -species in multi-profile (re)SPECIATE data sets. +(re)SPECIATE functions for studying relationships between +species in (re)SPECIATE data sets. -\code{\link{sp_species_cor}} generates a by-species correlation +\code{\link{rsp_species_cor}} generates a by-species correlation matrix of the supplied (re)SPECIATE data sets. } diff --git a/man/rsp.pls.Rd b/man/rsp.pls.Rd index 52782ef..4425c71 100644 --- a/man/rsp.pls.Rd +++ b/man/rsp.pls.Rd @@ -117,8 +117,8 @@ multiple options are available.} applies 'log' scaling to the primary Y axes of the plot.} } \value{ -\code{sp_pls_profile} returns a list of nls models, one per -profile/measurement set in \code{x}. The \code{pls_} functions work with +\code{rsp_pls_profile} returns a list of nls models, one per +profile/measurement set in \code{rsp}. The \code{pls_} functions work with these outputs. \code{pls_report} generates a \code{data.frame} of model outputs, and is used of several of the other \code{pls_} functions. \code{pls_fit_species}, \code{pls_refit_species} and @@ -146,12 +146,12 @@ these PLS models. \note{ This implementation of PLS applies the following modeling constraints: -1. It generates a model of \code{x} that is positively constrained linear +1. It generates a model of \code{rsp} that is positively constrained linear product of the profiles in \code{ref}, so outputs can only be zero or more. Although the model is generated using \code{\link{nls}}, which is a Nonlinear Least Squares (NLS) model, the fitting term applied in this case is linear. -2. The number of species in \code{x} must be more that the number of +2. The number of species in \code{rsp} must be more that the number of profiles in \code{ref} to reduce the likelihood of over-fitting. } From 1515d3e31921a9447fe50c83fa7d86415876e5d2 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Wed, 8 May 2024 19:29:04 +0100 Subject: [PATCH 10/18] news update --- .Rproj.user/shared/notebooks/paths | 1 + DESCRIPTION | 2 +- NEWS.md | 3 ++- R/rsp.reshape.R | 18 +++++++++++------- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 88ea646..61df68c 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -19,6 +19,7 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.R="787EA0C5" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.average.R="67ED42C3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.build.R="5A264727" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.cluster.R="B197B439" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.cor.R="DE099ED6" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.info.R="FD1BAD48" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.match.R="7AE83929" diff --git a/DESCRIPTION b/DESCRIPTION index bb352d5..68d2ef0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: respeciate Title: Speciation profiles for gases and aerosols Version: 0.3.0 -Date: 2024-05-07 +Date: 2024-05-08 Description: Access to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) . Type: Package diff --git a/NEWS.md b/NEWS.md index 1cd25a5..79b8e6e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Release Notes Version 0.3 * [0.3.0] - * released 2024-05-07 + * released 2024-05-08 * code and documentation refresh... * added as.respeciate method; replacing related unexported code * sp_profile, sp_build_rsp_x updates; both now use as.respeciate @@ -9,6 +9,7 @@ part of object class rebuild * sp_pad to rsp_pad; sp_rescale to rsp_rescale; class rebuild * sp_match_profile to rsp_match_profile; class rebuild + * sp_cor to rsp_cor; sp_distance to rsp_dist; class rebuild # Release Notes Version 0.2 diff --git a/R/rsp.reshape.R b/R/rsp.reshape.R index 7078f30..ca0aac5 100644 --- a/R/rsp.reshape.R +++ b/R/rsp.reshape.R @@ -68,8 +68,7 @@ -#' @rdname rsp.reshape -#' @export + ## now imports from xxx.r ## #' @import data.table @@ -86,6 +85,9 @@ #long_to_wide reshape ###################### +#' @rdname rsp.reshape +#' @export + rsp_dcast <- function(rsp, widen = "species"){ #################### @@ -154,6 +156,10 @@ rsp_dcast <- function(rsp, widen = "species"){ out } +###################################### +# rsp_dcast(..., widen) shortcuts +###################################### + #' @rdname rsp.reshape #' @export @@ -162,7 +168,6 @@ rsp_dcast_profile <- function(rsp, widen = "profile"){ } - #' @rdname rsp.reshape #' @export @@ -173,8 +178,6 @@ rsp_dcast_species <- function(rsp=rsp, widen = "species"){ -#' @rdname rsp.reshape -#' @export ## now imports from xxx.r ## #' @import data.table @@ -195,6 +198,9 @@ rsp_dcast_species <- function(rsp=rsp, widen = "species"){ #wide_to_long reshape ###################### +#' @rdname rsp.reshape +#' @export + rsp_melt_wide <- function(rsp, pad = TRUE, drop.nas = TRUE){ #################### @@ -302,8 +308,6 @@ rsp_melt_wide <- function(rsp, pad = TRUE, drop.nas = TRUE){ } } - - #output #need to rationalise outputs!!! #.rsp_build_respeciate(out) From 7ed5266a56194ed8f33e813b130f3b329832d668 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Thu, 9 May 2024 18:03:04 +0100 Subject: [PATCH 11/18] all unexported code renamed .rsp... --- .Rproj.user/shared/notebooks/paths | 7 +- R/respeciate.generics.R | 1024 +++++++++++++--------------- R/rsp.R | 6 +- R/rsp.average.R | 30 +- R/rsp.build.R | 29 +- R/rsp.cor.R | 2 +- R/rsp.match.R | 20 +- R/rsp.pad.R | 124 ++-- R/rsp.plot.R | 13 +- R/rsp.rescale.R | 2 +- R/xxx.R | 505 ++++++++------ man/respeciate.generics.Rd | 2 +- man/{sp.build.Rd => rsp.build.Rd} | 4 +- man/rsp.cor.Rd | 2 +- man/rsp.match.Rd | 2 +- man/rsp.rescale.Rd | 6 +- 16 files changed, 913 insertions(+), 865 deletions(-) rename man/{sp.build.Rd => rsp.build.Rd} (94%) diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 61df68c..76d9db9 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -27,14 +27,9 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pls.R="430E71B2" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.q.R="2721C15F" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.rescale.R="2C292C00" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.reshape.R="94C8EF32" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.x.R="4DA91187" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp_x.R="3CCD538B" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cluster.R="49C0F861" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.cor.R="3F1DA8E5" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sp.pls.R="EACFE9A4" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spq.R="3AD9ACC8" -C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/spx.R="CA18044A" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/sysdata.R="82103C52" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/xxx.R="3415FF44" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/README.Rmd="887EDA27" diff --git a/R/respeciate.generics.R b/R/respeciate.generics.R index 8a4f7b8..4c04d45 100644 --- a/R/respeciate.generics.R +++ b/R/respeciate.generics.R @@ -22,7 +22,7 @@ #' @param n when plotting or printing a multi-profile object, the #' maximum number of profiles to report. #' @param ... any extra arguments, mostly ignored except by -#' \code{plot} which passes them to \code{\link{sp_plot_profile}}. +#' \code{plot} which passes them to \code{\link{rsp_plot_profile}}. #' @param object like \code{x} but for \code{summary}. #' @note \code{respeciate} objects revert to #' \code{data.frame}s when not doing anything @@ -247,25 +247,25 @@ print.respeciate <- ########################## ########################## -rsp_print.respeciate.old <- function(x, n = NULL, ...){ - test <- .rsp_test_respeciate(x, level = 2, silent = TRUE) - if(test == "respeciate.profile.ref"){ - if(is.null(n)){ - n <- 100 - } - return(.rsp_print_respeciate_profile(x=x, n=n, ...)) - } - if(test == "respeciate.species.ref"){ - if(is.null(n)){ - n <- 10 - } - return(.rsp_print_respeciate_species(x=x, n=n, ...)) - } - if(is.null(n)){ - n <- 10 - } - .rsp_print_respeciate(x=x, n=n, ...) -} +#rsp_print.respeciate.old <- function(x, n = NULL, ...){ +# test <- .rsp_test_respeciate(x, level = 2, silent = TRUE) +# if(test == "respeciate.profile.ref"){ +# if(is.null(n)){ +# n <- 100 +# } +# return(.rsp_print_respeciate_profile(x=x, n=n, ...)) +# } +# if(test == "respeciate.species.ref"){ +# if(is.null(n)){ +# n <- 10 +# } +# return(.rsp_print_respeciate_species(x=x, n=n, ...)) +# } +# if(is.null(n)){ +# n <- 10 +# } +# .rsp_print_respeciate(x=x, n=n, ...) +#} ## rsp_print functions unexported ## further down @@ -333,7 +333,7 @@ print.rsp_pls <- function(x, n = NULL, ...){ #test is now set up to use data.table -#this is now sp_plot_profile +#this is now rsp_plot_profile plot.respeciate <- function(x, ...){ rsp_plot_profile(x, ...) @@ -353,73 +353,73 @@ plot.respeciate <- function(x, ...){ ########################## ########################## -rsp_plot.respeciate.old <- - function(x, n=NULL, id=NULL, order=TRUE, ...){ +#rsp_plot.respeciate.old <- +# function(x, n=NULL, id=NULL, order=TRUE, ...){ #add .value if not there ## don't think .value works - x <- .rsp_tidy_profile(x) +# x <- .rsp_tidy_profile(x) ##test object type - test <- rsp_test_respeciate(x, level=2, silent=TRUE) - if(test != "respeciate"){ - if(test %in% c("respeciate.profile.ref", "respeciate.species.ref")){ - stop("No plot method for respeciate.reference files.") - } else { - stop("suspect respeciate object!") - } +# test <- rsp_test_respeciate(x, level=2, silent=TRUE) +# if(test != "respeciate"){ +# if(test %in% c("respeciate.profile.ref", "respeciate.species.ref")){ +# stop("No plot method for respeciate.reference files.") +# } else { +# stop("suspect respeciate object!") +# } #don't stop - respeciate profile - } +# } ##test something to plot - if(nrow(x)==0){ +# if(nrow(x)==0){ ###################### #think about this ###################### #maybe stop() instead??? #stop("empty respeciate object?") - return(invisible(NULL)) - } +# return(invisible(NULL)) +# } #hold extra args # passing to plot - .xargs <- list(...) +# .xargs <- list(...) #test number of profiles #and subset x, etc... - test <- unique(x$PROFILE_CODE) - if(is.null(n) & is.null(id)){ - id <- 1:length(test) - } else { - if(!is.null(n)){ - id <- 1:n - } - } - test <- test[id] - x <- x[x$PROFILE_CODE %in% test,] +# test <- unique(x$PROFILE_CODE) +# if(is.null(n) & is.null(id)){ +# id <- 1:length(test) +# } else { +# if(!is.null(n)){ +# id <- 1:n +# } +# } +# test <- test[id] +# x <- x[x$PROFILE_CODE %in% test,] #above will die if n-th profile not there - if(length(n)>6){ - warning(paste("\n\t", length(test), - " profiles (might be too many; suggest 6 or less...)", - "\n", sep="")) - } +# if(length(n)>6){ +# warning(paste("\n\t", length(test), +# " profiles (might be too many; suggest 6 or less...)", +# "\n", sep="")) +# } - x <- rsp_test_profile(x) +# x <- rsp_test_profile(x) - if(any(x$.n>1)){ - warning(paste("\n\t", - " found duplicate species in profiles (merged and averaged...)", - "\n", sep="")) - } - x$SPECIES_NAME <- rsp_tidy_species_name(x$SPECIES_NAME) +# if(any(x$.n>1)){ +# warning(paste("\n\t", +# " found duplicate species in profiles (merged and averaged...)", +# "\n", sep="")) +# } +# x$SPECIES_NAME <- rsp_tidy_species_name(x$SPECIES_NAME) #################################### #issue profile names are not always unique #################################### - test <- x - test$SPECIES_ID <- ".default" - test <- rsp_test_profile(test) +# test <- x +# test$SPECIES_ID <- ".default" +# test <- rsp_test_profile(test) ################### #rep_test #can now replace this with data.table version @@ -427,14 +427,14 @@ rsp_plot.respeciate.old <- ################### #does this need a warning? - if(length(unique(test$PROFILE_NAME)) 6 warning not appearing !!! -#option to have col as a function ??? - -#decide what to do about stacking -#log / bad.log??? - -#say no to stack logs! - -#would like it to handle logs force origin to 0 for standard -# and minimum for logs ??? - -#strip label font size??? - -#key? to reorder the auto.key test and rectangles??? -# key=list(space="right",adj=0,title="Legends", -# points=list(pch=1, -# col=trellis.par.get("superpose.symbol")$col[1:length(labels)]), -# text=list(labels)) +# do.call(barplot, .xargs) -#plot types??? - -# - -#test -#my <- "C:\\Users\\trakradmin\\OneDrive - University of Leeds\\Documents\\pkg\\respeciate\\test\\uk.metals.aurn.2b.rds" -#my <- sp_build_rsp_x(readRDS(my)) -#rsp_plot(my) - - -######################### -#next -########################## - -#now very messy... -#what can we rationalise??? -#profile name shortening -#profile name to code option??? -#species name to species id option??? - -.rsp_plot <- - function(x, id, order=TRUE, - log=FALSE, ...){ - - #setup - ################## - #add .value if not there - x <- .rsp_tidy_profile(x) - #others refs - .x.args <- list(...) - .sp.ord <- unique(x$SPECIES_ID) - .sp.pro <- unique(x$PROFILE_NAME) - #n/profile handling - profile <- if (missing(id)) { - profile <- .sp.pro - } else { - id - } - if (is.numeric(profile)) { - if (all(profile == -1)) { - profile <- .sp.pro - } - else { - profile <- .sp.pro[profile] - } - } - if (!any(profile %in% .sp.pro) | any(is.na(profile))) { - stop("RSP> unknown profile(s) or missing ids, please check", call. = FALSE) - } - - if(length(profile)>8 & missing(id)){ - warning("RSP> ", length(profile), " profiles... ", - "plot foreshorten to 8 to reduce cluttering", - "\n\t (maybe use id to force larger range if sure)", - sep="", call.=FALSE) - profile <- profile[1:8] - } - x <- x[x$PROFILE_NAME %in% profile,] - - ##test object type - test <- rsp_test_respeciate(x, level=2, silent=TRUE) - if(test != "respeciate"){ - if(test %in% c("respeciate.profile.ref", "respeciate.species.ref")){ - stop("RSP> No plot method for respeciate.reference files.", - call. = FALSE) - } else { - stop("RSP> suspect respeciate object!", - call. = FALSE) - } - #don't stop - respeciate profile - } - - ##test something to plot - if(nrow(x)==0){ - ###################### - #think about this - ###################### - #maybe stop() instead??? - #stop("empty respeciate object?") - #maybe warning() aw well?? - return(invisible(NULL)) - } - - x <- rsp_test_profile(x) - - if(any(x$.n>1)){ - warning(paste("RSP> found duplicate species in profiles (merged and averaged...)", - sep=""), call.=FALSE) - } - x$SPECIES_NAME <- rsp_tidy_species_name(x$SPECIES_NAME) - - #################################### - #issue profile names are not always unique - #################################### - test <- x - test$SPECIES_ID <- ".default" - test <- rsp_test_profile(test) - ################### - #rep_test - #can now replace this with data.table version - #BUT check naming conventions for .n - ################### - - #does this need a warning? - if(length(unique(test$PROFILE_NAME)) found profiles with common names (making unique...)", - sep=""), call. = FALSE) - test$PROFILE_NAME <- make.unique(test$PROFILE_NAME) - x <- x[names(x) != "PROFILE_NAME"] - x <- merge(x, test[c("PROFILE_NAME", "PROFILE_CODE")], by="PROFILE_CODE") - } - - - #x$PROFILE_NAME <- make.unique(x$PROFILE_NAME) +# } - #order largest to smallest - ############################# - #like to also be able to order by molecular weight - ############################## - if(order){ - ################################ - #bit of a cheat... - ################################ - test <- x - test$PROFILE_CODE <- ".default" - test <- rsp_test_profile(test) - #previous barplot had bedside - if("stack" %in% names(.x.args) && .x.args$stack){ - test <- test[order(test$.total, decreasing = TRUE),] - xx <- unique(test$SPECIES_NAME) - } else { - test <- x[order(x$WEIGHT_PERCENT, decreasing = TRUE),] - xx <- unique(test$SPECIES_NAME) - } - } else { - xx <- unique(x$SPECIES_NAME) - } - x <- x[c("WEIGHT_PERCENT", "PROFILE_NAME", "SPECIES_NAME")] - - x$SPECIES_NAME <- factor(x$SPECIES_NAME, - levels = xx) - - ################## - #profile bar chart - ################## - p1.ls <- list(x= WEIGHT_PERCENT~SPECIES_NAME, - data=x, ylab="Profile Loading", xlab="", - #NB: prepanel seemed to break ylim when stacking - panel = function(x, y, origin, ylim, ...){ - rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), - panel.grid, ...) - if(missing(origin)){ - origin <- if(min(y, na.rm=TRUE) < 0 ) { - min(y, na.rm=TRUE) - 0.02 - } else { - 0 - } - } - panel.barchart(x=x, y=y, origin=origin, ylim=ylim, ...) - }, - between=list(y=.2), - scales=list(x=list(rot=90, - cex=0.7, - alternating=1), - y=list(rot=c(0,90), - cex=0.7, - alternating=3, - relation="free")) - ) - #, - #auto.key=list(space="right", columns = 1, - # cex=0.7, - # points=FALSE, - # rectangles=TRUE)) - ################# - #this may need refining... - - ##################### - #this is involved... - - if("col" %in% names(.x.args)){ - if(is.function(.x.args$col)){ - .x.args$col <- .x.args$col(length(profile)) - } - } - if(length(profile)>1){ - #panel or group profiles? - if("panel.profiles" %in% names(.x.args)){ - p1.ls$x <- WEIGHT_PERCENT~SPECIES_NAME | PROFILE_NAME - } else { - p1.ls$groups <- x$PROFILE_NAME - if(!"col" %in% names(p1.ls)){ - p1.ls$col <- rep(trellis.par.get("superpose.polygon")$col, - length.out=length(profile)) - } - } - } - if(log){ - if("stack" %in% names(.x.args) && .x.args$stack){ - stop("RSP> sorry currently don't stack logs...", - call. = FALSE) - } - #previous - p1.ls$scales$y$log <- 10 - p1.ls$yscale.components <- rsp_yscale.component.log10 - } - p1.ls <- modifyList(p1.ls, .x.args) - if("groups" %in% names(p1.ls) & length(profile)>1){ - #add key... if auto.key not there - .tmp <- if("col" %in% names(p1.ls)){ - rep(p1.ls$col, length.out = length(profile)) - } else { - rep(trellis.par.get("superpose.polygon")$col, - length.out=length(profile)) - } - p1.ls$key <- list(space="right", - #title="Legends", - rectangles=list(col=.tmp), - text = list(profile, cex=0.7)) - } - if("key" %in% names(.x.args)){ - p1.ls$key <- modifyList(p1.ls$key, .x.args$key) - } - if("col" %in% names(p1.ls)){ - p1.ls$par.settings = list(superpose.polygon = list(col = p1.ls$col), - superpose.symbol = list(fill = p1.ls$col)) - } - p1 <- do.call(barchart, p1.ls) - return(p1) - } ################################## #summary @@ -876,11 +617,275 @@ summary.respeciate <- +################################ +# not sure about this +################################ + +############################## +#plot.respeciate using lattice +############################## + +#to do +##################### + +#layout ??? +#n > 6 warning not appearing !!! +#option to have col as a function ??? + +#decide what to do about stacking +#log / bad.log??? + +#say no to stack logs! + +#would like it to handle logs force origin to 0 for standard +# and minimum for logs ??? + +#strip label font size??? + +#key? to reorder the auto.key test and rectangles??? +# key=list(space="right",adj=0,title="Legends", +# points=list(pch=1, +# col=trellis.par.get("superpose.symbol")$col[1:length(labels)]), +# text=list(labels)) + +#plot types??? + +# + +#test +#my <- "C:\\Users\\trakradmin\\OneDrive - University of Leeds\\Documents\\pkg\\respeciate\\test\\uk.metals.aurn.2b.rds" +#my <- sp_build_rsp_x(readRDS(my)) +#rsp_plot(my) + + +######################### +#next +########################## + +#now very messy... +#what can we rationalise??? +#profile name shortening +#profile name to code option??? +#species name to species id option??? + +#.rsp_plot <- +# function(x, id, order=TRUE, +# log=FALSE, ...){ +# +# #setup +# ################## +# #add .value if not there +# x <- .rsp_tidy_profile(x) +# #others refs +# .x.args <- list(...) +# .sp.ord <- unique(x$SPECIES_ID) +# .sp.pro <- unique(x$PROFILE_NAME) +# #n/profile handling +# profile <- if (missing(id)) { +# profile <- .sp.pro +# } else { +# id +# } +# if (is.numeric(profile)) { +# if (all(profile == -1)) { +# profile <- .sp.pro +# } +# else { +# profile <- .sp.pro[profile] +# } +# } +# if (!any(profile %in% .sp.pro) | any(is.na(profile))) { +# stop("RSP> unknown profile(s) or missing ids, please check", call. = FALSE) +# } +# +# if(length(profile)>8 & missing(id)){ +# warning("RSP> ", length(profile), " profiles... ", +# "plot foreshorten to 8 to reduce cluttering", +# "\n\t (maybe use id to force larger range if sure)", +# sep="", call.=FALSE) +# profile <- profile[1:8] +# } +# x <- x[x$PROFILE_NAME %in% profile,] +# +# ##test object type +# test <- rsp_test_respeciate(x, level=2, silent=TRUE) +# if(test != "respeciate"){ +# if(test %in% c("respeciate.profile.ref", "respeciate.species.ref")){ +# stop("RSP> No plot method for respeciate.reference files.", +# call. = FALSE) +# } else { +# stop("RSP> suspect respeciate object!", +# call. = FALSE) +# } +# #don't stop - respeciate profile +# } +# +# ##test something to plot +# if(nrow(x)==0){ +# ###################### +# #think about this +# ###################### +# #maybe stop() instead??? +# #stop("empty respeciate object?") +# #maybe warning() aw well?? +# return(invisible(NULL)) +# } +# +# x <- rsp_test_profile(x) +# +# if(any(x$.n>1)){ +# warning(paste("RSP> found duplicate species in profiles (merged and averaged...)", +# sep=""), call.=FALSE) +# } +# x$SPECIES_NAME <- rsp_tidy_species_name(x$SPECIES_NAME) +# +# #################################### +# #issue profile names are not always unique +# #################################### +# test <- x +# test$SPECIES_ID <- ".default" +# test <- rsp_test_profile(test) +# ################### +# #rep_test +# #can now replace this with data.table version +# #BUT check naming conventions for .n +# ################### +# +# #does this need a warning? +# if(length(unique(test$PROFILE_NAME)) found profiles with common names (making unique...)", +# sep=""), call. = FALSE) +# test$PROFILE_NAME <- make.unique(test$PROFILE_NAME) +# x <- x[names(x) != "PROFILE_NAME"] +# x <- merge(x, test[c("PROFILE_NAME", "PROFILE_CODE")], by="PROFILE_CODE") +# } +# +# +# #x$PROFILE_NAME <- make.unique(x$PROFILE_NAME) +# +# #order largest to smallest +# ############################# +# #like to also be able to order by molecular weight +# ############################## +# if(order){ +# ################################ +# #bit of a cheat... +# ################################ +# test <- x +# test$PROFILE_CODE <- ".default" +# test <- rsp_test_profile(test) +# #previous barplot had bedside +# if("stack" %in% names(.x.args) && .x.args$stack){ +# test <- test[order(test$.total, decreasing = TRUE),] +# xx <- unique(test$SPECIES_NAME) +# } else { +# test <- x[order(x$WEIGHT_PERCENT, decreasing = TRUE),] +# xx <- unique(test$SPECIES_NAME) +# } +# } else { +# xx <- unique(x$SPECIES_NAME) +# } +# x <- x[c("WEIGHT_PERCENT", "PROFILE_NAME", "SPECIES_NAME")] +# +# x$SPECIES_NAME <- factor(x$SPECIES_NAME, +# levels = xx) +# +# ################## +# #profile bar chart +# ################## +# p1.ls <- list(x= WEIGHT_PERCENT~SPECIES_NAME, +# data=x, ylab="Profile Loading", xlab="", +# #NB: prepanel seemed to break ylim when stacking +# panel = function(x, y, origin, ylim, ...){ +# rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), +# panel.grid, ...) +# if(missing(origin)){ +# origin <- if(min(y, na.rm=TRUE) < 0 ) { +# min(y, na.rm=TRUE) - 0.02 +# } else { +# 0 +# } +# } +# panel.barchart(x=x, y=y, origin=origin, ylim=ylim, ...) +# }, +# between=list(y=.2), +# scales=list(x=list(rot=90, +# cex=0.7, +# alternating=1), +# y=list(rot=c(0,90), +# cex=0.7, +# alternating=3, +# relation="free")) +# ) +# #, +# #auto.key=list(space="right", columns = 1, +# # cex=0.7, +# # points=FALSE, +# # rectangles=TRUE)) +# ################# +# #this may need refining... +# +# ##################### +# #this is involved... +# +# if("col" %in% names(.x.args)){ +# if(is.function(.x.args$col)){ +# .x.args$col <- .x.args$col(length(profile)) +# } +# } +# +# if(length(profile)>1){ +# #panel or group profiles? +# if("panel.profiles" %in% names(.x.args)){ +# p1.ls$x <- WEIGHT_PERCENT~SPECIES_NAME | PROFILE_NAME +# } else { +# p1.ls$groups <- x$PROFILE_NAME +# if(!"col" %in% names(p1.ls)){ +# p1.ls$col <- rep(trellis.par.get("superpose.polygon")$col, +# length.out=length(profile)) +# } +# } +# } +# +# if(log){ +# if("stack" %in% names(.x.args) && .x.args$stack){ +# stop("RSP> sorry currently don't stack logs...", +# call. = FALSE) +# } +# #previous +# p1.ls$scales$y$log <- 10 +# p1.ls$yscale.components <- rsp_yscale.component.log10 +# } +# p1.ls <- modifyList(p1.ls, .x.args) +# if("groups" %in% names(p1.ls) & length(profile)>1){ +# #add key... if auto.key not there +# .tmp <- if("col" %in% names(p1.ls)){ +# rep(p1.ls$col, length.out = length(profile)) +# } else { +# rep(trellis.par.get("superpose.polygon")$col, +# length.out=length(profile)) +# } +# p1.ls$key <- list(space="right", +# #title="Legends", +# rectangles=list(col=.tmp), +# text = list(profile, cex=0.7)) +# } +# if("key" %in% names(.x.args)){ +# p1.ls$key <- modifyList(p1.ls$key, .x.args$key) +# } +# if("col" %in% names(p1.ls)){ +# p1.ls$par.settings = list(superpose.polygon = list(col = p1.ls$col), +# superpose.symbol = list(fill = p1.ls$col)) +# } +# p1 <- do.call(barchart, p1.ls) +# return(p1) +# } + ################################# -#unexported code +#old code ################################# @@ -1012,63 +1017,12 @@ summary.respeciate <- -################################### -#class builds -################################### - -################################### -# hoping to drop this -# as.respeciate to supersede -################################### - - -#rsp_build_respeciate.spcs <- -# function(x, ...){ - #build - #add .value -# x <- rsp_tidy_profile(x) -# class(x) <- c("respeciate.spcs", "data.frame") -# x -# } - -#rsp_build_respeciate.ref <- -# function(x, ...){ - #build -# class(x) <- c("respeciate.ref", "data.frame") -# x -# } - -.rsp_build_respeciate <- - function(x, ...){ - x <- as.data.frame(x) - if("WEIGHT_PERCENT" %in% names(x)) { - x$.value <- x$WEIGHT_PERCENT - } - class(x) <- c("respeciate", class(x)) - x - } - - - -########################### -#split respeciate by profile -########################### -#currently not exported -#quick code assumed CODE is unique to profile -#need to test this -#not sure we are using this any more -# i think rsp_test, then rsp_test.2 replaced -# and code in plot.respeciate.old ??? -.rsp_split_profile <- function(x){ - ref <- unique(x$PROFILE_CODE) - lapply(ref, function(y) x[x$PROFILE_CODE==y,]) -} ######################## @@ -1122,122 +1076,122 @@ summary.respeciate <- ########################## ########################## -plot.respeciate.old <- - function(x, n=NULL, order=TRUE, ..., - legend.text=NULL, - args.legend = NULL){ - - #test number of profiles - #and subset x, etc... - test <- unique(x$PROFILE_CODE) - if(is.null(n)) n <- 1:length(test) - test <- test[n] - x <- x[x$PROFILE_CODE %in% test,] - #above will die if n-th profile not there - if(length(n)>6){ - warning(paste("\n\t", length(test), - " profiles (might be too many; suggest 6 or less...)", - "\n", sep="")) - } - test.names <- make.unique(sapply(test, - function(y) subset(x, - PROFILE_CODE==y)$PROFILE_NAME[1])) - - #check anything left to work with - if(length(test)==0){ - stop("empty (or bad) respeciate object?") - } - - #assuming multiple profiles - #build common data (could use dplyr) - x <- x[c("PROFILE_NAME", "PROFILE_CODE", - "SPECIES_NAME", "SPECIES_ID", "SPEC_MW", - "WEIGHT_PERCENT")] - x <- rsp_split_profile(x) - x <- suppressWarnings(Reduce(function(x, y) - merge(x=x, y=y, - by=c("SPECIES_ID", "SPECIES_NAME", - "SPEC_MW"), - all.x=T, all.y=T), x) - ) - #in case names not unique - names(x) <- make.names(names(x), unique=TRUE) - - #order largest to smallest - if(order){ - temp <- names(x)[grep("WEIGHT_PERCENT", names(x))] - temp <- apply(x[temp], 1, - function(y) sum(y, na.rm=TRUE)) - x <-x[rev(order(temp)),] - } - - #prepare plot - xx <- rsp_tidy_species_name(x$SPECIES_NAME) - x <- x[grep("WEIGHT_PERCENT", names(x))] - x[is.na(x)] <- 0 - ######################### - #above kills log but seems to be needed - #or we loose all records of one species if any are NA - b <- as.matrix(t(x)) - - #below now handled later - #if("beside" %in% names(list(...)) && - # list(...)$beside){ - # #need to replace this with something nicer - # temp <- rep(NA, length(xx) * length(n)) - # temp[(1:length(xx))*length(n)] <- xx - # xx <- temp - #} - - #plot legend handling - #could simplify this - if(is.null(legend.text)){ - legend.text <- test.names - } - if(is.null(args.legend)){ - args.legend <- list() - } - if(!"cex" %in% names(args.legend)){ - args.legend$cex <- 0.5 - } - if(!"x" %in% names(args.legend)){ - args.legend$x <- "topright" - } - - #need to do plot differently if horiz(ontal) - if("horiz" %in% names(list(...)) && - list(...)$horiz){ - #set up y annotation - ref <- max(nchar(xx), na.rm=TRUE) * 0.25 - if(ref>10) ref <- 10 #stop it getting silly with x names - op <- par(mar=c(2,ref,4,2)) - #plot standard - b <- barplot(b, yaxt="n", #space=0.5, - legend.text=legend.text, - args.legend =args.legend, - ...) - if(is.matrix(b)){ - b <- apply(b, 2, function(x) mean(x, na.rm=T)) - } - axis(2, at=b, labels=xx, las=2, tick=FALSE, cex.axis=0.5) - rm(op) - } else { - #set up x annotation - ref <- max(nchar(xx), na.rm=TRUE) * 0.25 - if(ref>10) ref <- 10 #stop it getting silly with x names - op <- par(mar=c(ref,4,4,2)) - #plot standard - b <- barplot(b, xaxt="n", #space=0.5, - legend.text=legend.text, - args.legend = args.legend, - ...) - if(is.matrix(b)){ - b <- apply(b, 2, function(x) mean(x, na.rm=T)) - } - axis(1, at=b, labels=xx, las=2, tick=FALSE, cex.axis=0.5) - rm(op) - } - } +#plot.respeciate.old <- +# function(x, n=NULL, order=TRUE, ..., +# legend.text=NULL, +# args.legend = NULL){ +# +# #test number of profiles +# #and subset x, etc... +# test <- unique(x$PROFILE_CODE) +# if(is.null(n)) n <- 1:length(test) +# test <- test[n] +# x <- x[x$PROFILE_CODE %in% test,] +# #above will die if n-th profile not there +# if(length(n)>6){ +# warning(paste("\n\t", length(test), +# " profiles (might be too many; suggest 6 or less...)", +# "\n", sep="")) +# } +# test.names <- make.unique(sapply(test, +# function(y) subset(x, +# PROFILE_CODE==y)$PROFILE_NAME[1])) +# +# #check anything left to work with +# if(length(test)==0){ +# stop("empty (or bad) respeciate object?") +# } +# +# #assuming multiple profiles +# #build common data (could use dplyr) +# x <- x[c("PROFILE_NAME", "PROFILE_CODE", +# "SPECIES_NAME", "SPECIES_ID", "SPEC_MW", +# "WEIGHT_PERCENT")] +# x <- rsp_split_profile(x) +# x <- suppressWarnings(Reduce(function(x, y) +# merge(x=x, y=y, +# by=c("SPECIES_ID", "SPECIES_NAME", +# "SPEC_MW"), +# all.x=T, all.y=T), x) +# ) +# #in case names not unique +# names(x) <- make.names(names(x), unique=TRUE) +# +# #order largest to smallest +# if(order){ +# temp <- names(x)[grep("WEIGHT_PERCENT", names(x))] +# temp <- apply(x[temp], 1, +# function(y) sum(y, na.rm=TRUE)) +# x <-x[rev(order(temp)),] +# } +# +# #prepare plot +# xx <- rsp_tidy_species_name(x$SPECIES_NAME) +# x <- x[grep("WEIGHT_PERCENT", names(x))] +# x[is.na(x)] <- 0 +# ######################### +# #above kills log but seems to be needed +# #or we loose all records of one species if any are NA +# b <- as.matrix(t(x)) +# +# #below now handled later +# #if("beside" %in% names(list(...)) && +# # list(...)$beside){ +# # #need to replace this with something nicer +# # temp <- rep(NA, length(xx) * length(n)) +# # temp[(1:length(xx))*length(n)] <- xx +# # xx <- temp +# #} +# +# #plot legend handling +# #could simplify this +# if(is.null(legend.text)){ +# legend.text <- test.names +# } +# if(is.null(args.legend)){ +# args.legend <- list() +# } +# if(!"cex" %in% names(args.legend)){ +# args.legend$cex <- 0.5 +# } +# if(!"x" %in% names(args.legend)){ +# args.legend$x <- "topright" +# } +# +# #need to do plot differently if horiz(ontal) +# if("horiz" %in% names(list(...)) && +# list(...)$horiz){ +# #set up y annotation +# ref <- max(nchar(xx), na.rm=TRUE) * 0.25 +# if(ref>10) ref <- 10 #stop it getting silly with x names +# op <- par(mar=c(2,ref,4,2)) +# #plot standard +# b <- barplot(b, yaxt="n", #space=0.5, +# legend.text=legend.text, +# args.legend =args.legend, +# ...) +# if(is.matrix(b)){ +# b <- apply(b, 2, function(x) mean(x, na.rm=T)) +# } +# axis(2, at=b, labels=xx, las=2, tick=FALSE, cex.axis=0.5) +# rm(op) +# } else { +# #set up x annotation +# ref <- max(nchar(xx), na.rm=TRUE) * 0.25 +# if(ref>10) ref <- 10 #stop it getting silly with x names +# op <- par(mar=c(ref,4,4,2)) +# #plot standard +# b <- barplot(b, xaxt="n", #space=0.5, +# legend.text=legend.text, +# args.legend = args.legend, +# ...) +# if(is.matrix(b)){ +# b <- apply(b, 2, function(x) mean(x, na.rm=T)) +# } +# axis(1, at=b, labels=xx, las=2, tick=FALSE, cex.axis=0.5) +# rm(op) +# } +# } diff --git a/R/rsp.R b/R/rsp.R index 0872276..2b910ef 100644 --- a/R/rsp.R +++ b/R/rsp.R @@ -59,8 +59,6 @@ ## local function to pad data using database??? -#' @rdname rsp -#' @export ## (now importing via xxx.r) ## #' @import data.table @@ -82,6 +80,9 @@ # based on previous sp_profile but using data.table # (0.1 version currently unexported sp_profile.old) +#' @rdname rsp +#' @export + rsp_profile <- function(..., include.refs=FALSE) { # code currently handles: @@ -166,6 +167,7 @@ rsp_profile <- function(..., include.refs=FALSE) { #' @rdname rsp #' @export + rsp <- function(...) { rsp_profile(...) } diff --git a/R/rsp.average.R b/R/rsp.average.R index a43d1f6..a14f159 100644 --- a/R/rsp.average.R +++ b/R/rsp.average.R @@ -29,16 +29,13 @@ #NOTE -#' @rdname rsp.average -#' @export -## #' @import data.table (in xxx.r) -# may need to set data.table specifically?? -# data.table::as.data.table, etc?? +##################### +#rsp_average_profile +##################### -###################### #average data -###################### +# multiple profiles to one mean averaged profile... ## in development @@ -69,6 +66,11 @@ #aa <- rsp_profile(sp_find_profile("ae8", by="profile_type")) #rsp_average_profile(aa) +#' @rdname rsp.average +#' @export +## #' @import data.table (in xxx.r) +# may need to set data.table specifically?? +# data.table::as.data.table, etc?? rsp_average_profile <- function(rsp, code = NULL, name = NULL, method = 1, ...){ @@ -144,12 +146,20 @@ rsp_average_profile <- function(rsp, code = NULL, name = NULL, method = 1, +################################# +############################### +## unexported +############################### +################################## + ##################################### -#sp_species_calc +#rsp_species_calc ##################################### -sp_species_calc <- function(x, calc = NULL, +# unfinished + +rsp_species_calc <- function(x, calc = NULL, id = NULL, name = NULL, ...){ #x is an rsp object @@ -158,7 +168,7 @@ sp_species_calc <- function(x, calc = NULL, .temp <- x #test we can use this..? print(calc) - .temp <- sp_dcast_species(.temp) + .temp <- rsp_dcast_species(.temp) if(length(grep("=", calc)) > 0){ print("is equals") } else { diff --git a/R/rsp.build.R b/R/rsp.build.R index b6d8aae..0e64c96 100644 --- a/R/rsp.build.R +++ b/R/rsp.build.R @@ -17,9 +17,10 @@ #' in \code{x} containing species name and id records, respectively. If not #' already named according to SPECIATE conventions, at least one of these will #' need to be assigned. -#' @param value (\code{character}) The name of the column in \code{x} +#' @param value (\code{character}) The name of the column in \code{x} #' containing measurement values. If not already named according to SPECIATE #' conventions, this will need to be assigned. +#' @param ... (any other arguments) currently ignored. #' @return \code{rsp_build}s attempt to build and return a (re)SPECIATE-like #' object that can be compared with data from re(SPECIATE). #' @note If you want to compare your data with profiles in the SPECIATE archive, @@ -43,7 +44,7 @@ ## weight_percent (and possibly .value) ############################## -# sp_build_rsp_x +# rsp_build_x ############################## # notes @@ -51,11 +52,10 @@ # 0.3. notes # went from sp_build_rsp_x to rsp_build_x -# using as.respeciate and adding rsp_x +# using as.respeciate and adding rsp_x subclass - -# rsp_build_x currently converts x as.data.frame(x) -# if tibble is loaded, any tibbles complicate things +# rsp_build_x currently converts x to data.frame (as.data.frame(x)) +# if tibble is loaded, this currently complicates things... # BUT might want to revisit this because it looked like: # the data structure was fine but @@ -68,7 +68,7 @@ # and drop back to tibble rather than data.frame.... -#' @rdname sp.build +#' @rdname rsp.build #' @export rsp_build_x <- @@ -76,7 +76,6 @@ rsp_build_x <- species_name, species_id, value, ...){ - # light build for a rsp_x data object # might need spec_mwt @@ -104,7 +103,7 @@ rsp_build_x <- # WEIGHT_PERCENT:if not there, if sent in call use # else if there use .value to look-up - # don't build/error if any of these missing and end of build + # should error if any of these missing at end of build # redundant? # currently not using ... @@ -147,28 +146,28 @@ rsp_build_x <- #current making all BUT values, character class if(!"PROFILE_NAME" %in% names(x) & (!missing(profile_name))){ if(!profile_name %in% names(x)){ - stop("sp_build> '", as.character(profile_name)[1], + stop("rsp_build> '", as.character(profile_name)[1], "' not in 'x'...", sep="", call. = FALSE) } x$PROFILE_NAME <- as.character(x[, profile_name]) } if(!"PROFILE_CODE" %in% names(x) & (!missing(profile_code))){ if(!profile_code %in% names(x)){ - stop("sp_build> '", as.character(profile_code)[1], + stop("rsp_build> '", as.character(profile_code)[1], "' not in 'x'...", sep="", call. = FALSE) } x$PROFILE_CODE <- as.character(x[, profile_code]) } if(!"SPECIES_NAME" %in% names(x) & (!missing(species_name))){ if(!species_name %in% names(x)){ - stop("sp_build> '", as.character(species_name)[1], + stop("rsp_build> '", as.character(species_name)[1], "' not in 'x'...", sep="", call. = FALSE) } x$SPECIES_NAME <- as.character(x[, species_name]) } if(!"SPECIES_ID" %in% names(x) & (!missing(species_id))){ if(!species_id %in% names(x)){ - stop("sp_build> '", as.character(species_id)[1], + stop("rsp_build> '", as.character(species_id)[1], "' not in 'x'...", sep="", call. = FALSE) } x$SPECIES_ID <- as.character(x[, species_id]) @@ -178,12 +177,12 @@ rsp_build_x <- if("WEIGHT_PERCENT" %in% names(x)){ x$.value <- x[, "WEIGHT_PERCENT"] } else { - stop("sp_build> 'value' not found for 'x'...", + stop("rsp_build> 'value' not found for 'x'...", sep="", call. = FALSE) } } else { if(!value %in% names(x)){ - stop("sp_build> '", as.character(value)[1], + stop("rsp_build> '", as.character(value)[1], "' not in 'x'...", sep="", call. = FALSE) } } diff --git a/R/rsp.cor.R b/R/rsp.cor.R index 4e5559d..01041d0 100644 --- a/R/rsp.cor.R +++ b/R/rsp.cor.R @@ -5,7 +5,7 @@ #' @description (re)SPECIATE functions for studying relationships between #' species in (re)SPECIATE data sets. -#' @description \code{\link{rsp_species_cor}} generates a by-species correlation +#' @description \code{\link{rsp_cor_species}} generates a by-species correlation #' matrix of the supplied (re)SPECIATE data sets. #' @param rsp \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. diff --git a/R/rsp.match.R b/R/rsp.match.R index 76a98d2..c999666 100644 --- a/R/rsp.match.R +++ b/R/rsp.match.R @@ -20,7 +20,7 @@ #' \code{\link{rsp_rescale}}. #' @param min.n \code{numeric} (default 8), the minimum number of paired #' species measurements in two profiles required for a match to be assessed. -#' See also \code{\link{rsp_species_cor}}. +#' See also \code{\link{rsp_cor_species}}. #' @param method Character (default 'pd'), the similarity measure to use, current #' options 'pd', the Pearson's Distance (1 - Pearson's correlation coefficient), #' or 'sid', the Standardized Identity Distance (See References). @@ -251,7 +251,7 @@ rsp_match_profile <- function(rsp, ref, matches=10, rescale=5, #min n ######################### #to do - # compare this and code in sp_species_cor + # compare this and code in rsp_cor_species # if/when we deal with this stop message this code may need to be updated ######################### @@ -437,22 +437,6 @@ rsp_match_profile <- function(rsp, ref, matches=10, rescale=5, -#need something to replace this that helps users build local profiles - -#basic build needs -# profile_name and profile_code -# species_name and species_id -# weight_percent (and possibly .value) - -########################### -# think this can go because we now have rsp_build_x??? - -rsp_ <- function(x){ - .o <- sp_profile(x) - .o$PROFILE_NAME <- paste("test", .o$PROFILE_NAME, sep=">") - .o$PROFILE_CODE <- "test" - .o -} diff --git a/R/rsp.pad.R b/R/rsp.pad.R index 5a29d13..85d7d8d 100644 --- a/R/rsp.pad.R +++ b/R/rsp.pad.R @@ -181,68 +181,68 @@ rsp_pad <- function(rsp, pad = "standard", drop.nas = TRUE){ #holding until testing on new code finished -sp_pad.old <- function(x, pad = "species", drop.nas = TRUE){ - - #tidy x - x <- rsp_tidy_profile(x) - #save class - .cls <- class(x) - out <- data.table::as.data.table(x) - - #set up padding for melts... - .long <- "nothing" - if(pad=="species"){ - .long <- "SPECIES_NAME" - } - if(pad=="profile"){ - .long <- "PROFILE_CODE" - } - - PROFILES <- data.table::as.data.table(sysdata$PROFILES) - SPECIES_PROPERTIES <- data.table::as.data.table(sysdata$SPECIES_PROPERTIES) - if(.long=="PROFILE_CODE"){ - #add in profile then species info - out <- merge(out, PROFILES, by = .long, all.y=FALSE, - all.x=TRUE, allow.cartesian=TRUE) - .tmp <- intersect(names(out), names(SPECIES_PROPERTIES)) - out <- merge(out, SPECIES_PROPERTIES, by = .tmp, all.y=FALSE, - all.x=TRUE, allow.cartesian=TRUE) - } - if(.long=="SPECIES_NAME"){ - #add in species then profiles info - out <- merge(out, SPECIES_PROPERTIES, by = .long, all.y=FALSE, - all.x=TRUE, allow.cartesian=TRUE) - .tmp <- intersect(names(out), names(PROFILES)) - out <- merge(out, PROFILES, by = .tmp, all.y=FALSE, - all.x=TRUE, allow.cartesian=TRUE) - } - #to get weight_percentage etc - if(pad %in% c("species", "profile", "weight")){ - SPECIES <- data.table::as.data.table(sysdata$SPECIES) - .tmp <- intersect(names(out), names(SPECIES)) - out <- merge(out, SPECIES, by = .tmp, all.y=FALSE, - all.x=TRUE, allow.cartesian=TRUE) - } else { - #not great but... - #if not padding WEIGHT_PERCENT has to be .value - out$WEIGHT_PERCENT <- out$.value - } - #drop.nas. - if(drop.nas){ - out <- out[!is.na(out$WEIGHT_PERCENT),] - } - ############################# - ##thinking about - ## removing all columns of just NAs... - # out[,which(unlist(lapply(out, function(x)!all(is.na(x))))), with=FALSE] - ## works if data.table object... - ############################ - - #not sure how to handle output... - #see notes - out <- as.data.frame(out) - rsp_build_respeciate(out) -} +#sp_pad.old <- function(x, pad = "species", drop.nas = TRUE){ +# +# #tidy x +# x <- rsp_tidy_profile(x) +# #save class +# .cls <- class(x) +# out <- data.table::as.data.table(x) +# +# #set up padding for melts... +# .long <- "nothing" +# if(pad=="species"){ +# .long <- "SPECIES_NAME" +# } +# if(pad=="profile"){ +# .long <- "PROFILE_CODE" +# } +# +# PROFILES <- data.table::as.data.table(sysdata$PROFILES) +# SPECIES_PROPERTIES <- data.table::as.data.table(sysdata$SPECIES_PROPERTIES) +# if(.long=="PROFILE_CODE"){ +# #add in profile then species info +# out <- merge(out, PROFILES, by = .long, all.y=FALSE, +# all.x=TRUE, allow.cartesian=TRUE) +# .tmp <- intersect(names(out), names(SPECIES_PROPERTIES)) +# out <- merge(out, SPECIES_PROPERTIES, by = .tmp, all.y=FALSE, +# all.x=TRUE, allow.cartesian=TRUE) +# } +# if(.long=="SPECIES_NAME"){ +# #add in species then profiles info +# out <- merge(out, SPECIES_PROPERTIES, by = .long, all.y=FALSE, +# all.x=TRUE, allow.cartesian=TRUE) +# .tmp <- intersect(names(out), names(PROFILES)) +# out <- merge(out, PROFILES, by = .tmp, all.y=FALSE, +# all.x=TRUE, allow.cartesian=TRUE) +# } +# #to get weight_percentage etc +# if(pad %in% c("species", "profile", "weight")){ +# SPECIES <- data.table::as.data.table(sysdata$SPECIES) +# .tmp <- intersect(names(out), names(SPECIES)) +# out <- merge(out, SPECIES, by = .tmp, all.y=FALSE, +# all.x=TRUE, allow.cartesian=TRUE) +# } else { +# #not great but... +# #if not padding WEIGHT_PERCENT has to be .value +# out$WEIGHT_PERCENT <- out$.value +# } +# #drop.nas. +# if(drop.nas){ +# out <- out[!is.na(out$WEIGHT_PERCENT),] +# } +# ############################# +# ##thinking about +# ## removing all columns of just NAs... +# # out[,which(unlist(lapply(out, function(x)!all(is.na(x))))), with=FALSE] +# ## works if data.table object... +# ############################ +# +# #not sure how to handle output... +# #see notes +# out <- as.data.frame(out) +# rsp_build_respeciate(out) +#} diff --git a/R/rsp.plot.R b/R/rsp.plot.R index 3fc431b..9cfd3d4 100644 --- a/R/rsp.plot.R +++ b/R/rsp.plot.R @@ -88,18 +88,16 @@ # that is do-able but may need an object class # (maybe like the openair code...) -#thinking about an sp_plot_compare(x, y) +#thinking about an rsp_plot_compare(x, y) # to compare profile x and profile(s) y # started project (in own-notes) ################################### -#sp_plot_profile +#rsp_plot_profile ################################### -#' @rdname rsp.plot -#' @export -# now done in xxx.r +# now any unexported code (.rsp...) should be in xxx.r ########################## #notes @@ -126,6 +124,9 @@ # what is x, how is it formatted, etc # then same for y, groups and cond... +#' @rdname rsp.plot +#' @export + rsp_plot_profile <- function(rsp, id, multi.profile = "group", order=TRUE, log=FALSE, ..., silent=FALSE){ @@ -140,7 +141,7 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", ###################### #to do #document issues - stop("RSP> Sorry, currently not stacking logs.", + stop("RSP> Sorry, currently not stacking log plots.", call. = FALSE) } } diff --git a/R/rsp.rescale.R b/R/rsp.rescale.R index 69fe872..7ef811e 100644 --- a/R/rsp.rescale.R +++ b/R/rsp.rescale.R @@ -8,7 +8,7 @@ #' a supplied (re)SPECIATE profile data set. This can be by profile or species #' subsets, and \code{rsp_rescale_profile} and \code{rsp_rescale_species} provide #' short-cuts to these options. -#' @param x A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) #' profiles. #' @param method numeric, the rescaling method to apply: #' 1 \code{x/total(x)}; diff --git a/R/xxx.R b/R/xxx.R index 3a6fabf..d600085 100644 --- a/R/xxx.R +++ b/R/xxx.R @@ -4,9 +4,9 @@ # standardise error messages, e.g. RSP> [function]: [issue] \n\t [fix]? -# make respeciate object argument rsp rather than x -# that helps sp_plot..() but not plot.respeciate() - +# made main respeciate object argument name rsp rather than x +# that helps rsp_plot..() if it passed args to lattice +# but not sure it really help with plot() if respeciate not loaded... ##################### #to check @@ -37,9 +37,9 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", # data.table used by: # rsp_test_profile, -# rsp_dcast_profile, and those that use dcast? -# sp_species_cor -# sp_profile_distance +# rsp_dcast..., rsp_melt... +# rsp_cor_species +# rsp_distance_profile # and others??? # need to identify them @@ -61,13 +61,99 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", #might be able to drop legend? # check plot.respeciate - +################################ ############################## -#common unexported +## common unexported ############################## +################################ # suggesting standardizing naming .rsp_[function_description] + +#.rsp_ +################################# +# tidy for rsp_x data setup + +#basic build needs +# profile_name and profile_code +# species_name and species_id +# weight_percent (and possibly .value) + +#notes +# think this can go because we now have rsp_build_x??? +# plus I don't think anyone but me (kr) has used it... + +.rsp_ <- function(x){ + .o <- rsp_profile(x) + .o$PROFILE_NAME <- paste("test", .o$PROFILE_NAME, sep=">") + .o$PROFILE_CODE <- "test" + .o +} + + + + +#.rsp_split_profile +####################################### +#split respeciate by profile + +#currently not exported +#quick code assumed CODE is unique to profile + +#need to test this + +#not sure we are using this any more ??? +# i think rsp_test, then rsp_test.2 replaced +# and code in plot.respeciate.old ??? + +.rsp_split_profile <- function(x){ + ref <- unique(x$PROFILE_CODE) + lapply(ref, function(y) x[x$PROFILE_CODE==y,]) +} + + + + + + +#.rsp_build_respeciate.... +################################# +# class builds + +# dropped +# rsp_build_respeciate.spcs +# rsp_build_respeciate.ref + +# hoping to drop last one... +# as.respeciate to supersede + +#rsp_build_respeciate.spcs <- +# function(x, ...){ +#build +#add .value +# x <- rsp_tidy_profile(x) +# class(x) <- c("respeciate.spcs", "data.frame") +# x +# } + +#rsp_build_respeciate.ref <- +# function(x, ...){ +#build +# class(x) <- c("respeciate.ref", "data.frame") +# x +# } + +.rsp_build_respeciate <- + function(x, ...){ + x <- as.data.frame(x) + if("WEIGHT_PERCENT" %in% names(x)) { + x$.value <- x$WEIGHT_PERCENT + } + class(x) <- c("respeciate", class(x)) + x + } + + #.rsp_plot_fix ######################### # general tidy function for data before plotting @@ -79,9 +165,9 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", #used by ################### #plot.respeciate -#sp_plot_profile +#rsp_plot_profile -#used by +#uses by #################### #.rsp_tidy_profile #.rsp_test_respeciate @@ -163,6 +249,11 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", ## could also test for .value +#used by +############################### +#.rsp_plot_fix + + .rsp_test_respeciate <- function(x, level = 1, silent = FALSE){ test <- class(x) @@ -205,6 +296,10 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", ## enabled in plot.respeciate, sp_profile_rescale, sp_profile_dcast ## rsp_test_profile +#used by +############################### +#.rsp_plot_fix + .rsp_tidy_profile <- function(x){ #.value is local version of weight if(!".value" %in% names(x)){ @@ -224,7 +319,6 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", #currently not exported #quick code to tidy species names -#currently used in plot.respeciate #note: not fully tested @@ -233,6 +327,10 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", # option foreshorten any names longer than [n] characters??? # similar function to tidy profile names +#used by +############################### +#plot.respeciate + .rsp_tidy_species_name <- function(x){ #attempts shorten names by remove other versions @@ -263,6 +361,10 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", #file:///C:/Users/trakradmin/Downloads/datatable.pdf ##rsp_test_profile(aa) +#used by +############################### +#.rsp_plot_fix + .rsp_test_profile <- function(x){ #set up .value if not there @@ -368,41 +470,15 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", # as.data.frame(out) #} +#################################### +#.rsp_col_key +#################################### +#color key for correlation matrices - - - - - - -##################### -#testing -##################### - -#playing - -#function(x, subset,...){ -# ans <- match.call() -# ans <- as.character(ans) -# return(ans) -#} - -#ggplot example -#require(ggplot2) -#ggplot() + geom_col(aes(y=SPECIES_NAME, x=WEIGHT_PERCENT), data=aa) + facet_grid(.~PROFILE_NAME) - - -############################ -#color key -############################ - -######################## -#using this in: -######################## - -#sp_species_cor - +#used by: +################################## +# rsp_cor_species #started with: #https://stackoverflow.com/questions/9314658/colorbar-from-custom-colorramppalette @@ -480,179 +556,175 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", # or passing to another package/better home # (not sure how that fits with package remit) -################################## -#used by: -################################## -# rsp_cor_species .rsp_col_key <- function(key, cols, x, y = NULL, - ticks, nticks, - na.col = "grey", na.cex = 0.25, - title = "", axes, bg, border, - type = 2, - ...){ - - #setup - op <- par(no.readonly = TRUE) - - if(missing(x)){ - #currently just doing this option - #like key.pos "top-left", key.style = 1 (horizontal, annotation below) - x <- 0.1 + ticks, nticks, + na.col = "grey", na.cex = 0.25, + title = "", axes, bg, border, + type = 2, + ...){ + + #setup + op <- par(no.readonly = TRUE) + + if(missing(x)){ + #currently just doing this option + #like key.pos "top-left", key.style = 1 (horizontal, annotation below) + x <- 0.1 + } + if(is.null(y)){ + y <- 0.9 + } + .min <- min(key, na.rm=TRUE) + .max <- max(key, na.rm=TRUE) + if(missing(ticks)){ + ticks <- pretty(c(.min, .max), 3) + } + if(missing(nticks)){ + nticks <- length(ticks) + } + .na.width <- na.cex * (.max-.min) + if(missing(bg)){ + bg <- "white" + } + if(missing(border)){ + border <- "black" + } + scale <- (length(cols)-1)/(.max-.min) + + #print(.max-.min) + #print(.na.width) + #print(.min) + #print(.max) + + #key.style 1 + if(type==1){ + #horizontal, header before, annotation after + #margins + .mai <- c(0.1,0.1,0.1,0.1) + if(title ==""){ + #no title + .fig <- c(x-0.1, x+0.1, y-0.04, y+0.1) + .wdt <- 12 + } else { + #title + .fig <- c(x-0.1, x+0.1, y-0.12, y+0.1) + .wdt <- 15 } - if(is.null(y)){ - y <- 0.9 + + if(is.na(na.col)){ + .brd <- c(.na.width, .na.width) + } else { + .brd <- c(.na.width, .na.width*2) } - .min <- min(key, na.rm=TRUE) - .max <- max(key, na.rm=TRUE) - if(missing(ticks)){ - ticks <- pretty(c(.min, .max), 3) + + #position col key + par(fig = .fig, mai = .mai, new=TRUE) + + #plot col key + + #region + plot(c(.min -.brd[2], .max+(.brd[1]*0.5)), c(-1, .wdt), + type='n', bty='n', xaxt='n', xlab='', + yaxt='n', ylab='', main="", font.main = 1) + #bg + border + rect(.min -.brd[2], -1, .max+(.brd[1]*0.5), .wdt, + col=bg, border=border) + #title + if(title !=""){ + text(.min+((.max-.min)/2)+(.na.width*0.75), 13, labels=title, col="black", cex=0.75) } - if(missing(nticks)){ - nticks <- length(ticks) + #col scale + for (i in 1:(length(cols)-0)) { + x <- (i-1)/scale + .min + rect(x,5,x+1/scale,10,col=cols[i], border=NA) } - .na.width <- na.cex * (.max-.min) - if(missing(bg)){ - bg <- "white" + #axes + lines(c(.min, .max), c(5,5), col="black") + for (i in ticks) { + lines(c(i,i), c(5,4),col="black") } - if(missing(border)){ - border <- "black" + #axes annotation + text(ticks, rep(2, length(ticks)), labels=ticks, + cex=0.75, adj=0.5) + #na block + if(!is.na(na.col)){ + rect(.min-(.na.width*0.5), 5,.min-(.na.width*1.5), 10, col=na.col, border="black") + text(.min-.na.width, 2, labels="NA", col="black", cex=0.75) } - scale <- (length(cols)-1)/(.max-.min) - -#print(.max-.min) -#print(.na.width) - #print(.min) - #print(.max) - - #key.style 1 - if(type==1){ - #horizontal, header before, annotation after - #margins - .mai <- c(0.1,0.1,0.1,0.1) - if(title ==""){ - #no title - .fig <- c(x-0.1, x+0.1, y-0.04, y+0.1) - .wdt <- 12 - } else { - #title - .fig <- c(x-0.1, x+0.1, y-0.12, y+0.1) - .wdt <- 15 - } - - if(is.na(na.col)){ - .brd <- c(.na.width, .na.width) - } else { - .brd <- c(.na.width, .na.width*2) - } - - #position col key - par(fig = .fig, mai = .mai, new=TRUE) - - #plot col key - - #region - plot(c(.min -.brd[2], .max+(.brd[1]*0.5)), c(-1, .wdt), - type='n', bty='n', xaxt='n', xlab='', - yaxt='n', ylab='', main="", font.main = 1) - #bg + border - rect(.min -.brd[2], -1, .max+(.brd[1]*0.5), .wdt, - col=bg, border=border) + + } + + if(type==2){ + #horizontal, header before, annotation after + #margins + .mai <- c(0.1,0.1,0.1,0.1) + if(title ==""){ + #no title + .fig <- c(x-0.05, x+0.05, y-0.2, y+0.1) + .wdt <- 12 + } else { #title - if(title !=""){ - text(.min+((.max-.min)/2)+(.na.width*0.75), 13, labels=title, col="black", cex=0.75) - } - #col scale - for (i in 1:(length(cols)-0)) { - x <- (i-1)/scale + .min - rect(x,5,x+1/scale,10,col=cols[i], border=NA) - } - #axes - lines(c(.min, .max), c(5,5), col="black") - for (i in ticks) { - lines(c(i,i), c(5,4),col="black") - } - #axes annotation - text(ticks, rep(2, length(ticks)), labels=ticks, - cex=0.75, adj=0.5) - #na block - if(!is.na(na.col)){ - rect(.min-(.na.width*0.5), 5,.min-(.na.width*1.5), 10, col=na.col, border="black") - text(.min-.na.width, 2, labels="NA", col="black", cex=0.75) - } + .fig <- c(x-0.05, x+0.05, y-0.2, y+0.1) + .wdt <- 15 + } + if(is.na(na.col)){ + .brd <- c(.na.width, .na.width) + } else { + .brd <- c(.na.width, .na.width*2) } - if(type==2){ - #horizontal, header before, annotation after - #margins - .mai <- c(0.1,0.1,0.1,0.1) - if(title ==""){ - #no title - .fig <- c(x-0.05, x+0.05, y-0.2, y+0.1) - .wdt <- 12 - } else { - #title - .fig <- c(x-0.05, x+0.05, y-0.2, y+0.1) - .wdt <- 15 - } - - if(is.na(na.col)){ - .brd <- c(.na.width, .na.width) - } else { - .brd <- c(.na.width, .na.width*2) - } - - #position col key - par(fig = .fig, mai = .mai, new=TRUE) - - #plot col key - - #region - plot(c(-1, .wdt), c(.min-.brd[2], .max+(.brd[1]*0.5)), - type='n', bty='n', xaxt='n', xlab='', - yaxt='n', ylab='', main="", font.main = 1) - #bg + border - rect(-1, .min-.brd[2], .wdt, .max+(.brd[1]*0.5), - col=bg, border=border) - #title - if(title !=""){ - text(.min+((.max-.min)/2)+(.na.width*0.75), 13, labels=title, - col="black", cex=0.75) - } - - #for (i in 1:(length(lut)-1)) { - # y = (i-1)/scale + min - # rect(0,y,10,y+1/scale, col=lut[i], border=NA) - #} - - #col scale - #################### - #note - #################### - #this needs work because rect needs colored border - #which kills transparent ranges... - #does that matter - for (i in 1:(length(cols))) { - y <- (i-1)/scale + .min - rect(5,y-(1/scale),10,y,col=cols[i], border=cols[i]) - } - #axes - lines(c(5,5), c(.min, .max), col="black") - for (i in ticks) { - lines(c(5,4), c(i,i), col="black") - } - #axes annotation - text(rep(2, length(ticks)), ticks, labels=ticks, - cex=0.75, adj=0.5) - #na block - if(!is.na(na.col)){ - rect(5, .min-(.na.width*0.5), 10, .min-(.na.width*1.5), col=na.col, border="black") - text(2, .min-.na.width, labels="NA", col="black", cex=0.75) - } + #position col key + par(fig = .fig, mai = .mai, new=TRUE) + + #plot col key + + #region + plot(c(-1, .wdt), c(.min-.brd[2], .max+(.brd[1]*0.5)), + type='n', bty='n', xaxt='n', xlab='', + yaxt='n', ylab='', main="", font.main = 1) + #bg + border + rect(-1, .min-.brd[2], .wdt, .max+(.brd[1]*0.5), + col=bg, border=border) + #title + if(title !=""){ + text(.min+((.max-.min)/2)+(.na.width*0.75), 13, labels=title, + col="black", cex=0.75) + } + #for (i in 1:(length(lut)-1)) { + # y = (i-1)/scale + min + # rect(0,y,10,y+1/scale, col=lut[i], border=NA) + #} + + #col scale + #################### + #note + #################### + #this needs work because rect needs colored border + #which kills transparent ranges... + #does that matter + for (i in 1:(length(cols))) { + y <- (i-1)/scale + .min + rect(5,y-(1/scale),10,y,col=cols[i], border=cols[i]) + } + #axes + lines(c(5,5), c(.min, .max), col="black") + for (i in ticks) { + lines(c(5,4), c(i,i), col="black") + } + #axes annotation + text(rep(2, length(ticks)), ticks, labels=ticks, + cex=0.75, adj=0.5) + #na block + if(!is.na(na.col)){ + rect(5, .min-(.na.width*0.5), 10, .min-(.na.width*1.5), col=na.col, border="black") + text(2, .min-.na.width, labels="NA", col="black", cex=0.75) } + } + par(op) } @@ -666,6 +738,35 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", + + + + +##################### +#testing +##################### + +#playing + +#function(x, subset,...){ +# ans <- match.call() +# ans <- as.character(ans) +# return(ans) +#} + +#ggplot example +#require(ggplot2) +#ggplot() + geom_col(aes(y=SPECIES_NAME, x=WEIGHT_PERCENT), data=aa) + facet_grid(.~PROFILE_NAME) + + + + + + + + + + ########################### #diagnostic ########################## diff --git a/man/respeciate.generics.Rd b/man/respeciate.generics.Rd index bc03961..353c115 100644 --- a/man/respeciate.generics.Rd +++ b/man/respeciate.generics.Rd @@ -27,7 +27,7 @@ as.respeciate(x, ...) object to be printed, plotted, etc.} \item{...}{any extra arguments, mostly ignored except by -\code{plot} which passes them to \code{\link{sp_plot_profile}}.} +\code{plot} which passes them to \code{\link{rsp_plot_profile}}.} \item{n}{when plotting or printing a multi-profile object, the maximum number of profiles to report.} diff --git a/man/sp.build.Rd b/man/rsp.build.Rd similarity index 94% rename from man/sp.build.Rd rename to man/rsp.build.Rd index 135bd73..634ac7d 100644 --- a/man/sp.build.Rd +++ b/man/rsp.build.Rd @@ -30,9 +30,11 @@ in \code{x} containing species name and id records, respectively. If not already named according to SPECIATE conventions, at least one of these will need to be assigned.} -\item{value}{(\code{character}) The name of the column in \code{x} +\item{value}{(\code{character}) The name of the column in \code{x} containing measurement values. If not already named according to SPECIATE conventions, this will need to be assigned.} + +\item{...}{(any other arguments) currently ignored.} } \value{ \code{rsp_build}s attempt to build and return a (re)SPECIATE-like diff --git a/man/rsp.cor.Rd b/man/rsp.cor.Rd index ab01d24..947e551 100644 --- a/man/rsp.cor.Rd +++ b/man/rsp.cor.Rd @@ -61,6 +61,6 @@ correlation matrix a plots it as a heat map, but arguments including (re)SPECIATE functions for studying relationships between species in (re)SPECIATE data sets. -\code{\link{rsp_species_cor}} generates a by-species correlation +\code{\link{rsp_cor_species}} generates a by-species correlation matrix of the supplied (re)SPECIATE data sets. } diff --git a/man/rsp.match.Rd b/man/rsp.match.Rd index bf7441b..988527d 100644 --- a/man/rsp.match.Rd +++ b/man/rsp.match.Rd @@ -34,7 +34,7 @@ comparing \code{rsp} and profiles in \code{ref}: options 0 to 5 handled by \item{min.n}{\code{numeric} (default 8), the minimum number of paired species measurements in two profiles required for a match to be assessed. -See also \code{\link{rsp_species_cor}}.} +See also \code{\link{rsp_cor_species}}.} \item{method}{Character (default 'pd'), the similarity measure to use, current options 'pd', the Pearson's Distance (1 - Pearson's correlation coefficient), diff --git a/man/rsp.rescale.Rd b/man/rsp.rescale.Rd index ec19ef6..f127aae 100644 --- a/man/rsp.rescale.Rd +++ b/man/rsp.rescale.Rd @@ -17,6 +17,9 @@ rsp_rescale_profile(rsp, method = 1, by = "profile") rsp_rescale_species(rsp, method = 2, by = "species") } \arguments{ +\item{rsp}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +profiles.} + \item{method}{numeric, the rescaling method to apply: 1 \code{x/total(x)}; 2 \code{x/mean(x)}; @@ -29,9 +32,6 @@ values.} \item{by}{character, when rescaling \code{x} with \code{\link{sp_rescale}}, the data type to group and rescale, currently \code{'species'} (default) or \code{'profile'}.} - -\item{x}{A \code{respeciate} object, a \code{data.frame} of re(SPECIATE) -profiles.} } \value{ \code{sp_rescale} and \code{sp_rescale} return the From 5e6e61bffac7b1887b2372db1df53eff0920ecde Mon Sep 17 00:00:00 2001 From: karlropkins Date: Thu, 9 May 2024 18:29:14 +0100 Subject: [PATCH 12/18] doc tidy re(SPECIATE) --- NEWS.md | 4 ++-- R/rsp.q.R | 10 +++++----- R/rsp.x.R | 8 ++++---- man/rsp.q.Rd | 10 +++++----- man/rsp.x.Rd | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index 79b8e6e..d63aae4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Release Notes Version 0.3 * [0.3.0] - * released 2024-05-08 + * released 2024-05-09 * code and documentation refresh... * added as.respeciate method; replacing related unexported code * sp_profile, sp_build_rsp_x updates; both now use as.respeciate @@ -9,7 +9,7 @@ part of object class rebuild * sp_pad to rsp_pad; sp_rescale to rsp_rescale; class rebuild * sp_match_profile to rsp_match_profile; class rebuild - * sp_cor to rsp_cor; sp_distance to rsp_dist; class rebuild + * sp_cor to rsp_cor; sp_distance to rsp_dist; class rebuild # Release Notes Version 0.2 diff --git a/R/rsp.q.R b/R/rsp.q.R index 77f1b50..9bd118c 100644 --- a/R/rsp.q.R +++ b/R/rsp.q.R @@ -1,19 +1,19 @@ #' @name rsp.q -#' @title rsp_q_ provide quick access to common re(SPECIATE) sub-samples +#' @title Quick access to common (re)SPECIATE subsets. #' @aliases rsp_q rsp_q_gas rsp_q_other rsp_q_pm rsp_q_pm.ae6 rsp_q_pm.ae8 #' rsp_q_pm.cr1 rsp_q_pm.simplified #' @description \code{rsp_q_} functions are quick access wrappers to commonly -#' requested re(SPECIATE) sub-samples. +#' requested (re)SPECIATE subsets. #' @return \code{rsp_q_} functions typically return a \code{respeciate} #' \code{data.frame} of the requested profiles. #' #' For example: #' -#' \code{rsp_q_gas()} returns all gaseous profiles in re(SPECIATE) +#' \code{rsp_q_gas()} returns all gaseous profiles in (re)SPECIATE #' (\code{PROFILE_TYPE == 'GAS'}). #' -#' \code{rsp_q_pm} returns all particulate matter (PM) profiles in re(SPECIATE) +#' \code{rsp_q_pm} returns all particulate matter (PM) profiles in (re)SPECIATE #' not classified as a special PM type (\code{PROFILE_TYPE == 'PM'}). #' #' The special PM types are subsets profiles intended for special @@ -21,7 +21,7 @@ #' \code{rsp_q_pm.ae8} (type \code{PM-AE8}), \code{rsp_q_pm.cr1} (type #' \code{PM-CR1}), and \code{rsp_q_pm.simplified} (type \code{PM-Simplified}). #' -#' \code{rsp_q_other} returns all profiles classified as other in re(SPECIATE) +#' \code{rsp_q_other} returns all profiles classified as other in (re)SPECIATE #' (\code{PROFILE_TYPE == 'OTHER'}). #' diff --git a/R/rsp.x.R b/R/rsp.x.R index 5f91fdc..f6238f8 100644 --- a/R/rsp.x.R +++ b/R/rsp.x.R @@ -1,15 +1,15 @@ #' @name rsp.x -#' @title rsp_x_ functions for grouping and subsetting re(SPECIATE) profiles +#' @title rsp_x_ functions for grouping and subsetting (re)SPECIATE profiles #' @aliases rsp_x rsp_x_copy rsp_x_nalkane rsp_x_btex # still wondering if these should be rsp_cut_... #' @description \code{rsp_x_} functions generate a vector of assignment -#' terms and can be used to subset or condition a supplied re(SPECIATE) +#' terms and can be used to subset or condition a supplied (re)SPECIATE #' \code{data.frame}. #' #' Most commonly, the \code{rsp_x_} functions accept a single input, a -#' re(SPECIATE) \code{data.frame} and return a logical vector of +#' (re)SPECIATE \code{data.frame} and return a logical vector of #' length \code{nrow(x)}, identifying species of interest as #' \code{TRUE}. So, for example, they can be used when #' \code{\link{subset}}ting in the form: @@ -23,7 +23,7 @@ #' also accepts a reference data set, \code{ref}, and a column identifier, #' \code{by}, and tests \code{rsp$by \%in\% unique(ref$by)}. #' -#' @param rsp a \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +#' @param rsp a \code{respeciate} object, a \code{data.frame} of (re)SPECIATE #' profiles. #' @param ref (\code{rsp_x_copy} only) a second \code{respeciate} object, to #' be used as reference when subsetting (or conditioning) \code{rsp}. diff --git a/man/rsp.q.Rd b/man/rsp.q.Rd index 4ac4fe0..86bde1b 100644 --- a/man/rsp.q.Rd +++ b/man/rsp.q.Rd @@ -10,7 +10,7 @@ \alias{rsp_q_pm.ae8} \alias{rsp_q_pm.cr1} \alias{rsp_q_pm.simplified} -\title{rsp_q_ provide quick access to common re(SPECIATE) sub-samples} +\title{Quick access to common (re)SPECIATE subsets.} \usage{ rsp_q_gas() @@ -32,10 +32,10 @@ rsp_q_pm.simplified() For example: -\code{rsp_q_gas()} returns all gaseous profiles in re(SPECIATE) +\code{rsp_q_gas()} returns all gaseous profiles in (re)SPECIATE (\code{PROFILE_TYPE == 'GAS'}). -\code{rsp_q_pm} returns all particulate matter (PM) profiles in re(SPECIATE) +\code{rsp_q_pm} returns all particulate matter (PM) profiles in (re)SPECIATE not classified as a special PM type (\code{PROFILE_TYPE == 'PM'}). The special PM types are subsets profiles intended for special @@ -43,10 +43,10 @@ applications, and these include \code{rsp_q_pm.ae6} (type \code{PM-AE6}), \code{rsp_q_pm.ae8} (type \code{PM-AE8}), \code{rsp_q_pm.cr1} (type \code{PM-CR1}), and \code{rsp_q_pm.simplified} (type \code{PM-Simplified}). -\code{rsp_q_other} returns all profiles classified as other in re(SPECIATE) +\code{rsp_q_other} returns all profiles classified as other in (re)SPECIATE (\code{PROFILE_TYPE == 'OTHER'}). } \description{ \code{rsp_q_} functions are quick access wrappers to commonly -requested re(SPECIATE) sub-samples. +requested (re)SPECIATE subsets. } diff --git a/man/rsp.x.Rd b/man/rsp.x.Rd index 5703f82..3e0119d 100644 --- a/man/rsp.x.Rd +++ b/man/rsp.x.Rd @@ -6,7 +6,7 @@ \alias{rsp_x} \alias{rsp_x_nalkane} \alias{rsp_x_btex} -\title{rsp_x_ functions for grouping and subsetting re(SPECIATE) profiles} +\title{rsp_x_ functions for grouping and subsetting (re)SPECIATE profiles} \usage{ rsp_x_copy(rsp, ref = NULL, by = "species_id") @@ -15,7 +15,7 @@ rsp_x_nalkane(rsp) rsp_x_btex(rsp) } \arguments{ -\item{rsp}{a \code{respeciate} object, a \code{data.frame} of re(SPECIATE) +\item{rsp}{a \code{respeciate} object, a \code{data.frame} of (re)SPECIATE profiles.} \item{ref}{(\code{rsp_x_copy} only) a second \code{respeciate} object, to @@ -35,11 +35,11 @@ identifies all species in the supplied reference data set. } \description{ \code{rsp_x_} functions generate a vector of assignment -terms and can be used to subset or condition a supplied re(SPECIATE) +terms and can be used to subset or condition a supplied (re)SPECIATE \code{data.frame}. Most commonly, the \code{rsp_x_} functions accept a single input, a -re(SPECIATE) \code{data.frame} and return a logical vector of +(re)SPECIATE \code{data.frame} and return a logical vector of length \code{nrow(x)}, identifying species of interest as \code{TRUE}. So, for example, they can be used when \code{\link{subset}}ting in the form: From 5d79393004b13a0d7017a5cd9f53a4c60993a3d4 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Mon, 13 May 2024 18:41:31 +0100 Subject: [PATCH 13/18] rsp_plots now track factors --- DESCRIPTION | 2 +- NEWS.md | 3 ++- R/rsp.plot.R | 65 +++++++++++++++++++++++++++++++++++----------------- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 68d2ef0..72cc4c5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: respeciate Title: Speciation profiles for gases and aerosols Version: 0.3.0 -Date: 2024-05-08 +Date: 2024-05-13 Description: Access to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) . Type: Package diff --git a/NEWS.md b/NEWS.md index d63aae4..1915710 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Release Notes Version 0.3 * [0.3.0] - * released 2024-05-09 + * released 2024-05-13 * code and documentation refresh... * added as.respeciate method; replacing related unexported code * sp_profile, sp_build_rsp_x updates; both now use as.respeciate @@ -10,6 +10,7 @@ * sp_pad to rsp_pad; sp_rescale to rsp_rescale; class rebuild * sp_match_profile to rsp_match_profile; class rebuild * sp_cor to rsp_cor; sp_distance to rsp_dist; class rebuild + * rsp_plots now track factors # Release Notes Version 0.2 diff --git a/R/rsp.plot.R b/R/rsp.plot.R index 9cfd3d4..bb9af43 100644 --- a/R/rsp.plot.R +++ b/R/rsp.plot.R @@ -141,13 +141,18 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", ###################### #to do #document issues - stop("RSP> Sorry, currently not stacking log plots.", + stop("RSP> Sorry, currently not stacking log plots", call. = FALSE) } } #others refs - #.sp.ord <- unique(x$SPECIES_ID) - .sp.pro <- unique(x$PROFILE_CODE) + #was profile_code; changed to profile_name + # might be an issue; some names not unique... + .sp.pro <- if(is.factor(x$PROFILE_NAME)) { + levels(x$PROFILE_NAME) + } else { + unique(x$PROFILE_NAME) + } #n/profile handling profile <- if (missing(id)) { .sp.pro @@ -175,7 +180,8 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", } profile <- profile[1:6] } - x <- x[x$PROFILE_CODE %in% profile,] + x <- x[x$PROFILE_NAME %in% profile,] + #check for duplicates, etc... #tidy naming etc... @@ -196,8 +202,9 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", #switching profile from profile_code to profile_name... # for plot labeling #################################### - profile <- unique(x$PROFILE_NAME) + #profile <- unique(x$PROFILE_NAME) #should think about other naming options??? + #(now using profile_name from start) #order largest to smallest ############################# @@ -225,7 +232,12 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", x$SPECIES_NAME <- factor(x$SPECIES_NAME, levels = xx) + if(!is.factor(x$PROFILE_NAME)){ + x$PROFILE_NAME <- factor(x$PROFILE_NAME, levels=unique(x$PROFILE_NAME)) + } + +#print(as.data.frame(x)) ################## #profile bar chart ################## @@ -286,7 +298,7 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", if("col" %in% names(p1.ls)){ if(is.function(p1.ls$col)){ p1.ls$col <- if("groups" %in% names(p1.ls)){ - p1.ls$col(length(profile)) + p1.ls$col(length(levels(x$PROFILE_NAME))) } else { p1.ls$col(1) } @@ -294,7 +306,7 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", } else { p1.ls$col <- if("groups" %in% names(p1.ls)){ rep(trellis.par.get("superpose.polygon")$col, - length.out=length(profile)) + length.out=length(levels(x$PROFILE_NAME))) } else { trellis.par.get("superpose.polygon")$col[1] } @@ -320,7 +332,7 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", .tmp <- list(space="top", #title="Legends", rectangles=list(col=rep(p1.ls$col, - length.out=length(profile))), + length.out=length(levels(x$PROFILE_NAME)))), text = list(profile, cex=0.7)) p1.ls$key <- if("key" %in% names(p1.ls)){ modifyList(.tmp, p1.ls$key) @@ -354,7 +366,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", .x.args <- list(...) ###################################### - #not sure we are using stack for this + #not sure we are using stack for this... ###################################### #currently not even trying to stack logs... if("stack" %in% names(.x.args) && .x.args$stack){ @@ -373,9 +385,11 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", #if already factor ??? # user could be forcing order ############################## - .sp.ord <- as.character(unique(x$SPECIES_ID)) - #.sp.pro <- unique(x$PROFILE_CODE) - #n/profile handling + .sp.ord <- if(is.factor(x$SPECIES_NAME)){ + levels(x$SPECIES_NAME) + } else { + as.character(unique(x$SPECIES_NAME)) + } species <- if (missing(id)) { .sp.ord } else { @@ -403,7 +417,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", } species <- species[1:20] } - x <- x[x$SPECIES_ID %in% species,] + x <- x[x$SPECIES_NAME %in% species,] #check for duplicates, etc... #tidy naming etc... @@ -421,15 +435,16 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", } #################################### - #current species ordering by id arg... + #current species ordering by name arg... #(see below about reordering) #################################### - species <- species[species %in% unique(x$SPECIES_ID)] - x$SPECIES_ID <- factor(x$SPECIES_ID, levels=species) - x <- x[order(x$SPECIES_ID),] - x$SPECIES_NAME <- factor(x$SPECIES_NAME, unique(x$SPECIES_NAME)) - species<- levels(x$SPECIES_NAME) - sp.ord <- as.numeric(factor(species, levels=sort(species))) + + species <- species[species %in% unique(x$SPECIES_NAME)] + x$SPECIES_NAME <- factor(x$SPECIES_NAME, levels=species) + x <- x[order(x$SPECIES_NAME),] + #sp.ord <- as.numeric(factor(species, levels=sort(species))) + #sp.ord <- 1:length(levels(x$SPECIES_NAME)) + ################################## #should think about other naming options??? @@ -466,6 +481,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", } x <- x[c(".value","PROFILE_CODE", "PROFILE_NAME", "SPECIES_NAME")] + #print(xx) ################## @@ -477,6 +493,13 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", #not padding, obviously not dropping nas... x <- rsp_melt_wide(rsp_dcast_species(x), pad=FALSE, drop.nas = FALSE) + if(!is.factor(x$PROFILE_NAME)){ + x$PROFILE_NAME <- factor(x$PROFILE_NAME, levels=unique(x$PROFILE_NAME)) + } + if(!is.factor(x$SPECIES_NAME)){ + x$SPECIES_NAME <- factor(x$SPECIES_NAME, levels=unique(x$SPECIES_NAME)) + } + ############################### #species handling ############################## @@ -574,7 +597,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", } } else { p1.ls$col <- if("groups" %in% names(p1.ls)){ - colorRampPalette(rainbow(12, s = 0.5, v = 1), + colorRampPalette(rainbow(12, s = 0.5, v = 1)[1:11], interpolate = "spline")(length(species)) #or: #colorRampPalette(rainbow(12, s = 0.5, v = 1),interpolate = "spline")(x) From c500882b983f2f4e2791b3b258c23e49e3eeee55 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Fri, 17 May 2024 16:05:56 +0100 Subject: [PATCH 14/18] pls_plot update --- .Rproj.user/shared/notebooks/paths | 3 + DESCRIPTION | 2 +- NAMESPACE | 1 + NEWS.md | 5 +- R/respeciate.generics.R | 2 +- R/rsp.build.R | 9 +- R/rsp.plot.R | 16 +- R/rsp.pls.R | 1500 ++-------------------------- R/rsp.pls.plot.R | 1272 +++++++++++++++++++++++ R/xxx.R | 603 ++++++++++- man/rsp.pls.Rd | 45 +- man/rsp.pls.plot.Rd | 46 + 12 files changed, 2041 insertions(+), 1463 deletions(-) create mode 100644 R/rsp.pls.plot.R create mode 100644 man/rsp.pls.plot.Rd diff --git a/.Rproj.user/shared/notebooks/paths b/.Rproj.user/shared/notebooks/paths index 76d9db9..496f918 100644 --- a/.Rproj.user/shared/notebooks/paths +++ b/.Rproj.user/shared/notebooks/paths @@ -3,6 +3,8 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/ITS/projects/Leeds_ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/ITS/projects/NERC_TRANSISTION/events/Clean Air Networks Conference/naei estimate.Rmd="1BB48545" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/_isolateContribution&breakPointAnalysis_KR_20230824.R="F18B98A3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/_projects/_paper_01_IntroToRespeciate/MS Access Versions/speciate_5.2_0/test.R="FCE2E494" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/_projects/_slides_02_CRC/r code 2/_marylebone_CRC_Slides_04.Rmd="5042A905" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/_projects/_slides_02_CRC/r code/_marylebone_CRC_Slides_04.Rmd="25A452C7" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/_projects/marylebone03/_marylebone_analysis_pls_01.Rmd="F2B723A3" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/_projects/marylebone03/_marylebone_initial_observations_01.Rmd="E72195E5" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/_projects/marylebone03/_marylebone_metals_03.Rmd="D2C38DFE" @@ -26,6 +28,7 @@ C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pad.R="FEC8C57D" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.plot.R="80B907E9" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pls.R="430E71B2" +C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.pls.plot.R="07565C15" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.q.R="2721C15F" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.rescale.R="2C292C00" C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate/test/respeciate/R/rsp.reshape.R="94C8EF32" diff --git a/DESCRIPTION b/DESCRIPTION index 72cc4c5..a737222 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: respeciate Title: Speciation profiles for gases and aerosols Version: 0.3.0 -Date: 2024-05-13 +Date: 2024-05-17 Description: Access to the US.EPA Speciate (v5.2) tool, to generate speciation profiles for gases and particles. More details in Simon et al (2010) . Type: Package diff --git a/NAMESPACE b/NAMESPACE index a6cc1b1..2faaac4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -93,6 +93,7 @@ importFrom(stats,formula) importFrom(stats,hclust) importFrom(stats,heatmap) importFrom(stats,lm) +importFrom(stats,na.omit) importFrom(stats,nls) importFrom(stats,nls.control) importFrom(stats,predict) diff --git a/NEWS.md b/NEWS.md index 1915710..6a48e0b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Release Notes Version 0.3 * [0.3.0] - * released 2024-05-13 + * released 2024-05-17 * code and documentation refresh... * added as.respeciate method; replacing related unexported code * sp_profile, sp_build_rsp_x updates; both now use as.respeciate @@ -10,7 +10,8 @@ * sp_pad to rsp_pad; sp_rescale to rsp_rescale; class rebuild * sp_match_profile to rsp_match_profile; class rebuild * sp_cor to rsp_cor; sp_distance to rsp_dist; class rebuild - * rsp_plots now track factors + * rsp_plots now track factors + * pls_plots updated for object class # Release Notes Version 0.2 diff --git a/R/respeciate.generics.R b/R/respeciate.generics.R index 4c04d45..303b849 100644 --- a/R/respeciate.generics.R +++ b/R/respeciate.generics.R @@ -302,7 +302,7 @@ print.rsp_pls <- function(x, n = NULL, ...){ temp <- unlist(lapply(x, function(x) !is.null(x))) temp <- length(temp[temp]) report <- paste(report, "\n list of ", length(x), " profile models", - "\n (", temp, " good)\n", sep="") + "\n (", temp, " fitted)\n", sep="") } cat(report) } diff --git a/R/rsp.build.R b/R/rsp.build.R index 0e64c96..b668d53 100644 --- a/R/rsp.build.R +++ b/R/rsp.build.R @@ -106,7 +106,7 @@ rsp_build_x <- # should error if any of these missing at end of build # redundant? - # currently not using ... + # currently only using to turn warning off... .x.args <- list(...) #adding the as.data.frame because @@ -227,7 +227,12 @@ rsp_build_x <- #pass via as.speciate to build rsp_x # note: this replaces previous local testing - x <- as.respeciate(x, test.rsp=TRUE) + test.rsp <- if("test.rsp" %in% names(.x.args)){ + .x.args$test.rsp + } else { + TRUE + } + x <- as.respeciate(x, test.rsp=test.rsp) #slip in rsp_x tag class(x) <- unique(c("rsp_x", class(x))) x diff --git a/R/rsp.plot.R b/R/rsp.plot.R index bb9af43..f245c1c 100644 --- a/R/rsp.plot.R +++ b/R/rsp.plot.R @@ -50,7 +50,7 @@ #uses unexported code # .rsp_plot_fix -# .rsp_yscale.component.log10 (currently in sp.pls.r) +# .rsp_yscale.component.log10 (currently in rsp.pls.r) @@ -70,11 +70,11 @@ #examples # maybe -# sp_plot_profile(spq_pm.ae8()) +# rsp_plot_profile(spq_pm.ae8()) # (allows most lattice style plot control, etc key=list(...)) # (but includes some short cuts to common handling, e.g. log=T to # log y scales and reformat y axes) -# sp_plot_profile(spq_pm.ae8(), key=list(space="top", columns=2), log=T) +# rsp_plot_profile(rsp_q_pm.ae8(), key=list(space="top", columns=2), log=T) #color defaults... #issue current default wraps if you exceed number of cols in default set. @@ -83,7 +83,6 @@ ## ?? could extrapolate the default colors using something like above ??? - # dennis asked for data as part of return # that is do-able but may need an object class # (maybe like the openair code...) @@ -245,7 +244,7 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", data=x, ylab="Profile Loading", xlab="", #NB: prepanel seemed to break ylim when stacking panel = function(x, y, origin, ...){ - rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), + .rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), panel.grid, ...) if(missing(origin)){ ###################################### @@ -286,7 +285,7 @@ rsp_plot_profile <- function(rsp, id, multi.profile = "group", #shortcut to scales(y(log)) and yscale.component if(log){ p1.ls$scales$y$log <- 10 - p1.ls$yscale.components <- rsp_yscale.component.log10 + p1.ls$yscale.components <- .rsp_yscale.component.log10 } #3. extra user settings @@ -540,7 +539,6 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", ############################## #species alignment - p1.ls <- list(x= .value~.x, data=x, ylab="Measurement", xlab=.xlab, type="l", @@ -548,7 +546,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", panel = function(x, y, ...){ at.x <- pretty(x) at.y <- pretty(y) - rsp_panelPal("grid", list(h=at.y,v=at.x, col="grey", lty=3), + .rsp_panelPal("grid", list(h=at.y,v=at.x, col="grey", lty=3), panel.abline, ...) panel.xyplot(x=x, y=y, ...) }, @@ -578,7 +576,7 @@ rsp_plot_species <- function(rsp, id, multi.species = "group", #shortcut to scales(y(log)) and yscale.component if(log){ p1.ls$scales$y$log <- 10 - p1.ls$yscale.components <- rsp_yscale.component.log10 + p1.ls$yscale.components <- .rsp_yscale.component.log10 } #3. extra user settings diff --git a/R/rsp.pls.R b/R/rsp.pls.R index a3f6941..a79f2a4 100644 --- a/R/rsp.pls.R +++ b/R/rsp.pls.R @@ -1,8 +1,7 @@ #' @name rsp.pls #' @title (re)SPECIATE profile Positive Least Squares models #' @aliases rsp_pls_profile pls_report pls_test pls_fit_species -#' pls_refit_species pls_rebuild pls_plot -#' pls_plot_species pls_plot_profile +#' pls_refit_species pls_rebuild #' @description Functions for Positive Least Squares (PSL) fitting of #' (re)SPECIATE profiles @@ -18,7 +17,7 @@ #' reference set are representative of the mix that make up the modeled #' sample. The \code{pls_} functions work with \code{rsp_pls_profile} #' outputs, and are intended to be used when refining and analyzing -#' these PLS models. +#' these PLS models. See also \code{pls_plot}s for PLS model plots. #' @param rsp A \code{respeciate} object, a \code{data.frame} of #' profiles in standard long form, intended for PLS modelling. @@ -32,8 +31,8 @@ #' range \code{1 - 2.5} are sometimes helpful. #' @param ... additional arguments, typically ignored or passed on to #' \code{\link{nls}}. -#' @param pls A \code{sp_pls_profile} output, only used by \code{pls_} -#' functions. +#' @param pls A \code{rsp_pls_profile} output, intended for use with +#' \code{pls_} functions. #' @param species for \code{pls_fit_species}, a data.frame of #' measurements of an additional species to be fitted to an existing #' PLS model, or for \code{pls_refit_species} a character vector of the @@ -57,32 +56,24 @@ #' \code{pls_refit_species}, \code{logical}, default \code{FALSE}, when #' building or rebuilding a PLS model, discard cases where \code{species} #' is missing. -#' @param n (for \code{pls_plot}s only) numeric or character -#' identifying the species or profile to plot. If numeric, these are treated -#' as indices of the species or profile, respectively, in the PLS model; if -#' character, species is treated as the name of species and profile is treated -#' as the profile code. Both can be concatenated to produce multiple plots and -#' the special case \code{n = -1} is a short cut to all species or profiles, -#' respectively. -#' @param type (for \code{pls_plot}s only) numeric, the plot type if -#' multiple options are available. -#' @param log (for \code{pls_plot_profile} only) logical, if \code{TRUE} this -#' applies 'log' scaling to the primary Y axes of the plot. -######################### -# need to check terminology for this... -# The zero handling is a based on offset in plot(..., log="y", off.set) -# but automatically estimated... +################################ +# to do... +################################ +# link above to pls plot help page? +# document methods and references + #' @return \code{rsp_pls_profile} returns a list of nls models, one per #' profile/measurement set in \code{rsp}. The \code{pls_} functions work with #' these outputs. \code{pls_report} generates a \code{data.frame} of #' model outputs, and is used of several of the other \code{pls_} #' functions. \code{pls_fit_species}, \code{pls_refit_species} and -#' \code{pls_fit_parent} return the supplied \code{sp_pls_profile} output, +#' \code{pls_fit_parent} return the supplied \code{rsp_pls_profile} output, #' updated on the basis of the \code{pls_} function action. -#' \code{pls_plot}s produce various plots commonly used in source -#' apportionment studies. +#' \code{pls_plot}s (documented separately) produce various plots +#' commonly used in source apportionment studies. + #' @note This implementation of PLS applies the following modeling constraints: #' @@ -92,11 +83,19 @@ #' which is a Nonlinear Least Squares (NLS) model, the fitting term applied #' in this case is linear. #' -#' 2. The number of species in \code{rsp} must be more that the number of -#' profiles in \code{ref} to reduce the likelihood of over-fitting. +#' 2. The model is fit in the form: +#' +#' \eqn{X_{i,j} = \sum\limits_{k=1}^{K}{N_{i,k} * M_{k,j} + e_{i,j}}} +#' +#' Where X is the data set of measurements, \code{rsp}, M is data set of +#' reference profiles, \code{ref}, N is the data set of source contributions, +#' the source apportion solution, to be solved by minimising e, the error terms. #' +#' 3. The number of species in \code{rsp} must be more that the number of +#' profiles in \code{ref} to reduce the likelihood of over-fitting. #' + # GENERAL NOTES # TO DO @@ -233,11 +232,13 @@ rsp_pls_profile <- function(rsp, ref, #build formula and model args .tmp <- names(.out) .tmp <- .tmp[!.tmp %in% c("SPECIES_ID", "SPECIES_NAME", "test")] + names(.out)[names(.out) %in% .tmp] <- paste(".m_", names(.out)[names(.out) %in% .tmp], + sep="") #zero cases for port function - .ls <- paste("m_", .tmp, sep="") + .ls <- paste(".n_", .tmp, sep="") .ls2 <- lapply(.ls, function(x){0}) names(.ls2) <- .ls - .for <- paste("(m_", .tmp, "*`", .tmp, "`)", sep="", collapse = "+") + .for <- paste("(.n_", .tmp, "*`.m_", .tmp, "`)", sep="", collapse = "+") .for <- as.formula(paste("test~", .for)) .wt <- 1/.out$test ############################ @@ -248,6 +249,8 @@ rsp_pls_profile <- function(rsp, ref, # should check how this is done? # might not translate sesnibly... # pass upper, default INF??? + #also switch m_[profile] to n_[profile] + # so we have commonly notation... .out[is.na(.out)] <- 0 #testing @@ -348,6 +351,13 @@ rsp_pls_profile <- function(rsp, ref, # if some species very large and some very small # doing them on an all results basis will be overly positive + +#test +#devtools::load_all() +#d1 <- readRDS("C:\\Users\\trakradmin\\OneDrive - University of Leeds\\Documents\\pkg\\respeciate\\test\\my.working.rds") +#ref <- rsp(c("4868", "4914", "8948", "91155", "91163", "95441", "95529")) +#mod <- rsp_pls_profile(d1, ref, power=2) + pls_report <- function(pls){ ans <- lapply(names(pls), function(x){ @@ -356,7 +366,7 @@ pls_report <- function(pls){ .out <- .xx$args$data .tmp <- summary(.xx$mod)$coefficients .p.mod <- .tmp[,4] - names(.p.mod) <- gsub("m_", "p_", names(.p.mod)) + names(.p.mod) <- gsub(".n_", ".p_", names(.p.mod)) .out <- data.frame(PROFILE_CODE = x, .out, t(.tmp[,1]), @@ -374,11 +384,21 @@ pls_report <- function(pls){ } ##################### - #thinking about + #working on ##################### - # adding x_[profile] (m_[profile] * profile) calculations here - # currently done on fly in some plots... - + # added x_[profile] (.n_[profile] * .m_[profile]) calculations here + # was done on fly in older plots... + # also changed m_[profile] to n_[profile] and [profile] to m_[profile] + # so annotation was consistent with equation in documentation... + # must be a better way of doing this... + + .tmp <- names(ans) + .tmp <- .tmp[grep("^.m_", .tmp)] + ans <- as.data.frame(ans) + for(i in .tmp){ + ans[,gsub("^.m_", ".x_", i)] <- ans[,gsub("^.m_", ".n_", i)] * ans[,i] + } + ans <- data.table::as.data.table(ans) ans$.value <- ans$test ####################################### @@ -395,7 +415,7 @@ pls_report <- function(pls){ ################################# # replacing with... ################################# - #by species calculate stats + #by-species calculate stats # guessing this could be done in data.table??? .sp.ref <- unique(ans$SPECIES_NAME) .tmp <- lapply(.sp.ref, function(x){ @@ -408,7 +428,7 @@ pls_report <- function(pls){ # .mod <- lm(pred ~ 0 + .value, data = .tmp) ########### - #(also noted in sp_pls_profile) + #(also noted in rsp_pls_profile) #if we need to calculate aic based on the method parameters... #need to read this: #https://stackoverflow.com/questions/39999456/aic-on-nls-on-r @@ -464,14 +484,16 @@ pls_test <- function(pls){ }) .sp <- data.table::rbindlist(.tmp) - #ref profiles - .pn <- names(.rp)[grepl("^p_", names(.rp))] + #pls + ###################### + # not sure if we should focus on 'good' or 'bad' p-score here... + .pn <- names(.rp)[grepl("^.p_", names(.rp))] .ans <- data.table::as.data.table(.rp)[, lapply(.SD, function(x){length(x[x>0.05])/length(x)}), .SDcols = .pn] .ans <- as.data.frame(.ans) .ans <- (1 - .ans)*100 - names(.ans) <- gsub("^p_", "gp_", names(.ans)) + names(.ans) <- gsub("^.p_", "gp_", names(.ans)) list(.species=.sp, .pls = .ans) @@ -694,33 +716,33 @@ pls_rebuild <- function(pls, species, power=1, if(any(!grepl(.mrk.nm, pls[[i]]$args$formula))){ #update formula .for <- as.character(pls[[i]]$args$formula) - .for[3] <- paste(.for[3], "+ (`m_", .mrk.nm, - "` * `", .mrk.nm, "`)", + .for[3] <- paste(.for[3], "+ (`.m_", .mrk.nm, + "` * `.n_", .mrk.nm, "`)", sep="") pls[[i]]$args$formula <- as.formula(paste(.for[2], .for[1], .for[3], sep="")) } if("start" %in% names(pls[[i]]$args)){ - if(!paste("m_", .mrk.nm, sep="") %in% names(pls[[i]]$args$start)){ - #print("adding m_ start") + if(!paste(".n_", .mrk.nm, sep="") %in% names(pls[[i]]$args$start)){ + #print("adding .n_ start") .arg <- pls[[i]]$args$start - .arg[[paste("m_", .mrk.nm, sep="")]] <-0 + .arg[[paste(".m_", .mrk.nm, sep="")]] <-0 pls[[i]]$args$start <- .arg } } if("lower" %in% names(pls[[i]]$args)){ - if(!paste("m_", .mrk.nm, sep="") %in% names(pls[[i]]$args$lower)){ - #print("adding m_ lower") + if(!paste(".n_", .mrk.nm, sep="") %in% names(pls[[i]]$args$lower)){ + #print("adding .n_ lower") .arg <- pls[[i]]$args$lower - .arg[[paste("m_", .mrk.nm, sep="")]] <-0 + .arg[[paste(".n_", .mrk.nm, sep="")]] <-0 pls[[i]]$args$lower <- .arg } } if("upper" %in% names(pls[[i]]$args)){ - if(!paste("m_", .mrk.nm, sep="") %in% names(pls[[i]]$args$upper)){ - #print("adding m_ upper") + if(!paste(".n_", .mrk.nm, sep="") %in% names(pls[[i]]$args$upper)){ + #print("adding .n_ upper") .arg <- pls[[i]]$args$upper - .arg[[paste("m_", .mrk.nm, sep="")]] <- Inf + .arg[[paste(".n_", .mrk.nm, sep="")]] <- Inf pls[[i]]$args$upper <- .arg } } @@ -794,13 +816,13 @@ pls_rebuild <- function(pls, species, power=1, # for the added #print(.data) - .ms <- names(.data)[grepl("^m_", names(.data))] - .for <- paste("(`", .ms, "`*`", gsub("^m_", "n_", .ms), "`)", + .ms <- names(.data)[grepl("^.n_", names(.data))] + .for <- paste("(`", .ms, "`*`", gsub("^.n_", ".m_", .ms), "`)", sep="", collapse = "+") .for <- as.formula(paste("refit~", .for)) .ns <- .ms - names(.ns) <- gsub("^m_", "n_", .ms) + names(.ns) <- gsub("^.n_", ".m_", .ms) #note ################## @@ -853,7 +875,7 @@ pls_rebuild <- function(pls, species, power=1, test = .data$refit, check.names=FALSE ) - names(.ans) <- gsub("^n_", "", names(.ans)) + names(.ans) <- gsub("^.n_", "", names(.ans)) #print("doing these") #print(.ans$PROFILE_CODE) @@ -881,7 +903,7 @@ pls_rebuild <- function(pls, species, power=1, .ms <- names(pls[[i]]$args$data) .ms <- .ms[!.ms %in% c("SPECIES_ID", "SPECIES_NAME", "test")] .ls <- lapply(.ms, function(x){0}) - names(.ls) <- paste("m_", .ms, sep="") + names(.ls) <- paste(".n_", .ms, sep="") .da <- pls[[i]]$args$data pls[[i]]$args$weights <- (1/pls[[i]]$args$data$test)^power pls[[i]]$args$control <- control @@ -889,26 +911,26 @@ pls_rebuild <- function(pls, species, power=1, #can these go now..? ################# if("start" %in% names(pls[[i]]$args)){ - if(!paste("m_", .mrk.nm, sep="") %in% names(pls[[i]]$args$start)){ - #print("adding m_ start") + if(!paste(".n_", .mrk.nm, sep="") %in% names(pls[[i]]$args$start)){ + #print("adding .n_ start") .arg <- pls[[i]]$args$start - #.arg[[paste("m_", .mrk.nm, sep="")]] <-0 + #.arg[[paste(".n_", .mrk.nm, sep="")]] <-0 pls[[i]]$args$start <- .arg } } if("lower" %in% names(pls[[i]]$args)){ - if(!paste("m_", .mrk.nm, sep="") %in% names(pls[[i]]$args$lower)){ - #print("adding m_ lower") + if(!paste(".n_", .mrk.nm, sep="") %in% names(pls[[i]]$args$lower)){ + #print("adding .n_ lower") .arg <- pls[[i]]$args$lower #.arg[[paste("m_", .mrk.nm, sep="")]] <-0 pls[[i]]$args$lower <- .arg } } if("upper" %in% names(pls[[i]]$args)){ - if(!paste("m_", .mrk.nm, sep="") %in% names(pls[[i]]$args$upper)){ - #print("adding m_ upper") + if(!paste(".n_", .mrk.nm, sep="") %in% names(pls[[i]]$args$upper)){ + #print("adding .n_ upper") .arg <- pls[[i]]$args$upper - #.arg[[paste("m_", .mrk.nm, sep="")]] <- Inf + #.arg[[paste(".n_", .mrk.nm, sep="")]] <- Inf pls[[i]]$args$upper <- .arg } } @@ -1036,1359 +1058,3 @@ pls_rebuild <- function(pls, species, power=1, -#################################### -################################### -## pls_plots -################################### -################################### - -#these are all draft - - -#################################### -#################################### -## pls_plot -#################################### -#################################### - - -#' @rdname rsp.pls -#' @export - -## now imports via data.table:: -## need this to kill the as.data.table load message -## #' @import data.table -## - -############################# -#this needs a lot of work -############################# - -# this uses unexported rsp_profile_pie function below... -# both pls_plot and rsp_profile_pie need work... - - -pls_plot <- function (pls, n, type = 1, ...){ - - #current using lattice/latticeExtra for the panelling/layers... - - #basic plots finished but... - # currently not passing arguments generally - # the par setting seem to be dropped when using plot(p) - # ahead of end of function - - ############################ - # nags - ############################ - - # type = 1 - ############################ - - # note sure about the layer naming - # zero is not bottom of barchart... - - # type = 2 - ############################ - - # the label positioning is messy (see not about nudge) - - # cex setting too small if only one panel... - - #wondering about - # https://latticeextra.r-forge.r-project.org/man/postdoc.html - # as an alternative to type=2 - # (but 100 percent measured rather than proportion...) - - ################# - #setup - ################# - .x.args <- list(...) - dat <- pls_report(pls) - .ord.pro.c <- rsp_profile_code_order(dat) - .sp.ref <- unique(dat$SPECIES_NAME) - #species - # now defaulting to all plots - species <- if (missing(n)) { - species <- .sp.ref - } - else { - n - } - if (is.numeric(species)) { - if (all(species == -1)) { - species <- .sp.ref - } - else { - species <- .sp.ref[species] - } - } - if (!any(species %in% .sp.ref)) { - stop("RSP_PLS> unknown species, please check", call. = FALSE) - } - ################################ - #note: - # could condition here BUT currently - # holding on to everything until just before plot - # might not need to do this.... - ################################# - - .sp.ord <- unique(dat$SPECIES_ID) - .sp.m.pro <- names(dat)[grep("^m_", names(dat))] - .sp.pro <- gsub("^m_", "", .sp.m.pro) - - #line col.... - .col <- lattice::trellis.par.get("superpose.line")$col[1] - - #bar cols - .cols <- if ("col" %in% names(.x.args)) { - #could include if you supply a function..? - #could use col.regions? - .cols <- .x.args$col - } - else { - .cols <- heat.colors(n = length(.sp.m.pro)) - } - if (length(.cols) != length(.sp.m.pro)) { - stop("pls_plot> halted; expecting ", length(.sp.m.pro), - "colours; given ", length(.cols), sep = "", call. = FALSE) - } - - ###################### - # build x_[profile] - ###################### - for (i in .sp.pro) { - dat[, paste("x_", i, sep = "")] <- dat[, paste("m_", - i, sep = "")] * dat[, i] - } - .sp.x.pro <- names(dat)[grep("^x_", names(dat))] - .rep <- dat[c("SPECIES_NAME", "SPECIES_ID", "PROFILE_CODE", - .sp.x.pro)] - .rep <- data.table::melt(data.table::as.data.table(.rep), - id = c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE")) - .tot <- data.table::as.data.table(dat) - .cs <- c(".value", "pred", .sp.x.pro) - .tot <- .tot[, lapply(.SD, function(x) sum(x, na.rm = TRUE)), - .SDcols = .cs, by = c("SPECIES_ID", "SPECIES_NAME")] - - ########################### - # now plotting as panels - # using - ########################### - - ###################################################### - # now using rsp_ function to track all pls model cases - # previous method only tracked valid cases for the plotted data - # so no gaps where models dropped/not built... - ######################################################### - .rep$.index <- as.numeric(factor(.rep$PROFILE_CODE, levels = .ord.pro.c, - ordered = TRUE)) - dat$.index <- as.numeric(factor(dat$PROFILE_CODE, levels = .ord.pro.c, - ordered = TRUE)) - .tmp <- dat[c("SPECIES_ID", "PROFILE_CODE", ".index", ".value", "pred")] - .rep <- data.table::merge.data.table(.rep, .tmp) - - .rep$variable <- gsub("^x_", "", .rep$variable) - - #print(names(.rep)) - #return(dat) - - .rep <- subset(as.data.frame(.rep), SPECIES_NAME %in% species) - - if (1 %in% type) { - - #lattice sets panel order based - .sp <- if(is.factor(.rep$SPECIES_NAME)){ - levels(.rep$SPECIES_NAME) - } else { - sort(unique(.rep$SPECIES_NAME)) - } - .sp <- .sp[.sp %in% .rep$SPECIES_NAME] - #.y.scale <- lapply(unique(.rep$SPECIES_NAME), function(x){ - .y.scale <- lapply(.sp, function(x){ - .tmp <- .rep[.rep$SPECIES_NAME==x,] - c(0, max(c(.tmp$.value, .tmp$pred), na.rm=TRUE)) - }) - ############################################### - #use loa method to generalise this? - ############################################### - - - p2 <- lattice::xyplot(.value ~ .index | SPECIES_NAME, .rep, - panel=lattice::panel.xyplot, - type="l", xlab="Sample [index]", - ylab="Measurement", - scales=list(relation="free"), - ylim=.y.scale) - - p <- lattice::barchart(value ~ factor(.index) | SPECIES_NAME, .rep, - groups=.rep$variable, stack=TRUE, - panel=function(x, y, col, groups, ..., subscripts){ - #grid control like loa - rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), - lattice::panel.grid, ...) - lattice::panel.barchart(x=x, y=y, col=col, - groups=groups, - subscripts=subscripts, ...) - .y <- .rep$.value[subscripts] - #col needs to be from option[1] - lattice::panel.xyplot(x=x, y=.y, - col=.col, - type="l", - subscripts=subscripts,...) - }, - scales=list(relation="free"), - #auto.key=list(space="top", columns=2, - # col.line=.cols, - # points=FALSE, rectangles=TRUE), - ylim=.y.scale, - col=.cols, - border=NA, - #par.settings = list(superpose.polygon = list(col = .cols, - # pch =c (15, 15)), - #superpose.symbol = list(fill = .cols)), - auto.key=list(space="top", columns = 3, - cex=0.8, - points=FALSE, - rectangles=TRUE)) #, - #xscale.components = function(lim,...){ - # lim <- as.numeric(as.character(lim)) - # ans <- lattice::xscale.components.default(lim=lim,...) - # print(ans) - # ans - #}) - plot(update(latticeExtra::doubleYScale(p2, p, add.axis = FALSE), - par.settings = list(superpose.polygon = list(col = .cols), - superpose.symbol = list(fill = .cols)))) - - #p2 <- lattice::xyplot(.value ~ factor(.index) | SPECIES_NAME, dat, - # type="l", scales=list(relation="free")) - #plot(cheat(p, latticeExtra::as.layer(p2))) - - #plot(latticeExtra::doubleYScale(p, p2, add.axis=FALSE, add.ylab2=FALSE)) - } - if (2 %in% type) { - - - p <- lattice::xyplot(value ~ .index | SPECIES_NAME, .rep, - groups=.rep$variable, - totals=.rep$.value, - scales=list(relation="free", - draw=FALSE), - ylab="", xlab="", - col = .cols, - auto.key=list(space="top", columns = 3, - cex=0.8, - points=FALSE, - rectangles=TRUE), - ylim=c(-2,2), xlim=c(-2,2), - between = list(x = 0.2, y = 0.2), - panel=rsp_panel.pie, - par.settings = list(superpose.polygon = list(col = .cols), - axis.line = list(col = 'transparent'), - superpose.symbol = list(fill = .cols)) - ) - plot(p) - - } - invisible(.rep) -} - - - -#test <- "C:/Users/trakradmin/OneDrive - University of Leeds/Documents/pkg/respeciate" -#mod <- readRDS(paste(test, "mod2.RDS", sep="/")) -#pls_plot(mod) - - - - - - -#################################### -#################################### -## pls_plot_species -#################################### -#################################### - - -#' @rdname rsp.pls -#' @export - -## now imports from xxx.r -## #' @import data.table - -############################# -#this needs a lot of work -############################# - - -pls_plot_species <- function (pls, n, type = 1, ...) -{ - ########################### - # setup - ########################### - .x.args <- list(...) - dat <- pls_report(pls) - .ord.pro.c <- rsp_profile_code_order(dat) - .sp.ref <- unique(dat$SPECIES_NAME) - species <- if (missing(n)) { - .sp.ref - #default option (print the lot...) - ############################ - #possibly a warning if lots of species to plot - ################## - } else { - n - } - if (is.numeric(species)) { - if (all(species == -1)) { - species <- .sp.ref - } - else { - species <- .sp.ref[species] - } - } - if (!any(species %in% .sp.ref)) { - stop("RSP_PLS> unknown species, please check", call. = FALSE) - } - ############################ - #if not earlier, then here? - #possibly a warning if lots of species to plot - ################## - - ######################### - #could drop a lot of this?? - ######################### - .xlb <- if ("xlab" %in% names(.x.args)) { - .x.args$xlab - } else { - "Measurement" - } - .ylb <- if ("ylab" %in% names(.x.args)) { - .x.args$ylab - } else { - "Model" - } - .bc <- if ("col" %in% names(.x.args)) { - .x.args$col - } else { - par("col") - } - .mc <- if ("mod.col" %in% names(.x.args)) { - .x.args$mod.col - } else { - "red" - } - dat <- subset(dat, SPECIES_NAME %in% species) - # lims <- range(c(d2$.value, d2$pred), na.rm = TRUE, finite = TRUE) - # mod <- lm(pred ~ 0 + .value, d2) - # .sum <- paste("y = ", signif(summary(mod)$coefficients[1, - # 1], 3), "x (adj.R2 = ", signif(summary(mod)$adj.r.squared, - # 3), ")", sep = "") - .lims <- lapply(species, function(x){ - .d <- subset(dat, SPECIES_NAME==x) - range(c(.d$pred, .d$.value), finite=TRUE, na.rm=TRUE) - }) - if (1 %in% type) { - p1.ls <- list(x=pred~.value | SPECIES_NAME, data=dat, - #prepanel forces x and y lims to same range - prepanel=function(...){ - .tmp <- prepanel.default.xyplot(...) - .tmp$xlim <- range(c(.tmp$xlim, .tmp$ylim)) - .tmp$ylim <- .tmp$xlim - .tmp - }, - panel= function(x, y, xlim, ylim, ...){ - #user control of grid - like loa... - rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), - lattice::panel.grid, ...) - #TO DO - #user control of y=x - panel.ablineq(a = 0, b = 1, adj = c(0,1), - col.line ="grey", lty=2, label="") - #user control of main plotted data via - # standard lattice - panel.xyplot(x=x, y=y, ...) - #CURRENTLY JUST col via mod.col - #user control of model - panel.ablineq(lm(y ~ x + 0), cex = 0.8, - x = min(c(x, y), na.rm=TRUE), - y = max(c(x, y), na.rm=TRUE), - r.squared = TRUE, adj = c(0,1), - sep = " (", sep.end = ")", - offset=0, varStyle = NULL, - col.line = .mc, col.text = .mc, digits = 2) - }, - xlab="Measurement", ylab="model", - scales=list(y=list(relation="free", - rot=90), - x=list(relation="free"))) - p1.ls <- modifyList(p1.ls, .x.args) - p <- do.call(xyplot, p1.ls) - plot(p) - - # plot(d2$.value, d2$pred, type = "n", main = i, col = .bc, - # xlab = .xlb, ylab = .ylb, xlim = lims, ylim = lims) - # grid() - # abline(a = 0, b = 1, col = "grey") - # points(d2$.value, d2$pred) - # abline(mod, col = .mc, lty = 2) - # text(lims[1], lims[2], .sum, adj = c(0, 1), cex = 0.75) - } - if (2 %in% type) { - #xlab - if(!"xlab" %in% names(.x.args)){ - .x.args$xlab <- "Sample [index]" - } - if(!"ylab" %in% names(.x.args)){ - .x.args$ylab <- "Measurement, Model" - } else { - if(length(.x.args$ylab)>1){ - if(!"key.text" %in% names(.x.args)){ - .x.args$key.text <- .x.args$ylab[1:2] - } - .x.args$ylab <- paste(.x.args$ylab[1], .x.args$ylab[2], sep =", ") - } - } - if(!"key.text" %in% names(.x.args)){ - .x.args$key.text <- c("Measurement", "Model") - } - if(!"col" %in% names(.x.args)){ - .x.args$col <- trellis.par.get("superpose.line")$col[1:2] - } - if("mod.col" %in% names(.x.args)){ - .x.args$col <- c(.x.args$col[1], .x.args$mod.col) - } - - - #ylab - #can to two terms for - - #if("ylab" %in% names(.x.args)){ - # if(length(.x.args$ylab)>1){ - # if(!"key.text" %in% names(.x.args)){ - # .x.args$key.text <- .x.args$ylab[1:2] - # } - # .x.args$ylab <- paste(.x.args$ylab[1], .x.args$ylab[2], sep =", ") - # } else { - # if(!"key.text" %in% names(.x.args)){ - # .x.args$key.text <- c("Measurement", "Model") - # } - # } - #} else { - # if(!"key.text" %in% names(.x.args)){ - # .x.args$key.text <- c("Measurement", "Model") - # } - # .x.args$ylab <- "Measurement, Model" - #} - - - - - ######################### - #previous code - ######################### - #plot(d2$.value, type = "n", main = i, col = .bc, - # ylab = .ylb, xlab = .xlb, ylim = lims) - #lines(d2$.value) - #lines(d2$pred, col = .mc) - ######################## - #using standardised index - #make 'ordered profile codes' at top - # before any subsetting... - # .ord.pro.c <- rsp_profile_code_order(dat) - dat$.index <- as.numeric(factor(dat$PROFILE_CODE, levels=.ord.pro.c, - ordered = TRUE)) - p2.ls <- list(x= .value + pred ~ .index | SPECIES_NAME, data=dat, - auto.key = list(text=.x.args$key.text, - space="top", columns=2), - type="l", - panel= function(...){ - rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), - panel.grid, ...) - lattice::panel.xyplot(...) - }, - scale=list(relation="free"), - par.settings = simpleTheme(col=.x.args$col)) - p2.ls <- modifyList(p2.ls, .x.args) - p <- do.call(xyplot, p2.ls) - plot(p) - ###################### - - # or any with any missing are plot on different x scale - # maybe find longest, take range for that - #xyplot(.value + .pred ~ ) - } - - invisible(dat) -} - - - -#################################### -#################################### -## pls_plot_profile -#################################### -#################################### - - -#' @rdname rsp.pls -#' @export - -## now imports from xxx.r -## #' @import data.table - - -############################# -#this needs a lot of work -############################# - -pls_plot_profile <- function (pls, n, log = FALSE, ...) -{ - ######################### - #previous plot used base r graphics - #this moved to lattice/latticeExtra - #so we can panel outputs - ######################### - - #setup - .x.args <- list(...) - .plt.args <- .x.args[names(.x.args %in% c())] - dat <- pls_report(pls) - .sp.ord <- unique(dat$SPECIES_ID) - .sp.m.pro <- names(dat)[grep("^m_", names(dat))] - .sp.pro <- gsub("^m_", "", .sp.m.pro) - #defaulting n to all profiles as one plot - profile <- if (missing(n)) { - profile <- .sp.pro - } else { - n - } - if (is.numeric(profile)) { - if (all(profile == -1)) { - profile <- .sp.pro - } - else { - profile <- .sp.pro[profile] - } - } - if (!any(profile %in% .sp.pro)) { - stop("RSP_PLS> unknown profile(s), please check", call. = FALSE) - } - - ######################### - #build x_[profile] - ######################### - m_profile <- paste("m_", profile, sep = "") - dat <- dat[c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE", - profile, m_profile, "pred", ".value")] - for (i in profile) { - dat[, paste("x_", i, sep = "")] <- dat[, paste("m_", - i, sep = "")] * dat[, i] - } - .rep <- data.table::as.data.table(dat) - .cols <- c(".value", "pred", paste("x_", profile, sep = "")) - .rep <- .rep[, lapply(.SD, function(x) sum(x, na.rm = TRUE)), - .SDcols = .cols, by = c("SPECIES_ID", "SPECIES_NAME")] - .rep <- as.data.frame(.rep) - - ######################### - # y2 setup - ######################### - # by default this is .value - # but might want mod prediction - if ("y2" %in% names(.x.args) && .x.args$y2 == "pred") { - for (i in profile) { - .rep[, paste("pc_", i, sep = "")] <- - (.rep[, paste("x_", i, sep = "")]/.rep$pred) * 100 - } - } - else { - for (i in profile) { - .rep[, paste("pc_", i, sep = "")] <- - (.rep[, paste("x_", i, sep = "")]/.rep$.value) * 100 - } - } - #might not need all of following now we - #we are not pulling apart to plot one at time... - dat <- dat[!duplicated(dat$SPECIES_NAME), ] - dat$PROFILE_NAME <- dat$PROFILE_NAME[1] - dat$PROFILE_CODE <- dat$PROFILE_CODE[1] - dat <- merge(.rep, dat[c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE", - profile)], ) - dat <- dat[order(ordered(dat$SPECIES_ID, levels = .sp.ord)), ] - - ################################ - # build pc_[profile] - ################################ - rownames(dat) <- 1:nrow(dat) - .ref <- names(dat)[grep("pc_", names(dat))] - .oth <- c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE", ".value", "pred") - .temp <- data.table::as.data.table(dat[c(.oth, gsub("pc_", "", .ref))]) - .d1 <- data.table::melt(.temp, measure.vars = gsub("pc_", "", .ref), - variable.name = "pls_profile", value.name = "loading") - .temp <- data.table::as.data.table(dat[c(.oth, .ref)]) - .d2 <- data.table::melt(.temp, measure.vars = .ref, - variable.name = "pls_profile", value.name = "percent_contr") - .d2$pls_profile <- gsub("pc_", "", .d2$pls_profile) - dat <- as.data.frame(merge(.d1, .d2, all=T)) - ############################# - - ############################ - #now using lattice to handle logs - ############### - #.dat <- dat - #don't need local version of dat because not changing data ahead of plot - #if(log){ - # .dat$loading <- log10(.dat$loading) - # .ylim <- lapply(profile, function(x){ - # .temp <- subset(.dat, pls_profile==x) - # .temp <- range(.temp$loading, na.rm=TRUE, finite=TRUE) - # if(.temp[1] == .temp[2]){ - # .temp <- c(.temp[1]-1, .temp[1]+1) - # } - # range(c(floor(.temp), ceiling(.temp))) - # }) - #} else { - # .ylim <- lapply(profile, function(x){ - # .temp <- subset(.dat, pls_profile==x) - # .temp <- range(.temp$loading, na.rm=TRUE, finite=TRUE) - # range(pretty(.temp)) - # }) - #} - - - ###################### - #plot - ###################### - #now using lattice/latticeExtra - ## - #think there is more here that can be generalized... - p1.ls <- list(x = loading~SPECIES_NAME | pls_profile, - data=dat, ylab="Source Loading", - panel = function(...){ - rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), - panel.grid, ...) - panel.barchart(...) - }, - between=list(y=.2), - scales=list(x=list(rot=90), - y=list(rot=c(0,90), - relation="free")), - layout=c(1,length(profile))) - if(log){ - p1.ls$scales$y$log=10 - p1.ls$yscale.components <- rsp_yscale.component.log10 - } - p1.ls <- modifyList(p1.ls, .x.args) - if(!"col" %in% names(p1.ls)){ - p1.ls$col <- trellis.par.get("superpose.line")$col[1] - } - p1 <- do.call(barchart, p1.ls) - if("mod" %in% names(.x.args) && !.x.args$mod){ - #if mod FALSE just plot 1 - plot(p1) - } else { - #add mod layer (total contributions) as y2 - .col2 <- if("mod.col" %in% names(.x.args)){ - .x.args$mod.col - } else { - trellis.par.get("superpose.line")$col[2] - } - p2.ls <- list(x = percent_contr ~ factor(SPECIES_NAME) | pls_profile, - pch=16, type=c("h", "p"), col= c(.col2, .col2), - ylab="Total Contribution (%)", - data=dat) - .tmp <- .x.args[grepl("^mod[.]", names(.x.args))] - if(length(.tmp)>0){ - names(.tmp) <- gsub("^mod[.]", "", names(.tmp)) - p2.ls <- modifyList(p2.ls, .tmp) - } - p2 <- do.call(xyplot, p2.ls) - plot(update(doubleYScale(p1, p2, add.ylab2 = TRUE), - par.settings = simpleTheme(col=c(p1.ls$col[1], .col2)))) - } - - ############ - #output - ############ - #could pass plot and data as list??? - return(invisible(dat)) -} - - - - - - - - - - - - - - - - - - -################ -################ -## unexported -################ -################ - -# profile code order -# get profile order in case you need it latter... - -rsp_profile_code_order <- function(data){ - .tmp <- data.table::as.data.table(data)[, .(ans=length(unique(PROFILE_CODE))),by="SPECIES_NAME"] - .tmp <- subset(.tmp, ans == max(.tmp$ans, na.rm=TRUE))$SPECIES_NAME - .tmp <- subset(data, SPECIES_NAME %in% .tmp) - sort(unique(.tmp$PROFILE_CODE)) -} - - -#log axis hander -#based on lattice text book method - -#issues?? -# could be problem with y padding when log=T and .value range is wide... - -rsp_yscale.component.log10 <- function(lim, ...) { - ans <- yscale.components.default(lim = lim, ...) - tick.at <- pretty(lim) - tick.at <- tick.at[tick.at == floor(tick.at)] - tick.at <- tick.at[tick.at < max(lim, na.rm=TRUE) & tick.at > min(lim, na.rm=TRUE)] - ans$left$ticks$at <- tick.at - ans$left$labels$at <- tick.at - ans$left$labels$labels <- c(format(10^(tick.at), - drop0trailing = TRUE, - scientific = FALSE)) - #print(ans$left$labels$labels) - ####################### - #need to sort of right labeling - # dropped for now... - #ans$right <- ans$left - ans -} - - -#lattice panel pal -#based on panel handler in loa - -rsp_panelPal <- function(.name, .ls, .panel, ...){ - .x.args <- list(...) - if(!.name %in% names(.x.args) || !is.logical(.x.args[[.name]]) || - .x.args[[.name]]){ - .name2 <- paste("^", .name, "[.]", sep="") - if(.name %in% names(.x.args) && is.list(.x.args[[.name]])){ - .tmp <- .x.args[[.name]] - if(length(.tmp)>0){ - names(.tmp) <- paste(.name, names(.tmp), sep=".") - .x.args <- modifyList(.tmp, .x.args) - } - } - .x.args <- .x.args[grepl(.name2, names(.x.args))] - if(length(.x.args)>0){ - names(.x.args) <- gsub(.name2, "", names(.x.args)) - .ls <- modifyList(.ls, .x.args) - } - do.call(.panel, .ls) - } -} - - - -# could move this into the function... - -rsp_panel.pie <- - function (x, y=NULL, groups=NULL, subscripts, totals=NULL, - labels = names(x), edges = 200, radius = 0.8, clockwise = FALSE, - init.angle = if (clockwise) 90 else 0, density = NULL, angle = 45, - col = NULL, border = 1, lty = NULL, main = NULL, ...) - { - - #this is graphics::pie with a couple of modifications... - #many thanks to... - #R Core Team (2023). _R: A Language and Environment for Statistical Computing_. R Foundation - #for Statistical Computing, Vienna, Austria. . - - #if (!is.numeric(x) || any(is.na(x) | x < 0)) - # stop("'x' values must be positive.") - - ######################### - #measurement totals - .y <- totals[subscripts] - ref <- sapply(unique(groups), function(g){ - sum(.y[groups==g], na.rm=TRUE) - }) - .total <- mean(ref, na.rm=TRUE) - - ########################## - #profile contributions to model - # as percentage of measurements - ans <- sapply(unique(groups), function(g){ - sum(y[groups==g], na.rm=TRUE) - }) - ans <- (ans / .total) * 100 - - ##################### - #cheat because following comes from - #pie function in base r... - x <- ans - - if (is.null(labels)) - labels <- as.character(unique(groups)) - else labels <- as.graphicsAnnot(labels) - labels = paste(labels, " (", - round(ans, digits=1), "%)", sep = "") - - if (any(x == 0)) { - labels <- labels[x != 0] - col <- col[x != 0] - x <- x[x != 0] - } - my.tot <- sum(x, na.rm=TRUE) - ######################## - #this adds extra void area - # if does not account for - # 99 percent of the - # measurements - if (my.tot < 99) { - x <- c(x, 100 - my.tot) - labels <- c(labels, "[hide]") - col <- c(col, NA) - init.angle <- init.angle + (((100 - my.tot)/200) * 360) - } - x <- c(0, cumsum(x)/sum(x)) - dx <- diff(x) - nx <- length(dx) - - ###################### - #???? - pin <- par("pin") - xlim <- ylim <- c(-1, 1) - if (pin[1L] > pin[2L]) - xlim <- (pin[1L]/pin[2L]) * xlim - else ylim <- (pin[2L]/pin[1L]) * ylim - - ######################## - #col setting - # this needs generalising like - # other pls_plot - if (is.null(col)) - col <- if (is.null(density)) - c("white", "lightblue", "mistyrose", "lightcyan", - "lavender", "cornsilk") - else par("fg") - - ######################## - #border setting - # needs generalising... - if (!is.null(border)) - border <- rep_len(border, nx) - - ############## - #lty - # needs generalising... - if (!is.null(lty)) - lty <- rep_len(lty, nx) - - ############## - #angle of segment - angle <- rep(angle, nx) - if (!is.null(density)) - density <- rep_len(density, nx) - twopi <- if (clockwise) - -2 * pi - else 2 * pi - t2xy <- function(t) { - t2p <- twopi * t + init.angle * pi/180 - list(x = radius * cos(t2p), y = radius * sin(t2p)) - } - ########################### - #like to nudge these if percent before and - # this one are both small - # (making labels close) - - for (i in 1L:nx) { - if (!as.character(labels[i]) == "[hide]") { - n <- max(2, floor(edges * dx[i])) - P <- t2xy(seq.int(x[i], x[i + 1], length.out = n)) - lattice::lpolygon(c(P$x, rev(P$x * 0.5)), c(P$y, rev(P$y * - 0.5)), density = density[i], angle = angle[i], - border = border[1], col = col[i], lty = lty[i]) - P <- t2xy(mean(x[i + 0:1])) - lab <- as.character(labels[i]) - if (!is.na(lab) && nzchar(lab)) { - lattice::llines(c(1, 1.2) * P$x, c(1, 1.2) * P$y) - lattice::ltext(1.3 * P$x, 1.3 * P$y, labels[i], xpd = TRUE, - cex=0.7, adj = ifelse(P$x < 0, 1, 0), ...) - } - } - } - lattice::ltext(0, 0, label = paste("sum\n", signif(my.tot, 3), "%", - sep = ""), cex=0.7) - } - - - - - -#think about -####################################### -# printing amount missing as a segment -# adding plot arg control like in plot.respeciate -# adding args to change the displacement of labels - -rsp_profile_pie <- function (x, labels = names(x), edges = 200, radius = 0.8, - clockwise = FALSE, - init.angle = if (clockwise) 90 else 0, - density = NULL, angle = 45, col = NULL, - border = NULL, lty = NULL, main = NULL, ...) -{ - #this is graphics::pie with a couple of modifications... - #many thanks to... - #R Core Team (2023). _R: A Language and Environment for Statistical Computing_. R Foundation - #for Statistical Computing, Vienna, Austria. . - - #print(labels) - #print(col) - - if (!is.numeric(x) || any(is.na(x) | x < 0)) - stop("'x' values must be positive.") - if (is.null(labels)) - labels <- as.character(seq_along(x)) - else labels <- as.graphicsAnnot(labels) - - #added to remove any source with a zero contribution - #but hold labels and col alignment - if(any(x==0)){ - labels <- labels[x!=0] - col <- col[x!=0] - x <- x[x!=0] - } - my.tot <- sum(x) - if(my.tot < 99){ - x <- c(x, 100-my.tot) - labels <- c(labels, "[hide]") - col <- c(col, NA) - init.angle <- init.angle + (((100-my.tot)/200)*360) - } - - x <- c(0, cumsum(x)/sum(x)) - dx <- diff(x) - nx <- length(dx) - plot.new() - pin <- par("pin") - xlim <- ylim <- c(-1, 1) - if (pin[1L] > pin[2L]) - xlim <- (pin[1L]/pin[2L]) * xlim - else ylim <- (pin[2L]/pin[1L]) * ylim - dev.hold() - on.exit(dev.flush()) - plot.window(xlim, ylim, "", asp = 1) - if (is.null(col)) - col <- if (is.null(density)) - c("white", "lightblue", "mistyrose", "lightcyan", - "lavender", "cornsilk") - else par("fg") -# if (!is.null(col)) -# col <- rep_len(col, nx) - if (!is.null(border)) - border <- rep_len(border, nx) - if (!is.null(lty)) - lty <- rep_len(lty, nx) - angle <- rep(angle, nx) - if (!is.null(density)) - density <- rep_len(density, nx) - twopi <- if (clockwise) - -2 * pi - else 2 * pi - t2xy <- function(t) { - t2p <- twopi * t + init.angle * pi/180 - list(x = radius * cos(t2p), y = radius * sin(t2p)) - } - - for (i in 1L:nx) { - - if(!as.character(labels[i]) == "[hide]"){ - n <- max(2, floor(edges * dx[i])) - P <- t2xy(seq.int(x[i], x[i + 1], length.out = n)) - #changed shape to include hole - polygon(c(P$x, rev(P$x*0.5)), c(P$y, rev(P$y*0.5)), - density = density[i], angle = angle[i], - border = border[i], col = col[i], lty = lty[i]) - P <- t2xy(mean(x[i + 0:1])) - lab <- as.character(labels[i]) - if (!is.na(lab) && nzchar(lab)) { - # 1.2 and 1.3 are the extenders when moving labels way from - # the pie plot itself - lines(c(1, 1.2) * P$x, c(1, 1.2) * P$y) - text(1.3 * P$x, 1.3 * P$y, labels[i], xpd = TRUE, - adj = ifelse(P$x < 0, 1, 0), ...) - } - } - } - - text(0,0, label=paste("sum\n",signif(my.tot, 3), "%", sep="")) - title(main = main, ...) - invisible(NULL) -} - - - -########################### -########################### -## pls_refit_species -########################### -########################### - - -# superseded by pls_fit_species -# now not exported - -# need to update the model handling so it is like sp_pls_profile -# this would sort power issue above -# also means the user can change setting themselves -# THINK ABOUT THIS -# they could make a pls that was not positively constrained - - -rsp_pls_refit_species <- function(pls, name, power=1, - ...){ - .xx <- pls_report(pls) - #name might want to be case-non-sensitive at some point - #think about how to do this one... - .data <- .xx[.xx$SPECIES_NAME==name,] - #get and hold all the m_ values - #update profile contributions for named species - .ms <- names(.data)[grepl("^m_", names(.xx))] - .xs <- gsub("^m_", "", .ms) - .for <- paste("(`", .ms, "`*`", .xs, "`)", - sep="", collapse = "+") - .for <- as.formula(paste("test~", .for)) - .da <- .data[!names(.data) %in% .xs] - - - .ls <- lapply(.xs, function(x){0}) - names(.ls) <- .xs - - ################# - #user might want to set this??? - - .ls2 <- lapply(.xs, function(x){.data[1, x]}) - names(.ls2) <- .xs - - mod <- nls(.for, data=.da, - #weights = 1/(.out$test^push), # think about weighting - start=.ls2, lower=.ls, - algorithm="port", - control=nls.control(tol=1e-5) #think about tolerance - ) - - .data[.xs] <- data.frame(t(coefficients(mod))) - - #lazy - .ans <- .data - - for(i in .ans$PROFILE_CODE){ - .ii <- subset(.ans, PROFILE_CODE==i) - .ii <- .ii[names(.ii) %in% names(pls[[i]]$args$data)] - .sp.ord <- unique(pls[[i]]$args$data$SPECIES_ID) - pls[[i]]$args$data <- subset(pls[[i]]$args$data, SPECIES_NAME!=name) - pls[[i]]$args$data <- rbind(pls[[i]]$args$data, .ii) - #put back in right order - pls[[i]]$args$data <- - pls[[i]]$args$data[order(ordered(pls[[i]]$args$data$SPECIES_ID, - levels=.sp.ord)),] - #rebuild model - .for <- as.character(formula(pls[[i]]$mod)) - .for <- as.formula(paste(.for[2], .for[1], .for[3], sep="")) - .ms <- names(pls[[i]]$args$data) - .ms <- .ms[!.ms %in% c("SPECIES_ID", "SPECIES_NAME", "test")] - .ls <- lapply(.ms, function(x){0}) - names(.ls) <- paste("m_", .ms, sep="") - .da <- pls[[i]]$args$data - - pls[[i]]$mod <- nls(.for, data=.da, - weights = (1/.da$test)^power, # think about weighting - start=.ls, lower=.ls, - algorithm="port", - control=nls.control(tol=1e-5, - warnOnly = TRUE) #think about tolerance - ) - } - - invisible(pls) - -} - - - -#################################### -#################################### -## pls_fit_parent -#################################### -#################################### - -# superseded by pls_fit_species -# now now exported - -# (like pls_refit_species) -# like to drop power from formals -# maybe ignore or pass overwrites via ...? - -# need to update the model handling so it is like sp_pls_profile -# this would sort power issue above -# also means the user can change setting themselves -# THINK ABOUT THIS -# they could make a pls that was not positively constrained -# this would also remove the start, lower and upper options -# from the formals... - -# parent could already be in x -# then parent could just be the name of parent??? - -# also a case for using this to add a non-parent to x -# e.g. pls_fit_unknown_species... -# to fit a species to the existing model as a source apportion of -# that unknown... -# in which case maybe this should just be a wrapper for that -# with the start, lower and upper like below - -# if we are setting start and lower -# start = lower if start is missing might be safer... - - -rsp_pls_fit_parent <- function(pls, parent, power=1, - start=100, - lower=50, upper=200, ...){ - - .out <- pls_report(pls) - #parent should only have one species - #and have same profiles as pls model data - #and its contribution to all sources is set by .value - - .out <- subset(.out, SPECIES_ID == unique(.out$SPECIES_ID)[1]) - .test <- c("PROFILE_CODE", ".value", "WEIGHT_PERCENT") - .test <- names(parent)[names(parent) %in% .test] - .data <- parent[.test] - names(.data)[2] <- "parent" - .data <- merge(.out, .data[c(1:2)]) - - #formula - .ms <- names(.data)[grepl("^m_", names(.out))] - .for <- paste("(`", .ms, "`*`", gsub("^m_", "n_", .ms), "`)", - sep="", collapse = "+") - .for <- as.formula(paste("parent~", .for)) - - .ns <- .ms - names(.ns) <- gsub("^m_", "n_", .ms) - .ls <- lapply(.ns, function(x){start}) - .ls2 <- lapply(.ns, function(x){lower}) - .ls3 <- lapply(.ns, function(x){upper}) - - mod <- nls(.for, data=.data, - #weights = (1/.out$test)^power, # think about weighting - start=.ls, - lower=.ls2, - upper=.ls3, - algorithm="port", - control=nls.control(tol=1e-5) #think about tolerance - ) - .ans <- data.frame( - PROFILE_CODE = .data$PROFILE_CODE, - SPECIES_ID = parent$SPECIES_ID[1], - SPECIES_NAME = parent$SPECIES_NAME[1], - t(coefficients(mod)), - test = .data$parent - ) - names(.ans) <- gsub("^n_", "", names(.ans)) - for(i in .ans$PROFILE_CODE){ - .ii <- subset(.ans, PROFILE_CODE==i) - .ii <- .ii[names(.ii) != "PROFILE_CODE"] - pls[[i]]$args$data <- - rbind(pls[[i]]$args$data, .ii) - #rebuild model - .for <- as.character(formula(pls[[i]]$mod)) - .for <- as.formula(paste(.for[2], .for[1], .for[3], sep="")) - .ms <- names(pls[[i]]$args$data) - .ms <- .ms[!.ms %in% c("SPECIES_ID", "SPECIES_NAME", "test")] - .ls <- lapply(.ms, function(x){0}) - names(.ls) <- paste("m_", .ms, sep="") - .da <- pls[[i]]$args$data - - pls[[i]]$mod <- nls(.for, data=.da, - weights = (1/.da$test)^power, # think about weighting - start=.ls, lower=.ls, - algorithm="port", - control=nls.control(tol=1e-5) #think about tolerance - ) - } - - pls - -} - - - -#previous version of rebuild... - - -rsp_pls_rebuild.old <- function(pls, species, power=1, - refit.profile=TRUE, ...){ - - x.args <- list(...) - #hiding model args - - .out <- pls_report(pls) - #species / parent should only have one species - # note: parent is name from previous function - # maybe change now??? - #and have same profiles as pls model data - #and its contribution to all sources is set by .value - - #note - ################################ - #following just done quickly to replace - # two previous functions pls_fit_parent and pls_refit_species - - if(is.character(species)){ - #assuming this is SPECIES_NAME of the species to be fit - #and species was in modelled data when pls was built... - if(!species[1] %in% .out$SPECIES_NAME){ - stop("RSP_PLS> 'species' not in PLS, please check", - call. = FALSE) - } - parent <- subset(.out, SPECIES_NAME == species[1]) - .out <- subset(.out, SPECIES_NAME != species[1]) - - } else { - #assuming this is respeciate object data.frame of right structure - parent <- species - } - #get a 'safe' profile - #not sure this will work if any sources are not fit to first species - .test <- .out[.out$pred>0,] - .out <- subset(.out, SPECIES_ID == unique(.test$SPECIES_ID)[1]) - .test <- c("PROFILE_CODE", ".value", "WEIGHT_PERCENT") - .test <- names(parent)[names(parent) %in% .test] - .data <- parent[.test] - names(.data)[2] <- "parent" - .data <- merge(.out, .data[c(1:2)]) - - ###################### - #for trace - #add parent to this as m_dummy? - #that should fit as n_dummy = 1... - - ########### - #cheat - ############# - - #print(.data) - - #formula - #changed .out to .data in next line - .ms <- names(.data)[grepl("^m_", names(.data))] - .for <- paste("(`", .ms, "`*`", gsub("^m_", "n_", .ms), "`)", - sep="", collapse = "+") - .for <- as.formula(paste("parent~", .for)) - - .ns <- .ms - names(.ns) <- gsub("^m_", "n_", .ms) - - #note - ################## - #model handling temp update - #lower, start and upper - lower <- if("lower" %in% names(x.args)){ - x.args$lower - } else { - 0 - } - start <- if("start" %in% names(x.args)){ - x.args$start - } else { - lower - } - upper <- if("upper" %in% names(x.args)){ - x.args$upper - } else { - Inf - } - - .ls <- lapply(.ns, function(x){start}) - .ls2 <- lapply(.ns, function(x){lower}) - .ls3 <- lapply(.ns, function(x){upper}) - - #print(.data) - #print(.for) - - mod <- nls(.for, data=.data, - #weights = (1/.out$test)^power, - #no weighting currently because species are all the same here! - start=.ls, - lower=.ls2, - upper=.ls3, - algorithm="port", - control=nls.control(tol=1e-5) #think about tolerance - ) - .ans <- data.frame( - PROFILE_CODE = .data$PROFILE_CODE, - SPECIES_ID = parent$SPECIES_ID[1], - SPECIES_NAME = parent$SPECIES_NAME[1], - t(coefficients(mod)), - test = .data$parent - ) - names(.ans) <- gsub("^n_", "", names(.ans)) - - #print(.ans) - - for(i in .ans$PROFILE_CODE){ - .ii <- subset(.ans, PROFILE_CODE==i) - .ii <- .ii[names(.ii) != "PROFILE_CODE"] - .nn <- pls[[i]]$args$data - .nn <- subset(.nn, !SPECIES_NAME %in% unique(.ii$SPECIES_NAME)) - ########### - #cheat - ############# - #print(i) - #print(names(.nn)) - #print(names(.ii)) - ######################## - - pls[[i]]$args$data <- rbind(.nn, .ii) - #rebuild model - .for <- as.character(formula(pls[[i]]$mod)) - .for <- as.formula(paste(.for[2], .for[1], .for[3], sep="")) - .ms <- names(pls[[i]]$args$data) - .ms <- .ms[!.ms %in% c("SPECIES_ID", "SPECIES_NAME", "test")] - .ls <- lapply(.ms, function(x){0}) - names(.ls) <- paste("m_", .ms, sep="") - .da <- pls[[i]]$args$data - - - #print(.for) - #note - ############################# - # option to not do this refit? - if(refit.profile){ - pls[[i]]$mod <- nls(.for, data=.da, - weights = (1/.da$test)^power, # think about weighting - start=.ls, lower=.ls, - algorithm="port", - control=control #think about tolerance - ) - } - } - - pls - -} - - - diff --git a/R/rsp.pls.plot.R b/R/rsp.pls.plot.R new file mode 100644 index 0000000..f3ff717 --- /dev/null +++ b/R/rsp.pls.plot.R @@ -0,0 +1,1272 @@ +#' @name rsp.pls.plot +#' @title Plots for use with (re)SPECIATE profile Positive Least Squares models +#' @aliases pls.plot pls_plot pls_plot_species pls_plot_profile + +#' @description +#' The \code{pls_plot} functions are intended for use with PLS models built +#' using \code{rsp_pls_profile} (documented separately). They generate some +#' plots commonly used with source apportionment model outputs. + +#' @param pls A \code{sp_pls_profile} output, intended for use with +#' \code{pls_} functions. +#' @param id numeric or character +#' identifying the species or profile to plot. If numeric, these are treated +#' as indices of the species or profile, respectively, in the PLS model; if +#' character, species is treated as the name of species and profile is treated +#' as the profile code. Both can be concatenated to produce multiple plots and +#' the special case \code{id = -1} is a short cut to all species or profiles, +#' respectively. +#' @param plot.type numeric, the plot type if +#' multiple options are available. +#' @param ... other arguments, typically passed on to the associated +#' \code{lattice} plot. +#' @param log (for \code{pls_plot_profile} only) logical, if \code{TRUE} this +#' applies 'log' scaling to the primary Y axes of the plot. + +######################### +# need to check terminology for this... +# The zero handling is a based on offset in plot(..., log="y", off.set) +# but automatically estimated... +# shifted type to plot.type because it conflicts with type in lattice::xyplot.... + +#' @return \code{pls_plot}s produce various plots commonly used in source +#' apportionment studies. + +# GENERAL NOTES + +# TO DO + +# these all need code tidying + +# check individual function notes + + + +#################################### +################################### +## pls_plots +################################### +################################### + +#these are all draft + + +################################### +################################### +## pls_plot +################################### +################################### + + +#' @rdname rsp.pls.plot +#' @export + +## this replaces previous pls_plot (now pls_plot.old) + +## now imports via data.table:: +## need this to kill the as.data.table load message +## #' @import data.table + +#test +#devtools::load_all() +#d1 <- readRDS("C:\\Users\\trakradmin\\OneDrive - University of Leeds\\Documents\\pkg\\respeciate\\test\\my.working.rds") +#ref <- rsp(c("4868", "4914", "8948", "91155", "91163", "95441", "95529")) +#mod <- rsp_pls_profile(d1, ref, power=2) +#pls_plot(mod) + + +pls_plot <- function (pls, plot.type = 1, ...){ + + #current using lattice/latticeExtra for the panelling/layers... + + ######################## + # to do + ######################## + # id + + #basic plots in development... + # plot element ordering + # currently as it comes... + # because *I think* pls_report kills all pre-model handling... + # no id handling + # that maybe need to be in plot type... + # maybe also want to do it at end + # so missing case locations are retained (if needed for plot)... + + ############################ + # nags + ############################ + + # pls_plot(..., horizontal=FALSE) errors + # should flip x and y... + + # type = 1 + ############################ + #in development + #pls fit summary + # simple proportional fit plot + #think like + # https://latticeextra.r-forge.r-project.org/man/postdoc.html + # (but without the 100 percent (proportion=1) limit...) + + + # type = 2 + ############################ + #to do? + + ################# + #setup + ################# + .x.args <- list(...) + dat <- pls_report(pls) + .ord.pro.c <- .rsp_profile_code_order(dat) + ###################################### + #option to not do name simplification? + # + dat$SPECIES_NAME <- .rsp_tidy_species_name(dat$SPECIES_NAME) + .sp.ref <- unique(dat$SPECIES_NAME) + + #type + if(!plot.type %in% c(1)){ + stop("pls_plot: plot.type unknown, check ?pls_plot", + call. = FALSE) + } + + ############################ + #type 1 + ############################ + if(plot.type==1){ + ################################## + # note + # maybe use .rsp_get_prop_from_pls + # but check naming change...??? + ################################## + .tmp <- names(dat) + .tmp <- .tmp[grep("^.x_", .tmp)] + .refs <- c(.tmp, "pred") + #make summary pls. prop.table + .ans <- lapply(.sp.ref, function(x){ + .tmp <- subset(dat, SPECIES_NAME==x) + .d2 <- .tmp[1, c("SPECIES_NAME", .refs)] + for(.ref in .refs){ + #use only paired cases to calculate skew... + .tmp2 <- .tmp[c(.ref, ".value")] + .tmp2[.tmp2==0] <- NA + .tmp2 <- na.omit(.tmp2) + .d2[, .ref] <- sum(.tmp2[,.ref], na.rm=TRUE) / sum(.tmp2[,".value"], na.rm=TRUE) + } + .d2 + }) + .ans <- do.call(rbind, .ans) + + #barchart formula + .for <- paste(.tmp, collapse="+") + .for <- as.formula(paste("SPECIES_NAME~", .for, sep="")) + .tmp <- gsub("^.x_", "", .tmp) + #plot lists + gr.ls <- list(h=-1, v=-1, col="grey", lty=3) + if("grid" %in% names(.x.args) && is.list(.x.args$grid)){ + gr.ls <- modifyList(gr.ls, .x.args$grid) + .x.args$grid <- NULL + } + bar.ls <- list(v=c(0.5,1,2), lty=c(3,2,3), col="red") + if("bars" %in% names(.x.args) && is.list(.x.args$bars)){ + bar.ls <- modifyList(bar.ls, .x.args$bars) + .x.args$bars <- NULL + } + if("col" %in% names(.x.args)){ + #could allow function as col input??? + .cols <- rep(.x.args$col, length.out=length(.tmp)) + } else { + .cols <- rainbow(length(.tmp)) + .x.args$col <- .cols + } + ky.ls <- list(space="right", text=list(text=.tmp), + rect=list(col=.cols)) + if("key" %in% names(.x.args) && is.list(.x.args$key)){ + if(any(c("x", "y") %in% names(.x.args$key))){ + ky.ls$space <- NULL + } + .x.args$key <- modifyList(ky.ls, .x.args$key) + } else { + .x.args$key <- ky.ls + } + ##################################### + #note + #maybe use .rsp_panelpal for grid and bars + # see pls_plot_profile ??? + ##################################### + pl.ls <- list(x=.for, data=.ans, origin=0, stack=TRUE, + grid=TRUE, bars=TRUE, xlim=c(-0.025, NA), + xlab="mean(model) / mean(measurements)", + #prepanel=function(...){ + # ans <- lattice::prepanel.default.bwplot(...) + # print(ans) + # ans + #}, + panel=function(...){ + .temp <- list(...) + if(.temp$grid){ + do.call(panel.grid, gr.ls) + } + panel.barchart(...) + if(.temp$bars){ + do.call(panel.abline, bar.ls) + } + } + ) + pl.ls <- modifyList(pl.ls, .x.args) + p <- do.call(barchart, pl.ls) + } + + #output + ############################ + #this needs working up based on input from Dennis... + plot(p) + return(invisible(.ans)) +} + + + + + + + + + +#################################### +#################################### +## pls_plot_profile +#################################### +#################################### + + +#' @rdname rsp.pls.plot +#' @export + +## now imports from xxx.r +## #' @import data.table + + +############################# +#this needs a lot of work +############################# + +#test +#devtools::load_all() +#d1 <- readRDS("C:\\Users\\trakradmin\\OneDrive - University of Leeds\\Documents\\pkg\\respeciate\\test\\my.working.rds") +#ref <- rsp(c("4868", "4914", "8948", "91155", "91163", "95441", "95529")) +#mod <- rsp_pls_profile(d1, ref, power=2) +#pls_plot_profile(mod) + + +# log scale may need work +# but that is in rsp_plot_profile/unexported functions... + +pls_plot_profile <- function (pls, plot.type=1, log = FALSE, ...) +{ + #new version of pls_plot + + #to do + ############################## + # log (needs better axes control) but that is in rsp_profile_plot + # id needs to be enabled... + + #setup + ############################# + .x.args <- list(...) + dat <- pls_report(pls) + .ord.pro.c <- .rsp_profile_code_order(dat) + ###################################### + #option to not do name simplification? + # + dat$SPECIES_NAME <- .rsp_tidy_species_name(dat$SPECIES_NAME) + .sp.ref <- unique(dat$SPECIES_NAME) + + #plot.type control + if(!plot.type %in% c(1)){ + stop("pls_plot_profile: plot.type unknown, check ?pls_plot_profile", + call. = FALSE) + } + + ############################ + #type 1 + ############################ + if(plot.type==1){ + #make first plot and output .ans + ############################## + #get profiles .m_ columns + .ans <- .rsp_get_m_from_pls(dat) + .p1.prof <- unique(.ans$PROFILE_CODE) + #send to rsp_plot_profile with any user arguments + # to make first plot + #set cols + #set cols + p1.ls <- list(rsp=.ans, layout =c(1, length(.p1.prof)), log=log, + multi.profile = "panel", id=1:length(.p1.prof), + order=FALSE, silent=TRUE) + if(!"col" %in% names(p1.ls)){ + #maybe need better handling + p1.ls$col <- trellis.par.get("superpose.symbol")$col[1] + } + #issue with species_code not being known made this... + p1.ls <- modifyList(p1.ls, .x.args) + p1 <- do.call(rsp_plot_profile, p1.ls) + + #make second plot and .ans2 + ###################################### + .ans2 <- .rsp_get_prop_from_pls(dat) + .ans2$.pc <- .ans2$.prop * 100 + #could do this in the panel so any missing is greyed out ??? + .ans2$.pc[is.na(.ans2$.pc)] <- 0 + p2.ls <- .rsp_panelPal("tc", list(x =.pc~factor(SPECIES_NAME)|factor(PROFILE_CODE), + data=.ans2, + type=c("h", "p"), pch=18, layout=c(1,7), + ylab="Total Contribution (%)", + scales=list(x=list(rot=90))), + #note: function is cheat to use .rsp... outside lattice + # could make it the default if no panel set in call??? + function(...){list(...)}, ...) + #if tc layer not turned off.. + if(!is.null(p2.ls)){ + if(!"col" %in% names(p2.ls)){ + #maybe need better handling + p2.ls$col <- trellis.par.get("superpose.symbol")$col[2] + } + p2 <- do.call(xyplot, p2.ls) + p1 <- update(doubleYScale(p1, p2, add.ylab2 = TRUE), + par.settings = simpleTheme(col=c(p1.ls$col[1], p2.ls$col[1])) + ) + } + } + + #output + ############################ + #this needs working up based on input from Dennis... + plot(p1) + return(invisible(list(profile = .ans, tc = .ans2))) + +} + + + + + + + + +#################################### +#################################### +## pls_plot_species +#################################### +#################################### + + + + +#' @rdname rsp.pls.plot +#' @export + +## now imports from xxx.r +## #' @import data.table + + +############################# +#this needs a lot of work +############################# + +#test +#devtools::load_all() +#d1 <- readRDS("C:\\Users\\trakradmin\\OneDrive - University of Leeds\\Documents\\pkg\\respeciate\\test\\my.working.rds") +#ref <- rsp(c("4868", "4914", "8948", "91155", "91163", "95441", "95529")) +#mod <- rsp_pls_profile(d1, ref, power=2) +#pls_plot_species(mod) + +# id enabled but +# species order is always as supplied... +# probably actually alphabetic +# look like order(character(unique(PROFILE_CODE))) + +# to do +# limit default output to < 7 plots? +# .rsp_panelpal handling like other plots +# layer .mod ??? +# log ??? (not sure it is needed/useful) +# decide how to reorder or rename species, profiles and x data +# (do this in plots and data ???) +# decide how to modify .index + +pls_plot_species <- function (pls, id, plot.type=1, ...) +{ + #new version of pls_plot + + #to do + ############################## + # most stuff + # log not sure about doing them... + # id + + #setup + ############################# + .x.args <- list(...) + dat <- pls_report(pls) + .ord.pro.c <- .rsp_profile_code_order(dat) + .sp.ref <- unique(dat$SPECIES_NAME) + my.species <- if (missing(id)) { + .sp.ref + #default option (print the lot...) + ############################ + #possibly a warning if lots of species to plot + ################## + } else { + id + } + if (is.numeric(my.species)) { + if (all(my.species == -1)) { + my.species <- .sp.ref + } + else { + my.species <- .sp.ref[my.species] + } + } + if(length(my.species)>6 & missing(id)){ + #to think about + # option to turn off warning??? + # (using in older versions of code) + #if(!silent){ + warning("RSP/PLS> ", length(my.species), " species... ", + "just showing first 6 to reduce plot clutter", + "\n\t (maybe use id to force larger range if sure)", + sep="", call.=FALSE) + #} + my.species <- my.species[1:6] + } + + ###################################### + #option to not do name simplification? + # + dat$SPECIES_NAME <- .rsp_tidy_species_name(dat$SPECIES_NAME) + .sp.ref <- unique(dat$SPECIES_NAME) + my.species <- .rsp_tidy_species_name(my.species) + + if (!any(my.species %in% .sp.ref)) { + stop("pls_plot_species> unknown species, please check", call. = FALSE) + } + + .tmp <- dat[c("SPECIES_NAME", "PROFILE_CODE", ".value")] + .tmp <- data.table::as.data.table(.tmp) + .tmp <- data.table::dcast(.tmp, PROFILE_CODE ~ SPECIES_NAME, + mean, + na.rm=TRUE, + value.var = ".value") + .tmp2 <- data.table::melt(.tmp, id.vars="PROFILE_CODE", variable.name="SPECIES_NAME", + value.name=".value") + .tmp <- dat[c("SPECIES_NAME", "PROFILE_CODE", "pred")] + .tmp <- data.table::as.data.table(.tmp) + .tmp <- data.table::dcast(.tmp, PROFILE_CODE ~ SPECIES_NAME, + mean, + na.rm=TRUE, + value.var = "pred") + .tmp <- data.table::melt(.tmp, id.vars="PROFILE_CODE", variable.name="SPECIES_NAME", + value.name="pred") + .tmp <- data.table::merge.data.table(.tmp2, .tmp) + .tmp <- as.data.frame(.tmp) + .tmp$.index <- as.numeric(factor(.tmp$PROFILE_CODE, levels=.ord.pro.c, + ordered = TRUE)) + .tmp<- .tmp[order(.tmp$.index),] + + #plot.type control + if(!plot.type %in% c(1,2)){ + stop("pls_plot_species: plot.type unknown, check ?pls_plot_profile", + call. = FALSE) + } + + .tmp <- subset(.tmp, SPECIES_NAME %in% my.species) + + ############################ + #type 1 + ############################ + if(plot.type==1){ + .mc <- if ("mod.col" %in% names(.x.args)) { + .x.args$mod.col + } else { + "red" + } + plt <- list(x=pred~.value | SPECIES_NAME, data=.tmp, + #prepanel forces x and y lims to same range + prepanel=function(...){ + .tmp <- prepanel.default.xyplot(...) + .tmp$xlim <- range(c(.tmp$xlim, .tmp$ylim)) + .tmp$ylim <- .tmp$xlim + .tmp + }, + panel= function(x, y, xlim, ylim, ...){ + #user control of grid - like loa... + .rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), + lattice::panel.grid, ...) + #TO DO + #user control of y=x + panel.ablineq(a = 0, b = 1, adj = c(0,1), + col.line ="grey", lty=2, label="") + #user control of main plotted data via + # standard lattice + panel.xyplot(x=x, y=y, ...) + #CURRENTLY JUST col via mod.col + #user control of model + panel.ablineq(lm(y ~ x + 0), cex = 0.8, + x = min(c(x, y), na.rm=TRUE), + y = max(c(x, y), na.rm=TRUE), + r.squared = TRUE, adj = c(0,1), + sep = " (", sep.end = ")", + offset=0, varStyle = NULL, + col.line = .mc, col.text = .mc, digits = 2) + }, + xlab="Measurement", ylab="model", + scales=list(y=list(relation="free", + rot=90), + x=list(relation="free"))) + plt <- modifyList(plt, .x.args) + p <- do.call(xyplot, plt) + + } + if(plot.type==2){ + #xlab + if(!"xlab" %in% names(.x.args)){ + .x.args$xlab <- "Sample [index]" + } + if(!"ylab" %in% names(.x.args)){ + .x.args$ylab <- "Measurement, Model" + } else { + if(length(.x.args$ylab)>1){ + if(!"key.text" %in% names(.x.args)){ + .x.args$key.text <- .x.args$ylab[1:2] + } + .x.args$ylab <- paste(.x.args$ylab[1], .x.args$ylab[2], sep =", ") + } + } + if(!"key.text" %in% names(.x.args)){ + .x.args$key.text <- c("Measurement", "Model") + } + if(!"col" %in% names(.x.args)){ + .x.args$col <- trellis.par.get("superpose.line")$col[1:2] + } + if("mod.col" %in% names(.x.args)){ + .x.args$col <- c(.x.args$col[1], .x.args$mod.col) + } + p2.ls <- list(x= .value + pred ~ .index | SPECIES_NAME, data=.tmp, + auto.key = list(text=.x.args$key.text, + space="top", columns=2), + type="l", + panel= function(...){ + .rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), + panel.grid, ...) + lattice::panel.xyplot(...) + }, + scale=list(relation="free"), + par.settings = simpleTheme(col=.x.args$col)) + p2.ls <- modifyList(p2.ls, .x.args) + p <- do.call(xyplot, p2.ls) + plot(p) + + } + + #output + ############################ + #this needs working up based on input from Dennis... + plot(p) + return(invisible(.tmp)) + +} + + + + + + + + + + + + + + +###################################### +###################################### +## unexported +###################################### +###################################### + +#old +# holding until new versions are fully up and running... + + + +## pls_plot.old + +############################ +# currently not exporting... +############################ + +# #' @rdname rsp.pls.plot +# #' @export + +## now imports via data.table:: +## need this to kill the as.data.table load message +## #' @import data.table +## + +############################# +#this needs a lot of work +############################# + +# this uses unexported rsp_profile_pie function below... +# both pls_plot and rsp_profile_pie need work... + + +pls_plot.old <- function (pls, n, plot.type = 1, ...){ + + #current using lattice/latticeExtra for the panelling/layers... + + #basic plots finished but... + # currently not passing arguments generally + # the par setting seem to be dropped when using plot(p) + # ahead of end of function + + ############################ + # nags + ############################ + + # type = 1 + ############################ + + # note sure about the layer naming + # zero is not bottom of barchart... + + # type = 2 + ############################ + + # the label positioning is messy (see not about nudge) + + # cex setting too small if only one panel... + + #wondering about + # https://latticeextra.r-forge.r-project.org/man/postdoc.html + # as an alternative to type=2 + # (but 100 percent measured rather than proportion...) + + ################# + #setup + ################# + .x.args <- list(...) + dat <- pls_report(pls) + .ord.pro.c <- .rsp_profile_code_order(dat) + #dat$SPECIES_NAME <- .rsp_tidy_species_name(dat$SPECIES_NAME) + .sp.ref <- unique(dat$SPECIES_NAME) + #species + # now defaulting to all plots + species <- if (missing(n)) { + species <- .sp.ref + } + else { + n + } + if (is.numeric(species)) { + if (all(species == -1)) { + species <- .sp.ref + } + else { + species <- .sp.ref[species] + } + } + if (!any(species %in% .sp.ref)) { + stop("RSP_PLS> unknown species, please check", call. = FALSE) + } + ################################ + #note: + # could condition here BUT currently + # holding on to everything until just before plot + # might not need to do this.... + ################################# + + .sp.ord <- unique(dat$SPECIES_ID) + ##################################### + #messy at moment... + .sp.m.pro <- names(dat)[grep("^.n_", names(dat))] + .sp.pro <- gsub("^.n_", "", .sp.m.pro) + + #line col.... + .col <- lattice::trellis.par.get("superpose.line")$col[1] + + #bar cols + .cols <- if ("col" %in% names(.x.args)) { + #could include if you supply a function..? + #could use col.regions? + .cols <- .x.args$col + } + else { + .cols <- heat.colors(n = length(.sp.m.pro)) + } + if (length(.cols) != length(.sp.m.pro)) { + stop("pls_plot> halted; expecting ", length(.sp.m.pro), + "colours; given ", length(.cols), sep = "", call. = FALSE) + } + + ###################### + # build x_[profile] + ###################### + #now done in pls_report + for (i in .sp.pro) { + dat[, paste(".x_", i, sep = "")] <- dat[, paste(".m_", i, sep = "")] * + dat[, paste(".n_", i, sep = "")] + } + .sp.x.pro <- names(dat)[grep("^.x_", names(dat))] + .rep <- dat[c("SPECIES_NAME", "SPECIES_ID", "PROFILE_CODE", + .sp.x.pro)] + .rep <- data.table::melt(data.table::as.data.table(.rep), + id = c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE")) + .tot <- data.table::as.data.table(dat) + .cs <- c(".value", "pred", .sp.x.pro) + .tot <- .tot[, lapply(.SD, function(x) sum(x, na.rm = TRUE)), + .SDcols = .cs, by = c("SPECIES_ID", "SPECIES_NAME")] + + ########################### + # now plotting as panels + # using + ########################### + + ###################################################### + # now using rsp_ function to track all pls model cases + # previous method only tracked valid cases for the plotted data + # so no gaps where models dropped/not built... + ######################################################### + .rep$.index <- as.numeric(factor(.rep$PROFILE_CODE, levels = .ord.pro.c, + ordered = TRUE)) + dat$.index <- as.numeric(factor(dat$PROFILE_CODE, levels = .ord.pro.c, + ordered = TRUE)) + + .tmp <- dat[c("SPECIES_ID", "PROFILE_CODE", ".index", ".value", "pred")] + .rep <- data.table::merge.data.table(.rep, .tmp) + + .rep$variable <- gsub("^x_", "", .rep$variable) + + #print(names(.rep)) + #return(dat) + + .rep <- subset(as.data.frame(.rep), SPECIES_NAME %in% species) + + if (1 %in% plot.type) { + + #lattice sets panel order based + .sp <- if(is.factor(.rep$SPECIES_NAME)){ + levels(.rep$SPECIES_NAME) + } else { + sort(unique(.rep$SPECIES_NAME)) + } + .sp <- .sp[.sp %in% .rep$SPECIES_NAME] + #.y.scale <- lapply(unique(.rep$SPECIES_NAME), function(x){ + .y.scale <- lapply(.sp, function(x){ + .tmp <- .rep[.rep$SPECIES_NAME==x,] + c(0, max(c(.tmp$.value, .tmp$pred), na.rm=TRUE)) + }) + ############################################### + #use loa method to generalise this? + ############################################### + + p2 <- lattice::xyplot(.value ~ .index | SPECIES_NAME, .rep, + panel=lattice::panel.xyplot, + type="s", xlab="Sample [index]", + ylab="Measurement", + scales=list(relation="free"), + ylim=.y.scale) + + p <- lattice::barchart(value ~ factor(.index) | SPECIES_NAME, .rep, + groups=.rep$variable, stack=TRUE, + panel=function(x, y, col, groups, ..., subscripts){ + #grid control like loa + .rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), + lattice::panel.grid, ...) + lattice::panel.barchart(x=x, y=y, col=col, + groups=groups, + subscripts=subscripts, ...) + .y <- .rep$.value[subscripts] + #col needs to be from option[1] + lattice::panel.xyplot(x=x, y=.y, + col=.col, + type="l", + subscripts=subscripts,...) + }, + scales=list(relation="free"), + #auto.key=list(space="top", columns=2, + # col.line=.cols, + # points=FALSE, rectangles=TRUE), + ylim=.y.scale, + col=.cols, + border=NA, + #par.settings = list(superpose.polygon = list(col = .cols, + # pch =c (15, 15)), + #superpose.symbol = list(fill = .cols)), + auto.key=list(space="top", columns = 3, + cex=0.8, + points=FALSE, + rectangles=TRUE)) #, + #xscale.components = function(lim,...){ + # lim <- as.numeric(as.character(lim)) + # ans <- lattice::xscale.components.default(lim=lim,...) + # print(ans) + # ans + #}) + plot(update(latticeExtra::doubleYScale(p2, p, add.axis = FALSE), + par.settings = list(superpose.polygon = list(col = .cols), + superpose.symbol = list(fill = .cols)))) + + #p2 <- lattice::xyplot(.value ~ factor(.index) | SPECIES_NAME, dat, + # type="l", scales=list(relation="free")) + #plot(cheat(p, latticeExtra::as.layer(p2))) + + #plot(latticeExtra::doubleYScale(p, p2, add.axis=FALSE, add.ylab2=FALSE)) + } + if (2 %in% plot.type) { + + + p <- lattice::xyplot(value ~ .index | SPECIES_NAME, .rep, + groups=.rep$variable, + totals=.rep$.value, + scales=list(relation="free", + draw=FALSE), + ylab="", xlab="", + col = .cols, + auto.key=list(space="top", columns = 3, + cex=0.8, + points=FALSE, + rectangles=TRUE), + ylim=c(-2,2), xlim=c(-2,2), + between = list(x = 0.2, y = 0.2), + panel=.rsp_panel.pie, + par.settings = list(superpose.polygon = list(col = .cols), + axis.line = list(col = 'transparent'), + superpose.symbol = list(fill = .cols)) + ) + plot(p) + + } + invisible(.rep) +} + + + + +######################## +#currently not exporting +######################## + + +pls_plot_profile.old <- function (pls, n, log = FALSE, ...) +{ + ######################### + #previous plot used base r graphics + #this moved to lattice/latticeExtra + #so we can panel outputs + ######################### + + #setup + .x.args <- list(...) + .plt.args <- .x.args[names(.x.args %in% c())] + dat <- pls_report(pls) + .sp.ord <- unique(dat$SPECIES_ID) + .sp.m.pro <- names(dat)[grep("^.m_", names(dat))] + .sp.pro <- gsub("^.m_", "", .sp.m.pro) + #defaulting n to all profiles as one plot + profile <- if (missing(n)) { + profile <- .sp.pro + } else { + n + } + if (is.numeric(profile)) { + if (all(profile == -1)) { + profile <- .sp.pro + } + else { + profile <- .sp.pro[profile] + } + } + if (!any(profile %in% .sp.pro)) { + stop("RSP_PLS> unknown profile(s), please check", call. = FALSE) + } + + ######################### + #build x_[profile] + ######################### + n_profile <- paste(".n_", profile, sep = "") + m_profile <- paste(".m_", profile, sep = "") + dat <- dat[c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE", + n_profile, m_profile, "pred", ".value")] + for (i in profile) { + dat[, paste(".x_", i, sep = "")] <- dat[, paste(".m_", i, sep = "")] * + dat[, paste(".n_", i, sep = "")] + } + + .rep <- data.table::as.data.table(dat) + .cols <- c(".value", "pred", paste(".x_", profile, sep = "")) + .rep <- .rep[, lapply(.SD, function(x) sum(x, na.rm = TRUE)), + .SDcols = .cols, by = c("SPECIES_ID", "SPECIES_NAME")] + .rep <- as.data.frame(.rep) + + ######################### + # y2 setup + ######################### + # by default this is .value + # but might want mod prediction + if ("y2" %in% names(.x.args) && .x.args$y2 == "pred") { + for (i in profile) { + .rep[, paste("pc_", i, sep = "")] <- + (.rep[, paste(".x_", i, sep = "")]/.rep$pred) * 100 + } + } + else { + for (i in profile) { + .rep[, paste("pc_", i, sep = "")] <- + (.rep[, paste(".x_", i, sep = "")]/.rep$.value) * 100 + } + } + #might not need all of following now we + #we are not pulling apart to plot one at time... + dat <- dat[!duplicated(dat$SPECIES_NAME), ] + dat$PROFILE_NAME <- dat$PROFILE_NAME[1] + dat$PROFILE_CODE <- dat$PROFILE_CODE[1] + dat <- merge(.rep, dat[c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE", + m_profile)], ) + dat <- dat[order(ordered(dat$SPECIES_ID, levels = .sp.ord)), ] + + + ################################ + # build pc_[profile] + ################################ + rownames(dat) <- 1:nrow(dat) + .ref <- names(dat)[grep("pc_", names(dat))] + .oth <- c("SPECIES_ID", "SPECIES_NAME", "PROFILE_CODE", ".value", "pred") + .temp <- data.table::as.data.table(dat[c(.oth, gsub("^pc_", ".x_", .ref))]) + .d1 <- data.table::melt(.temp, measure.vars = gsub("^pc_", ".x_", .ref), + variable.name = "pls_profile", value.name = "loading") + .temp <- data.table::as.data.table(dat[c(.oth, .ref)]) + .d2 <- data.table::melt(.temp, measure.vars = .ref, + variable.name = "pls_profile", value.name = "percent_contr") + .d2$pls_profile <- gsub("^pc_", ".x_", .d2$pls_profile) + dat <- as.data.frame(merge(.d1, .d2, all=T)) + ############################# + + ############################ + #now using lattice to handle logs + ############### + #.dat <- dat + #don't need local version of dat because not changing data ahead of plot + #if(log){ + # .dat$loading <- log10(.dat$loading) + # .ylim <- lapply(profile, function(x){ + # .temp <- subset(.dat, pls_profile==x) + # .temp <- range(.temp$loading, na.rm=TRUE, finite=TRUE) + # if(.temp[1] == .temp[2]){ + # .temp <- c(.temp[1]-1, .temp[1]+1) + # } + # range(c(floor(.temp), ceiling(.temp))) + # }) + #} else { + # .ylim <- lapply(profile, function(x){ + # .temp <- subset(.dat, pls_profile==x) + # .temp <- range(.temp$loading, na.rm=TRUE, finite=TRUE) + # range(pretty(.temp)) + # }) + #} + + ###################### + #plot + ###################### + #now using lattice/latticeExtra + ## + #think there is more here that can be generalized... + p1.ls <- list(x = loading~SPECIES_NAME | pls_profile, + data=dat, ylab="Source Loading", + panel = function(...){ + .rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), + panel.grid, ...) + panel.barchart(...) + }, + between=list(y=.2), + scales=list(x=list(rot=90), + y=list(rot=c(0,90), + relation="free")), + layout=c(1,length(profile))) + if(log){ + p1.ls$scales$y$log=10 + p1.ls$yscale.components <- .rsp_yscale.component.log10 + } + p1.ls <- modifyList(p1.ls, .x.args) + if(!"col" %in% names(p1.ls)){ + p1.ls$col <- trellis.par.get("superpose.line")$col[1] + } + p1 <- do.call(barchart, p1.ls) + if("mod" %in% names(.x.args) && !.x.args$mod){ + #if mod FALSE just plot 1 + plot(p1) + } else { + #add mod layer (total contributions) as y2 + .col2 <- if("mod.col" %in% names(.x.args)){ + .x.args$mod.col + } else { + trellis.par.get("superpose.line")$col[2] + } + p2.ls <- list(x = percent_contr ~ factor(SPECIES_NAME) | pls_profile, + pch=16, type=c("h", "p"), col= c(.col2, .col2), + ylab="Total Contribution (%)", + data=dat) + .tmp <- .x.args[grepl("^mod[.]", names(.x.args))] + if(length(.tmp)>0){ + names(.tmp) <- gsub("^mod[.]", "", names(.tmp)) + p2.ls <- modifyList(p2.ls, .tmp) + } + p2 <- do.call(xyplot, p2.ls) + plot(update(doubleYScale(p1, p2, add.ylab2 = TRUE), + par.settings = simpleTheme(col=c(p1.ls$col[1], .col2)))) + } + + ############ + #output + ############ + #could pass plot and data as list??? + return(invisible(dat)) +} + + + +## #' @rdname rsp.pls.plot +## #' @export + +## now imports from xxx.r +## #' @import data.table + +############################# +#this needs a lot of work +############################# + +#currently not exporting + +pls_plot_species.old <- function (pls, id, plot.type = 1, ...) +{ + + #not including NAs.... + + ########################### + # setup + ########################### + .x.args <- list(...) + dat <- pls_report(pls) + .ord.pro.c <- .rsp_profile_code_order(dat) + .sp.ref <- unique(dat$SPECIES_NAME) + species <- if (missing(id)) { + .sp.ref + #default option (print the lot...) + ############################ + #possibly a warning if lots of species to plot + ################## + } else { + id + } + if (is.numeric(species)) { + if (all(species == -1)) { + species <- .sp.ref + } + else { + species <- .sp.ref[species] + } + } + if (!any(species %in% .sp.ref)) { + stop("RSP_PLS> unknown species, please check", call. = FALSE) + } + ############################ + #if not earlier, then here? + #possibly a warning if lots of species to plot + ################## + + ######################### + #could drop a lot of this?? + ######################### + .xlb <- if ("xlab" %in% names(.x.args)) { + .x.args$xlab + } else { + "Measurement" + } + .ylb <- if ("ylab" %in% names(.x.args)) { + .x.args$ylab + } else { + "Model" + } + .bc <- if ("col" %in% names(.x.args)) { + .x.args$col + } else { + par("col") + } + .mc <- if ("mod.col" %in% names(.x.args)) { + .x.args$mod.col + } else { + "red" + } + dat <- subset(dat, SPECIES_NAME %in% species) + # lims <- range(c(d2$.value, d2$pred), na.rm = TRUE, finite = TRUE) + # mod <- lm(pred ~ 0 + .value, d2) + # .sum <- paste("y = ", signif(summary(mod)$coefficients[1, + # 1], 3), "x (adj.R2 = ", signif(summary(mod)$adj.r.squared, + # 3), ")", sep = "") + .lims <- lapply(species, function(x){ + .d <- subset(dat, SPECIES_NAME==x) + range(c(.d$pred, .d$.value), finite=TRUE, na.rm=TRUE) + }) + if (1 %in% plot.type) { + .mc <- if ("mod.col" %in% names(.x.args)) { + .x.args$mod.col + } else { + "red" + } + p1.ls <- list(x=pred~.value | SPECIES_NAME, data=dat, + #prepanel forces x and y lims to same range + prepanel=function(...){ + .tmp <- prepanel.default.xyplot(...) + .tmp$xlim <- range(c(.tmp$xlim, .tmp$ylim)) + .tmp$ylim <- .tmp$xlim + .tmp + }, + panel= function(x, y, xlim, ylim, ...){ + #user control of grid - like loa... + .rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), + lattice::panel.grid, ...) + #TO DO + #user control of y=x + panel.ablineq(a = 0, b = 1, adj = c(0,1), + col.line ="grey", lty=2, label="") + #user control of main plotted data via + # standard lattice + panel.xyplot(x=x, y=y, ...) + #CURRENTLY JUST col via mod.col + #user control of model + panel.ablineq(lm(y ~ x + 0), cex = 0.8, + x = min(c(x, y), na.rm=TRUE), + y = max(c(x, y), na.rm=TRUE), + r.squared = TRUE, adj = c(0,1), + sep = " (", sep.end = ")", + offset=0, varStyle = NULL, + col.line = .mc, col.text = .mc, digits = 2) + }, + xlab="Measurement", ylab="model", + scales=list(y=list(relation="free", + rot=90), + x=list(relation="free"))) + p1.ls <- modifyList(p1.ls, .x.args) + p <- do.call(xyplot, p1.ls) + plot(p) + + # plot(d2$.value, d2$pred, type = "n", main = i, col = .bc, + # xlab = .xlb, ylab = .ylb, xlim = lims, ylim = lims) + # grid() + # abline(a = 0, b = 1, col = "grey") + # points(d2$.value, d2$pred) + # abline(mod, col = .mc, lty = 2) + # text(lims[1], lims[2], .sum, adj = c(0, 1), cex = 0.75) + } + if (2 %in% plot.type) { + #xlab + if(!"xlab" %in% names(.x.args)){ + .x.args$xlab <- "Sample [index]" + } + if(!"ylab" %in% names(.x.args)){ + .x.args$ylab <- "Measurement, Model" + } else { + if(length(.x.args$ylab)>1){ + if(!"key.text" %in% names(.x.args)){ + .x.args$key.text <- .x.args$ylab[1:2] + } + .x.args$ylab <- paste(.x.args$ylab[1], .x.args$ylab[2], sep =", ") + } + } + if(!"key.text" %in% names(.x.args)){ + .x.args$key.text <- c("Measurement", "Model") + } + if(!"col" %in% names(.x.args)){ + .x.args$col <- trellis.par.get("superpose.line")$col[1:2] + } + if("mod.col" %in% names(.x.args)){ + .x.args$col <- c(.x.args$col[1], .x.args$mod.col) + } + + + #ylab + #can to two terms for + + #if("ylab" %in% names(.x.args)){ + # if(length(.x.args$ylab)>1){ + # if(!"key.text" %in% names(.x.args)){ + # .x.args$key.text <- .x.args$ylab[1:2] + # } + # .x.args$ylab <- paste(.x.args$ylab[1], .x.args$ylab[2], sep =", ") + # } else { + # if(!"key.text" %in% names(.x.args)){ + # .x.args$key.text <- c("Measurement", "Model") + # } + # } + #} else { + # if(!"key.text" %in% names(.x.args)){ + # .x.args$key.text <- c("Measurement", "Model") + # } + # .x.args$ylab <- "Measurement, Model" + #} + + + + + ######################### + #previous code + ######################### + #plot(d2$.value, type = "n", main = i, col = .bc, + # ylab = .ylb, xlab = .xlb, ylim = lims) + #lines(d2$.value) + #lines(d2$pred, col = .mc) + ######################## + #using standardised index + #make 'ordered profile codes' at top + # before any subsetting... + # .ord.pro.c <- rsp_profile_code_order(dat) + dat$.index <- as.numeric(factor(dat$PROFILE_CODE, levels=.ord.pro.c, + ordered = TRUE)) + dat<- dat[order(dat$.index),] + p2.ls <- list(x= .value + pred ~ .index | SPECIES_NAME, data=dat, + auto.key = list(text=.x.args$key.text, + space="top", columns=2), + type="l", + panel= function(...){ + .rsp_panelPal("grid", list(h=-1,v=-1, col="grey", lty=3), + panel.grid, ...) + lattice::panel.xyplot(...) + }, + scale=list(relation="free"), + par.settings = simpleTheme(col=.x.args$col)) + p2.ls <- modifyList(p2.ls, .x.args) + p <- do.call(xyplot, p2.ls) + plot(p) + ###################### + + # or any with any missing are plot on different x scale + # maybe find longest, take range for that + #xyplot(.value + .pred ~ ) + } + + invisible(dat) +} + + + + + + + + + + + diff --git a/R/xxx.R b/R/xxx.R index d600085..e0bccfb 100644 --- a/R/xxx.R +++ b/R/xxx.R @@ -49,7 +49,7 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", #' @importFrom latticeExtra doubleYScale panel.ablineq #' @importFrom data.table ":=" #' @importFrom stats sd cophenetic cor cutree dist hclust heatmap AIC -#' as.formula coefficients formula lm nls nls.control predict update +#' as.formula coefficients formula lm nls nls.control predict update na.omit #' @importFrom utils modifyList head packageVersion #' @importFrom graphics axis barplot par legend lines rect text abline #' grid mtext plot.new plot.window points polygon title @@ -93,6 +93,8 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", + + #.rsp_split_profile ####################################### #split respeciate by profile @@ -734,6 +736,605 @@ utils::globalVariables(c("sysdata", ".SD", "ans", "control", +################ +################ +## unexported +## from pls.plot... +################ +################ + +# profile code order +# get profile order in case you need it latter... + +.rsp_profile_code_order <- function(data){ + .tmp <- data.table::as.data.table(data)[, .(ans=length(unique(PROFILE_CODE))),by="SPECIES_NAME"] + .tmp <- subset(.tmp, ans == max(.tmp$ans, na.rm=TRUE))$SPECIES_NAME + .tmp <- subset(data, SPECIES_NAME %in% .tmp) + sort(unique(.tmp$PROFILE_CODE)) +} + + +#log axis hander +#based on lattice text book method + +#issues?? +# could be problem with y padding when log=T and .value range is wide... + +.rsp_yscale.component.log10 <- function(lim, ...) { + ans <- yscale.components.default(lim = lim, ...) + tick.at <- pretty(lim) + tick.at <- tick.at[tick.at == floor(tick.at)] + tick.at <- tick.at[tick.at < max(lim, na.rm=TRUE) & tick.at > min(lim, na.rm=TRUE)] + ans$left$ticks$at <- tick.at + ans$left$labels$at <- tick.at + ans$left$labels$labels <- c(format(10^(tick.at), + drop0trailing = TRUE, + scientific = FALSE)) + #print(ans$left$labels$labels) + ####################### + #need to sort of right labeling + # dropped for now... + #ans$right <- ans$left + ans +} + + +#lattice panel pal +#based on panel handler in loa + +.rsp_panelPal <- function(.name, .ls, .panel, ...){ + .x.args <- list(...) + if(!.name %in% names(.x.args) || !is.logical(.x.args[[.name]]) || + .x.args[[.name]]){ + .name2 <- paste("^", .name, "[.]", sep="") + if(.name %in% names(.x.args) && is.list(.x.args[[.name]])){ + .tmp <- .x.args[[.name]] + if(length(.tmp)>0){ + names(.tmp) <- paste(.name, names(.tmp), sep=".") + .x.args <- modifyList(.tmp, .x.args) + } + } + .x.args <- .x.args[grepl(.name2, names(.x.args))] + if(length(.x.args)>0){ + names(.x.args) <- gsub(.name2, "", names(.x.args)) + .ls <- modifyList(.ls, .x.args) + } + do.call(.panel, .ls) + } +} + + + +# could move this into the function... + +.rsp_panel.pie <- + function (x, y=NULL, groups=NULL, subscripts, totals=NULL, + labels = names(x), edges = 200, radius = 0.8, clockwise = FALSE, + init.angle = if (clockwise) 90 else 0, density = NULL, angle = 45, + col = NULL, border = 1, lty = NULL, main = NULL, ...) + { + + #this is graphics::pie with a couple of modifications... + #many thanks to... + #R Core Team (2023). _R: A Language and Environment for Statistical Computing_. R Foundation + #for Statistical Computing, Vienna, Austria. . + + #if (!is.numeric(x) || any(is.na(x) | x < 0)) + # stop("'x' values must be positive.") + + ######################### + #measurement totals + .y <- totals[subscripts] + ref <- sapply(unique(groups), function(g){ + sum(.y[groups==g], na.rm=TRUE) + }) + .total <- mean(ref, na.rm=TRUE) + + ########################## + #profile contributions to model + # as percentage of measurements + ans <- sapply(unique(groups), function(g){ + sum(y[groups==g], na.rm=TRUE) + }) + ans <- (ans / .total) * 100 + + ##################### + #cheat because following comes from + #pie function in base r... + x <- ans + + if (is.null(labels)) + labels <- as.character(unique(groups)) + else labels <- as.graphicsAnnot(labels) + labels = paste(labels, " (", + round(ans, digits=1), "%)", sep = "") + + if (any(x == 0)) { + labels <- labels[x != 0] + col <- col[x != 0] + x <- x[x != 0] + } + my.tot <- sum(x, na.rm=TRUE) + ######################## + #this adds extra void area + # if does not account for + # 99 percent of the + # measurements + if (my.tot < 99) { + x <- c(x, 100 - my.tot) + labels <- c(labels, "[hide]") + col <- c(col, NA) + init.angle <- init.angle + (((100 - my.tot)/200) * 360) + } + x <- c(0, cumsum(x)/sum(x)) + dx <- diff(x) + nx <- length(dx) + + ###################### + #???? + pin <- par("pin") + xlim <- ylim <- c(-1, 1) + if (pin[1L] > pin[2L]) + xlim <- (pin[1L]/pin[2L]) * xlim + else ylim <- (pin[2L]/pin[1L]) * ylim + + ######################## + #col setting + # this needs generalising like + # other pls_plot + if (is.null(col)) + col <- if (is.null(density)) + c("white", "lightblue", "mistyrose", "lightcyan", + "lavender", "cornsilk") + else par("fg") + + ######################## + #border setting + # needs generalising... + if (!is.null(border)) + border <- rep_len(border, nx) + + ############## + #lty + # needs generalising... + if (!is.null(lty)) + lty <- rep_len(lty, nx) + + ############## + #angle of segment + angle <- rep(angle, nx) + if (!is.null(density)) + density <- rep_len(density, nx) + twopi <- if (clockwise) + -2 * pi + else 2 * pi + t2xy <- function(t) { + t2p <- twopi * t + init.angle * pi/180 + list(x = radius * cos(t2p), y = radius * sin(t2p)) + } + ########################### + #like to nudge these if percent before and + # this one are both small + # (making labels close) + + for (i in 1L:nx) { + if (!as.character(labels[i]) == "[hide]") { + n <- max(2, floor(edges * dx[i])) + P <- t2xy(seq.int(x[i], x[i + 1], length.out = n)) + lattice::lpolygon(c(P$x, rev(P$x * 0.5)), c(P$y, rev(P$y * + 0.5)), density = density[i], angle = angle[i], + border = border[1], col = col[i], lty = lty[i]) + P <- t2xy(mean(x[i + 0:1])) + lab <- as.character(labels[i]) + if (!is.na(lab) && nzchar(lab)) { + lattice::llines(c(1, 1.2) * P$x, c(1, 1.2) * P$y) + lattice::ltext(1.3 * P$x, 1.3 * P$y, labels[i], xpd = TRUE, + cex=0.7, adj = ifelse(P$x < 0, 1, 0), ...) + } + } + } + lattice::ltext(0, 0, label = paste("sum\n", signif(my.tot, 3), "%", + sep = ""), cex=0.7) + } + + + + + +#think about +####################################### +# printing amount missing as a segment +# adding plot arg control like in plot.respeciate +# adding args to change the displacement of labels + +.rsp_profile_pie <- function (x, labels = names(x), edges = 200, radius = 0.8, + clockwise = FALSE, + init.angle = if (clockwise) 90 else 0, + density = NULL, angle = 45, col = NULL, + border = NULL, lty = NULL, main = NULL, ...) +{ + #this is graphics::pie with a couple of modifications... + #many thanks to... + #R Core Team (2023). _R: A Language and Environment for Statistical Computing_. R Foundation + #for Statistical Computing, Vienna, Austria. . + + #print(labels) + #print(col) + + if (!is.numeric(x) || any(is.na(x) | x < 0)) + stop("'x' values must be positive.") + if (is.null(labels)) + labels <- as.character(seq_along(x)) + else labels <- as.graphicsAnnot(labels) + + #added to remove any source with a zero contribution + #but hold labels and col alignment + if(any(x==0)){ + labels <- labels[x!=0] + col <- col[x!=0] + x <- x[x!=0] + } + my.tot <- sum(x) + if(my.tot < 99){ + x <- c(x, 100-my.tot) + labels <- c(labels, "[hide]") + col <- c(col, NA) + init.angle <- init.angle + (((100-my.tot)/200)*360) + } + + x <- c(0, cumsum(x)/sum(x)) + dx <- diff(x) + nx <- length(dx) + plot.new() + pin <- par("pin") + xlim <- ylim <- c(-1, 1) + if (pin[1L] > pin[2L]) + xlim <- (pin[1L]/pin[2L]) * xlim + else ylim <- (pin[2L]/pin[1L]) * ylim + dev.hold() + on.exit(dev.flush()) + plot.window(xlim, ylim, "", asp = 1) + if (is.null(col)) + col <- if (is.null(density)) + c("white", "lightblue", "mistyrose", "lightcyan", + "lavender", "cornsilk") + else par("fg") + # if (!is.null(col)) + # col <- rep_len(col, nx) + if (!is.null(border)) + border <- rep_len(border, nx) + if (!is.null(lty)) + lty <- rep_len(lty, nx) + angle <- rep(angle, nx) + if (!is.null(density)) + density <- rep_len(density, nx) + twopi <- if (clockwise) + -2 * pi + else 2 * pi + t2xy <- function(t) { + t2p <- twopi * t + init.angle * pi/180 + list(x = radius * cos(t2p), y = radius * sin(t2p)) + } + + for (i in 1L:nx) { + + if(!as.character(labels[i]) == "[hide]"){ + n <- max(2, floor(edges * dx[i])) + P <- t2xy(seq.int(x[i], x[i + 1], length.out = n)) + #changed shape to include hole + polygon(c(P$x, rev(P$x*0.5)), c(P$y, rev(P$y*0.5)), + density = density[i], angle = angle[i], + border = border[i], col = col[i], lty = lty[i]) + P <- t2xy(mean(x[i + 0:1])) + lab <- as.character(labels[i]) + if (!is.na(lab) && nzchar(lab)) { + # 1.2 and 1.3 are the extenders when moving labels way from + # the pie plot itself + lines(c(1, 1.2) * P$x, c(1, 1.2) * P$y) + text(1.3 * P$x, 1.3 * P$y, labels[i], xpd = TRUE, + adj = ifelse(P$x < 0, 1, 0), ...) + } + } + } + + text(0,0, label=paste("sum\n",signif(my.tot, 3), "%", sep="")) + title(main = main, ...) + invisible(NULL) +} + + + +########################### +########################### +## pls_refit_species +########################### +########################### + + +# superseded by pls_fit_species +# not not exported + +# need to update the model handling so it is like sp_pls_profile +# this would sort power issue above +# also means the user can change setting themselves +# THINK ABOUT THIS +# they could make a pls that was not positively constrained + + +.rsp_pls_refit_species <- function(pls, name, power=1, + ...){ + .xx <- pls_report(pls) + #name might want to be case-non-sensitive at some point + #think about how to do this one... + .data <- .xx[.xx$SPECIES_NAME==name,] + #get and hold all the m_ values + #update profile contributions for named species + .ms <- names(.data)[grepl("^m_", names(.xx))] + .xs <- gsub("^m_", "", .ms) + .for <- paste("(`", .ms, "`*`", .xs, "`)", + sep="", collapse = "+") + .for <- as.formula(paste("test~", .for)) + .da <- .data[!names(.data) %in% .xs] + + + .ls <- lapply(.xs, function(x){0}) + names(.ls) <- .xs + + ################# + #user might want to set this??? + + .ls2 <- lapply(.xs, function(x){.data[1, x]}) + names(.ls2) <- .xs + + mod <- nls(.for, data=.da, + #weights = 1/(.out$test^push), # think about weighting + start=.ls2, lower=.ls, + algorithm="port", + control=nls.control(tol=1e-5) #think about tolerance + ) + + .data[.xs] <- data.frame(t(coefficients(mod))) + + #lazy + .ans <- .data + + for(i in .ans$PROFILE_CODE){ + .ii <- subset(.ans, PROFILE_CODE==i) + .ii <- .ii[names(.ii) %in% names(pls[[i]]$args$data)] + .sp.ord <- unique(pls[[i]]$args$data$SPECIES_ID) + pls[[i]]$args$data <- subset(pls[[i]]$args$data, SPECIES_NAME!=name) + pls[[i]]$args$data <- rbind(pls[[i]]$args$data, .ii) + #put back in right order + pls[[i]]$args$data <- + pls[[i]]$args$data[order(ordered(pls[[i]]$args$data$SPECIES_ID, + levels=.sp.ord)),] + #rebuild model + .for <- as.character(formula(pls[[i]]$mod)) + .for <- as.formula(paste(.for[2], .for[1], .for[3], sep="")) + .ms <- names(pls[[i]]$args$data) + .ms <- .ms[!.ms %in% c("SPECIES_ID", "SPECIES_NAME", "test")] + .ls <- lapply(.ms, function(x){0}) + names(.ls) <- paste("m_", .ms, sep="") + .da <- pls[[i]]$args$data + + pls[[i]]$mod <- nls(.for, data=.da, + weights = (1/.da$test)^power, # think about weighting + start=.ls, lower=.ls, + algorithm="port", + control=nls.control(tol=1e-5, + warnOnly = TRUE) #think about tolerance + ) + } + + invisible(pls) + +} + + + +#################################### +#################################### +## pls_fit_parent +#################################### +#################################### + +# superseded by pls_fit_species +# not now exported + +# (like pls_refit_species) +# like to drop power from formals +# maybe ignore or pass overwrites via ...? + +# need to update the model handling so it is like sp_pls_profile +# this would sort power issue above +# also means the user can change setting themselves +# THINK ABOUT THIS +# they could make a pls that was not positively constrained +# this would also remove the start, lower and upper options +# from the formals... + +# parent could already be in x +# then parent could just be the name of parent??? + +# also a case for using this to add a non-parent to x +# e.g. pls_fit_unknown_species... +# to fit a species to the existing model as a source apportion of +# that unknown... +# in which case maybe this should just be a wrapper for that +# with the start, lower and upper like below + +# if we are setting start and lower +# start = lower if start is missing might be safer... + + +.rsp_pls_fit_parent <- function(pls, parent, power=1, + start=100, + lower=50, upper=200, ...){ + + .out <- pls_report(pls) + #parent should only have one species + #and have same profiles as pls model data + #and its contribution to all sources is set by .value + + .out <- subset(.out, SPECIES_ID == unique(.out$SPECIES_ID)[1]) + .test <- c("PROFILE_CODE", ".value", "WEIGHT_PERCENT") + .test <- names(parent)[names(parent) %in% .test] + .data <- parent[.test] + names(.data)[2] <- "parent" + .data <- merge(.out, .data[c(1:2)]) + + #formula + .ms <- names(.data)[grepl("^m_", names(.out))] + .for <- paste("(`", .ms, "`*`", gsub("^m_", "n_", .ms), "`)", + sep="", collapse = "+") + .for <- as.formula(paste("parent~", .for)) + + .ns <- .ms + names(.ns) <- gsub("^m_", "n_", .ms) + .ls <- lapply(.ns, function(x){start}) + .ls2 <- lapply(.ns, function(x){lower}) + .ls3 <- lapply(.ns, function(x){upper}) + + mod <- nls(.for, data=.data, + #weights = (1/.out$test)^power, # think about weighting + start=.ls, + lower=.ls2, + upper=.ls3, + algorithm="port", + control=nls.control(tol=1e-5) #think about tolerance + ) + .ans <- data.frame( + PROFILE_CODE = .data$PROFILE_CODE, + SPECIES_ID = parent$SPECIES_ID[1], + SPECIES_NAME = parent$SPECIES_NAME[1], + t(coefficients(mod)), + test = .data$parent + ) + names(.ans) <- gsub("^n_", "", names(.ans)) + for(i in .ans$PROFILE_CODE){ + .ii <- subset(.ans, PROFILE_CODE==i) + .ii <- .ii[names(.ii) != "PROFILE_CODE"] + pls[[i]]$args$data <- + rbind(pls[[i]]$args$data, .ii) + #rebuild model + .for <- as.character(formula(pls[[i]]$mod)) + .for <- as.formula(paste(.for[2], .for[1], .for[3], sep="")) + .ms <- names(pls[[i]]$args$data) + .ms <- .ms[!.ms %in% c("SPECIES_ID", "SPECIES_NAME", "test")] + .ls <- lapply(.ms, function(x){0}) + names(.ls) <- paste("m_", .ms, sep="") + .da <- pls[[i]]$args$data + + pls[[i]]$mod <- nls(.for, data=.da, + weights = (1/.da$test)^power, # think about weighting + start=.ls, lower=.ls, + algorithm="port", + control=nls.control(tol=1e-5) #think about tolerance + ) + } + + pls + +} + + + + + +####################################### +######################################## +## .rsp_get_[something]_from_pls +##################################### +####################################### + +#for use with pls outputs + +#note: these current expect pls_report([rsp_pls]) as ONLY input dat + +.rsp_get_m_from_pls <- function(dat){ + + #get m profiles from a pls model + ############################################# + + #currently assumes you are giving it pls_report output... + # + + #get m data + ########################### + .refs <- names(dat)[grepl("^[.]m_", names(dat))] + .tmp <- dat[c("SPECIES_NAME", .refs)] + .tmp <- .tmp[!duplicated(.tmp$SPECIES_NAME),] + + #restructure + ######################### + #renaming columns + .tmp <- data.table::melt.data.table(data.table::as.data.table(.tmp), + id.var="SPECIES_NAME") + .tmp <- as.data.frame(.tmp) + names(.tmp)[names(.tmp)=="variable"] <- "PROFILE_CODE" + .tmp$PROFILE_CODE <- as.character(.tmp$PROFILE_CODE) + .tmp$PROFILE_CODE <- gsub("^.m_", "", .tmp$PROFILE_CODE) + names(.tmp)[names(.tmp)=="value"] <- ".value" + #addition cheats so it is respeciate-like + .tmp$PROFILE_NAME <- .tmp$PROFILE_CODE + .tmp$SPECIES_ID <- .tmp$SPECIES_NAME + .tmp$WEIGHT_PERCENT <- .tmp$.value + ##similay using rsp_build_x + ##makes rsp_x but some codes may not be assigned... + #.p1.prof <- unique(.tmp$PROFILE_CODE) + #.ans <- rsp_build_x(.tmp, test.rsp=FALSE) + #.cheat <- .ans$SPECIES_ID[is.na(.ans$SPECIES_ID)] + #if(length(.cheat)>0){ + # .ans$SPECIES_ID[is.na(.ans$SPECIES_ID)] <- .ans$SPECIES_NAME[is.na(.ans$SPECIES_ID)] + #} + + #output + #want to be rsp_x at some point... + .tmp +} + +.rsp_get_prop_from_pls <- function(dat){ + + #get x/.value table from pls model... + ######################################### + + #currently assumes you are giving it pls_report output... + + #get x data, etc + .tmp <- names(dat) + .tmp <- .tmp[grep("^.x_", .tmp)] + .refs <- c(.tmp, "pred") + .sp.ref <- unique(dat$SPECIES_NAME) + #make summary pls. prop.table + .ans2 <- lapply(.sp.ref, function(x){ + .tmp <- subset(dat, SPECIES_NAME==x) + .d2 <- .tmp[1, c("SPECIES_NAME", .refs)] + for(.ref in .refs){ + #use only paired cases to calculate skew... + .tmp2 <- .tmp[c(.ref, ".value")] + .tmp2[.tmp2==0] <- NA + .tmp2 <- na.omit(.tmp2) + .d2[, .ref] <- sum(.tmp2[,.ref], na.rm=TRUE) / sum(.tmp2[,".value"], na.rm=TRUE) + } + .d2 + }) + .ans2 <- do.call(rbind, .ans2) + + #restructure to output + .ans2 <- .ans2[names(.ans2)!="pred"] + .ans2 <- data.table::melt(data.table::as.data.table(.ans2), + id.var="SPECIES_NAME") + .ans2 <- as.data.frame(.ans2) + names(.ans2)[names(.ans2)=="variable"] <- "PROFILE_CODE" + .ans2$PROFILE_CODE <- gsub("^[.]x_", "", as.character(.ans2$PROFILE_CODE)) + names(.ans2)[names(.ans2)=="value"] <- ".prop" + + #output + .ans2 + +} + + diff --git a/man/rsp.pls.Rd b/man/rsp.pls.Rd index 4425c71..b1f86c8 100644 --- a/man/rsp.pls.Rd +++ b/man/rsp.pls.Rd @@ -8,9 +8,6 @@ \alias{pls_fit_species} \alias{pls_refit_species} \alias{pls_rebuild} -\alias{pls_plot} -\alias{pls_plot_species} -\alias{pls_plot_profile} \title{(re)SPECIATE profile Positive Least Squares models} \usage{ rsp_pls_profile(rsp, ref, power = 1, ...) @@ -48,12 +45,6 @@ pls_rebuild( drop.missing = FALSE, ... ) - -pls_plot(pls, n, type = 1, ...) - -pls_plot_species(pls, n, type = 1, ...) - -pls_plot_profile(pls, n, log = FALSE, ...) } \arguments{ \item{rsp}{A \code{respeciate} object, a \code{data.frame} of @@ -72,8 +63,8 @@ range \code{1 - 2.5} are sometimes helpful.} \item{...}{additional arguments, typically ignored or passed on to \code{\link{nls}}.} -\item{pls}{A \code{sp_pls_profile} output, only used by \code{pls_} -functions.} +\item{pls}{A \code{rsp_pls_profile} output, intended for use with +\code{pls_} functions.} \item{species}{for \code{pls_fit_species}, a data.frame of measurements of an additional species to be fitted to an existing @@ -101,20 +92,6 @@ fitting (or refitting) a species, treat it as source marker.} \code{pls_refit_species}, \code{logical}, default \code{FALSE}, when building or rebuilding a PLS model, discard cases where \code{species} is missing.} - -\item{n}{(for \code{pls_plot}s only) numeric or character -identifying the species or profile to plot. If numeric, these are treated -as indices of the species or profile, respectively, in the PLS model; if -character, species is treated as the name of species and profile is treated -as the profile code. Both can be concatenated to produce multiple plots and -the special case \code{n = -1} is a short cut to all species or profiles, -respectively.} - -\item{type}{(for \code{pls_plot}s only) numeric, the plot type if -multiple options are available.} - -\item{log}{(for \code{pls_plot_profile} only) logical, if \code{TRUE} this -applies 'log' scaling to the primary Y axes of the plot.} } \value{ \code{rsp_pls_profile} returns a list of nls models, one per @@ -122,10 +99,10 @@ profile/measurement set in \code{rsp}. The \code{pls_} functions work with these outputs. \code{pls_report} generates a \code{data.frame} of model outputs, and is used of several of the other \code{pls_} functions. \code{pls_fit_species}, \code{pls_refit_species} and -\code{pls_fit_parent} return the supplied \code{sp_pls_profile} output, +\code{pls_fit_parent} return the supplied \code{rsp_pls_profile} output, updated on the basis of the \code{pls_} function action. -\code{pls_plot}s produce various plots commonly used in source -apportionment studies. +\code{pls_plot}s (documented separately) produce various plots +commonly used in source apportionment studies. } \description{ Functions for Positive Least Squares (PSL) fitting of @@ -141,7 +118,7 @@ source apportionment based on the assumption that the profiles in the reference set are representative of the mix that make up the modeled sample. The \code{pls_} functions work with \code{rsp_pls_profile} outputs, and are intended to be used when refining and analyzing -these PLS models. +these PLS models. See also \code{pls_plot}s for PLS model plots. } \note{ This implementation of PLS applies the following modeling constraints: @@ -152,6 +129,14 @@ zero or more. Although the model is generated using \code{\link{nls}}, which is a Nonlinear Least Squares (NLS) model, the fitting term applied in this case is linear. -2. The number of species in \code{rsp} must be more that the number of +2. The model is fit in the form: + + \eqn{X_{i,j} = \sum\limits_{k=1}^{K}{N_{i,k} * M_{k,j} + e_{i,j}}} + + Where X is the data set of measurements, \code{rsp}, M is data set of + reference profiles, \code{ref}, N is the data set of source contributions, + the source apportion solution, to be solved by minimising e, the error terms. + +3. The number of species in \code{rsp} must be more that the number of profiles in \code{ref} to reduce the likelihood of over-fitting. } diff --git a/man/rsp.pls.plot.Rd b/man/rsp.pls.plot.Rd new file mode 100644 index 0000000..39783f7 --- /dev/null +++ b/man/rsp.pls.plot.Rd @@ -0,0 +1,46 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rsp.pls.plot.R +\name{rsp.pls.plot} +\alias{rsp.pls.plot} +\alias{pls_plot} +\alias{pls.plot} +\alias{pls_plot_species} +\alias{pls_plot_profile} +\title{Plots for use with (re)SPECIATE profile Positive Least Squares models} +\usage{ +pls_plot(pls, plot.type = 1, ...) + +pls_plot_profile(pls, plot.type = 1, log = FALSE, ...) + +pls_plot_species(pls, id, plot.type = 1, ...) +} +\arguments{ +\item{pls}{A \code{sp_pls_profile} output, intended for use with +\code{pls_} functions.} + +\item{plot.type}{numeric, the plot type if +multiple options are available.} + +\item{...}{other arguments, typically passed on to the associated +\code{lattice} plot.} + +\item{log}{(for \code{pls_plot_profile} only) logical, if \code{TRUE} this +applies 'log' scaling to the primary Y axes of the plot.} + +\item{id}{numeric or character +identifying the species or profile to plot. If numeric, these are treated +as indices of the species or profile, respectively, in the PLS model; if +character, species is treated as the name of species and profile is treated +as the profile code. Both can be concatenated to produce multiple plots and +the special case \code{id = -1} is a short cut to all species or profiles, +respectively.} +} +\value{ +\code{pls_plot}s produce various plots commonly used in source +apportionment studies. +} +\description{ +The \code{pls_plot} functions are intended for use with PLS models built +using \code{rsp_pls_profile} (documented separately). They generate some +plots commonly used with source apportionment model outputs. +} From 33e53de783786b81fa78dc7a6988b59cb554dc34 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Fri, 17 May 2024 16:16:24 +0100 Subject: [PATCH 15/18] plot for pls model --- R/respeciate.generics.R | 17 +++++++++++++++++ README.Rmd | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/R/respeciate.generics.R b/R/respeciate.generics.R index 303b849..719b654 100644 --- a/R/respeciate.generics.R +++ b/R/respeciate.generics.R @@ -341,6 +341,23 @@ plot.respeciate <- function(x, ...){ +#' @rdname respeciate.generics +#' @method plot rsp_pls +#' @export + +########################## +#notes +########################## +#all pls_plots are currently being redrafted +# (finish that then rethink this) + +plot.rsp_pls <- function(x, ...){ + pls_plot(x, ...) +} + + + + ######################### #to do ######################### diff --git a/README.Rmd b/README.Rmd index 1773459..0f0e005 100644 --- a/README.Rmd +++ b/README.Rmd @@ -31,7 +31,7 @@ Find profiles based on search criteria ```{r } library(respeciate) -x <- sp_find_profile("Ethanol") +x <- rsp_find_profile("Ethanol") x ``` @@ -39,7 +39,7 @@ x ## speciate ```{r } -p <- sp_profile("8833") +p <- rsp_profile("8833") ``` ## plot @@ -54,7 +54,7 @@ plot(p) ... using lattice barchart syntax ```{r fig.width=12, fig.height=5, fig.align="center", dpi=400} -p2 <- sp_profile(c(8833, 8850)) +p2 <- rsp_profile(c(8833, 8850)) plot(p2, key=list(space="top")) ``` From 7acfdde97541b140c3729f29750c0044e8c95cc8 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Fri, 17 May 2024 16:18:52 +0100 Subject: [PATCH 16/18] namespace --- NAMESPACE | 1 + man/respeciate.generics.Rd | 3 +++ 2 files changed, 4 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 2faaac4..b66ae30 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ S3method(as.respeciate,default) S3method(plot,respeciate) +S3method(plot,rsp_pls) S3method(print,respeciate) S3method(print,rsp_pls) S3method(summary,respeciate) diff --git a/man/respeciate.generics.Rd b/man/respeciate.generics.Rd index 353c115..cc5471b 100644 --- a/man/respeciate.generics.Rd +++ b/man/respeciate.generics.Rd @@ -7,6 +7,7 @@ \alias{print.respeciate} \alias{print.rsp_pls} \alias{plot.respeciate} +\alias{plot.rsp_pls} \alias{summary.respeciate} \title{respeciate.generics} \usage{ @@ -20,6 +21,8 @@ as.respeciate(x, ...) \method{plot}{respeciate}(x, ...) +\method{plot}{rsp_pls}(x, ...) + \method{summary}{respeciate}(object, ...) } \arguments{ From 18c67657e496bd132ea47597cb7aace058785400 Mon Sep 17 00:00:00 2001 From: karlropkins Date: Fri, 17 May 2024 16:30:02 +0100 Subject: [PATCH 17/18] readme rebuild --- README.md | 24 ++++++++++++------------ man/figures/unnamed-chunk-6-1.png | Bin 143522 -> 143700 bytes 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ac3a1d1..4a8027a 100644 --- a/README.md +++ b/README.md @@ -19,23 +19,23 @@ Find profiles based on search criteria ``` r library(respeciate) -x <- sp_find_profile("Ethanol") +x <- rsp_find_profile("Ethanol") x -#> respeciate profile reference -#> 0291 1070 1071 1132 1149 1301 1302 1303 1304 1314 5473 5474 5475 5477 5478 5479 -#> 5481 5482 5483 5485 5486 5487 5489 5490 5491 5493 5494 5495 5496 5497 5498 5499 -#> 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 -#> 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 -#> 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 -#> 8200 8210 8215 8733 8736 8751 8751a 8752 8754 8755 8757 8758 8760 8761 8763 -#> 8764 8765 8766 8767 8768 ... -#> > 160 profiles [showing first 100] +#> respeciate profile list: 160 +#> [NO SPECIES] +#> (CODE 0291) Surface Coating Solvent - Methyl Alcohol +#> (CODE 1070) Alcohols Production - Methanol - Purge Gas Vent +#> (CODE 1071) Alcohols Production - Methanol - Distillation Vent +#> (CODE 1132) Ethanolamines +#> (CODE 1149) Methanol +#> (CODE 1301) 10% Ethanol Composite (Hot Soak + Diurnal) Evaporative +#> > showing 6 of 160 ``` ## speciate ``` r -p <- sp_profile("8833") +p <- rsp_profile("8833") ``` ## plot @@ -52,7 +52,7 @@ plot(p) … using lattice barchart syntax ``` r -p2 <- sp_profile(c(8833, 8850)) +p2 <- rsp_profile(c(8833, 8850)) plot(p2, key=list(space="top")) ``` diff --git a/man/figures/unnamed-chunk-6-1.png b/man/figures/unnamed-chunk-6-1.png index f4a086f60718d4f1585ecb2fdaedb852264f8bc2..ce01d3b81621b5399f3d912acf6cfaa1dbefbc37 100644 GIT binary patch delta 115231 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

Vb=C3tSeog*n$t|{$uZ3}FO(Q0_A&v`L0(@S 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 delta 115078 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�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

!3fq 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 From 894390892087e030b2a5d9b6bb943b728b29939f Mon Sep 17 00:00:00 2001 From: karlropkins Date: Fri, 17 May 2024 16:37:14 +0100 Subject: [PATCH 18/18] web update new version --- docs/404.html | 2 +- docs/LICENSE-text.html | 2 +- docs/authors.html | 10 +- docs/index.html | 31 ++- docs/news/index.html | 35 ++- docs/pkgdown.yml | 4 +- docs/reference/figures/unnamed-chunk-6-1.png | Bin 143522 -> 143700 bytes docs/reference/index.html | 56 ++--- docs/reference/respeciate-package.html | 6 +- docs/reference/respeciate.generics.html | 42 +++- docs/reference/rsp.average.html | 128 ++++++++++ docs/reference/rsp.build.html | 143 +++++++++++ docs/reference/rsp.cluster.html | 115 +++++++++ docs/reference/rsp.cor.html | 157 ++++++++++++ docs/reference/rsp.html | 137 +++++++++++ docs/reference/rsp.info.html | 150 ++++++++++++ docs/reference/rsp.match.html | 163 +++++++++++++ docs/reference/rsp.pad.html | 123 ++++++++++ docs/reference/rsp.plot.html | 172 +++++++++++++ docs/reference/rsp.pls.html | 239 +++++++++++++++++++ docs/reference/rsp.pls.plot.html | 130 ++++++++++ docs/reference/rsp.q.html | 127 ++++++++++ docs/reference/rsp.rescale.html | 143 +++++++++++ docs/reference/rsp.reshape.html | 165 +++++++++++++ docs/reference/rsp.x.html | 144 +++++++++++ docs/reference/sysdata.html | 2 +- 26 files changed, 2351 insertions(+), 75 deletions(-) create mode 100644 docs/reference/rsp.average.html create mode 100644 docs/reference/rsp.build.html create mode 100644 docs/reference/rsp.cluster.html create mode 100644 docs/reference/rsp.cor.html create mode 100644 docs/reference/rsp.html create mode 100644 docs/reference/rsp.info.html create mode 100644 docs/reference/rsp.match.html create mode 100644 docs/reference/rsp.pad.html create mode 100644 docs/reference/rsp.plot.html create mode 100644 docs/reference/rsp.pls.html create mode 100644 docs/reference/rsp.pls.plot.html create mode 100644 docs/reference/rsp.q.html create mode 100644 docs/reference/rsp.rescale.html create mode 100644 docs/reference/rsp.reshape.html create mode 100644 docs/reference/rsp.x.html diff --git a/docs/404.html b/docs/404.html index 4e9525c..9c4c3c2 100644 --- a/docs/404.html +++ b/docs/404.html @@ -39,7 +39,7 @@ respeciate - 0.2.6 + 0.3.0 diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html index 8d2a259..0267d7f 100644 --- a/docs/LICENSE-text.html +++ b/docs/LICENSE-text.html @@ -17,7 +17,7 @@ respeciate - 0.2.6 + 0.3.0 diff --git a/docs/authors.html b/docs/authors.html index 14e7d53..2133874 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -17,7 +17,7 @@ respeciate - 0.2.6 + 0.3.0 @@ -65,15 +65,15 @@

Citation

-

Ibarra-Espinosa S, Ropkins K (2023). +

Ibarra-Espinosa S, Ropkins K (2024). respeciate: Speciation profiles for gases and aerosols. -R package version 0.2.6, https://github.com/atmoschem/respeciate. +R package version 0.3.0, https://github.com/atmoschem/respeciate.

@Manual{,
   title = {respeciate: Speciation profiles for gases and aerosols},
   author = {Sergio Ibarra-Espinosa and Karl Ropkins},
-  year = {2023},
-  note = {R package version 0.2.6},
+  year = {2024},
+  note = {R package version 0.3.0},
   url = {https://github.com/atmoschem/respeciate},
 }
diff --git a/docs/index.html b/docs/index.html index 73885f3..3fb1df8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -18,9 +18,8 @@ - + respeciate: Speciation profiles for gases and aerosols — respeciate-package • respeciaterespeciate.generics — respeciate.generics • respeciate(re)SPECIATE data averaging functions — rsp.average • respeciate + + +
+
+ + + +
+
+ + +
+

Functions to build composite (re)SPECIATE profiles

+

rsp_average_profile generates an average composite +of a supplied multi-profile respeciate object.

+
+ +
+
rsp_average_profile(rsp, code = NULL, name = NULL, method = 1, ...)
+
+ +
+

Arguments

+
rsp
+

A respeciate object, a data.frame of re(SPECIATE) +profiles.

+ + +
code
+

required character, the unique profile code to assign to the +average profile.

+ + +
name
+

character, the profile name to assign to the average +profile. If not supplied, this defaults to a collapsed list of the codes +of all the profiles averaged.

+ + +
method
+

numeric, the averaging method to apply: Currently only 1 (default) +mean(rsp).

+ + +
...
+

additional arguments, currently ignored

+ +
+
+

Value

+ + +

rsp_average_profile returns a single profile average +version of the supplied respeciate profile.

+
+
+

Note

+

In development function; arguments and outputs likely to be subject to +change.

+

This is one of the very few respeciate functions that modifies the +WEIGHT_PERCENT column of the respectiate data.frame.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.build.html b/docs/reference/rsp.build.html new file mode 100644 index 0000000..f9be556 --- /dev/null +++ b/docs/reference/rsp.build.html @@ -0,0 +1,143 @@ + +Building respeciate-like Objects — rsp.build • respeciate + + +
+
+ + + +
+
+ + +
+

rsp function(s) to reconfigure data.frames (and similar +object classes) for use with data and functions in re(SPECIATE).

+
+ +
+
rsp_build_x(
+  x,
+  profile_code,
+  profile_name,
+  species_name,
+  species_id,
+  value,
+  ...
+)
+
+ +
+

Arguments

+
x
+

data.frame or similar (i.e. +something that can be coerced into a data.frame using +as.data.frame) to be converted into a respeciate object.

+ + +
profile_name, profile_code
+

(character) The name of the column +in x containing profile name and code records, respectively. If not +already named according to SPECIATE conventions, at least one of these will +need to be assigned.

+ + +
species_name, species_id
+

(character) The name of the column +in x containing species name and id records, respectively. If not +already named according to SPECIATE conventions, at least one of these will +need to be assigned.

+ + +
value
+

(character) The name of the column in x +containing measurement values. If not already named according to SPECIATE +conventions, this will need to be assigned.

+ + +
...
+

(any other arguments) currently ignored.

+ +
+
+

Value

+ + +

rsp_builds attempt to build and return a (re)SPECIATE-like +object that can be compared with data from re(SPECIATE).

+
+
+

Note

+

If you want to compare your data with profiles in the SPECIATE archive, +you need to use EPA SPECIATE conventions when assigning species names and +identifiers. Currently, we are working on options to improve on this (and +very happy to discuss if anyone has ideas), but current best suggestion is: +(1) identify the SPECIATE species code for each of the species in your data set, +and (2) assign these as species_id when rsp_building. The +function will then associate the species_name from SPECIATE species +records.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.cluster.html b/docs/reference/rsp.cluster.html new file mode 100644 index 0000000..25de91b --- /dev/null +++ b/docs/reference/rsp.cluster.html @@ -0,0 +1,115 @@ + +(re)SPECIATE profile cluster analysis methods — rsp.cluster • respeciate + + +
+
+ + + +
+
+ + +
+

(re)SPECIATE functions for studying similarities (or +dissimilarities) within (re)SPECIATE data sets

+

rsp_distance_profile calculates the statistical distance +between re(SPECIATE) profiles, and clusters profiles according to nearness.

+
+ +
+
rsp_distance_profile(rsp, output = c("plot", "report"))
+
+ +
+

Arguments

+
rsp
+

A respeciate object, a data.frame of re(SPECIATE) +profiles.

+ + +
output
+

Character vector, required function output: 'report' the +calculated distance matrix; 'plot' a heat map of that distance +matrix.

+ +
+
+

Value

+ + +

Depending on the output option, sp_distance_profile returns +one or more of the following: the correlation matrix, a heat map of the +correlation matrix.

+
+
+

Note

+

Please note: function in development; structure and arguments may be +subject to change.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.cor.html b/docs/reference/rsp.cor.html new file mode 100644 index 0000000..356602e --- /dev/null +++ b/docs/reference/rsp.cor.html @@ -0,0 +1,157 @@ + +(re)SPECIATE Species Correlations — rsp.cor • respeciate + + +
+
+ + + +
+
+ + +
+

(re)SPECIATE functions for studying relationships between +species in (re)SPECIATE data sets.

+

rsp_cor_species generates a by-species correlation +matrix of the supplied (re)SPECIATE data sets.

+
+ +
+
rsp_cor_species(
+  rsp,
+  min.n = 3,
+  cols = c("#80FFFF", "#FFFFFF", "#FF80FF"),
+  na.col = "#CFCFCF",
+  heatmap.args = TRUE,
+  key.args = TRUE,
+  report = "silent"
+)
+
+ +
+

Arguments

+
rsp
+

respeciate object, a data.frame of re(SPECIATE) +profiles.

+ + +
min.n
+

numeric (default 3), the minimum number of species measurements +needed in a profile for the function to use it in correlation calculations. +Here, it should be noted that this does not guarantee the three matched +pairs of measurements needed to calculate a correlation coefficient because +not all profiles contain all species, so there may still be insufficient +overlap on a case-by-case basis.

+ + +
cols
+

a series of numeric, character or other class values +that can be translated into a color gradient, used to color valid cases when +generating plots and color keys, default c("#80FFFF", "#FFFFFF", "#FF80FF") +equivalent to cm.colors output.

+ + +
na.col
+

numeric, character or other class that can be +translated into a single color, used to color NAs when generating +plots and color keys, default grey "#CFCFCF".

+ + +
heatmap.args
+

logical or list, heat map settings. Options +include TRUE (default) to generate the heat map without modification; +FALSE to not plot it; or a list of heat map options to alter the plot +default appearance. The plot, a standard heat map with the dendrograms +removed, is generated using heatmap, so see associated +documentation for valid options.

+ + +
key.args
+

logical or list, color key settings if plotting +the correlation matrix heat map. Options include TRUE (default) to +generate the key without modification; FALSE to not include the key; +or a list of options to alter the key appearance.

+ + +
report
+

logical or character, the required function +output. Options include: 'silent' (default), to return the +correlation matrix invisibly; TRUE to return the matrix +(visibly); and, FALSE to not return it.

+ +
+
+

Value

+ + +

By default rsp_cor_species invisibly returns the calculated +correlation matrix a plots it as a heat map, but arguments including +heatmap and report can be used to modify function outputs.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.html b/docs/reference/rsp.html new file mode 100644 index 0000000..88c2346 --- /dev/null +++ b/docs/reference/rsp.html @@ -0,0 +1,137 @@ + +rsp_profile — rsp • respeciate + + +
+
+ + + +
+
+ + +
+

Getting profile(s) from the R (re)SPECIATE archive

+
+ +
+
rsp_profile(..., include.refs = FALSE)
+
+rsp(...)
+
+ +
+

Arguments

+
...
+

The function assumes all inputs (except include.refs) +are SPECIES_CODEs (the unique descriptor the EPA assigns to all +profiles in SPECIATE) or sources of profile information and requests these +form the local (re)SPECIATE archive. Typically, simple +objects like character and numeric vectors, as assumed to profile codes and +composite data-types like respeciate objects or data.frame, +are assumed to contain a named PROFILE_CODE column. All potential +profile codes are requested and unrecognized codes are ignored.

+ + +
include.refs
+

logical, if profile reference information should be +included when extracting the requested profile(s) from the archive, default +FALSE.

+ +
+
+

Value

+ + +

rsp_profile or the short-hand rsp return an object of +respeciate class, a data.frame containing one or more profile +from the local (re)SPECIATE archive.

+
+
+

Note

+

The option include.refs adds profile source reference +information to the returned respeciate data set. The default option +is to not include these because some profiles have several associated +references and including these replicates records, once per reference. +respeciate code is written to handle this but if you are developing +own methods or code and include references in any profile build you may be +biasing some analyses in favor of those multiple-reference profile unless +you check and account such cases.

+
+
+

References

+

Simon, H., Beck, L., Bhave, P.V., Divita, F., Hsu, Y., Luecken, D., +Mobley, J.D., Pouliot, G.A., Reff, A., Sarwar, G. and Strum, M., 2010. +The development and uses of EPA SPECIATE database. +Atmospheric Pollution Research, 1(4), pp.196-206.

+
+ +
+

Examples

+
if (FALSE) {
+x <- rsp_profile(8833, 8850)
+plot(x)}
+
+
+
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.info.html b/docs/reference/rsp.info.html new file mode 100644 index 0000000..6ca207f --- /dev/null +++ b/docs/reference/rsp.info.html @@ -0,0 +1,150 @@ + +re(SPECIATE) information — rsp.info • respeciate + + +
+
+ + + +
+
+ + +
+

Functions that provide (re)SPECIATE +source information. +rsp_info generates a brief version report for the currently installed +(re)SPECIATE data sets. +rsp_profile_info searches the currently installed (re)SPECIATE +data sets for profile records. +rsp_species_info searches the currently installed (re)SPECIATE +data sets for species records.

+
+ +
+
rsp_info()
+
+rsp_profile_info(..., by = "keywords", partial = TRUE)
+
+rsp_find_profile(...)
+
+rsp_species_info(..., by = "species_name", partial = TRUE)
+
+rsp_find_species(...)
+
+ +
+

Arguments

+
...
+

character(s), any search term(s) to use when searching +the local (re)SPECIATE archive for relevant records using +rsp_profile_info or rsp_species_info.

+ + +
by
+

character, the section of the archive to +search, by default 'keywords' for rsp_profile_info and +'species_names' for sp_species_info.

+ + +
partial
+

logical, if TRUE (default) +rsp_profile_info or rsp_profile_info use partial matching.

+ +
+
+

Value

+ + +

rsp_info provides a brief version information report on the +currently installed (re)SPECIATE archive.

+ + +

rsp_profile_info returns a data.frame of +profile information, as a respeciate object. +rsp_species_info returns a data.frame of +species information as a respeciate object.

+
+ +
+

Examples

+
if (FALSE) {
+profile <- "Ethanol"
+pr <- rsp_find_profile(profile)
+pr
+
+species <- "Ethanol"
+sp <- rsp_find_species(species)
+sp}
+
+
+
+
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.match.html b/docs/reference/rsp.match.html new file mode 100644 index 0000000..85c689d --- /dev/null +++ b/docs/reference/rsp.match.html @@ -0,0 +1,163 @@ + +Find nearest matches from reference set of profiles — rsp.match • respeciate + + +
+
+ + + +
+
+ + +
+

rsp_match_profile compares a supplied species +(re)SPECIATE profile (or similar data set) and a reference set of +supplied profiles and attempt to identify nearest matches on the +basis of similarity.

+
+ +
+
rsp_match_profile(
+  rsp,
+  ref,
+  matches = 10,
+  rescale = 5,
+  min.n = 8,
+  method = "pd",
+  test.rsp = FALSE
+)
+
+ +
+

Arguments

+
rsp
+

A respeciate object or similar data.frame containing +a species profile to be compared with profiles in ref. If rsp +contains more than one profile, these are averaged (using +rsp_average_profile), and the average compared.

+ + +
ref
+

A respeciate object, a data.frame containing a +multiple species profiles, to be used as reference library when identifying +nearest matches for rsp.

+ + +
matches
+

Numeric (default 10), the maximum number of profile matches to +report.

+ + +
rescale
+

Numeric (default 5), the data scaling method to apply before +comparing rsp and profiles in ref: options 0 to 5 handled by +rsp_rescale.

+ + +
min.n
+

numeric (default 8), the minimum number of paired +species measurements in two profiles required for a match to be assessed. +See also rsp_cor_species.

+ + +
method
+

Character (default 'pd'), the similarity measure to use, current +options 'pd', the Pearson's Distance (1 - Pearson's correlation coefficient), +or 'sid', the Standardized Identity Distance (See References).

+ + +
test.rsp
+

Logical (default FALSE). The match process self-tests by adding +rsp to ref, which should generate a perfect fit=0 score. Setting +test.rsp to TRUE retains this as an extra record.

+ +
+
+

Value

+ + +

rsp_match_profile returns a fit report: a data.frame of +up to n fit reports for the nearest matches to rsp from the +reference profile data set, ref.

+
+
+

References

+

Distance metrics are based on recommendations by Belis et al (2015) +and as implemented in Mooibroek et al (2022):

+

Belis, C.A., Pernigotti, D., Karagulian, F., Pirovano, G., Larsen, B.R., +Gerboles, M., Hopke, P.K., 2015. A new methodology to assess the performance +and uncertainty of source apportionment models in intercomparison +exercises. Atmospheric Environment, 119, 35–44. +https://doi.org/10.1016/j.atmosenv.2015.08.002.

+

Mooibroek, D., Sofowote, U.M. and Hopke, P.K., 2022. Source apportionment of +ambient PM10 collected at three sites in an urban-industrial area with +multi-time resolution factor analyses. Science of The Total Environment, +850, p.157981. http://dx.doi.org/10.1016/j.scitotenv.2022.157981.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.pad.html b/docs/reference/rsp.pad.html new file mode 100644 index 0000000..8bb0485 --- /dev/null +++ b/docs/reference/rsp.pad.html @@ -0,0 +1,123 @@ + +(re)SPECIATE profile padding functions — rsp.pad • respeciate + + +
+
+ + + +
+
+ + +
+

Functions for padding respeciate objects.

+

rsp_pad pads a supplied (re)SPECIATE profile data set +with profile and species meta-data.

+
+ +
+
rsp_pad(rsp, pad = "standard", drop.nas = TRUE)
+
+ +
+

Arguments

+
rsp
+

A respeciate object, a data.frame of re(SPECIATE) +profiles.

+ + +
pad
+

character, type of meta data padding, current options +'profile', 'species', 'weight', 'reference', +'standard' (default; all but 'reference'), and 'all' +(all).

+ + +
drop.nas
+

logical, discard any rows where WEIGHT_PERCENT is +NA, default TRUE.

+ +
+
+

Value

+ + +

rsp_pad returns supplied respeciate data set, with +requested additional profile and species meta-data added as additional +data.frame columns. See Note.

+
+
+

Note

+

Some data handling can remove (re)SPECIATE meta-data, +and rsp_pads provide a quick rebuild/repair. For example, +rsp_dcasting to a (by-species or by-profile) widened +form strips some meta-data, and padding is used as part of the +rsp_melt_wide to re-add this meta-data +when returning the data set to its standard long form.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.plot.html b/docs/reference/rsp.plot.html new file mode 100644 index 0000000..87b8edb --- /dev/null +++ b/docs/reference/rsp.plot.html @@ -0,0 +1,172 @@ + +plotting (re)SPECIATE profiles — rsp.plot • respeciate + + +
+
+ + + +
+
+ + +
+

General plots for respeciate objects.

+

rsp_plot functions generate plots for supplied +(re)SPECIATE data sets.

+
+ +
+
rsp_plot_profile(
+  rsp,
+  id,
+  multi.profile = "group",
+  order = TRUE,
+  log = FALSE,
+  ...,
+  silent = FALSE
+)
+
+rsp_plot_species(
+  rsp,
+  id,
+  multi.species = "group",
+  order = FALSE,
+  log = FALSE,
+  ...,
+  silent = FALSE
+)
+
+ +
+

Arguments

+
rsp
+

A respeciate object, a data.frame of re(SPECIATE) +profiles.

+ + +
id
+

numeric, the indices of profiles or species to use when +plotting with rsp_plot_profile or rsp_plot_species, +respectively. For example, rsp_plot_profile(rsp, id=1:6) plots +first 6 profiles in respeciate object rsp.

+ + +
multi.profile
+

character, how rsp_plot_profile should +handle multiple profiles, e.g. 'group' or 'panel' (default +group).

+ + +
order
+

logical, order the species in the +profile(s) by relative abundance before plotting.

+ + +
log
+

logical, log y scale when plotting.

+ + +
...
+

any additional arguments, typically passed on the lattice +plotting functions.

+ + +
silent
+

logical, hide warnings when generating plots (default +FALSE)

+ + +
multi.species,
+

character, like multi.profile in +sp_plot_profile but for species in sp_plot_species.

+ +
+
+

Value

+ + +

sp_plot graph, plot, etc usually as a trellis object.

+
+
+

Note

+

These functions are currently in development, so may change.

+
+
+

References

+

Most respeciate plots make extensive use of +lattice and latticeExtra code:

+

Sarkar D (2008). Lattice: Multivariate Data Visualization with R. +Springer, New York. ISBN 978-0-387-75968-5, http://lmdvr.r-forge.r-project.org.

+

Sarkar D, Andrews F (2022). latticeExtra: Extra Graphical Utilities Based +on Lattice. R package version 0.6-30, +https://CRAN.R-project.org/package=latticeExtra.

+

They also incorporate ideas from loa:

+

Ropkins K (2023). loa: various plots, options and add-ins for use with lattice. +R package version 0.2.48.3, https://CRAN.R-project.org/package=loa.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.pls.html b/docs/reference/rsp.pls.html new file mode 100644 index 0000000..b415c60 --- /dev/null +++ b/docs/reference/rsp.pls.html @@ -0,0 +1,239 @@ + +(re)SPECIATE profile Positive Least Squares models — rsp.pls • respeciate + + +
+
+ + + +
+
+ + +
+

Functions for Positive Least Squares (PSL) fitting of +(re)SPECIATE profiles

+

rsp_pls_profile builds PSL models for supplied profile(s) using +the nls function, the 'port' algorithm and a lower +limit of zero for all model outputs to enforce the positive fits. The +modeled profiles are typically from an external source, e.g. a +measurement campaign, and are fit as a linear additive series of reference +profiles, here typically from (re)SPECIATE, to provide a measure of +source apportionment based on the assumption that the profiles in the +reference set are representative of the mix that make up the modeled +sample. The pls_ functions work with rsp_pls_profile +outputs, and are intended to be used when refining and analyzing +these PLS models. See also pls_plots for PLS model plots.

+
+ +
+
rsp_pls_profile(rsp, ref, power = 1, ...)
+
+pls_report(pls)
+
+pls_test(pls)
+
+pls_fit_species(
+  pls,
+  species,
+  power = 1,
+  refit.profile = TRUE,
+  as.marker = FALSE,
+  drop.missing = FALSE,
+  ...
+)
+
+pls_refit_species(
+  pls,
+  species,
+  power = 1,
+  refit.profile = TRUE,
+  as.marker = FALSE,
+  drop.missing = FALSE,
+  ...
+)
+
+pls_rebuild(
+  pls,
+  species,
+  power = 1,
+  refit.profile = TRUE,
+  as.marker = FALSE,
+  drop.missing = FALSE,
+  ...
+)
+
+ +
+

Arguments

+
rsp
+

A respeciate object, a data.frame of +profiles in standard long form, intended for PLS modelling.

+ + +
ref
+

A respeciate object, a data.frame of +profiles also in standard long form, used as the set of candidate +source profiles when fitting rsp.

+ + +
power
+

A numeric, an additional factor to be added to +weightings when fitting the PLS model. This is applied in the form +weight^power, and increasing this, increases the relative +weighting of the more heavily weighted measurements. Values in the +range 1 - 2.5 are sometimes helpful.

+ + +
...
+

additional arguments, typically ignored or passed on to +nls.

+ + +
pls
+

A rsp_pls_profile output, intended for use with +pls_ functions.

+ + +
species
+

for pls_fit_species, a data.frame of +measurements of an additional species to be fitted to an existing +PLS model, or for pls_refit_species a character vector of the +names of species already included in the model to be refit. Both are +multiple-species wrappers for pls_rebuild, a general-purpose +PLS fitter than only handles single species.

+ + +
refit.profile
+

(for pls_fit_species, pls_refit_species +and pls_rebuild) logical. When fitting a new species (or +refitted an existing species), all other species in the reference +profiles are held 'as is' and added species is fit to the source +contribution time-series of the previous PLS model. By default, the full PLS +model is then refit using the revised ref source profile to generate +a PLS model based on the revised source profiles (i.e., ref + new species +or ref + refit species). However, this second step can be omitted using +refit.profile=FALSE if you want to use the supplied species +as an indicator rather than a standard member of the apportionment model.

+ + +
as.marker
+

for pls_rebuild, pls_fit_species and +pls_refit_species, logical, default FALSE, when +fitting (or refitting) a species, treat it as source marker.

+ + +
drop.missing
+

for pls_rebuild, pls_fit_species and +pls_refit_species, logical, default FALSE, when +building or rebuilding a PLS model, discard cases where species +is missing.

+ +
+
+

Value

+ + +

rsp_pls_profile returns a list of nls models, one per +profile/measurement set in rsp. The pls_ functions work with +these outputs. pls_report generates a data.frame of +model outputs, and is used of several of the other pls_

+ + +

functions. pls_fit_species, pls_refit_species and +pls_fit_parent return the supplied rsp_pls_profile output, +updated on the basis of the pls_ function action. +pls_plots (documented separately) produce various plots +commonly used in source apportionment studies.

+
+
+

Note

+

This implementation of PLS applies the following modeling constraints:

+

1. It generates a model of rsp that is positively constrained linear +product of the profiles in ref, so outputs can only be +zero or more. Although the model is generated using nls, +which is a Nonlinear Least Squares (NLS) model, the fitting term applied +in this case is linear.

+

2. The model is fit in the form:

+

\(X_{i,j} = \sum\limits_{k=1}^{K}{N_{i,k} * M_{k,j} + e_{i,j}}\)

+

Where X is the data set of measurements, rsp, M is data set of + reference profiles, ref, N is the data set of source contributions, + the source apportion solution, to be solved by minimising e, the error terms.

+

3. The number of species in rsp must be more that the number of +profiles in ref to reduce the likelihood of over-fitting.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.pls.plot.html b/docs/reference/rsp.pls.plot.html new file mode 100644 index 0000000..c29199f --- /dev/null +++ b/docs/reference/rsp.pls.plot.html @@ -0,0 +1,130 @@ + +Plots for use with (re)SPECIATE profile Positive Least Squares models — rsp.pls.plot • respeciate + + +
+
+ + + +
+
+ + +
+

The pls_plot functions are intended for use with PLS models built +using rsp_pls_profile (documented separately). They generate some +plots commonly used with source apportionment model outputs.

+
+ +
+
pls_plot(pls, plot.type = 1, ...)
+
+pls_plot_profile(pls, plot.type = 1, log = FALSE, ...)
+
+pls_plot_species(pls, id, plot.type = 1, ...)
+
+ +
+

Arguments

+
pls
+

A sp_pls_profile output, intended for use with +pls_ functions.

+ + +
plot.type
+

numeric, the plot type if +multiple options are available.

+ + +
...
+

other arguments, typically passed on to the associated +lattice plot.

+ + +
log
+

(for pls_plot_profile only) logical, if TRUE this +applies 'log' scaling to the primary Y axes of the plot.

+ + +
id
+

numeric or character +identifying the species or profile to plot. If numeric, these are treated +as indices of the species or profile, respectively, in the PLS model; if +character, species is treated as the name of species and profile is treated +as the profile code. Both can be concatenated to produce multiple plots and +the special case id = -1 is a short cut to all species or profiles, +respectively.

+ +
+
+

Value

+ + +

pls_plots produce various plots commonly used in source +apportionment studies.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.q.html b/docs/reference/rsp.q.html new file mode 100644 index 0000000..85c91e6 --- /dev/null +++ b/docs/reference/rsp.q.html @@ -0,0 +1,127 @@ + +Quick access to common (re)SPECIATE subsets. — rsp.q • respeciate + + +
+
+ + + +
+
+ + +
+

rsp_q_ functions are quick access wrappers to commonly +requested (re)SPECIATE subsets.

+
+ +
+
rsp_q_gas()
+
+rsp_q_other()
+
+rsp_q_pm()
+
+rsp_q_pm.ae6()
+
+rsp_q_pm.ae8()
+
+rsp_q_pm.cr1()
+
+rsp_q_pm.simplified()
+
+ +
+

Value

+ + +

rsp_q_ functions typically return a respeciate

+ + +

data.frame of the requested profiles.

+ + +

For example:

+ + +

rsp_q_gas() returns all gaseous profiles in (re)SPECIATE +(PROFILE_TYPE == 'GAS').

+ + +

rsp_q_pm returns all particulate matter (PM) profiles in (re)SPECIATE +not classified as a special PM type (PROFILE_TYPE == 'PM').

+ + +

The special PM types are subsets profiles intended for special +applications, and these include rsp_q_pm.ae6 (type PM-AE6), +rsp_q_pm.ae8 (type PM-AE8), rsp_q_pm.cr1 (type +PM-CR1), and rsp_q_pm.simplified (type PM-Simplified).

+ + +

rsp_q_other returns all profiles classified as other in (re)SPECIATE +(PROFILE_TYPE == 'OTHER').

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.rescale.html b/docs/reference/rsp.rescale.html new file mode 100644 index 0000000..ed8a92a --- /dev/null +++ b/docs/reference/rsp.rescale.html @@ -0,0 +1,143 @@ + +(re)SPECIATE profile rescaling functions — rsp.rescale • respeciate + + +
+
+ + + +
+
+ + +
+

Functions for rescaling

+

rsp_rescale rescales the percentage weight records in +a supplied (re)SPECIATE profile data set. This can be by profile or species +subsets, and rsp_rescale_profile and rsp_rescale_species provide +short-cuts to these options.

+
+ +
+
rsp_rescale(rsp, method = 2, by = "species")
+
+rsp_rescale_profile(rsp, method = 1, by = "profile")
+
+rsp_rescale_species(rsp, method = 2, by = "species")
+
+ +
+

Arguments

+
rsp
+

A respeciate object, a data.frame of re(SPECIATE) +profiles.

+ + +
method
+

numeric, the rescaling method to apply: + 1 x/total(x); + 2 x/mean(x); + 3 x-min(x)/max(x)-min(x); + 4 x-mean(x)/sd(x); + 5 x/max(x). +The alternative 0 returns the records to their original +values.

+ + +
by
+

character, when rescaling x with +sp_rescale, the data type to group and rescale, +currently 'species' (default) or 'profile'.

+ +
+
+

Value

+ + +

sp_rescale and sp_rescale return the +respeciate profile with the percentage weight records rescaled using +the requested method. See Note.

+
+
+

Note

+

Data sometimes needs to be normalised, e.g. when applying some +statistical analyses. Rather than modify the EPA records in the +WEIGHT_PERCENT column, respeciate creates a duplicate column +.value which is modified by operations like sp_rescale_profile +and sp_rescale_species. This means rescaling is always applied to +the source information, rather than rescaling an already rescaled value, +and the EPA records are retained unaffected. So, the original source +information can be easily recovered.

+
+
+

References

+

Dowle M, Srinivasan A (2023). data.table: Extension of `data.frame`. + R package version 1.14.8, https://CRAN.R-project.org/package=data.table.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.reshape.html b/docs/reference/rsp.reshape.html new file mode 100644 index 0000000..af827eb --- /dev/null +++ b/docs/reference/rsp.reshape.html @@ -0,0 +1,165 @@ + +(re)SPECIATE profile reshaping functions — rsp.reshape • respeciate + + +
+
+ + + +
+
+ + +
+

Functions for reshaping (re)SPECIATE profiles

+

rsp_dcast and rsp_melt_wide reshape supplied +(re)SPECIATE profile(s). rsp_dcast converts these from their supplied +long form to a widened form, dcasting the data set by either species +or profiles depending on the widen setting applied. +rsp_dcast_profile and rsp_dcast_species are wrappers for these +options. rsp_melt_wide attempts to return a previously widened data +set to the original long form.

+
+ +
+
rsp_dcast(rsp, widen = "species")
+
+rsp_dcast_profile(rsp, widen = "profile")
+
+rsp_dcast_species(rsp = rsp, widen = "species")
+
+rsp_melt_wide(rsp, pad = TRUE, drop.nas = TRUE)
+
+ +
+

Arguments

+
rsp
+

A respeciate object, a data.frame of re(SPECIATE) +profiles in standard long form or widened form using +rsp_dcast and rsp_melt_wide, respectively.

+ + +
widen
+

character, when widening rsp with +rsp_dcast, the data type to dcast, +currently 'species' (default) or 'profile'. See Note.

+ + +
pad
+

logical or character, when melting a previously widened +data set, should output be re-populated with species and/or profile +meta-data, discarded when widening. This is currently handled by +rsp_pad. The default TRUE applies standard settings, +so does not include profile sources reference meta-data. (See +rsp_pad for other options).

+ + +
drop.nas
+

logical, when melting a previously widened +data set, should output be stripped of any rows containing empty +weight/value columns. Because not all profile contains all species, the +dcast/melt process can generate empty rows, and this step +attempt account for that when working with standard re(SPECIATE) +profiles. It is, however, sometimes useful to check first, e.g. when +building profiles yourself.

+ +
+
+

Value

+ + +

rsp_dcast returns the wide form of the supplied +respeciate profile. rsp_melt_wide

+ + +

returns the (standard) long form of a previously widened profile.

+
+
+

Note

+

Conventional long-to-wide reshaping of data, or dcasting, can +be slow and memory inefficient. So, respeciate uses the +data.table::dcast +method. The rsp_dcast_species method, +applied using widen='species', is effectively:

+

dcast(..., PROFILE_CODE+PROFILE_NAME~SPECIES_NAME, value.var="WEIGHT_PERCENT")

+

And, the alternative widen='profile':

+

dcast(..., SPECIES_ID+SPECIES_NAME~PROFILE_CODE, value.var="WEIGHT_PERCENT")

+

Although, respeciate uses a local version of WEIGHT_PERCENT called +.value, so the EPA source information can easily be recovered. See also +sp_rescale_profile.

+
+
+

References

+

Dowle M, Srinivasan A (2023). _data.table: Extension of `data.frame`_. + R package version 1.14.8, <https://CRAN.R-project.org/package=data.table>.

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/rsp.x.html b/docs/reference/rsp.x.html new file mode 100644 index 0000000..b8600c7 --- /dev/null +++ b/docs/reference/rsp.x.html @@ -0,0 +1,144 @@ + +rsp_x_ functions for grouping and subsetting (re)SPECIATE profiles — rsp.x • respeciate + + +
+
+ + + +
+
+ + +
+

rsp_x_ functions generate a vector of assignment +terms and can be used to subset or condition a supplied (re)SPECIATE +data.frame.

+

Most commonly, the rsp_x_ functions accept a single input, a +(re)SPECIATE data.frame and return a logical vector of +length nrow(x), identifying species of interest as +TRUE. So, for example, they can be used when +subsetting in the form:

+

subset(rsp, rsp_x_nalkane(rsp))

+

... to extract just n-alkane records from a supplied respeciate +object rsp.

+

However, some accept additional arguments. For example, rsp_x_copy +also accepts a reference data set, ref, and a column identifier, +by, and tests rsp$by %in% unique(ref$by).

+
+ +
+
rsp_x_copy(rsp, ref = NULL, by = "species_id")
+
+rsp_x_nalkane(rsp)
+
+rsp_x_btex(rsp)
+
+ +
+

Arguments

+
rsp
+

a respeciate object, a data.frame of (re)SPECIATE +profiles.

+ + +
ref
+

(rsp_x_copy only) a second respeciate object, to +be used as reference when subsetting (or conditioning) rsp.

+ + +
by
+

(rsp_x_copy only) character, the name of the column +in ref to copy when subsetting (or conditioning) rsp.

+ +
+
+

Value

+ + +

rsp_x_copy outputs can be modified but, by default, it +identifies all species in the supplied reference data set.

+ + +

rsp_x_nalkane identifies (straight chain) C1 to C40 n-alkanes.

+ + +

rsp_x_btex identifies the BTEX group of aromatic hydrocarbons +(benzene, toluene, ethyl benzene, and M-, O- and P-xylene).

+
+ +
+ +
+ + +
+ +
+

Site built with pkgdown 2.0.7.

+
+ +
+ + + + + + + + diff --git a/docs/reference/sysdata.html b/docs/reference/sysdata.html index 020a396..8cea5b8 100644 --- a/docs/reference/sysdata.html +++ b/docs/reference/sysdata.html @@ -19,7 +19,7 @@ respeciate - 0.2.6 + 0.3.0