Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
refelectrodes/R/SHE.R
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
558 lines (517 sloc)
30.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#' Potentials as SHE | |
#' | |
#' This function just outputs a tidy dataframe with potential vs SHE for | |
#' different scales, electrolytes, concentrations, and temperatures. | |
#' Using data from literature. | |
#' | |
#' @return tidy dataframe with the following columns | |
#' \tabular{ll}{ | |
#' \code{electrode} \tab reference electrode \cr | |
#' \code{electrolyte} \tab electrolyte \cr | |
#' \code{conc.num} \tab concentration of electrolyte, mol/L \cr | |
#' \code{conc.string} \tab concentration of electrolyte, as string, may also note temperature at which conc \cr | |
#' \code{temp} \tab temperature / degrees Celsius \cr | |
#' \code{SHE} \tab potential vs SHE / volt \cr | |
#' \code{sid} \tab set id, just for housekeeping inside this function \cr | |
#' \code{reference} \tab BibTeX reference \cr | |
#' \code{dEdT} \tab temperature coefficient / volt/kelvin \cr | |
#' } | |
#' @export | |
potentials.as.SHE <- function() { | |
# scale name should be one of canonical (see RefCanonicalName) | |
# follow the convention of "each row one observation" (at different temperatures) | |
# all potentials vs SHE | |
potentials <- | |
as.data.frame( | |
matrix(data = | |
# electrode # electrolyte # conc/M # conc label # temp # pot vs SHE # set id # ref | |
c("AgCl/Ag", "NaCl(aq)", "5.9", "saturated", "25", "0.2630", "9", "CRC 97th ed., 97-05-22", | |
"AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "10", "0.215", "1", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "15", "0.212", "1", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "20", "0.208", "1", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "25", "0.205", "1", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "30", "0.201", "1", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "35", "0.197", "1", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "40", "0.193", "1", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "4.2", "saturated", "10", "0.214", "2", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "4.2", "saturated", "15", "0.209", "2", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "4.2", "saturated", "20", "0.204", "2", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "4.2", "saturated", "25", "0.199", "2", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "4.2", "saturated", "30", "0.194", "2", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "4.2", "saturated", "35", "0.189", "2", "Sawyer1995", | |
"AgCl/Ag", "KCl(aq)", "4.2", "saturated", "40", "0.184", "2", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "10", "0.336", "3", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "15", "0.336", "3", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "20", "0.336", "3", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "25", "0.336", "3", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "30", "0.335", "3", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "35", "0.334", "3", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "40", "0.334", "3", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "10", "0.287", "4", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "20", "0.284", "4", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "25", "0.283", "4", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "30", "0.282", "4", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "40", "0.278", "4", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "10", "0.256", "5", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "15", "0.254", "5", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "20", "0.252", "5", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "25", "0.250", "5", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "30", "0.248", "5", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "35", "0.246", "5", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "40", "0.244", "5", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "10", "0.254", "6", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "15", "0.251", "6", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "20", "0.248", "6", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "25", "0.244", "6", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "30", "0.241", "6", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "35", "0.238", "6", "Sawyer1995", | |
"Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "40", "0.234", "6", "Sawyer1995", | |
"AVS", "", "", "", "25", "-4.44", "7", "Trasatti1986", | |
"SHE", "", "", "", "-273.15", "0.00", "8", "Inzelt2013", | |
"SHE", "", "", "", "0", "0.00", "8", "Inzelt2013", | |
"SHE", "", "", "", "25", "0.00", "8", "Inzelt2013", | |
# arbitrary max T=580C (temp at which sodalime glass loses rigidity) | |
"SHE", "", "", "", "580", "0.00", "8", "Inzelt2013", | |
"Li", "", "1.0", "1.0M at 25C", "25", "-3.0401", "10", "CRC 97th ed., 97-05-22", | |
"Na", "", "1.0", "1.0M at 25C", "25", "-2.71", "11", "CRC 97th ed., 97-05-22", | |
"Mg", "", "1.0", "1.0M at 25C", "25", "-2.372", "12", "CRC 97th ed., 97-05-22"), | |
ncol = 8, | |
byrow = TRUE), stringsAsFactors = FALSE) | |
colnames(potentials) <- | |
c("electrode", | |
"electrolyte", | |
"conc.num", | |
"conc.string", | |
"temp", | |
"SHE", | |
"sid", | |
"reference") | |
# convert these columns to type numeric | |
potentials[, c("conc.num", "temp", "SHE")] <- | |
as.numeric(as.character(unlist(potentials[, c("conc.num", "temp", "SHE")]))) | |
# make room for a dE/dT column | |
potentials$dEdT <- as.numeric(NA) | |
# calculate temperature coefficient (dE/dT) for each scale, concentration, and electrolyte (ie. set id) | |
for (s in 1:length(unique(potentials$sid))) { | |
# sid column eas added to data just to make this calculation here easier | |
subspot <- potentials[which(potentials$sid == unique(potentials$sid)[s]), ] | |
# a linear fit will give us temperature coefficient as slope | |
lm.subspot <- stats::lm(SHE ~ temp, data = subspot) | |
potentials[which(potentials$sid == unique(potentials$sid)[s]), "dEdT"] <- | |
lm.subspot$coefficients[2] | |
} | |
return(potentials) | |
} | |
#' Convert from electrochemical or physical scale to SHE | |
#' | |
#' 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 electrolyte optional, specify electrolyte solution, e.g., "KCl(aq)". Must match value in \code{as.SHE.data$electrolyte}. | |
#' @param concentration of electrolyte in mol/L, or as the string "saturated" | |
#' @param temperature of system in degrees Celsius | |
#' @param as.SHE.data dataframe with dataset | |
#' | |
#' @return potential in SHE scale | |
#' @export | |
as.SHE <- function(potential, | |
scale, | |
electrolyte = "", | |
concentration = "saturated", | |
temperature = 25, | |
as.SHE.data = refelectrodes::potentials.as.SHE()) { | |
# 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 | |
# potential and scale vectors supplied by user could have arbitrary length | |
# just make sure potential and scale args have the same length (or length(scale) == 1) | |
if (length(potential) == 0 | length(scale) == 0) { | |
stop("Potential or scale arguments cannot be empty!") | |
} else if (length(potential) != length(scale)) { | |
# stop, unless length(scale) == 1 where we will assume it should be recycled | |
if (length(scale) == 1) { | |
message("Arg <scale> has unit length. We'll recycle it to match length of <potential>.") | |
scale <- rep(scale, length(potential)) | |
} else { | |
stop("Length of <potential> and <scale> must be equal OR <scale> may be unit length.") | |
} | |
} | |
arglength <- length(potential) | |
# make the args concentration, temperature and electrolyte this same length, | |
# unless the user supplied them (only necessary for length > 1) | |
if (arglength > 1) { | |
# handle two cases: | |
# 1. user did not touch concentration, temperature and electrolyte args. | |
# Assume they forgot and reset their length and print a message | |
# 2. user did change concentration or temperature or electrolyte, 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) & | |
identical(electrolyte, formals(as.SHE)$electrolyte)) { | |
# case 1 | |
# message("NOTE: default concentration and temperature values used for all potentials and scales.") | |
message(paste0("The default concentration (", formals(as.SHE)$concentration, ") and temperature (", formals(as.SHE)$temperature, "C) will be assumed for all your potential/scale values.")) | |
concentration <- rep(concentration, arglength) | |
temperature <- rep(temperature, arglength) | |
electrolyte <- rep(electrolyte, arglength) | |
} else { | |
# case 2 | |
stop("Concentration, temperature and electrolyte arguments must have the same number of elements as potential and scale!") | |
} | |
} | |
## we can now safely assume that length(<args>) == arglength | |
# place args into a single dataframe | |
# this way, we can correlate columns to each other by row | |
dfargs <- | |
data.frame(potential = potential, | |
scale = refelectrodes::RefCanonicalName(scale), | |
electrolyte = electrolyte, | |
concentration = concentration, | |
temperature = temperature, | |
stringsAsFactors = FALSE) | |
# add column to keep track of vacuum scale | |
# dfargs$vacuum <- as.logical(FALSE) | |
# add column to hold calc potential vs SHE | |
dfargs$SHE <- as.numeric(NA) | |
## From here on, ONLY access the arguments via this dataframe | |
## That is, use dfargs$electrolyte, NOT electrolyte | |
# SHE scale special considerations | |
# 1. concentration is constant for SHE | |
if (any(dfargs$scale == refelectrodes::RefCanonicalName("SHE"))) { | |
dfargs$concentration[which(dfargs$scale == refelectrodes::RefCanonicalName("SHE"))] <- "" | |
dfargs$electrolyte[which(dfargs$scale == refelectrodes::RefCanonicalName("SHE"))] <- "" | |
} | |
# AVS scale special considerations | |
# 1. concentration is meaningless for AVS | |
if (any(dfargs$scale == refelectrodes::RefCanonicalName("AVS"))) { | |
# concentration is meaningless for AVS (no electrolyte) so for those rows, we'll reset it | |
dfargs$concentration[which(dfargs$scale == refelectrodes::RefCanonicalName("AVS"))] <- "" | |
dfargs$electrolyte[which(dfargs$scale == refelectrodes::RefCanonicalName("AVS"))] <- "" | |
# dfargs$vacuum[which(dfargs$scale == refelectrodes::RefCanonicalName("AVS"))] <- TRUE | |
} | |
# now just work our way through dfargs, line-by-line to determine potential as SHE | |
# all necessary conditions should be recorded right here in dfargs | |
for (p in 1:dim(dfargs)[1]) { | |
## WE ARE NOW WORKING ROW-BY-ROW THROUGH THE SUPPLIED ARGUMENTS IN dfargs | |
# Step-wise matching: | |
# + first, we subset against electrode scale. If dataset only has one row, done. Else, | |
# + we subset against either conc.string or conc.num. Stop if zero rows in dataset (error), otherwise proceed. | |
# Our "dataset" is the literature data supplied via the argument as.SHE.data | |
this.data.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) | |
# subset.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) | |
if (dim(this.data.scale)[1] > 1) { | |
# continue matching, now against conc.string or conc.num | |
if (is.character(dfargs$concentration[p])) { | |
this.data.concentration <- | |
# subset.concentration <- | |
subset(this.data.scale, conc.string == dfargs$concentration[p]) | |
} else { | |
this.data.concentration <- | |
# subset.concentration <- | |
subset(this.data.scale, conc.num == dfargs$concentration[p]) | |
} | |
# stop if the resulting dataframe after matching contains no rows | |
if (dim(this.data.concentration)[1] == 0) { | |
stop(paste0("Failed to find any matching entries in dataset for ", | |
paste(dfargs[p, ], collapse = " ", sep = ""))) | |
} | |
# Note: it's ok at this point if the resulting dataset contains more than one row as | |
# more matching will be done below | |
# If we haven't had reason to stop(), we should be good | |
# just housekeeping: rename the variable so we don't have to edit code below | |
this.SHE.data <- this.data.concentration | |
# subset.SHE.data <- subset.concentration | |
} else { | |
# just housekeeping again | |
this.SHE.data <- this.data.scale | |
# subset.SHE.data <- subset.scale | |
} | |
## Electrolyte | |
# == We would like to transparently handle the following scenario: | |
# || if the user did not specify electrolyte solution (which we can check by using formals()) | |
# || but the dataset (after subsetting against scale and concentration above) still contains | |
# || more than one electrolyte | |
# >> Approach: we'll specify a "fallback" electrolyte, KCl (usually that's what the user wants) | |
# >> and inform/warn about it | |
# KCl is a good assumption, as we always have KCl | |
# for the cases where an electrode system has more than one electrolyte | |
fallback.electrolyte <- "KCl(aq)" | |
if (length(unique(this.SHE.data$electrolyte)) > 1) { | |
if (formals(as.SHE)$electrolyte == "") { | |
warning(paste0("More than one electrolyte ", | |
"available for E(", dfargs$scale[p], ") in dataset. ", | |
"I'll assume you want ", fallback.electrolyte, ".")) | |
this.SHE.data <- | |
subset(this.SHE.data, electrolyte == fallback.electrolyte) | |
} else { | |
# else the user did change the electrolyte arg, use the user's value | |
this.SHE.data <- | |
subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) | |
# but stop if the resulting dataframe contains no rows | |
if (dim(this.SHE.data)[1] == 0) stop("Your choice of electrolyte does not match any data!") | |
} | |
} else { | |
# dataset contains only one unique electrolyte | |
# again, check if electrolyte in arg matches the one in dataset | |
# if it does, great, if it does not, print a message and use it anyway | |
if (unique(this.SHE.data$electrolyte) == dfargs$electrolyte[p]) { | |
this.SHE.data <- | |
subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) | |
} else { | |
# whatever electrolyte the user supplied does not match what's left in the datasubset | |
# but at this point the user is probably better served by returning the electrolyte we have | |
# along with an informative message (that's the only reason for the if-else below) | |
electrolytes.in.subset <- | |
unique(subset(as.SHE.data, electrode == dfargs$scale[p])$electrolyte) | |
if (dfargs$electrolyte[p] == "") { | |
message( | |
paste0('Electrolyte "" (empty string) not in dataset for E(', | |
dfargs$scale[p], '). ', | |
'These electrolytes are: ', | |
paste(electrolytes.in.subset, collapse = ', or '), '.', | |
"I'll assume you want ", fallback.electrolyte, ".") | |
) | |
} else { | |
message(paste0("Electrolyte ", dfargs$electrolyte[p], " not in dataset for E(", | |
dfargs$scale[p], "). ", | |
"These electrolytes are: ", | |
paste(electrolytes.in.subset, collapse = ", or "), ".", | |
"I'll assume you want ", fallback.electrolyte, ".") | |
) | |
} | |
} | |
} | |
# temperature | |
# either happens to match a temperature in the dataset, or we interpolate | |
# (under the assumption that potential varies linearly with temperature) | |
if (!any(this.SHE.data$temp == dfargs$temperature[p])) { | |
# sought temperature was not available in dataset, check that it falls inside | |
# note: important to use less/more-than-or-equal in case data only contains one value | |
if ((dfargs$temperature[p] <= max(this.SHE.data$temp)) && (dfargs$temperature[p] >= min(this.SHE.data$temp))) { | |
# within dataset range, do linear interpolation | |
lm.subset <- stats::lm(SHE ~ temp, data = this.SHE.data) | |
# interpolated temperature, calculated based on linear regression | |
# (more accurate than simple linear interpolation with approx()) | |
pot.interp <- | |
lm.subset$coefficients[2] * dfargs$temperature[p] + lm.subset$coefficients[1] | |
### CALC POTENTIAL vs SHE | |
dfargs$SHE[p] <- | |
ifelse(dfargs$scale[p] == "AVS", | |
pot.interp - dfargs$potential[p], | |
pot.interp + dfargs$potential[p]) | |
} | |
} else { | |
# requested temperature does exist in dataset | |
### CALC POTENTIAL vs SHE | |
dfargs$SHE[p] <- | |
ifelse(dfargs$scale[p] == "AVS", | |
subset(this.SHE.data, temp == dfargs$temperature[p])$SHE - dfargs$potential[p], | |
subset(this.SHE.data, temp == dfargs$temperature[p])$SHE + dfargs$potential[p]) | |
} | |
} | |
return(dfargs$SHE) | |
} | |
#' Convert from SHE scale to another electrochemical or physical scale | |
#' | |
#' Convert an arbitrary number of potentials vs SHE to another electrochemical | |
#' scale (or the vacuum scale). | |
#' The available target scales are those listed by \code{\link{potentials.as.SHE}}. | |
#' | |
#' @param potential potential in volt | |
#' @param scale name of the target scale | |
#' @param electrolyte optional, specify electrolyte solution, e.g., "KCl(aq)". Must match one of the values in \code{\link{potentials.as.SHE}$electrolyte} | |
#' @param concentration of electrolyte in mol/L, or as the string "saturated" | |
#' @param temperature of system in degrees Celsius | |
#' @param as.SHE.data by default this parameter reads the full dataset \code{\link{potentials.as.SHE}} | |
#' | |
#' @return potential in the specified target scale | |
#' @export | |
from.SHE <- function(potential, | |
scale, | |
electrolyte = "", | |
concentration = "saturated", | |
temperature = 25, | |
as.SHE.data = refelectrodes::potentials.as.SHE()) { | |
# 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 | |
# potential and scale vectors supplied by user could have arbitrary length | |
# just make sure potential and scale args have the same length (or length(scale) == 1) | |
if (length(potential) == 0 | length(scale) == 0) { | |
stop("Potential or scale arguments cannot be empty!") | |
} else if (length(potential) != length(scale)) { | |
# stop, unless length(scale) == 1 where we will assume it should be recycled | |
if (length(scale) == 1) { | |
message("Arg <scale> has unit length. We'll recycle it to match length of <potential>.") | |
scale <- rep(scale, length(potential)) | |
} else { | |
stop("Length of <potential> and <scale> must be equal OR <scale> may be unit length.") | |
} | |
} | |
arglength <- length(potential) | |
# make the args concentration, temperature and electrolyte this same length, | |
# unless the user supplied them (only necessary for length > 1) | |
if (arglength > 1) { | |
# handle two cases: | |
# 1. user did not touch concentration, temperature and electrolyte args. | |
# Assume they forgot and reset their length and print a message | |
# 2. user did change concentration or temperature or electrolyte, 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(from.SHE)$concentration) & | |
identical(temperature, formals(from.SHE)$temperature) & | |
identical(electrolyte, formals(from.SHE)$electrolyte)) { | |
# case 1 | |
message(paste0("The default concentration (", formals(from.SHE)$concentration, ") and temperature (", formals(from.SHE)$temperature, "C) will be assumed for all your potential/scale values.")) | |
concentration <- rep(concentration, arglength) | |
temperature <- rep(temperature, arglength) | |
electrolyte <- rep(electrolyte, arglength) | |
} else { | |
# case 2 | |
stop("Concentration, temperature and electrolyte arguments must have the same number of elements as potential and scale!") | |
} | |
} | |
## we can now safely assume that length(<args>) == arglength | |
# place args into a single dataframe | |
# this way, we can correlate columns to each other by row | |
dfargs <- | |
data.frame(potential = potential, # vs SHE | |
scale = refelectrodes::RefCanonicalName(scale), # target scale | |
electrolyte = electrolyte, | |
concentration = concentration, | |
temperature = temperature, | |
stringsAsFactors = FALSE) | |
## From here on, ONLY access the arguments via this dataframe | |
## That is, use dfargs$electrolyte, NOT electrolyte (and so on) | |
# SHE scale special considerations | |
# 1. concentration is constant for SHE | |
if (any(dfargs$scale == refelectrodes::RefCanonicalName("SHE"))) { | |
dfargs$concentration[which(dfargs$scale == refelectrodes::RefCanonicalName("SHE"))] <- "" | |
dfargs$electrolyte[which(dfargs$scale == refelectrodes::RefCanonicalName("SHE"))] <- "" | |
} | |
# AVS scale special considerations | |
# 1. concentration is meaningless for AVS | |
if (any(dfargs$scale == refelectrodes::RefCanonicalName("AVS"))) { | |
# concentration is meaningless for AVS (no electrolyte) so for those rows, we'll reset it | |
dfargs$concentration[which(dfargs$scale == refelectrodes::RefCanonicalName("AVS"))] <- "" | |
dfargs$electrolyte[which(dfargs$scale == refelectrodes::RefCanonicalName("AVS"))] <- "" | |
} | |
# now just work our way through dfargs, line-by-line to determine potential as SHE | |
# all necessary conditions should be recorded right here in dfargs | |
for (p in 1:dim(dfargs)[1]) { | |
## WE ARE NOW WORKING ROW-BY-ROW THROUGH THE SUPPLIED ARGUMENTS IN dfargs | |
# Step-wise matching: | |
# + first, we subset against electrode scale. If dataset only has one row, done. Else, | |
# + we subset against either conc.string or conc.num. Stop if zero rows in dataset (error), otherwise proceed. | |
# Our "dataset" is the literature data supplied via the argument as.SHE.data | |
this.data.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) | |
# subset.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) | |
if (dim(this.data.scale)[1] > 1) { | |
# continue matching, now against conc.string or conc.num | |
if (is.character(dfargs$concentration[p])) { | |
this.data.concentration <- | |
subset(this.data.scale, conc.string == dfargs$concentration[p]) | |
} else { | |
this.data.concentration <- | |
subset(this.data.scale, conc.num == dfargs$concentration[p]) | |
} | |
# stop if the resulting dataframe after matching contains no rows | |
if (dim(this.data.concentration)[1] == 0) { | |
stop(paste0("Failed to find any matching entries in dataset for ", | |
paste(dfargs[p, ], collapse = " ", sep = ""))) | |
} | |
# Note: it's ok at this point if the resulting dataset contains more than one row as | |
# more matching will be done below | |
# If we haven't had reason to stop(), we should be good | |
# just housekeeping: rename the variable so we don't have to edit code below | |
this.SHE.data <- this.data.concentration | |
} else { | |
# just housekeeping again | |
this.SHE.data <- this.data.scale | |
} | |
## Electrolyte | |
# == We would like to transparently handle the following scenario: | |
# || if the user did not specify electrolyte solution (which we can check by using formals()) | |
# || but the dataset (after subsetting against scale and concentration above) still contains | |
# || more than one electrolyte | |
# >> Approach: we'll specify a "fallback" electrolyte, KCl (usually that's what the user wants) | |
# >> and inform/warn about it | |
# KCl is a good assumption, as we always have KCl for the cases where | |
# an electrode system has more than one electrolyte | |
fallback.electrolyte <- "KCl(aq)" | |
if (length(unique(this.SHE.data$electrolyte)) > 1) { | |
if (formals(as.SHE)$electrolyte == "") { | |
warning(paste0("More than one electrolyte ", | |
"available for E(", dfargs$scale[p], ") in dataset. ", | |
"I'll assume you want ", fallback.electrolyte, ".")) | |
this.SHE.data <- | |
subset(this.SHE.data, electrolyte == fallback.electrolyte) | |
} else { | |
# else the user did change the electrolyte arg, use the user's value | |
this.SHE.data <- | |
subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) | |
# but stop if the resulting dataframe contains no rows | |
if (dim(this.SHE.data)[1] == 0) stop("Your choice of electrolyte does not match any data!") | |
} | |
} else { | |
# dataset contains only one unique electrolyte | |
# again, check if electrolyte in arg matches the one in dataset | |
# if it does, great, if it does not, print a message and use it anyway | |
if (unique(this.SHE.data$electrolyte) == dfargs$electrolyte[p]) { | |
this.SHE.data <- | |
subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) | |
} else { | |
# whatever electrolyte the user supplied does not match what's left in the datasubset | |
# but at this point the user is probably better served by returning the electrolyte we have | |
# along with an informative message (that's the only reason for the if-else below) | |
electrolytes.in.subset <- | |
unique(subset(as.SHE.data, electrode == dfargs$scale[p])$electrolyte) | |
if (dfargs$electrolyte[p] == "") { | |
message( | |
paste0('Electrolyte "" (empty string) not in dataset for E(', | |
dfargs$scale[p], '). ', | |
'These electrolytes are: ', | |
paste(electrolytes.in.subset, collapse = ', or '), '.', | |
"I'll assume you want ", fallback.electrolyte, ".") | |
) | |
} else { | |
message(paste0("Electrolyte ", dfargs$electrolyte[p], " not in dataset for E(", | |
dfargs$scale[p], "). ", | |
"These electrolytes are: ", | |
paste(electrolytes.in.subset, collapse = ", or "), ".", | |
"I'll assume you want ", fallback.electrolyte, ".") | |
) | |
} | |
} | |
} | |
# temperature | |
# either happens to match a temperature in the dataset, or we interpolate | |
# (under the assumption that potential varies linearly with temperature) | |
if (!any(this.SHE.data$temp == dfargs$temperature[p])) { | |
# sought temperature was not available in dataset, check that it falls inside | |
# note: important to use less/more-than-or-equal in case data only contains one value | |
if ((dfargs$temperature[p] <= max(this.SHE.data$temp)) && (dfargs$temperature[p] >= min(this.SHE.data$temp))) { | |
# within dataset range, do linear interpolation | |
lm.subset <- stats::lm(SHE ~ temp, data = this.SHE.data) | |
# interpolated temperature, calculated based on linear regression | |
# (more accurate than simple linear interpolation with approx()) | |
pot.interp <- | |
lm.subset$coefficients[2] * dfargs$temperature[p] + lm.subset$coefficients[1] | |
### CALC POTENTIAL vs requested scale | |
dfargs$potentialvsscale[p] <- | |
ifelse(dfargs$scale[p] == "AVS", | |
pot.interp - dfargs$potential[p], | |
dfargs$potential[p] - pot.interp) | |
} | |
} else { | |
# requested temperature does exist in dataset | |
### CALC POTENTIAL vs requested scale | |
dfargs$potentialvsscale[p] <- | |
ifelse(dfargs$scale[p] == "AVS", | |
subset(this.SHE.data, temp == dfargs$temperature[p])$SHE - dfargs$potential[p], | |
dfargs$potential[p] - subset(this.SHE.data, temp == dfargs$temperature[p])$SHE) | |
} | |
} | |
return(dfargs$potentialvsscale) | |
} |