as.SHE() now supports arguments with arbitrary length.

Ended up completely rewriting as.SHE() and some of the companion functions.
master
Taha Ahmed 8 years ago
parent dc0fcb0629
commit 82f635f3a3

@ -1,7 +1,7 @@
Package: common Package: common
Type: Package Type: Package
Title: chepec common Title: chepec common
Version: 0.0.0.9001 Version: 0.0.0.9002
Description: Commonly used functions and scripts. Description: Commonly used functions and scripts.
Authors@R: person("Taha", "Ahmed", email = "taha@chepec.se", role = c("aut", "cre")) Authors@R: person("Taha", "Ahmed", email = "taha@chepec.se", role = c("aut", "cre"))
License: GPL-3 License: GPL-3

@ -35,10 +35,14 @@ SHE2AVS <- function(she) {
#' (as defined by this package). #' (as defined by this package).
#' This function tries to match against as many variations as possible for each #' This function tries to match against as many variations as possible for each
#' reference electrode. #' reference electrode.
#' The entire point of this function is to decrease the mental load on the user
#' by not requiring them to remember a particular label or name for each reference
#' electrode, instead almost any sufficiently distinct label or string will still
#' be correctly identified.
#' #'
#' @param refname string #' @param refname string or a vector of strings
#' #'
#' @return the canonical name or empty string #' @return vector with corresponding "canonical" name or empty string (if none found)
#' @export #' @export
RefCanonicalName <- function(refname) { RefCanonicalName <- function(refname) {
# scale names # scale names
@ -73,33 +77,35 @@ RefCanonicalName <- function(refname) {
"Li/Li+", "Li/Li+",
"Lithium") "Lithium")
# to match the lowercase version, use tolower() # defining refname in this manner makes sure to get all possible combinations
# perhaps also replace hyphens and slashes with space? # but there might be a number of duplicates, but those we can
# get rid of in the next step
matches <- electrode <-
data.frame(electrode = names(electrode.system), data.frame(refname =
m = rep(0, length(electrode.system)), # here we create lower-case version of electrode.system,
# and version with symbols (-/) subbed with spaces
c(unname(unlist(electrode.system)),
tolower(unname(unlist(electrode.system))),
gsub("[-/]", " ", unname(unlist(electrode.system)))),
refcanon =
rep(sub("[0-9]$", "", names(unlist(electrode.system))), 3),
stringsAsFactors = FALSE) stringsAsFactors = FALSE)
# detect and remove duplicates
# loop over electrode systems electrode <-
for (i in 1:length(electrode.system)) { electrode[!duplicated(electrode$refname),]
# check for a match in any cell of this row, # reset row numbering in dataframe just for good measure
# also trying all lower-case and substituting symbols with spaces row.names(electrode) <- 1:dim(electrode)[1]
if (any(electrode.system[[i]] == refname) ||
any(tolower(electrode.system[[i]]) == refname) || # pre-allocate the return vector
any(gsub("[-/]", " ", electrode.system[[i]]) == refname)) { refcanon <- rep("", length(refname))
matches$m[i] <- 1 # now all we have to do is check each user-submitted refname against
} # electrode$refname and return the value on the same row but next column
for (i in 1:length(refname)) {
refcanon[i] <-
electrode$refcanon[which(electrode$refname == refname[i])]
} }
# if everything went as expected we should have just one match return(refcanon)
if (sum(matches$m) != 1) {
# something wrong (should probably add warn/error here)
# for now, just return empty string
return("")
} else {
return(matches$electrode[which(matches$m == 1)])
}
} }
@ -212,7 +218,10 @@ potentials.as.SHE <- function() {
#' Convert from electrochemical or electronic scale to SHE #' Convert from electrochemical or electronic scale to SHE
#' #'
#' @param potential in the original scale, V or eV #' Convert an arbitrary number of potentials against any known electrochemical
#' scale (or the electronic vacuum scale) to potential vs SHE.
#'
#' @param potential potential in volt
#' @param scale name of the original scale #' @param scale name of the original scale
#' @param concentration of electrolyte in mol/L, or as the string "saturated" #' @param concentration of electrolyte in mol/L, or as the string "saturated"
#' @param temperature of system in degrees Celsius #' @param temperature of system in degrees Celsius
@ -229,109 +238,104 @@ as.SHE <- function(potential,
# if the supplied temperature does not exist in the data, this function will attempt to interpolate # if the supplied temperature does not exist in the data, this function will attempt to interpolate
# note that concentration has to match, no interpolation is attempted for conc # note that concentration has to match, no interpolation is attempted for conc
if (RefCanonicalName(scale) == "") { # make this work for arbitrary-length vectors of potential and scale
warning("as.SHE(): Sorry, you have supplied an unrecognised electrode scale.") # make sure potential and scale args have the same length
return(NA) if (length(potential) == 0 | length(scale) == 0) {
stop("Arguments potential or scale cannot be empty!")
} else if (length(potential) != length(scale)) {
stop("Arguments potential and scale must have equal number of elements")
} }
# there is the simple case of arglength <- length(potential)
if (RefCanonicalName(scale) == "SHE") { # make the concentration and temperature args to this same length,
warning("This function can only convert from scales other than SHE!") # unless the user supplied them (only necessary for > 1)
return(NA) if (arglength > 1) {
# handle two cases:
# 1. user did not touch concentration or temperature args.
# Assume they forgot and reset their length and print a message
# 2. user did change concentration or temperature, but still failed to
# ensure length equal to arglength. In this case, abort.
# note: we can get the default value set in the function call using formals()
if (identical(concentration, formals(as.SHE)$concentration) &
identical(temperature, formals(as.SHE)$temperature)) {
# case 1
# message("NOTE: default concentration and temperature values used for all potentials and scales.")
message(paste0("Default concentration (", formals(as.SHE)$concentration, ") and default temperature (", formals(as.SHE)$temperature, "C) used for all supplied potential and scale values."))
concentration <- rep(concentration, arglength)
temperature <- rep(temperature, arglength)
} else {
# case 2
stop("Arguments concentration and temperature must have same number of elements as potential and scale!")
}
} }
# AVS needs special consideration ## we can now safely assume that length(<args>) == arglength
if (RefCanonicalName(scale) == "AVS") { # place args into a single dataframe
# reset arg concentration # this way, we can correlate columns to each other by row
concentration <- "" df <-
# second, since AVS scale goes in the opposite direction to the electrochemical scales data.frame(potential = potential,
# we will define our own function scale = RefCanonicalName(scale),
negifavs <- function(a, b) { concentration = concentration,
a - b temperature = temperature,
} stringsAsFactors = FALSE)
} else { # add column to keep track of vacuum scale
# we will define the same function differently for df$vacuum <- as.logical(FALSE)
# the case we're not dealing with AVS # add column to hold calc potential vs SHE
negifavs <- function(a, b) { df$SHE <- as.numeric(NA)
a + b
} # AVS scale special considerations
# 1. concentration is meaningless
# 2. direction is opposite of electrochemical scales, requiring change of sign
if (any(df$scale == RefCanonicalName("AVS"))) {
# concentration is meaningless for AVS (no electrolyte)
# so for those rows, we'll reset it
df$concentration[which(df$scale == RefCanonicalName("AVS"))] <- ""
df$vacuum[which(df$scale == RefCanonicalName("AVS"))] <- TRUE
} }
if (is.character(concentration)) { # now just work our way through df, line-by-line to determine potential as SHE
# supplied concentration is character string # all necessary conditions should be recorded right here in df
subspot <- for (p in 1:dim(df)[1]) {
subset(subset(as.SHE.data, if (is.character(df$concentration[p])) {
electrode == RefCanonicalName(scale)), subset.SHE.data <-
conc.string == concentration) subset(subset(as.SHE.data, conc.string == df$concentration[p]),
# if either "scale" or "concentration" are not found in the data, subspot will contain zero rows electrode == df$scale[p])
if (dim(subspot)[1] == 0) {
warning("as.SHE(): Supplied scale or concentration does not exist in data. Returning NA.")
return(NA)
}
# so far, we have
# scale: checked!
# concentration: checked!
# only temperature remains to be handled
# temperature value could happen to match a value in the data, or lie somewhere in between
# note: we will not allow extrapolation
if (!any(subspot$temp == temperature)) {
# if sought temperature is not available in dataset, check that it falls inside
if ((temperature < max(subspot$temp)) && (temperature > min(subspot$temp))) {
# within dataset range, do linear interpolation
lm.subspot <- stats::lm(SHE ~ temp, data = subspot)
# interpolated temperature, calculated based on linear regression
# (more accurate than simple linear interpolation with approx())
potinterp <-
lm.subspot$coefficients[2] * temperature + lm.subspot$coefficients[1]
### CALC RETURN POTENTIAL
return(negifavs(potinterp, potential))
} else {
# outside dataset range, warning and return NA (we don't extrapolate)
warning("as.SHE(): the temperature you requested falls outside data range.")
return(NA)
}
} else { } else {
# requested temperature does exist in dataset subset.SHE.data <-
### CALC RETURN POTENTIAL subset(subset(as.SHE.data, conc.num == df$concentration[p]),
return(negifavs(subset(subspot, temp == temperature)$SHE, potential)) electrode == df$scale[p])
} }
# outer-most if-else
} else { # temperature
# supplied concentration is numeric # either happens to match a temperature in the dataset, or we interpolate
# note: all code inside this else is the same as inside the if, # (under the assumption that potential varies linearly with temperature)
# just for the case of numeric concentration if (!any(subset.SHE.data$temp == df$temperature[p])) {
subspot <- # sought temperature was not available in dataset, check that it falls inside
subset(subset(as.SHE.data, if ((df$temperature[p] < max(subset.SHE.data$temp)) &&
electrode == RefCanonicalName(scale)), (df$temperature[p] > min(subset.SHE.data$temp))) {
conc.num == concentration)
# if either "scale" or "concentration" are not found in the data, subspot will contain zero rows
if (dim(subspot)[1] == 0) {
warning("as.SHE(): Supplied scale or concentration does not exist in data. Returning NA.")
return(NA)
}
if (!any(subspot$temp == temperature)) {
# if sought temperature is not available in dataset, check that it falls inside
if ((temperature < max(subspot$temp)) && (temperature > min(subspot$temp))) {
# within dataset range, do linear interpolation # within dataset range, do linear interpolation
lm.subspot <- stats::lm(SHE ~ temp, data = subspot) lm.subset <- stats::lm(SHE ~ temp, data = subset.SHE.data)
# interpolated temperature, calculated based on linear regression # interpolated temperature, calculated based on linear regression
# (more accurate than simple linear interpolation with approx()) # (more accurate than simple linear interpolation with approx())
potinterp <- pot.interp <-
lm.subspot$coefficients[2] * temperature + lm.subspot$coefficients[1] lm.subset$coefficients[2] * df$temperature[p] + lm.subset$coefficients[1]
### CALC RETURN POTENTIAL ### CALC POTENTIAL vs SHE
return(negifavs(potinterp, potential)) df$SHE[p] <-
} else { ifelse(df$vacuum[p],
# outside dataset range, warning and return NA (we don't extrapolate) pot.interp - df$potential[p],
warning("as.SHE(): the temperature you requested falls outside data range.") pot.interp + df$potential[p])
return(NA)
} }
} else { } else {
# requested temperature does exist in dataset # requested temperature does exist in dataset
### CALC RETURN POTENTIAL ### CALC POTENTIAL vs SHE
return(negifavs(subset(subspot, temp == temperature)$SHE, potential)) df$SHE[p] <-
ifelse(df$vacuum[p],
subset(subset.SHE.data, temp == df$temperature[p])$SHE - df$potential[p],
subset(subset.SHE.data, temp == df$temperature[p])$SHE + df$potential[p])
} }
} }
return(df$SHE)
} }

