Skip to content

Commit 2466fad

Browse files
committed
Add docs and license
1 parent 4ba8a8a commit 2466fad

File tree

6 files changed

+430
-12
lines changed

6 files changed

+430
-12
lines changed

.Rbuildignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
^drob\.Rproj$
22
^\.Rproj\.user$
3-
^.vscode$
3+
^.vscode$
4+
^LICENSE\.md$

DESCRIPTION

+4-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ Authors@R:
1111
email = "[email protected]",
1212
role = c("cre", "aut")
1313
)
14-
URL: hhttps://github.com/memeplex/drob
14+
URL: https://github.com/memeplex/drob
1515
BugReports: https://github.com/memeplex/drob/issues
16-
License: `use_mit_license()`
16+
License: MIT + file LICENSE
1717
Encoding: UTF-8
1818
Roxygen: list(markdown = TRUE)
1919
RoxygenNote: 7.3.1
2020
Imports:
2121
drc (>= 3.0.1),
22-
DEoptimR (>= 1.1.3)
22+
DEoptimR (>= 1.1.3),
23+
MASS

LICENSE.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# MIT License
2+
3+
Copyright (c) 2024 drob authors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

R/drob.R

+184-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
library(MASS)
12
library(drc)
23
library(DEoptimR)
34

@@ -24,14 +25,14 @@ fpl <- list(
2425
a / (1 + a)
2526
)
2627
},
27-
init = function(x, y, scale = 3) {
28+
init = function(x, y, extend = 3) {
2829
coef <- summary(drm(y ~ x, fct = LL.4()))$coefficients
2930
idx <- if (coef[1] >= 0) c(3, 1, 4, 2) else c(2, 1, 4, 3)
3031
t <- unname(coef[idx, 1])
3132
se <- unname(coef[idx, 2])
3233
t[2] <- abs(t[2])
33-
extend <- function(x, dir) x + dir * scale * abs(x)
34-
init <- list(t = t, se = se, lower = extend(t, -1), upper = extend(t, +1))
34+
bound <- function(x, dir) x + dir * extend * abs(x)
35+
init <- list(t = t, se = se, lower = bound(t, -1), upper = bound(t, +1))
3536
init$lower[c(2, 3)] <- 1e-100
3637
init
3738
}
@@ -74,29 +75,201 @@ bisquare <- function(k) {
7475
#'
7576
#' @return The `rho`-based M-estimate of scale for `r`.
7677
#'
78+
#' @examples
79+
#' m_scale(r, bisquare(1.55))
80+
#'
7781
#' @export
7882
m_scale <- function(r, rho, extend = 5) {
7983
f <- function(s) mean(rho(r / s)) - 0.5
8084
m <- median(abs(r))
8185
uniroot(f, lower = 0, upper = extend * m, check.conv = TRUE)$root
8286
}
8387

88+
#' Compute robust estimates of dose-response model parameters
89+
#'
90+
#' `drob` computes an M-estimate of location, given data `x` and `y` and model
91+
#' `model`, potentially nonlinear and heteroscedastic. It implements a 3-step
92+
#' procedure in the spirit of MM-estimation:
93+
#' - Step 1: heuristically computes an initial location estimate `t0`.
94+
#' - Step 2: computes scale estimates `s` for each dose, based on the residuals
95+
#' with respect to `t0`.
96+
#' - Step 3: starting from `t0` and scaling by `s` a final location estimate `t`
97+
#' is found iteratively.
98+
#' Each step can be fine-tuned using the parameters described below.
99+
#'
100+
#' @param x A vector of doses. It is expected that each dose is repeated enough
101+
#' times so as to be able to compute a good estimate of scale conditional to
102+
#' the dose in step 2.
103+
#'
104+
#' @param y A vector of responses with the same length than `x`.
105+
#'
106+
#' @param model The string `"fpl"` for the predefined 4PL model or, more
107+
#' generally, a list describing a model with at least two mandatory elements:
108+
#' - `fun`: the model function, which takes doses `x` and parameters `t` as
109+
#' its two only arguments.
110+
#' - `init`: an initialization function that is able to produce lower and
111+
#' upper search bounds for step 1. It takes `x`, `y` as arguments, as well
112+
#' as an optional `extend` argument that controls the extension of the
113+
#' search region.
114+
#'
115+
#' A third optional element can also be included in the list:
116+
#' - `grad`: the gradient of `fun`, which has the same signature. When `grad`
117+
#' is present it will be used both for computing a gradient in step 3
118+
#' (if `qn_gr` is `TRUE`) and for computing the standard errors of the
119+
#' returned estimates.
120+
#'
121+
#' @param step_1 The loss function used for computing `t0`. It may be a function
122+
#' that takes a vector of residuals as its only argument or one of the following
123+
#' predefined strings:
124+
#' - `"lts"`: upper-trimmed mean (up to `lts_q`) of squared residuals.
125+
#' - `"lms"`: median of squared residuals.
126+
#' - `"ml1"`: mean of absolute residuals. This implements an M-estimate with
127+
#' `rho(r)` equal to `|r|`, i.e. L1-regression.
128+
#' - `"sl1"`: median of absolute residuals. This implements an S-estimate with
129+
#' `rho(r)` equal to `I(|r| > 1)`.
130+
#' - `"mbi"`: loss for bisquare M-estimate with cutoff point `mbi_k`, scaled by
131+
#' `mad(y)` so as to make it scale-equivariant.
132+
#' - `"sbi"`: (the default) loss for bisquare S-estimate with cutoff point
133+
#' `sbi_k`. The root finding routine will be extended by `ms_extend` (see the
134+
#' documentation for `m_scale`).
135+
#'
136+
#' `loss(y - model$fun(x, t))` will be minimized with respect to `t` using a
137+
#' differential evolution routine provided by the `DEoptimR` package. Lower
138+
#' and upper bounds for the search come from `model$init` as documented for
139+
#' the `model` parameter. Other arguments passed to `JDEoptim` may be overriden
140+
#' by passing a `de_args` function. If the optimizer fails to converge to a
141+
#' solution, the entire `drob` function execution is aborted with an error.
142+
#'
143+
#' @param step_2 The function used for computing a scale estimate for each dose.
144+
#' It may be a function taking a vector of residuals (with respect to `t0`,
145+
#' computed in step 1) and returning a scale estimate, or one of the following
146+
#' predefined strings:
147+
#' - `"sl1"`: median of absolute residuals. This is an M-estimate of scale with
148+
#' `rho(r)` equal to `I(|r| > 1)`. It is scaled by `sl1_k`.
149+
#' - `"sbi"`: (the default) bisquare M-estimate of scale with cutoff point
150+
#' `sbi_k`.
151+
#'
152+
#' The default values for `sl1_k` and `sbi_k` make the estimates equal to 1
153+
#' under the standard normal distribution. The function is called once for
154+
#' each different dose, passing it a vector with the responses for such dose.
155+
#'
156+
#' @param step_3 The loss function used for computing `t`. It may be the
157+
#' string `"mbi"` (the default) for a bisquare loss defining an M-estimate
158+
#' with cutoff point `mbi_k` or, more generally, a list containing at the least
159+
#' the following mandatory element:
160+
#' - `rho`: a rho-function taking a vector of scaled residuals.
161+
#'
162+
#' The list may also contain two optional arguments:
163+
#' - `psi`: the first derivative of `rho`.
164+
#' - `dpsi`: the second derivative of `rho`.
165+
#'
166+
#' When `psi` and `dpsi` are present (as well as `model$grad`) standard errors
167+
#' of the returned estimates will be computed. Moreover, when `psi` is present
168+
#' (as well as `model$grad` and also `qn_gr` is `TRUE`) a gradient function
169+
#' is composed and passed as the argument for the parameter `gr` of `optim`.
170+
#'
171+
#' This step minimizes `loss((y - model$fun(x, t)) / s)` with respect to `t`.
172+
#' It uses the standard `optim` routine with initial parameter `t0` (also
173+
#' used for parameter scaling, see the `parscale` control parameter of `optim`)
174+
#' and sequentially attempting the following three methods in order:
175+
#' 1. Quasi-Newton L-BFGS-B with the same bounds than in step 1.
176+
#' 2. Quasi-Newton BFGS.
177+
#' 3. Conjugate gradient CG.
178+
#'
179+
#' The result of the first successful optimizer is kept. If all optimizers
180+
#' fail to converge to a solution, the entire `drob` function execution is
181+
#' aborted with an error.
182+
#'
183+
#' Other arguments passed to `optim` may be overriden by passing a `qn_args`
184+
#' function.
185+
#'
186+
#' @param lts_q The proportion of data to delete for the upper-trimmed mean
187+
#' loss (used when `step_1` is `"lts"`). By default 0.5 to achieve a high
188+
#' breakdown point.
189+
#'
190+
#' @param mbi_k The cutoff point used for bisquare M-estimates (argument
191+
#' `"mbi"` to `step_1` or `step_3`). By default 3.44 to achieve about 85%
192+
#' asymptotic efficiency under the normal distribution. Other typical values
193+
#' and their asymptotic efficiencies are: 3.14 (80%), 3.88 (90%), 4.68 (95%).
194+
#'
195+
#' @param sbi_k The cutoff point used for bisquare M-estimates of scale and,
196+
#' consequently, also for bisquare S-estimates (argument `"sbi"` to `step_1`
197+
#' or `step_2`). By default 1.548 to achieve a breakdown point of about 0.5.
198+
#'
199+
#' @param sl1_k Scaling factor for the median of absolute residuals (
200+
#' argument `"sl1"` to `step_2`). By default 1.48 so that scale equals 1 under
201+
#' the standard normal distribution.
202+
#'
203+
#' @param de_args A function that takes a list with the arguments to be pased
204+
#' to `JDEoptim` in step 1 and returns and arbitrarily modified list of
205+
#' arguments. By default it returns its argument unmodified.
206+
#'
207+
#' @param qn_args A function that takes a list with the arguments to be pased
208+
#' to `optim` in step 3 and returns and arbitrarily modified list of
209+
#' arguments. By default it returns its argument unmodified.
210+
#'
211+
#' @param qn_gr A flag that indicates if a composed function is passed as the
212+
#' argument to the parameter `gr` of the `optim` routine in step 3. Besides
213+
#' pass `TRUE` to `qn_gr`, for this to be the case both `model$grad` and
214+
#' `loss$psi` must be provided. Otherwise, a finite-difference approximation
215+
#' will be used as per usual. By default `FALSE`.
216+
#'
217+
#' @param ms_extend When computing bisquare M-estimates of scale and,
218+
#' consequently, also when computing bisquare S-estimates, `ms_extend` will
219+
#' be based to `m_scale` in order to extend the root finding interval (for
220+
#' further details, refer to the documentation for `m_scale`). By default 5.
221+
#'
222+
#' @param init_extend Passed to `model$init` in order to extend the search
223+
#' region defined by the lower and bounds passed to the differential
224+
#' evolution routine in step 1 and to the quasi-Newton L-BFGS-B routine in
225+
#' step 3. By default 3.
226+
#'
227+
#' @return A list containing the following elements:
228+
#' - `t`: a vector with the final location estimate, produced by step 3.
229+
#' - `t0`: a vector with the initial location estimate, produced by step 1.
230+
#' - `s`: a vector with a scale estimate for each dose, produced by step 2.
231+
#' `names(s)` gives the corresponding doses.
232+
#' - `init`: a list with the results of the initialization step for the model.
233+
#' It usually includes a non-robust location estimate with its standard error
234+
#' estimates, besides lower and upper search bounds.
235+
#' - `loss`: the value of the loss function for step 3 evaluated at `t`.
236+
#'
237+
#' If `model$grad`, `loss$psi` and `loss$dpsi` are all provided, the resulting
238+
#' list will also include a further element:
239+
#' - `se`: asymptotic standard error estimates for `t`.
240+
#'
241+
#' @examples
242+
#'
243+
#' set.seed(0)
244+
#' t <- c(10, 10, 40, 20)
245+
#' x <- rep(seq(1, 100, 10), each = 30)
246+
#' n <- length(x)
247+
#' cont <- runif(n) > 0.9
248+
#' e <- rnorm(n, ifelse(cont, 5, 0), (x / 100) * ifelse(cont, 3, 1))
249+
#' y <- fpl$fun(x, t) + e
250+
#' est <- drob(x, y)
251+
#' plot(x, y)
252+
#' lines(x, fpl$fun(x, t), lwd = 2)
253+
#' lines(x, fpl$fun(x, est$init$t), col = 2)
254+
#' lines(x, fpl$fun(x, est$t), col = 3)
255+
#' legend("topleft", legend = c("t", "t_ls", "t_m"), lty = rep(1, 3), col = 1:3)
256+
#'
84257
#' @export
85258
drob <- function( # nolint
86259
x, y,
87260
model = "fpl",
88261
step_1 = "sbi",
89262
step_2 = "sbi",
90263
step_3 = "mbi",
91-
lts_q = 0.7,
264+
lts_q = 0.5,
92265
mbi_k = 3.44,
93-
sbi_k = 1.55,
94-
sli_k = 1.48,
266+
sbi_k = 1.548,
267+
sl1_k = 1.48,
95268
de_args = identity,
96269
qn_args = identity,
97270
qn_gr = FALSE,
98271
ms_extend = 5,
99-
init_extend = 5
272+
init_extend = 3
100273
) {
101274
select <- function(arg, ...) if (is.character(arg)) switch(arg, ...) else arg
102275
mbi <- bisquare(mbi_k)
@@ -136,7 +309,7 @@ drob <- function( # nolint
136309

137310
scale <- select(
138311
step_2,
139-
"sl1" = function(r) median(abs(r)) * sli_k,
312+
"sl1" = function(r) median(abs(r)) * sl1_k,
140313
"sbi" = function(r) m_scale(r, sbi$rho, ms_extend),
141314
stop("Step 2: invalid value '", step_2, "'")
142315
)
@@ -189,6 +362,8 @@ drob <- function( # nolint
189362
}
190363

191364

365+
366+
192367
# https://cran.r-project.org/web/packages/robustbase/vignettes/psi_functions.pdf
193368
# The constant k for 95% efficiency of the regression estimator is 4.685 and
194369
# the constant for a breakdown point of 0.5 of the S-estimator is 1.548
@@ -197,3 +372,4 @@ drob <- function( # nolint
197372
# p.130
198373
# When computing an initial estimate β0 for the MM-estimate,
199374
# the bisquare ρ works quite well and we recommend its use for this purpose.
375+

0 commit comments

Comments
 (0)