#' Predict x from y for a linear calibration
#'
#' This function predicts x values using a univariate linear model that has
#' been generated for the purpose of calibrating a measurement method.
#' Prediction intervals are given at the specified confidence level. The
#' calculation method was taken from Massart et al. (1997). In particular,
#' Equations 8.26 and 8.28 were combined in order to yield a general treatment
#' of inverse prediction for univariate linear models, taking into account
#' weights that have been used to create the linear model, and at the same time
#' providing the possibility to specify a precision in sample measurements
#' differing from the precision in standard samples used for the calibration.
#' This is elaborated in the package vignette.
#'
#' This is an implementation of Equation (8.28) in the Handbook of Chemometrics
#' and Qualimetrics, Part A, Massart et al (1997), page 200, validated with
#' Example 8 on the same page, extended as specified in the package vignette
#'
#' @aliases inverse.predict inverse.predict.lm inverse.predict.rlm
#' inverse.predict.default
#' @param object A univariate model object of class \code{\link{lm}} or
#' \code{\link[MASS:rlm]{rlm}} with model formula \code{y ~ x} or \code{y ~ x -
#' 1}.
#' @param newdata A vector of observed y values for one sample.
#' @param \dots Placeholder for further arguments that might be needed by
#' future implementations.
#' @param ws The weight attributed to the sample. This argument is obligatory
#' if \code{object} has weights.
#' @param alpha The error tolerance level for the confidence interval to be
#' reported.
#' @param var.s The estimated variance of the sample measurements. The default
#' is to take the residual standard error from the calibration and to adjust it
#' using \code{ws}, if applicable. This means that \code{var.s} overrides
#' \code{ws}.
#' @return A list containing the predicted x value, its standard error and a
#' confidence interval.
#' @note The function was validated with examples 7 and 8 from Massart et al.
#' (1997). Note that the behaviour of inverse.predict changed with chemCal
#' version 0.2.1. Confidence intervals for x values obtained from calibrations
#' with replicate measurements did not take the variation about the means into
#' account. Please refer to the vignette for details.
#' @references Massart, L.M, Vandenginste, B.G.M., Buydens, L.M.C., De Jong,
#' S., Lewi, P.J., Smeyers-Verbeke, J. (1997) Handbook of Chemometrics and
#' Qualimetrics: Part A, p. 200
#' @importFrom stats optimize predict qt
#' @examples
#'
#' # This is example 7 from Chapter 8 in Massart et al. (1997)
#' m <- lm(y ~ x, data = massart97ex1)
#' inverse.predict(m, 15) # 6.1 +- 4.9
#' inverse.predict(m, 90) # 43.9 +- 4.9
#' inverse.predict(m, rep(90,5)) # 43.9 +- 3.2
#'
#' # For reproducing the results for replicate standard measurements in example 8,
#' # we need to do the calibration on the means when using chemCal > 0.2
#' weights <- with(massart97ex3, {
#' yx <- split(y, x)
#' ybar <- sapply(yx, mean)
#' s <- round(sapply(yx, sd), digits = 2)
#' w <- round(1 / (s^2), digits = 3)
#' })
#'
#' massart97ex3.means <- aggregate(y ~ x, massart97ex3, mean)
#'
#' m3.means <- lm(y ~ x, w = weights, data = massart97ex3.means)
#'
#' inverse.predict(m3.means, 15, ws = 1.67) # 5.9 +- 2.5
#' inverse.predict(m3.means, 90, ws = 0.145) # 44.1 +- 7.9
#'
#'
#' @export
inverse.predict <- function(object, newdata, ...,
ws = "auto", alpha = 0.05, var.s = "auto")
{
UseMethod("inverse.predict")
}
#' @export
inverse.predict.default <- function(object, newdata, ...,
ws = "auto", alpha = 0.05, var.s = "auto")
{
stop("Inverse prediction only implemented for univariate lm objects.")
}
#' @export
inverse.predict.lm <- function(object, newdata, ...,
ws = "auto", alpha = 0.05, var.s = "auto")
{
yname = names(object$model)[[1]]
xname = names(object$model)[[2]]
if (ws == "auto") {
ws <- ifelse(length(object$weights) > 0, mean(object$weights), 1)
}
if (length(object$weights) > 0) {
w <- object$weights
} else {
w <- rep(1, nrow(object$model))
}
.inverse.predict(object = object, newdata = newdata,
ws = ws, alpha = alpha, var.s = var.s, w = w, xname = xname, yname = yname)
}
#' @export
inverse.predict.rlm <- function(object, newdata, ...,
ws = "auto", alpha = 0.05, var.s = "auto")
{
yname = names(object$model)[[1]]
xname = names(object$model)[[2]]
if (ws == "auto") {
ws <- mean(object$w)
}
w <- object$w
.inverse.predict(object = object, newdata = newdata,
ws = ws, alpha = alpha, var.s = var.s, w = w, xname = xname, yname = yname)
}
.inverse.predict <- function(object, newdata, ws, alpha, var.s, w, xname, yname)
{
if (length(object$coef) > 2)
stop("More than one independent variable in your model - not implemented")
if (alpha <= 0 | alpha >= 1)
stop("Alpha should be between 0 and 1 (exclusive)")
ybars <- mean(newdata)
m <- length(newdata)
n <- nrow(object$model)
df <- n - length(object$coef)
xi <- object$model[[2]]
yi <- object$model[[1]]
yihat <- object$fitted.values
se <- sqrt(sum(w * (yi - yihat)^2)/df)
if (var.s == "auto") {
var.s <- se^2/ws
}
b1 <- object$coef[[xname]]
ybarw <- sum(w * yi)/sum(w)
# This is the adapted form of equation 8.28 (see package vignette)
sxhats <- 1/b1 * sqrt(
(var.s / m) +
se^2 * (1/sum(w) +
(ybars - ybarw)^2 * sum(w) /
(b1^2 * (sum(w) * sum(w * xi^2) - sum(w * xi)^2)))
)
if (names(object$coef)[1] == "(Intercept)") {
b0 <- object$coef[["(Intercept)"]]
} else {
b0 <- 0
}
xs <- (ybars - b0) / b1
t <- qt(1-0.5*alpha, n - 2)
conf <- t * sxhats
result <- list("Prediction"=xs,"Standard Error"=sxhats,
"Confidence"=conf, "Confidence Limits"=c(xs - conf, xs + conf))
return(result)
}