@ -7,15 +7,19 @@
RefCanonicalName(refname) RefCanonicalName(refname)
} }
\arguments{ \arguments{
\item{refname}{string} \item{refname}{string or a vector of strings}
} }
\value{ \value{
the canonical name or empty string vector with corresponding "canonical" name or empty string (if none found)
} }
\description{ \description{
Given a reference electrode label, this function returns its canonical name Given a reference electrode label, this function returns its canonical name
(as defined by this package). (as defined by this package).
This function tries to match against as many variations as possible for each This function tries to match against as many variations as possible for each
reference electrode. reference electrode.
The entire point of this function is to decrease the mental load on the user
by not requiring them to remember a particular label or name for each reference
electrode, instead almost any sufficiently distinct label or string will still
be correctly identified.
} }

@ -8,7 +8,7 @@ as.SHE(potential, scale, concentration = "saturated", temperature = 25,
as.SHE.data = potentials.as.SHE()) as.SHE.data = potentials.as.SHE())
} }
\arguments{ \arguments{
\item{potential}{in the original scale, V or eV} \item{potential}{potential in volt}
\item{scale}{name of the original scale} \item{scale}{name of the original scale}
@ -22,6 +22,7 @@ as.SHE(potential, scale, concentration = "saturated", temperature = 25,
potential in SHE scale potential in SHE scale
} }
\description{ \description{
Convert from electrochemical or electronic scale to SHE Convert an arbitrary number of potentials against any known electrochemical
scale (or the electronic vacuum scale) to potential vs SHE.
} }

Loading…
Cancel
Save