1 |
# FIND PEAKS |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
#' @export |
|
6 |
#' @rdname peaks_find |
|
7 |
#' @aliases peaks_find,numeric,numeric-method |
|
8 |
setMethod( |
|
9 |
f = "peaks_find", |
|
10 |
signature = signature(x = "numeric", y = "numeric"), |
|
11 |
definition = function(x, y, method = "MAD", SNR = 2, m = NULL, ...) { |
|
12 |
## Validation |
|
13 | 1x |
assert_length(y, length(x)) |
14 | 1x |
method <- match.arg(method, several.ok = FALSE) |
15 | 1x |
if (is.null(m)) { |
16 | ! |
m <- as.integer(length(x) * 0.05) |
17 | ! |
if (m %% 2 == 0) m <- m + 1 |
18 |
} |
|
19 | ||
20 |
## Noise threshold |
|
21 | 1x |
threshold <- NULL |
22 | 1x |
if (SNR != 0) { |
23 | 1x |
noise <- switch ( |
24 | 1x |
method, |
25 | 1x |
MAD = stats::mad |
26 |
) |
|
27 | 1x |
threshold <- noise(y, ...) * SNR |
28 | 1x |
index_noise <- y < threshold |
29 | 1x |
y[index_noise] <- 0 |
30 |
} |
|
31 | ||
32 |
## Windows |
|
33 | 1x |
shape <- diff(sign(diff(y, na.pad = FALSE))) |
34 | 1x |
win <- window_sliding(length(x), m, i = which(shape < 0) + 1L) |
35 | ||
36 |
## Peaks detection |
|
37 | 1x |
pks <- vapply( |
38 | 1x |
X = win, |
39 | 1x |
FUN = function(w, k, data) { |
40 | 10x |
i <- length(w) - k # Middle of the window |
41 | 10x |
p <- if (all(data[w[-i]] <= data[w[i]])) w[i] else 0 |
42 | 10x |
return(p) |
43 |
}, |
|
44 | 1x |
FUN.VALUE = numeric(1), |
45 | 1x |
k = (m - 1) / 2, |
46 | 1x |
data = y |
47 |
) |
|
48 | ||
49 | 1x |
xy <- list(x = x[pks], y = y[pks]) |
50 | 1x |
attr(xy, "noise") <- threshold |
51 | 1x |
xy |
52 |
} |
|
53 |
) |
|
54 | ||
55 |
#' @export |
|
56 |
#' @rdname peaks_find |
|
57 |
#' @aliases peaks_find,ANY,missing-method |
|
58 |
setMethod( |
|
59 |
f = "peaks_find", |
|
60 |
signature = signature(x = "ANY", y = "missing"), |
|
61 |
definition = function(x, method = "MAD", SNR = 2, m = NULL, ...) { |
|
62 | 1x |
xy <- grDevices::xy.coords(x) |
63 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, method = method, SNR = SNR, |
64 | 1x |
m = m, ...) |
65 |
} |
|
66 |
) |
|
67 | ||
68 |
#' @export |
|
69 |
#' @rdname peaks_fwhm |
|
70 |
#' @aliases peaks_fwhm,numeric,numeric-method |
|
71 |
setMethod( |
|
72 |
f = "peaks_fwhm", |
|
73 |
signature = signature(x = "numeric", y = "numeric"), |
|
74 |
definition = function(x, y, center) { |
|
75 | 1x |
assert_length(y, length(x)) |
76 | ||
77 | 1x |
i <- which_nearest(x, center) |
78 | 1x |
peak_height <- y[i] |
79 | 1x |
half_max <- peak_height / 2 |
80 | ||
81 | 1x |
scale_for_roots <- y - half_max |
82 | 1x |
root_indices <- which(diff(sign(scale_for_roots)) != 0) |
83 | ||
84 | 1x |
tmp <- sort(c(root_indices, i)) |
85 | 1x |
k <- which(tmp == i) |
86 | ||
87 | 1x |
root_left <- root_indices[k - 1] |
88 | 1x |
root_right <- root_indices[k] |
89 | ||
90 | 1x |
HWHM_left <- x[i] - x[root_left] |
91 | 1x |
HWHM_right <- x[root_right] - x[i] |
92 | ||
93 |
# xy <- list(x = c(x[root_left], x[root_right]), y = c(half_max, half_max)) |
|
94 | ||
95 | 1x |
FWHM <- 2 * min(c(HWHM_left, HWHM_right)) |
96 | 1x |
return(FWHM) |
97 |
} |
|
98 |
) |
|
99 | ||
100 |
#' @export |
|
101 |
#' @rdname peaks_fwhm |
|
102 |
#' @aliases peaks_fwhm,ANY,missing-method |
|
103 |
setMethod( |
|
104 |
f = "peaks_fwhm", |
|
105 |
signature = signature(x = "ANY", y = "missing"), |
|
106 |
definition = function(x, center) { |
|
107 | 1x |
xy <- grDevices::xy.coords(x) |
108 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, center = center) |
109 |
} |
|
110 |
) |
1 |
# BASELINE |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
# Linear ======================================================================= |
|
6 |
#' @export |
|
7 |
#' @rdname baseline_linear |
|
8 |
#' @aliases baseline_linear,numeric,numeric-method |
|
9 |
setMethod( |
|
10 |
f = "baseline_linear", |
|
11 |
signature = c(x = "numeric", y = "numeric"), |
|
12 |
definition = function(x, y, points = range(x)) { |
|
13 | 1x |
assert_length(y, length(x)) |
14 | ||
15 |
## Find the nearest value |
|
16 | 1x |
z <- vapply(X = points, FUN = function(i, x) which_nearest(x, i), |
17 | 1x |
FUN.VALUE = numeric(1), x = x) |
18 | 1x |
zi <- which(x >= min(points) & x <= max(points)) |
19 | ||
20 | 1x |
data <- data.frame(x, y) |
21 | 1x |
fit <- stats::lm(y ~ x, data = data, subset = z) |
22 | 1x |
pred <- stats::predict(fit, data.frame(x = x[zi])) |
23 | ||
24 | 1x |
bsl <- rep(NA_real_, length(x)) |
25 | 1x |
bsl[zi] <- pred |
26 | ||
27 | 1x |
xy <- list(x = x, y = bsl) |
28 | 1x |
attr(xy, "method") <- "linear baseline" |
29 | 1x |
xy |
30 |
} |
|
31 |
) |
|
32 | ||
33 |
#' @export |
|
34 |
#' @rdname baseline_linear |
|
35 |
#' @aliases baseline_linear,ANY,missing-method |
|
36 |
setMethod( |
|
37 |
f = "baseline_linear", |
|
38 |
signature = c(x = "ANY", y = "missing"), |
|
39 |
definition = function(x, points = range(x)) { |
|
40 | 1x |
xy <- grDevices::xy.coords(x) |
41 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, points = points) |
42 |
} |
|
43 |
) |
|
44 | ||
45 |
# Polynomial =================================================================== |
|
46 |
#' @export |
|
47 |
#' @rdname baseline_polynomial |
|
48 |
#' @aliases baseline_polynomial,numeric,numeric-method |
|
49 |
setMethod( |
|
50 |
f = "baseline_polynomial", |
|
51 |
signature = c(x = "numeric", y = "numeric"), |
|
52 |
definition = function(x, y, d = 3, tolerance = 0.001, stop = 100) { |
|
53 | 1x |
assert_length(y, length(x)) |
54 | ||
55 | 1x |
polynom <- cbind(1 / sqrt(length(x)), stats::poly(x, degree = d)) |
56 | ||
57 | 1x |
old_y <- y |
58 | 1x |
start <- 0 |
59 | 1x |
convergence <- FALSE |
60 | 1x |
while (!convergence) { |
61 | 839x |
new_y <- polynom %*% crossprod(polynom, old_y) |
62 | 839x |
new_y <- pmin(y, new_y) |
63 | ||
64 | 839x |
criterion <- sum(abs((new_y - old_y) / old_y), na.rm = TRUE) |
65 | 839x |
convergence <- criterion < tolerance |
66 | ||
67 | 839x |
old_y <- new_y |
68 | 839x |
start <- start + 1 |
69 | 839x |
if (start >= stop) { |
70 | ! |
warning(tr_("Convergence not reached."), call. = FALSE) |
71 | ! |
break |
72 |
} |
|
73 |
} |
|
74 | ||
75 | 1x |
xy <- list(x = x, y = new_y) |
76 | 1x |
attr(xy, "method") <- "polynomial baseline" |
77 | 1x |
xy |
78 |
} |
|
79 |
) |
|
80 | ||
81 |
#' @export |
|
82 |
#' @rdname baseline_polynomial |
|
83 |
#' @aliases baseline_polynomial,ANY,missing-method |
|
84 |
setMethod( |
|
85 |
f = "baseline_polynomial", |
|
86 |
signature = c(x = "ANY", y = "missing"), |
|
87 |
definition = function(x, d = 3, tolerance = 0.001, stop = 100) { |
|
88 | 1x |
xy <- grDevices::xy.coords(x) |
89 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, d = d, tolerance = tolerance, |
90 | 1x |
stop = stop) |
91 |
} |
|
92 |
) |
|
93 | ||
94 |
# Rolling Ball ================================================================= |
|
95 |
#' @export |
|
96 |
#' @rdname baseline_rollingball |
|
97 |
#' @aliases baseline_rollingball,numeric,numeric-method |
|
98 |
setMethod( |
|
99 |
f = "baseline_rollingball", |
|
100 |
signature = c(x = "numeric", y = "numeric"), |
|
101 |
definition = function(x, y, m, s) { |
|
102 | 1x |
n <- length(x) |
103 | 1x |
assert_length(y, n) |
104 | ||
105 |
## Windows |
|
106 | 1x |
win_minmax <- window_sliding(n, m) |
107 | 1x |
win_smooth <- window_sliding(n, s) |
108 | ||
109 |
## Minimize |
|
110 | 1x |
T1 <- vapply( |
111 | 1x |
X = win_minmax, |
112 | 1x |
FUN = function(i, data) { |
113 | 2989x |
min(data[i]) |
114 |
}, |
|
115 | 1x |
FUN.VALUE = numeric(1), |
116 | 1x |
data = y |
117 |
) |
|
118 | ||
119 |
## Maximize |
|
120 | 1x |
T2 <- vapply( |
121 | 1x |
X = win_minmax, |
122 | 1x |
FUN = function(i, data) { |
123 | 2989x |
max(data[i]) |
124 |
}, |
|
125 | 1x |
FUN.VALUE = numeric(1), |
126 | 1x |
data = T1 |
127 |
) |
|
128 | ||
129 |
## Smooth |
|
130 | 1x |
T3 <- vapply( |
131 | 1x |
X = win_smooth, |
132 | 1x |
FUN = function(i, data) { |
133 | 2989x |
mean(data[i]) |
134 |
}, |
|
135 | 1x |
FUN.VALUE = numeric(1), |
136 | 1x |
data = T2 |
137 |
) |
|
138 | ||
139 | 1x |
xy <- list(x = x, y = T3) |
140 | 1x |
attr(xy, "method") <- "rolling ball baseline" |
141 | 1x |
xy |
142 |
} |
|
143 |
) |
|
144 | ||
145 |
#' @export |
|
146 |
#' @rdname baseline_rollingball |
|
147 |
#' @aliases baseline_rollingball,ANY,missing-method |
|
148 |
setMethod( |
|
149 |
f = "baseline_rollingball", |
|
150 |
signature = c(x = "ANY", y = "missing"), |
|
151 |
definition = function(x, m, s) { |
|
152 | 1x |
xy <- grDevices::xy.coords(x) |
153 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, m = m, s = s) |
154 |
} |
|
155 |
) |
|
156 | ||
157 |
# Rubberband =================================================================== |
|
158 |
#' @export |
|
159 |
#' @rdname baseline_rubberband |
|
160 |
#' @aliases baseline_rubberband,numeric,numeric-method |
|
161 |
setMethod( |
|
162 |
f = "baseline_rubberband", |
|
163 |
signature = c(x = "numeric", y = "numeric"), |
|
164 |
definition = function(x, y, noise = 0, spline = TRUE, ...) { |
|
165 | 2x |
assert_length(y, length(x)) |
166 | ||
167 |
## (chull returns points in clockwise order) |
|
168 | 2x |
pts <- grDevices::chull(x, y) |
169 | ||
170 |
## Check that ncol(y) is a position 1, |
|
171 |
## if not, rotate chull vertex index so that ncol(y) is at position 1 |
|
172 |
## then keep only index from ncol(y) to 1 (i.e. lower part of the hull) |
|
173 | 2x |
v_max <- which.max(pts) - 1 |
174 | 2x |
if (v_max > 0) pts <- c(pts[-seq_len(v_max)], pts[seq_len(v_max)]) |
175 | 2x |
pts <- pts[1:which.min(pts)] |
176 |
# First and last point must be minima, if not remove them |
|
177 | ! |
if (pts[2] == pts[1] - 1) pts <- pts[-1] # Last point |
178 | 2x |
pts <- rev(pts) # Sort in ascending order |
179 | ! |
if (pts[2] == pts[1] + 1) pts <- pts[-1] # First point |
180 | ||
181 | 2x |
bsl <- stats::approx(x = x[pts], y = y[pts], xout = x, method = "linear")$y |
182 | 2x |
if (spline) { |
183 | 2x |
pts <- which(y <= bsl + noise) |
184 | 2x |
if (length(pts) > 3) { |
185 | 2x |
spl <- stats::smooth.spline(x[pts], y[pts], ...) |
186 | 2x |
tmp <- stats::predict(spl, x, 0)$y |
187 |
} else { |
|
188 | ! |
tmp <- stats::spline(x[pts], y[pts], xout = x)$y |
189 |
} |
|
190 |
} |
|
191 | ||
192 |
## Check baseline |
|
193 | 2x |
if (anyNA(tmp)) { |
194 | ! |
msg <- tr_("Failed to estimate the baseline, please check your parameters.") |
195 | ! |
stop(msg, call. = FALSE) |
196 |
} |
|
197 | ||
198 | 2x |
xy <- list(x = x, y = bsl) |
199 | 2x |
attr(xy, "method") <- "rubberband baseline" |
200 | 2x |
xy |
201 |
} |
|
202 |
) |
|
203 | ||
204 |
#' @export |
|
205 |
#' @rdname baseline_rubberband |
|
206 |
#' @aliases baseline_rubberband,ANY,missing-method |
|
207 |
setMethod( |
|
208 |
f = "baseline_rubberband", |
|
209 |
signature = c(x = "ANY", y = "missing"), |
|
210 |
definition = function(x, noise = 0, spline = TRUE, ...) { |
|
211 | 1x |
xy <- grDevices::xy.coords(x) |
212 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, noise = noise, spline = spline, ...) |
213 |
} |
|
214 |
) |
|
215 | ||
216 |
# SNIP ========================================================================= |
|
217 |
#' @export |
|
218 |
#' @rdname baseline_snip |
|
219 |
#' @aliases baseline_snip,numeric,numeric-method |
|
220 |
setMethod( |
|
221 |
f = "baseline_snip", |
|
222 |
signature = c(x = "numeric", y = "numeric"), |
|
223 |
definition = function(x, y, LLS = FALSE, decreasing = FALSE, n = 100) { |
|
224 | 3x |
assert_length(y, length(x)) |
225 | ||
226 |
## LLS operator |
|
227 | 3x |
y <- if (LLS) LLS(y) else y |
228 | ||
229 | 3x |
k <- length(y) |
230 | 3x |
iter <- if (decreasing) rev(seq_len(n)) else seq_len(n) |
231 | ||
232 | 3x |
tmp <- y |
233 | 3x |
for (p in iter) { |
234 | 200x |
for (i in p:(k - p)) { |
235 | 582800x |
a <- y[i] |
236 | 582800x |
b <- (y[i - p] + y[i + p]) / 2 |
237 | 582800x |
tmp[i] <- min(a, b) |
238 |
} |
|
239 | 200x |
y <- tmp |
240 |
} |
|
241 | ||
242 |
## Inverse LLS operator |
|
243 | 3x |
bsl <- if (LLS) inverseLLS(y) else y |
244 | ||
245 |
## Check baseline |
|
246 | 3x |
if (anyNA(bsl)) { |
247 | ! |
msg <- tr_("Failed to estimate the baseline, please check your parameters.") |
248 | ! |
stop(msg, call. = FALSE) |
249 |
} |
|
250 | ||
251 | 3x |
xy <- list(x = x, y = bsl) |
252 | 3x |
attr(xy, "method") <- "SNIP" |
253 | 3x |
xy |
254 |
} |
|
255 |
) |
|
256 | ||
257 |
#' @export |
|
258 |
#' @rdname baseline_snip |
|
259 |
#' @aliases baseline_snip,ANY,missing-method |
|
260 |
setMethod( |
|
261 |
f = "baseline_snip", |
|
262 |
signature = c(x = "ANY", y = "missing"), |
|
263 |
definition = function(x, LLS = FALSE, decreasing = FALSE, n = 100) { |
|
264 | 3x |
xy <- grDevices::xy.coords(x) |
265 | 3x |
methods::callGeneric(x = xy$x, y = xy$y, LLS = LLS, decreasing = decreasing, |
266 | 3x |
n = n) |
267 |
} |
|
268 |
) |
|
269 | ||
270 |
#' LLS operator |
|
271 |
#' |
|
272 |
#' @param x A [`numeric`] vector. |
|
273 |
#' @keywords internal |
|
274 |
#' @noRd |
|
275 |
LLS <- function(x) { |
|
276 | 2x |
log(log(sqrt(x + 1) + 1) + 1) |
277 |
} |
|
278 |
inverseLLS <- function(x) { |
|
279 | 2x |
(exp(exp(x) - 1) - 1)^2 - 1 |
280 |
} |
|
281 | ||
282 |
# AsLS ========================================================================= |
|
283 |
#' @export |
|
284 |
#' @rdname baseline_asls |
|
285 |
#' @aliases baseline_asls,numeric,numeric-method |
|
286 |
setMethod( |
|
287 |
f = "baseline_asls", |
|
288 |
signature = c(x = "numeric", y = "numeric"), |
|
289 |
definition = function(x, y, p = 0.01, lambda = 10^4, stop = 100) { |
|
290 | 1x |
assert_Matrix() |
291 | 1x |
assert_length(y, length(x)) |
292 | ||
293 | 1x |
m <- length(y) |
294 | 1x |
E <- Matrix::Diagonal(m) |
295 | 1x |
D <- Matrix::diff(E, lag = 1, differences = 2) |
296 | ||
297 | 1x |
w <- rep(1, m) # weights |
298 | ||
299 | 1x |
start <- 0 |
300 | 1x |
convergence <- FALSE |
301 | 1x |
while (!convergence) { |
302 | 8x |
W <- Matrix::Diagonal(x = w) |
303 | 8x |
C <- Matrix::chol(W + (lambda * Matrix::t(D) %*% D)) |
304 | 8x |
z <- Matrix::solve(C, Matrix::solve(Matrix::t(C), w * y)) |
305 |
## Prior to v1.6, Matrix::solve(a=<Matrix>, b=<vector>) returns a matrix |
|
306 | 8x |
z <- as.numeric(z) |
307 | 8x |
w0 <- p * (y > z) + (1 - p) * (y < z) |
308 | 1x |
if (isTRUE(all.equal(w, w0))) convergence <- TRUE |
309 | ||
310 | 8x |
w <- w0 |
311 | 8x |
start <- start + 1 |
312 | 8x |
if (start >= stop) { |
313 | ! |
warning(tr_("Convergence not reached."), call. = FALSE) |
314 | ! |
break |
315 |
} |
|
316 |
} |
|
317 | ||
318 | 1x |
xy <- list(x = x, y = z) |
319 | 1x |
attr(xy, "method") <- "AsLS" |
320 | 1x |
xy |
321 |
} |
|
322 |
) |
|
323 | ||
324 |
#' @export |
|
325 |
#' @rdname baseline_asls |
|
326 |
#' @aliases baseline_asls,ANY,missing-method |
|
327 |
setMethod( |
|
328 |
f = "baseline_asls", |
|
329 |
signature = c(x = "ANY", y = "missing"), |
|
330 |
definition = function(x, p = 0.01, lambda = 10^4, stop = 100) { |
|
331 | 1x |
xy <- grDevices::xy.coords(x) |
332 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, lambda = lambda, p = p, stop = stop) |
333 |
} |
|
334 |
) |
|
335 | ||
336 |
# Peak Filling ================================================================= |
|
337 |
#' @export |
|
338 |
#' @rdname baseline_peakfilling |
|
339 |
#' @aliases baseline_peakfilling,numeric,numeric-method |
|
340 |
setMethod( |
|
341 |
f = "baseline_peakfilling", |
|
342 |
signature = c(x = "numeric", y = "numeric"), |
|
343 |
definition = function(x, y, n, m, by = 10, lambda = 1600, d = 2, sparse = FALSE) { |
|
344 | 1x |
assert_length(y, length(x)) |
345 | ||
346 |
## Number of bucket intervals |
|
347 | 1x |
by <- length(x) / by |
348 | ||
349 |
## Exponential decrease in interval width |
|
350 | 1x |
win <- m |
351 | 1x |
if (n != 1) { |
352 | 1x |
i <- log10(m) * (1 - (0:(n - 2)) / (n - 1)) |
353 | 1x |
win <- ceiling((10)^c(i, 0)) |
354 |
} |
|
355 | ||
356 |
## Smoothing |
|
357 | 1x |
smoothed <- smooth_whittaker(x, y, lambda = lambda, d = d, sparse = sparse) |
358 | 1x |
x <- smoothed$x |
359 | 1x |
y <- smoothed$y |
360 | ||
361 |
## Subsampling |
|
362 | 1x |
breaks <- cut(seq_along(x), breaks = by, labels = FALSE) |
363 | 1x |
mid <- unlist(tapply(X = x, INDEX = breaks, FUN = mean, simplify = FALSE)) |
364 | 1x |
bin <- unlist(tapply(X = y, INDEX = breaks, FUN = min, simplify = FALSE)) |
365 | ||
366 |
## Suppression |
|
367 | 1x |
iter <- seq_len(n) |
368 | 1x |
for (i in iter) { |
369 | 10x |
win_0 <- win[i] # Current half window width |
370 | ||
371 |
## Interval cut-off close to edges |
|
372 | 10x |
k <- seq(from = 2, to = by - 1, by = 1) |
373 | 10x |
j <- by - k + 1 |
374 | 10x |
v <- pmin(k - 1, win_0, by - k) |
375 | ||
376 |
## Point-wise iteration to the right |
|
377 | 2960x |
a <- mapply(function(k, v, bin) { mean(bin[(k - v):(k + v)]) }, k, v, |
378 | 10x |
MoreArgs = list(bin = bin)) |
379 | 10x |
bin[k] <- pmin(a, bin[k]) # Baseline suppression |
380 | ||
381 |
## Point-wise iteration to the left |
|
382 | 2960x |
a <- mapply(function(j, v, bin) { mean(bin[(j - v):(j + v)]) }, j, v, |
383 | 10x |
MoreArgs = list(bin = bin)) |
384 | 10x |
bin[j] <- pmin(a, bin[j]) # Baseline suppression |
385 |
} |
|
386 | ||
387 |
## Stretch |
|
388 | 1x |
mid[1] <- x[1] |
389 | 1x |
mid[by] <- x[length(x)] |
390 | 1x |
bsl <- stats::approx(mid, bin, x)$y |
391 | ||
392 | 1x |
xy <- list(x = x, y = bsl) |
393 | 1x |
attr(xy, "method") <- "4S Peak Filling" |
394 | 1x |
xy |
395 |
} |
|
396 |
) |
|
397 | ||
398 |
#' @export |
|
399 |
#' @rdname baseline_peakfilling |
|
400 |
#' @aliases baseline_peakfilling,ANY,missing-method |
|
401 |
setMethod( |
|
402 |
f = "baseline_peakfilling", |
|
403 |
signature = c(x = "ANY", y = "missing"), |
|
404 |
definition = function(x, n, m, by = 10, lambda = 1600, d = 2, sparse = FALSE) { |
|
405 | 1x |
xy <- grDevices::xy.coords(x) |
406 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, n = n, m = m, by = by, |
407 | 1x |
lambda = lambda, d = d, sparse = sparse) |
408 |
} |
|
409 |
) |
1 |
# HELPERS |
|
2 | ||
3 |
## https://michaelchirico.github.io/potools/articles/developers.html |
|
4 |
tr_ <- function(...) { |
|
5 | 5x |
enc2utf8(gettext(paste0(...), domain = "R-alkahest")) |
6 |
} |
|
7 | ||
8 |
#' Find the Nearest Value in a Vector |
|
9 |
#' |
|
10 |
#' @param x A [`numeric`] vector. |
|
11 |
#' @param value A [`numeric`] value. |
|
12 |
#' @return An [`integer`]. |
|
13 |
#' @keywords internal |
|
14 |
#' @noRd |
|
15 |
which_nearest <- function(x, value) { |
|
16 | 7x |
which.min(abs(x - value)) |
17 |
} |
|
18 | ||
19 |
#' Check Odd Numbers |
|
20 |
#' |
|
21 |
#' @param x A [`numeric`] vector. |
|
22 |
#' @return |
|
23 |
#' Throws an error, if any, and returns `x` invisibly otherwise. |
|
24 |
#' @keywords internal |
|
25 |
#' @noRd |
|
26 |
assert_odd <- function(x) { |
|
27 | 11x |
arg <- deparse(substitute(x)) |
28 | 11x |
if (any(round(x) %% 2 == 0)) { |
29 | 1x |
msg <- sprintf(tr_("%s must be an odd integer (%g)."), sQuote(arg), x) |
30 | 1x |
stop(msg, call. = FALSE) |
31 |
} |
|
32 | 10x |
invisible(x) |
33 |
} |
|
34 | ||
35 |
#' Check Object Length |
|
36 |
#' |
|
37 |
#' @param x An object to be checked. |
|
38 |
#' @param expected An appropriate expected value. |
|
39 |
#' @return |
|
40 |
#' Throws an error, if any, and returns `x` invisibly otherwise. |
|
41 |
#' @keywords internal |
|
42 |
#' @noRd |
|
43 |
assert_length <- function(x, expected) { |
|
44 | 52x |
arg <- deparse(substitute(x)) |
45 | 52x |
if (length(x) != expected) { |
46 | 1x |
str <- tr_("%s must be of length %d; not %d.") |
47 | 1x |
msg <- sprintf(str, sQuote(arg), expected, length(x)) |
48 | 1x |
stop(msg, call. = FALSE) |
49 |
} |
|
50 | 51x |
invisible(x) |
51 |
} |
|
52 | ||
53 |
assert_Matrix <- function() { |
|
54 | 3x |
if (!requireNamespace("Matrix", quietly = TRUE)) { |
55 | ! |
msg <- tr_("The Matrix package is required. Please install it.") |
56 | ! |
stop(msg, call. = FALSE) |
57 |
} |
|
58 | 3x |
invisible() |
59 |
} |
1 |
# XRD |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
#' @export |
|
6 |
#' @rdname ka2_strip_penalized |
|
7 |
#' @aliases ka2_strip_penalized,numeric,numeric-method |
|
8 |
setMethod( |
|
9 |
f = "ka2_strip_penalized", |
|
10 |
signature = signature(x = "numeric", y = "numeric"), |
|
11 |
definition = function(x, y, lambda, wave = c(1.54060, 1.54443), tau = 0.5, |
|
12 |
nseg = 1, progress = interactive()) { |
|
13 | ! |
assert_Matrix() |
14 | ! |
assert_length(y, length(x)) |
15 | ||
16 |
## Calculate doublet distance |
|
17 | ! |
wl1 <- wave[1] |
18 | ! |
wl2 <- wave[2] |
19 | ! |
wr <- wl2 / wl1 |
20 | ! |
d2r <- pi / 180 |
21 | ! |
u <- sin(d2r * x / 2) / wl1 |
22 | ! |
delta <- 2 * asin(wr * sin(d2r * x / 2)) / d2r - x |
23 | ||
24 | ! |
m <- length(y) |
25 | ! |
n <- length(lambda) |
26 | ! |
if (nseg == 1) nseg <- signif(m * 0.50, 1) |
27 | ||
28 |
## Empty matrices to store the results |
|
29 | ! |
aic <- phi <- psi <- rep(0, n) |
30 | ! |
Yhat <- Yhat2 <- matrix(data = 0, nrow = m, ncol = n) |
31 | ! |
mu <- matrix(data = 0, nrow = m, ncol = n) |
32 | ! |
sr <- matrix(data = 0, nrow = m, ncol = n) |
33 | ||
34 |
## Prepare model matrices |
|
35 | ! |
xr <- max(x) |
36 | ! |
xl <- min(x) - 1 |
37 | ! |
xd <- x - delta |
38 | ! |
dx <- (xr - xl) / nseg |
39 | ! |
knots <- seq(xl - 3 * dx, xr + 3 * dx, by = dx) |
40 | ||
41 | ! |
B1 <- sbase(x, lower = xl, upper = xr, n = nseg, deg = 3) |
42 | ! |
B2 <- sbase(xd, lower = xl, upper = xr, n = nseg, deg = 3) |
43 | ||
44 | ! |
B <- Matrix::rbind2(B1, B2) |
45 | ! |
Cb <- Matrix::Diagonal(m) |
46 | ! |
C <- Matrix::cbind2(Cb, tau * Cb) |
47 | ||
48 |
## Prepare penalty |
|
49 | ! |
E <- Matrix::Diagonal(ncol(B1)) |
50 | ! |
D <- Matrix::diff(E, diff = 2) |
51 | ||
52 |
## Initialize |
|
53 | ! |
bst <- Matrix::solve( |
54 | ! |
Matrix::crossprod(B1, B1) + 1e-4 * E, |
55 | ! |
Matrix::crossprod(B1, log((y + 1) / 2)) |
56 |
) |
|
57 | ||
58 | ! |
if (progress) pb <- utils::txtProgressBar(min = 0, max = n, style = 3) |
59 | ||
60 |
## Estimate for all lambda |
|
61 | ! |
n_lambda <- seq_len(n) |
62 | ! |
for (i in n_lambda) { |
63 | ! |
M <- penalized_strip_ka2(y, B, C, D, bst, lambda[i]) |
64 | ! |
aic[i] <- M$aic |
65 | ! |
Yhat[, i] <- M$yhat |
66 | ! |
Yhat2[, i] <- M$yhat2 |
67 | ! |
sr[,i] <- M$res |
68 | ! |
mu[, i] <- M$mu |
69 | ! |
phi[i] <- M$phi |
70 | ! |
psi[i] <- M$psi |
71 | ! |
if (progress) utils::setTxtProgressBar(pb, i) |
72 |
} |
|
73 | ||
74 | ! |
if (progress) close(pb) |
75 | ||
76 |
## Find optimal model and report |
|
77 | ! |
k <- which.min(aic) |
78 | ! |
mu <- mu[, k] |
79 | ! |
op_lamb <- lambda[k] |
80 | ! |
op_aic <- aic[k] |
81 | ! |
yhat <- Yhat[, k] |
82 | ! |
yhat2 <- Yhat2[, k] |
83 | ! |
sr <- sr[, k] |
84 | ||
85 | ! |
xy <- list(x = x, y = yhat, ka2 = yhat2, smoothed = mu) |
86 | ! |
xy |
87 |
} |
|
88 |
) |
|
89 | ||
90 |
#' @export |
|
91 |
#' @rdname ka2_strip_penalized |
|
92 |
#' @aliases ka2_strip_penalized,ANY,missing-method |
|
93 |
setMethod( |
|
94 |
f = "ka2_strip_penalized", |
|
95 |
signature = signature(x = "ANY", y = "missing"), |
|
96 |
definition = function(x, lambda, wave = c(1.54060, 1.54443), tau = 0.5, |
|
97 |
nseg = 1, progress = interactive()) { |
|
98 | ! |
xy <- grDevices::xy.coords(x) |
99 | ! |
methods::callGeneric(x = xy$x, y = xy$y, lambda = lambda, wave = wave, |
100 | ! |
tau = tau, nseg = nseg, progress = progress) |
101 |
} |
|
102 |
) |
|
103 | ||
104 | ||
105 |
#' Truncated p-th Power |
|
106 |
#' |
|
107 |
#' @param x A [`numeric`] vector on which the basis is calculated. |
|
108 |
#' @param knot A [`numeric`] scalar giving the truncation point. |
|
109 |
#' @param p A [`numeric`] scalar specifying the power for the basis |
|
110 |
#' (e.g. \eqn{p = 3} for cubic TPF). |
|
111 |
#' @return |
|
112 |
#' A [`numeric`] vector (piece-wise defined basis functions for \eqn{x > t}). |
|
113 |
#' @author P. Eilers |
|
114 |
#' @note |
|
115 |
#' Slightly modified from P. Eilers' [JOPS::tpower()]. |
|
116 |
#' @references |
|
117 |
#' Eilers, P. H. C. and Marx, B. D. (2021). *Practical Smoothing, The Joys of |
|
118 |
#' P-splines.* Cambridge: Cambridge University Press. |
|
119 |
#' @keywords internal |
|
120 |
#' @noRd |
|
121 |
tpower <- function(x, knot, p) { |
|
122 | ! |
(x - knot) ^ p * (x > knot) |
123 |
} |
|
124 | ||
125 |
#' B-spline Design Matrix |
|
126 |
#' |
|
127 |
#' Computes a B-spline design matrix using evenly spaced knots. |
|
128 |
#' @param x A [`numeric`] vector of values at which the B-spline basis functions |
|
129 |
#' are to be evaluated. |
|
130 |
#' @param lower A [`numeric`] vector giving the lower limit of the domain of `x`. |
|
131 |
#' @param upper A [`numeric`] vector giving the upper limit of the domain of `x`. |
|
132 |
#' @param n A [`numeric`] scalar specifying the number of equally sized segments |
|
133 |
#' between `lower` and `upper`. |
|
134 |
#' @param deg A [`numeric`] scalar specifyingthe degree of the splines, |
|
135 |
#' usually 1, 2, or 3 (the default). |
|
136 |
#' @return |
|
137 |
#' A `matrix` with `length(x)` rows and `n + deg` columns. |
|
138 |
#' @note |
|
139 |
#' Slightly modified from P. Eilers and B. Marx's [JOPS::bbase()]. |
|
140 |
#' @author P. Eilers, B. Marx |
|
141 |
#' @references |
|
142 |
#' Eilers, P. H. C. and Marx, B. D. (2021). *Practical Smoothing, The Joys of |
|
143 |
#' P-splines.* Cambridge: Cambridge University Press. |
|
144 |
#' @keywords internal |
|
145 |
#' @noRd |
|
146 |
bbase <- function(x, lower = min(x), upper = max(x), n = 10, deg = 3) { |
|
147 |
## Check domain and adjust it if necessary |
|
148 | ! |
if (lower > min(x)) { |
149 | ! |
lower <- min(x) |
150 | ! |
warning(sprintf(tr_("Left boundary adjusted to %g."), lower), call. = FALSE) |
151 |
} |
|
152 | ! |
if (upper < max(x)) { |
153 | ! |
upper <- max(x) |
154 | ! |
warning(sprintf(tr_("Right boundary adjusted to %g."), upper), call. = FALSE) |
155 |
} |
|
156 | ||
157 |
## Function for B-spline basis |
|
158 | ! |
dx <- (upper - lower) / n |
159 | ! |
knots <- seq(lower - deg * dx, upper + deg * dx, by = dx) |
160 | ! |
P <- outer(x, knots, tpower, deg) |
161 | ! |
n <- dim(P)[[2L]] |
162 | ! |
D <- diff(diag(n), diff = deg + 1) / (gamma(deg + 1) * dx ^ deg) |
163 | ! |
B <- (-1) ^ (deg + 1) * P %*% t(D) |
164 | ||
165 |
## Make B-splines exactly zero beyond their end knots |
|
166 | ! |
nb <- ncol(B) |
167 | ! |
sk <- knots[(1:nb) + deg + 1] |
168 | ! |
mask <- outer(x, sk, '<') |
169 | ! |
B <- B * mask |
170 | ||
171 | ! |
B |
172 |
} |
|
173 | ||
174 |
#' Sparse B-spline Basis Matrix |
|
175 |
#' |
|
176 |
#' Computes a B-spline basis matrix using evenly spaced knots. |
|
177 |
#' @param x A [`numeric`] vector of values at which the B-spline basis functions |
|
178 |
#' are to be evaluated. |
|
179 |
#' @param lower A [`numeric`] vector giving the lower limit of the domain of `x`. |
|
180 |
#' @param upper A [`numeric`] vector giving the upper limit of the domain of `x`. |
|
181 |
#' @param n A [`numeric`] scalar specifying the number of equally sized segments |
|
182 |
#' between `lower` and `upper`. |
|
183 |
#' @param deg A [`numeric`] scalar specifyingthe degree of the splines, |
|
184 |
#' usually 1, 2, or 3 (the default). |
|
185 |
#' @return |
|
186 |
#' A [sparse matrix][Matrix::sparseMatrix()] with `length(x)` rows and |
|
187 |
#' `n + deg` columns. |
|
188 |
#' @note |
|
189 |
#' Slightly modified from P. Eilers [JOPS::spbase()]. |
|
190 |
#' @author P. Eilers, B. Marx |
|
191 |
#' @references |
|
192 |
#' Eilers, P. H. C. and Marx, B. D. (2021). *Practical Smoothing, The Joys of |
|
193 |
#' P-splines.* Cambridge: Cambridge University Press. |
|
194 |
#' @keywords internal |
|
195 |
#' @noRd |
|
196 |
sbase <- function(x, lower = min(x), upper = max(x), n = 10, deg = 3) { |
|
197 |
## Check domain and adjust it if necessary |
|
198 | ! |
if (lower > min(x)) { |
199 | ! |
lower <- min(x) |
200 | ! |
warning(sprintf(tr_("Left boundary adjusted to %g."), lower), call. = FALSE) |
201 |
} |
|
202 | ! |
if (upper < max(x)) { |
203 | ! |
upper <- max(x) |
204 | ! |
warning(sprintf(tr_("Right boundary adjusted to %g."), upper), call. = FALSE) |
205 |
} |
|
206 | ||
207 |
## Reduce x to first interval between knots |
|
208 | ! |
dx <- (upper - lower) / n |
209 | ! |
ix <- floor((x - lower) / (1.0000001 * dx)) |
210 | ! |
xx <- (x - lower) - ix * dx |
211 | ||
212 |
## Full basis for reduced x |
|
213 | ! |
Br <- bbase(xx, lower = 0, upper = 0 + dx, n = 1, deg = deg) |
214 | ||
215 |
## Compute proper rows, columns |
|
216 | ! |
m <- length(x) |
217 | ! |
p <- ncol(Br) |
218 | ! |
i_row <- rep(seq_len(m), each = p) |
219 | ! |
i_col <- rep(seq_len(p), times = m) + rep(ix, each = p) |
220 | ||
221 |
## Sparse matrix |
|
222 | ! |
b <- as.vector(t(Br)) |
223 | ! |
Bs <- Matrix::sparseMatrix(i = i_row, j = i_col, x = b, dims = c(m, n + deg)) |
224 | ! |
Bs <- Bs[, -ncol(Bs), drop = FALSE] # ??? |
225 | ||
226 | ! |
Bs |
227 |
} |
|
228 | ||
229 |
#' @param y description |
|
230 |
#' @param B description |
|
231 |
#' @param C description |
|
232 |
#' @param D description |
|
233 |
#' @param beta description |
|
234 |
#' @param lambda description |
|
235 |
#' @author J. J. de Rooi *et al.* (original R code). |
|
236 |
#' @references |
|
237 |
#' de Rooi, J. J., van der Pers, N. M., Hendrikx, R. W. A., Delhez, R., |
|
238 |
#' Böttger A. J. and Eilers, P. H. C. (2014). Smoothing of X-ray diffraction |
|
239 |
#' data and Ka2 elimination using penalized likelihood and the composite link |
|
240 |
#' model. *Journal of Applied Crystallography*, 47: 852-860. |
|
241 |
#' \doi{10.1107/S1600576714005809} |
|
242 |
#' @keywords internal |
|
243 |
#' @noRd |
|
244 |
penalized_strip_ka2 <- function(y, B, C, D, beta, lambda) { |
|
245 | ! |
m <- length(y) |
246 | ||
247 |
## Penalty |
|
248 | ! |
P <- lambda * Matrix::t(D) %*% D |
249 | ||
250 |
## Fit iteratively |
|
251 | ! |
delta_beta <- 1 |
252 | ! |
limit <- 1 # Stop condition to prevent infinite loop |
253 | ! |
while (delta_beta >= 0.0001 & limit <= 30) { |
254 | ! |
eta <- B %*% beta |
255 | ! |
gam <- exp(eta) |
256 | ! |
mu <- as.numeric(C %*% gam) |
257 | ||
258 | ! |
M <- Matrix::Diagonal(x = 1 / mu) |
259 | ! |
X <- (M %*% C %*% Matrix::Diagonal(x = as.numeric(gam))) %*% B |
260 | ! |
W <- Matrix::Diagonal(x = mu) |
261 | ! |
G <- Matrix::crossprod(X, W) %*% X |
262 | ||
263 | ! |
new_beta <- Matrix::solve(G + P, Matrix::crossprod(X, (y - mu)) + G %*% beta) |
264 | ! |
delta_beta <- max(abs(new_beta - beta)) |
265 | ! |
beta <- new_beta |
266 | ||
267 | ! |
limit <- limit + 1 |
268 |
} |
|
269 | ||
270 |
## AIC |
|
271 | ! |
H <- Matrix::solve(G + P, G) |
272 | ! |
ed <- sum(Matrix::diag(H)) |
273 | ! |
dev <- 2 * sum(y * log((y + (y == 0)) / mu)) |
274 | ! |
aic <- dev + 2 * ed |
275 | ||
276 |
## Standardized residuals for counts |
|
277 | ! |
sr <- (y - mu) / sqrt(mu) |
278 | ! |
sdr <- sqrt(sum(sr^2) / (m - ed)) |
279 | ||
280 |
## L-curve |
|
281 | ! |
phi <- log(sum((D %*% beta)^2)) |
282 | ! |
psi <- log(sum(sr^2)) |
283 | ||
284 |
## estimated signals |
|
285 | ! |
yhat <- gam[1:m] |
286 | ! |
yhat2 <- gam[(m + 1):length(gam)] * 0.5 |
287 | ||
288 |
## return values |
|
289 | ! |
list(yhat = yhat, yhat2 = yhat2, mu = mu, aic = aic, res = sr, |
290 | ! |
beta = beta, phi = phi, psi = psi) |
291 |
} |
1 |
# SMOOTHING |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
# Unweighted smoothing ========================================================= |
|
6 |
#' @export |
|
7 |
#' @rdname smooth_rectangular |
|
8 |
#' @aliases smooth_rectangular,numeric,numeric-method |
|
9 |
setMethod( |
|
10 |
f = "smooth_rectangular", |
|
11 |
signature = signature(x = "numeric", y = "numeric"), |
|
12 |
definition = function(x, y, m = 3) { |
|
13 | 1x |
assert_length(y, length(x)) |
14 | ||
15 |
## Windows |
|
16 | 1x |
index <- window_sliding(length(x), m) |
17 | ||
18 | 1x |
y <- vapply( |
19 | 1x |
X = index, |
20 | 1x |
FUN = function(i, data) mean(data[i]), |
21 | 1x |
FUN.VALUE = numeric(1), |
22 | 1x |
data = y |
23 |
) |
|
24 | ||
25 | 1x |
xy <- list(x = x, y = y) |
26 | 1x |
attr(xy, "method") <- "unweighted smoothing" |
27 | 1x |
xy |
28 |
} |
|
29 |
) |
|
30 | ||
31 |
#' @export |
|
32 |
#' @rdname smooth_rectangular |
|
33 |
#' @aliases smooth_rectangular,ANY,missing-method |
|
34 |
setMethod( |
|
35 |
f = "smooth_rectangular", |
|
36 |
signature = signature(x = "ANY", y = "missing"), |
|
37 |
definition = function(x, m) { |
|
38 | 1x |
xy <- grDevices::xy.coords(x) |
39 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, m = m) |
40 |
} |
|
41 |
) |
|
42 | ||
43 |
# Weighted smoothing =========================================================== |
|
44 |
#' @export |
|
45 |
#' @rdname smooth_triangular |
|
46 |
#' @aliases smooth_triangular,numeric,numeric-method |
|
47 |
setMethod( |
|
48 |
f = "smooth_triangular", |
|
49 |
signature = signature(x = "numeric", y = "numeric"), |
|
50 |
definition = function(x, y, m = 3) { |
|
51 |
# Validation |
|
52 | 1x |
assert_length(y, length(x)) |
53 | 1x |
assert_odd(m) |
54 | ||
55 |
# Index |
|
56 | 1x |
k <- (m - 1) / 2 |
57 | 1x |
index_k <- seq_len(k) |
58 | 1x |
index_y <- seq_along(y) |
59 | 1x |
index_m <- c(index_k, rep_len(k + 1, length(y) - 2 * k), rev(index_k)) - 1 |
60 | ||
61 | 1x |
y <- mapply( |
62 | 1x |
FUN = function(i, k, data) { |
63 | 100x |
j <- seq_len(k) |
64 | 100x |
w <- c(j, k + 1, rev(j)) |
65 | 100x |
index <- seq(from = i - k, to = i + k, by = 1) |
66 | 100x |
stats::weighted.mean(x = data[index], w = w) |
67 |
}, |
|
68 | 1x |
i = index_y, k = index_m, |
69 | 1x |
MoreArgs = list(data = y) |
70 |
) |
|
71 | ||
72 | 1x |
xy <- list(x = x, y = y) |
73 | 1x |
attr(xy, "method") <- "weighted smoothing" |
74 | 1x |
xy |
75 |
} |
|
76 |
) |
|
77 | ||
78 |
#' @export |
|
79 |
#' @rdname smooth_triangular |
|
80 |
#' @aliases smooth_triangular,ANY,missing-method |
|
81 |
setMethod( |
|
82 |
f = "smooth_triangular", |
|
83 |
signature = signature(x = "ANY", y = "missing"), |
|
84 |
definition = function(x, m) { |
|
85 | 1x |
xy <- grDevices::xy.coords(x) |
86 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, m = m) |
87 |
} |
|
88 |
) |
|
89 | ||
90 |
# Loess smoothing ============================================================== |
|
91 |
#' @export |
|
92 |
#' @rdname smooth_loess |
|
93 |
#' @aliases smooth_loess,numeric,numeric-method |
|
94 |
setMethod( |
|
95 |
f = "smooth_loess", |
|
96 |
signature = signature(x = "numeric", y = "numeric"), |
|
97 |
definition = function(x, y, span = 0.75, ...) { |
|
98 | 1x |
assert_length(y, length(x)) |
99 | ||
100 | 1x |
points <- data.frame(x, y) |
101 | 1x |
fit <- stats::loess(y ~ x, data = points, span = span, ...) |
102 | 1x |
bsl <- stats::predict(fit) |
103 | ||
104 | 1x |
xy <- list(x = x, y = bsl) |
105 | 1x |
attr(xy, "method") <- "loess smoothing" |
106 | 1x |
xy |
107 |
} |
|
108 |
) |
|
109 | ||
110 |
#' @export |
|
111 |
#' @rdname smooth_loess |
|
112 |
#' @aliases smooth_loess,ANY,missing-method |
|
113 |
setMethod( |
|
114 |
f = "smooth_loess", |
|
115 |
signature = signature(x = "ANY", y = "missing"), |
|
116 |
definition = function(x, span = 0.75, ...) { |
|
117 | 1x |
xy <- grDevices::xy.coords(x) |
118 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, span = span, ...) |
119 |
} |
|
120 |
) |
|
121 | ||
122 |
# Savitzky-Golay filter ======================================================== |
|
123 |
#' @export |
|
124 |
#' @rdname smooth_savitzky |
|
125 |
#' @aliases smooth_savitzky,numeric,numeric-method |
|
126 |
setMethod( |
|
127 |
f = "smooth_savitzky", |
|
128 |
signature = signature(x = "numeric", y = "numeric"), |
|
129 |
definition = function(x, y, m = 3, p = 2) { |
|
130 |
## Validation |
|
131 | 1x |
assert_length(y, length(x)) |
132 | 1x |
assert_odd(m) |
133 | ||
134 | 1x |
k <- (m - 1) / 2 |
135 | 1x |
i <- seq(from = -k, to = k, by = 1) |
136 | 1x |
j <- utils::head(utils::tail(seq_along(y), n = -k), n = -k) |
137 | 1x |
conv <- coef_savitzky(m, p) |
138 | ||
139 | 1x |
smoothed <- vapply( |
140 | 1x |
X = j, |
141 | 1x |
FUN = function(j, i, conv, data) { |
142 | 80x |
sum(conv * data[j + i]) |
143 |
}, |
|
144 | 1x |
FUN.VALUE = double(1), |
145 | 1x |
i = i, |
146 | 1x |
conv = conv, |
147 | 1x |
data = y |
148 |
) |
|
149 | 1x |
y[j] <- smoothed |
150 | ||
151 | 1x |
xy <- list(x = x, y = y) |
152 | 1x |
attr(xy, "method") <- "Savitzky-Golay filter" |
153 | 1x |
xy |
154 |
} |
|
155 |
) |
|
156 | ||
157 |
#' @export |
|
158 |
#' @rdname smooth_savitzky |
|
159 |
#' @aliases smooth_savitzky,ANY,missing-method |
|
160 |
setMethod( |
|
161 |
f = "smooth_savitzky", |
|
162 |
signature = signature(x = "ANY", y = "missing"), |
|
163 |
definition = function(x, m, p) { |
|
164 | 1x |
xy <- grDevices::xy.coords(x) |
165 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, m = m, p = p) |
166 |
} |
|
167 |
) |
|
168 | ||
169 |
coef_savitzky <- function(m, p = 2) { |
|
170 | 1x |
k <- (m - 1) / 2 |
171 | 1x |
z <- seq(from = -k, to = k, by = 1) |
172 | 1x |
J <- vapply(X = c(0, p), FUN = function(p, z) z^p, z, FUN.VALUE = double(m)) |
173 | 1x |
(solve(t(J) %*% J) %*% t(J))[1, , drop = TRUE] |
174 |
} |
|
175 | ||
176 |
# Whittaker smoothing ========================================================== |
|
177 |
#' @export |
|
178 |
#' @rdname smooth_whittaker |
|
179 |
#' @aliases smooth_whittaker,numeric,numeric-method |
|
180 |
setMethod( |
|
181 |
f = "smooth_whittaker", |
|
182 |
signature = signature(x = "numeric", y = "numeric"), |
|
183 |
definition = function(x, y, lambda = 1600, d = 2, sparse = FALSE) { |
|
184 | 3x |
assert_length(y, length(x)) |
185 | 3x |
m <- length(y) |
186 | ||
187 | 3x |
if (sparse) { |
188 | 2x |
assert_Matrix() |
189 | ||
190 | 2x |
E <- Matrix::Diagonal(m) |
191 | 2x |
D <- Matrix::diff(E, lag = 1, differences = d) |
192 | 2x |
B <- Matrix::chol(E + (lambda * Matrix::t(D) %*% D)) |
193 | 2x |
z <- Matrix::solve(B, Matrix::solve(Matrix::t(B), y)) |
194 |
## Prior to v1.6, Matrix::solve(a=<Matrix>, b=<vector>) returns a matrix |
|
195 | 2x |
z <- as.numeric(z) |
196 |
} else { |
|
197 | 1x |
E <- diag(m) |
198 | 1x |
D <- diff(E, lag = 1, differences = d) |
199 | 1x |
B <- E + (lambda * t(D) %*% D) |
200 | 1x |
z <- solve(B, y) |
201 |
} |
|
202 | ||
203 | 3x |
xy <- list(x = x, y = z) |
204 | 3x |
attr(xy, "method") <- "Whittaker smoothing" |
205 | 3x |
xy |
206 |
} |
|
207 |
) |
|
208 | ||
209 |
#' @export |
|
210 |
#' @rdname smooth_whittaker |
|
211 |
#' @aliases smooth_whittaker,ANY,missing-method |
|
212 |
setMethod( |
|
213 |
f = "smooth_whittaker", |
|
214 |
signature = signature(x = "ANY", y = "missing"), |
|
215 |
definition = function(x, lambda = 1600, d = 2, sparse = FALSE) { |
|
216 | 2x |
xy <- grDevices::xy.coords(x) |
217 | 2x |
methods::callGeneric(x = xy$x, y = xy$y, lambda = lambda, d = d, |
218 | 2x |
sparse = sparse) |
219 |
} |
|
220 |
) |
|
221 | ||
222 |
# Penalized likelihood smoothing =============================================== |
|
223 |
#' @export |
|
224 |
#' @rdname smooth_likelihood |
|
225 |
#' @aliases smooth_likelihood,numeric,numeric-method |
|
226 |
setMethod( |
|
227 |
f = "smooth_likelihood", |
|
228 |
signature = signature(x = "numeric", y = "numeric"), |
|
229 |
definition = function(x, y, lambda, d = 2, SE = FALSE, |
|
230 |
progress = interactive()) { |
|
231 | ! |
assert_Matrix() |
232 | ! |
assert_length(y, length(x)) |
233 | ||
234 | ! |
m <- length(y) |
235 | ! |
n <- length(lambda) |
236 | ||
237 | ! |
aic <- rep(0, n) |
238 | ! |
mu <- matrix(data = 0, nrow = m, ncol = n) |
239 | ! |
se <- matrix(data = 0, nrow = m, ncol = n) |
240 | ! |
res <- matrix(data = 0, nrow = m, ncol = n) |
241 | ||
242 | ! |
if (progress) pb <- utils::txtProgressBar(min = 0, max = n, style=3) |
243 | ||
244 | ! |
n_lambda <- seq_len(n) |
245 | ! |
for (i in n_lambda) { |
246 | ! |
like <- penalized_likelihood(y, lambda = lambda[i], d = d, SE = SE) |
247 | ||
248 | ! |
aic[i] <- like$aic |
249 | ! |
mu[, i] <- like$mu |
250 | ! |
se[, i] <- like$se |
251 | ! |
res[, i] <- like$res |
252 | ||
253 | ! |
if (progress) utils::setTxtProgressBar(pb, i) |
254 |
} |
|
255 | ||
256 | ! |
if (progress) close(pb) |
257 | ||
258 | ! |
k <- which.min(aic) |
259 | ! |
mu <- mu[, k] |
260 | ! |
se <- se[, k] |
261 | ! |
res <- res[, k] |
262 | ! |
op_lambda <- lambda[k] |
263 | ! |
op_aic <- aic[k] |
264 | ||
265 | ! |
xy <- list(x = x, y = mu, aic = aic, se = se, residuals = res, |
266 | ! |
optimal_aic = op_aic, optimal_lambda = op_lambda) |
267 | ! |
attr(xy, "method") <- "penalized likelihood smoothing" |
268 | ! |
xy |
269 |
} |
|
270 |
) |
|
271 | ||
272 |
#' @export |
|
273 |
#' @rdname smooth_likelihood |
|
274 |
#' @aliases smooth_likelihood,ANY,missing-method |
|
275 |
setMethod( |
|
276 |
f = "smooth_likelihood", |
|
277 |
signature = signature(x = "ANY", y = "missing"), |
|
278 |
definition = function(x, lambda, d = 2, SE = FALSE, |
|
279 |
progress = interactive()) { |
|
280 | ! |
xy <- grDevices::xy.coords(x) |
281 | ! |
methods::callGeneric(x = xy$x, y = xy$y, lambda = lambda, d = d, |
282 | ! |
SE = SE, progress = progress) |
283 |
} |
|
284 |
) |
|
285 | ||
286 |
penalized_likelihood <- function(y, lambda, d = 2, SE = FALSE) { |
|
287 | ||
288 | ! |
m <- length(y) |
289 | ! |
z <- log(y + 1) |
290 | ! |
E <- Matrix::Diagonal(m) |
291 | ! |
D <- Matrix::diff(E, differences = d) |
292 | ! |
P <- lambda * Matrix::crossprod(D, D) |
293 | ! |
mu <- exp(z) |
294 | ||
295 | ! |
delta_mu <- 1 |
296 | ! |
limit <- 1 # Stop condition to prevent infinite loop |
297 | ! |
while (delta_mu >= 0.0001 & limit <= 10) { |
298 | ! |
W <- Matrix::Diagonal(x = mu) |
299 | ! |
z <- Matrix::solve(W + P, y - mu + mu * z) |
300 | ! |
new_mu <- exp(as.numeric(z)) # Drop S4 class |
301 | ! |
delta_mu <- max(abs(new_mu - mu)) |
302 | ! |
mu <- new_mu |
303 | ||
304 | ! |
limit <- limit + 1 |
305 |
} |
|
306 | ||
307 |
## AIC |
|
308 | ! |
H <- Matrix::solve(W + P) %*% W |
309 | ! |
ed <- sum(Matrix::diag(H)) |
310 | ! |
dev <- 2 * sum(y * log((y + 1e-10) / mu)) |
311 | ! |
aic <- dev + 2 * ed * m / (m - ed) |
312 | ||
313 |
## Standard error |
|
314 | ! |
se <- rep(NA_real_, m) |
315 | ! |
if (isTRUE(SE)) { |
316 | ! |
HH <- Matrix::tcrossprod(H %*% W, H) |
317 | ! |
se <- sqrt(Matrix::diag(HH)) |
318 |
} |
|
319 | ||
320 |
## Standardized residuals for counts |
|
321 | ! |
res <- (y - mu) / sqrt(mu) |
322 | ||
323 | ! |
list(mu = mu, aic = aic, se = se, res = res) |
324 |
} |
1 |
# SIGNAL PROCESSING |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
# Bind ========================================================================= |
|
6 |
#' @export |
|
7 |
#' @rdname signal_bind |
|
8 |
#' @aliases signal_bind,ANY-method |
|
9 |
setMethod( |
|
10 |
f = "signal_bind", |
|
11 |
signature = "ANY", |
|
12 |
definition = function(...) { |
|
13 | ||
14 | 1x |
signal <- list(...) |
15 | 1x |
coords <- lapply(X = signal, FUN = grDevices::xy.coords) |
16 | ||
17 | 1x |
y <- lapply(X = coords, FUN = `[[`, i = "y") |
18 | 1x |
ny <- lengths(y) |
19 | 1x |
if (any(ny != mean(ny))) { |
20 | ! |
msg <- tr_("All objects must have the same number of data points.") |
21 | ! |
stop(msg, call. = FALSE) |
22 |
} |
|
23 | 1x |
y <- do.call(rbind, y) |
24 | ||
25 |
## Get names |
|
26 | 1x |
subst <- substitute(list(...))[-1] |
27 | 1x |
arg_names <- vapply(X = subst, FUN = deparse, FUN.VALUE = character(1)) |
28 | 1x |
rownames(y) <- arg_names |
29 | 1x |
y |
30 |
} |
|
31 |
) |
|
32 | ||
33 |
# Mean ========================================================================= |
|
34 |
#' @export |
|
35 |
#' @rdname signal_mean |
|
36 |
#' @aliases signal_mean,ANY-method |
|
37 |
setMethod( |
|
38 |
f = "signal_mean", |
|
39 |
signature = "ANY", |
|
40 |
definition = function(...) { |
|
41 | ||
42 | 3x |
signal <- list(...) |
43 | 3x |
coords <- lapply(X = signal, FUN = grDevices::xy.coords) |
44 | ||
45 | 3x |
x <- lapply(X = coords, FUN = `[[`, i = "x") |
46 | 3x |
nx <- lengths(x) |
47 | ||
48 | 3x |
y <- lapply(X = coords, FUN = `[[`, i = "y") |
49 | 3x |
ny <- lengths(y) |
50 | ||
51 | 3x |
if (any(nx != mean(nx)) | any(ny != mean(ny))) { |
52 | 2x |
msg <- tr_("All objects must have the same number of data points.") |
53 | 2x |
stop(msg, call. = FALSE) |
54 |
} |
|
55 | ||
56 | 1x |
x <- do.call(cbind, x) |
57 | 1x |
x <- rowMeans(x) |
58 | 1x |
y <- do.call(cbind, y) |
59 | 1x |
y <- rowMeans(y) |
60 | ||
61 | 1x |
xy <- list(x = x, y = y) |
62 | 1x |
xy |
63 |
} |
|
64 |
) |
|
65 | ||
66 |
# Subset ======================================================================= |
|
67 |
#' @export |
|
68 |
#' @rdname subset |
|
69 |
#' @aliases signal_select,numeric,numeric-method |
|
70 |
setMethod( |
|
71 |
f = "signal_select", |
|
72 |
signature = c(x = "numeric", y = "numeric"), |
|
73 |
definition = function(x, y, from, to) { |
|
74 | 2x |
assert_length(y, length(x)) |
75 | ||
76 |
## Find the nearest value |
|
77 | 2x |
from <- x[which_nearest(x, from)] |
78 | 2x |
to <- x[which_nearest(x, to)] |
79 | 2x |
idx <- which(x >= from & x <= to) |
80 | ||
81 | 2x |
xy <- list(x = x[idx], y = y[idx]) |
82 | 2x |
xy |
83 |
} |
|
84 |
) |
|
85 | ||
86 |
#' @export |
|
87 |
#' @rdname subset |
|
88 |
#' @aliases signal_select,ANY,missing-method |
|
89 |
setMethod( |
|
90 |
f = "signal_select", |
|
91 |
signature = c(x = "ANY", y = "missing"), |
|
92 |
definition = function(x, from, to) { |
|
93 | 2x |
xy <- grDevices::xy.coords(x) |
94 | 2x |
methods::callGeneric(x = xy$x, y = xy$y, from = from, to = to) |
95 |
} |
|
96 |
) |
|
97 | ||
98 |
#' @export |
|
99 |
#' @rdname subset |
|
100 |
#' @aliases signal_slice,numeric,numeric-method |
|
101 |
setMethod( |
|
102 |
f = "signal_slice", |
|
103 |
signature = c(x = "numeric", y = "numeric"), |
|
104 |
definition = function(x, y, subset) { |
|
105 | 3x |
assert_length(y, length(x)) |
106 | ||
107 | 3x |
i <- as.integer(subset) |
108 | ||
109 | 3x |
if (!all(i > 0) & !all(i < 0)) { |
110 | ! |
msg <- tr_("A vector of strictly positive or negative integers is expected.") |
111 | ! |
stop(msg, call. = FALSE) |
112 |
} |
|
113 | ||
114 | 3x |
xy <- list(x = x[i], y = y[i]) |
115 | 3x |
xy |
116 |
} |
|
117 |
) |
|
118 | ||
119 |
#' @export |
|
120 |
#' @rdname subset |
|
121 |
#' @aliases signal_slice,ANY,missing-method |
|
122 |
setMethod( |
|
123 |
f = "signal_slice", |
|
124 |
signature = c(x = "ANY", y = "missing"), |
|
125 |
definition = function(x, subset) { |
|
126 | 3x |
xy <- grDevices::xy.coords(x) |
127 | 3x |
methods::callGeneric(x = xy$x, y = xy$y, subset = subset) |
128 |
} |
|
129 |
) |
|
130 | ||
131 |
# Shift ======================================================================== |
|
132 |
#' @export |
|
133 |
#' @rdname signal_shift |
|
134 |
#' @aliases signal_shift,numeric,numeric-method |
|
135 |
setMethod( |
|
136 |
f = "signal_shift", |
|
137 |
signature = c(x = "numeric", y = "numeric"), |
|
138 |
definition = function(x, y, lag) { |
|
139 | 1x |
assert_length(y, length(x)) |
140 | ||
141 | 1x |
x <- x + lag |
142 | 1x |
xy <- list(x = x, y = y) |
143 | 1x |
xy |
144 |
} |
|
145 |
) |
|
146 | ||
147 |
#' @export |
|
148 |
#' @rdname signal_shift |
|
149 |
#' @aliases signal_shift,ANY,missing-method |
|
150 |
setMethod( |
|
151 |
f = "signal_shift", |
|
152 |
signature = c(x = "ANY", y = "missing"), |
|
153 |
definition = function(x, lag) { |
|
154 | 1x |
xy <- grDevices::xy.coords(x) |
155 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, lag = lag) |
156 |
} |
|
157 |
) |
|
158 | ||
159 |
# Drift ======================================================================== |
|
160 |
#' @export |
|
161 |
#' @rdname signal_drift |
|
162 |
#' @aliases signal_drift,numeric,numeric,numeric-method |
|
163 |
setMethod( |
|
164 |
f = "signal_drift", |
|
165 |
signature = c(x = "numeric", y = "numeric", lag = "numeric"), |
|
166 |
definition = function(x, y, lag) { |
|
167 | 4x |
assert_length(y, length(x)) |
168 | ||
169 | 4x |
y <- y + lag |
170 | 4x |
xy <- list(x = x, y = y) |
171 | 4x |
xy |
172 |
} |
|
173 |
) |
|
174 | ||
175 |
#' @export |
|
176 |
#' @rdname signal_drift |
|
177 |
#' @aliases signal_drift,ANY,missing,ANY-method |
|
178 |
setMethod( |
|
179 |
f = "signal_drift", |
|
180 |
signature = c(x = "ANY", y = "missing", lag = "ANY"), |
|
181 |
definition = function(x, lag, subtract = FALSE) { |
|
182 | 4x |
xy <- grDevices::xy.coords(x) |
183 | 4x |
zz <- grDevices::xy.coords(lag) |
184 | 4x |
lag <- if (subtract) -zz$y else zz$y |
185 | 4x |
methods::callGeneric(x = xy$x, y = xy$y, lag = lag) |
186 |
} |
|
187 |
) |
|
188 | ||
189 |
# Correct ====================================================================== |
|
190 |
#' @export |
|
191 |
#' @rdname signal_correct |
|
192 |
#' @aliases signal_correct,numeric,numeric-method |
|
193 |
setMethod( |
|
194 |
f = "signal_correct", |
|
195 |
signature = c(x = "numeric", y = "numeric"), |
|
196 |
definition = function(x, y, method = c("linear", "polynomial", "asls", |
|
197 |
"rollingball", "rubberband", |
|
198 |
"SNIP", "4S"), ...) { |
|
199 |
## Validation |
|
200 | 1x |
method <- match.arg(method, several.ok = FALSE) |
201 | ||
202 |
## Get method |
|
203 | 1x |
f <- switch( |
204 | 1x |
method, |
205 | 1x |
asls = baseline_asls, |
206 | 1x |
linear = baseline_linear, |
207 | 1x |
polynomial = baseline_polynomial, |
208 | 1x |
rollingball = baseline_rollingball, |
209 | 1x |
rubberband = baseline_rubberband, |
210 | 1x |
SNIP = baseline_snip, |
211 | 1x |
`4S` = baseline_peakfilling |
212 |
) |
|
213 | ||
214 |
## Baseline estimation |
|
215 | 1x |
bsl <- f(x = x, y = y, ...) |
216 | ||
217 | 1x |
xy <- list(x = x, y = y - bsl$y) |
218 | 1x |
attr(xy, "method") <- method |
219 | 1x |
xy |
220 |
} |
|
221 |
) |
|
222 | ||
223 |
#' @export |
|
224 |
#' @rdname signal_correct |
|
225 |
#' @aliases signal_correct,ANY,missing-method |
|
226 |
setMethod( |
|
227 |
f = "signal_correct", |
|
228 |
signature = c(x = "ANY", y = "missing"), |
|
229 |
definition = function(x, method = c("linear", "polynomial", "asls", |
|
230 |
"rollingball", "rubberband", |
|
231 |
"SNIP", "4S"), ...) { |
|
232 | 1x |
xy <- grDevices::xy.coords(x) |
233 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, method = method, ...) |
234 |
} |
|
235 |
) |
1 |
# SCALING |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
# Area ========================================================================= |
|
6 |
#' @export |
|
7 |
#' @rdname rescale_area |
|
8 |
#' @aliases rescale_area,numeric,numeric-method |
|
9 |
setMethod( |
|
10 |
f = "rescale_area", |
|
11 |
signature = signature(x = "numeric", y = "numeric"), |
|
12 |
definition = function(x, y, method = c("rectangle", "trapezoid"), ...) { |
|
13 |
## Validation |
|
14 | 1x |
assert_length(y, length(x)) |
15 | 1x |
method <- match.arg(method, several.ok = FALSE) |
16 | ||
17 |
## Get method |
|
18 | 1x |
f <- switch( |
19 | 1x |
method, |
20 | 1x |
rectangle = integrate_rectangle, |
21 | 1x |
trapezoid = integrate_trapezoid |
22 |
) |
|
23 | ||
24 |
## Baseline estimation |
|
25 | 1x |
auc <- f(x = x, y = y, ...) |
26 | 1x |
y <- y / auc |
27 | ||
28 | 1x |
xy <- list(x = x, y = y) |
29 | 1x |
xy |
30 |
} |
|
31 |
) |
|
32 | ||
33 |
#' @export |
|
34 |
#' @rdname rescale_area |
|
35 |
#' @aliases rescale_area,ANY,missing-method |
|
36 |
setMethod( |
|
37 |
f = "rescale_area", |
|
38 |
signature = signature(x = "ANY", y = "missing"), |
|
39 |
definition = function(x, method = c("rectangle", "trapezoid"), ...) { |
|
40 | 1x |
xy <- grDevices::xy.coords(x) |
41 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, method = method, ...) |
42 |
} |
|
43 |
) |
|
44 | ||
45 |
# Total ======================================================================== |
|
46 |
#' @export |
|
47 |
#' @rdname rescale_total |
|
48 |
#' @aliases rescale_total,numeric,numeric-method |
|
49 |
setMethod( |
|
50 |
f = "rescale_total", |
|
51 |
signature = signature(x = "numeric", y = "numeric"), |
|
52 |
definition = function(x, y, total = 1) { |
|
53 | 1x |
assert_length(y, length(x)) |
54 | ||
55 | 1x |
y <- (y * total) / sum(abs(y)) |
56 | 1x |
xy <- list(x = x, y = y) |
57 | 1x |
xy |
58 |
} |
|
59 |
) |
|
60 | ||
61 |
#' @export |
|
62 |
#' @rdname rescale_total |
|
63 |
#' @aliases rescale_total,ANY,missing-method |
|
64 |
setMethod( |
|
65 |
f = "rescale_total", |
|
66 |
signature = signature(x = "ANY", y = "missing"), |
|
67 |
definition = function(x, total = 1) { |
|
68 | 1x |
xy <- grDevices::xy.coords(x) |
69 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, total = total) |
70 |
} |
|
71 |
) |
|
72 | ||
73 |
# Min-Max ====================================================================== |
|
74 |
#' @export |
|
75 |
#' @rdname rescale_range |
|
76 |
#' @aliases rescale_range,numeric,numeric-method |
|
77 |
setMethod( |
|
78 |
f = "rescale_range", |
|
79 |
signature = signature(x = "numeric", y = "numeric"), |
|
80 |
definition = function(x, y, min = 0, max = 1) { |
|
81 |
## Validation |
|
82 | 5x |
assert_length(y, length(x)) |
83 | 5x |
if (min > max) { |
84 | 1x |
msg <- sprintf(tr_("%s (%g) must be lower than %s (%g)."), |
85 | 1x |
sQuote("min"), min, sQuote("max"), max) |
86 | 1x |
stop(msg, call. = FALSE) |
87 |
} |
|
88 | ||
89 | 4x |
y <- (y - min(y)) / (max(y) - min(y)) * (max - min) + min |
90 | 4x |
xy <- list(x = x, y = y) |
91 | 4x |
xy |
92 |
} |
|
93 |
) |
|
94 | ||
95 |
#' @export |
|
96 |
#' @rdname rescale_range |
|
97 |
#' @aliases rescale_range,ANY,missing-method |
|
98 |
setMethod( |
|
99 |
f = "rescale_range", |
|
100 |
signature = signature(x = "ANY", y = "missing"), |
|
101 |
definition = function(x, min = 0, max = 1) { |
|
102 | 1x |
xy <- grDevices::xy.coords(x) |
103 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, min = min, max = max) |
104 |
} |
|
105 |
) |
|
106 | ||
107 |
#' @export |
|
108 |
#' @rdname rescale_range |
|
109 |
#' @aliases rescale_min,numeric,numeric-method |
|
110 |
setMethod( |
|
111 |
f = "rescale_min", |
|
112 |
signature = signature(x = "numeric", y = "numeric"), |
|
113 |
definition = function(x, y, min = 0) { |
|
114 | 2x |
rescale_range(x, y, min = min, max = max(y)) |
115 |
} |
|
116 |
) |
|
117 | ||
118 |
#' @export |
|
119 |
#' @rdname rescale_range |
|
120 |
#' @aliases rescale_min,ANY,missing-method |
|
121 |
setMethod( |
|
122 |
f = "rescale_min", |
|
123 |
signature = signature(x = "ANY", y = "missing"), |
|
124 |
definition = function(x, min = 0) { |
|
125 | 2x |
xy <- grDevices::xy.coords(x) |
126 | 2x |
methods::callGeneric(x = xy$x, y = xy$y, min = min) |
127 |
} |
|
128 |
) |
|
129 | ||
130 |
#' @export |
|
131 |
#' @rdname rescale_range |
|
132 |
#' @aliases rescale_max,numeric,numeric-method |
|
133 |
setMethod( |
|
134 |
f = "rescale_max", |
|
135 |
signature = signature(x = "numeric", y = "numeric"), |
|
136 |
definition = function(x, y, max = 1) { |
|
137 | 2x |
rescale_range(x, y, min = min(y), max = max) |
138 |
} |
|
139 |
) |
|
140 | ||
141 |
#' @export |
|
142 |
#' @rdname rescale_range |
|
143 |
#' @aliases rescale_max,ANY,missing-method |
|
144 |
setMethod( |
|
145 |
f = "rescale_max", |
|
146 |
signature = signature(x = "ANY", y = "missing"), |
|
147 |
definition = function(x, max = 1) { |
|
148 | 2x |
xy <- grDevices::xy.coords(x) |
149 | 2x |
methods::callGeneric(x = xy$x, y = xy$y, max = max) |
150 |
} |
|
151 |
) |
|
152 | ||
153 |
# Transform ==================================================================== |
|
154 |
#' @export |
|
155 |
#' @rdname rescale_transform |
|
156 |
#' @aliases rescale_transform,numeric,numeric-method |
|
157 |
setMethod( |
|
158 |
f = "rescale_transform", |
|
159 |
signature = signature(x = "numeric", y = "numeric"), |
|
160 |
definition = function(x, y, f, ...) { |
|
161 | 1x |
assert_length(y, length(x)) |
162 | ||
163 | 1x |
y <- f(y, ...) |
164 | 1x |
xy <- list(x = x, y = y) |
165 | 1x |
xy |
166 |
} |
|
167 |
) |
|
168 | ||
169 |
#' @export |
|
170 |
#' @rdname rescale_transform |
|
171 |
#' @aliases rescale_transform,ANY,missing-method |
|
172 |
setMethod( |
|
173 |
f = "rescale_transform", |
|
174 |
signature = signature(x = "ANY", y = "missing"), |
|
175 |
definition = function(x, f, ...) { |
|
176 | 1x |
xy <- grDevices::xy.coords(x) |
177 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, f = f, ...) |
178 |
} |
|
179 |
) |
|
180 | ||
181 |
# SNV ========================================================================== |
|
182 |
#' @export |
|
183 |
#' @rdname rescale_snv |
|
184 |
#' @aliases rescale_snv,numeric,numeric-method |
|
185 |
setMethod( |
|
186 |
f = "rescale_snv", |
|
187 |
signature = signature(x = "numeric", y = "numeric"), |
|
188 |
definition = function(x, y, ...) { |
|
189 |
## Validation |
|
190 | 1x |
assert_length(y, length(x)) |
191 | ||
192 | 1x |
y <- (y - mean(y)) / stats::sd(y) |
193 | ||
194 | 1x |
xy <- list(x = x, y = y) |
195 | 1x |
xy |
196 |
} |
|
197 |
) |
|
198 | ||
199 |
#' @export |
|
200 |
#' @rdname rescale_snv |
|
201 |
#' @aliases rescale_snv,ANY,missing-method |
|
202 |
setMethod( |
|
203 |
f = "rescale_snv", |
|
204 |
signature = signature(x = "ANY", y = "missing"), |
|
205 |
definition = function(x, ...) { |
|
206 | 1x |
xy <- grDevices::xy.coords(x) |
207 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, ...) |
208 |
} |
|
209 |
) |
1 |
# GENERIC METHODS |
|
2 | ||
3 |
# Baseline ===================================================================== |
|
4 |
#' Asymmetric Least Squares Smoothing |
|
5 |
#' |
|
6 |
#' Baseline estimation with asymmetric least squares smoothing. |
|
7 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
8 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
9 |
#' @param p A length-one [`numeric`] vector giving the asymmetry |
|
10 |
#' (\eqn{0.001 < p < 0.1} is a good choice for a signal with positive peaks). |
|
11 |
#' @param lambda A length-one [`numeric`] vector giving the smoothing parameter. |
|
12 |
#' @param stop An [`integer`] giving the stopping rule (i.e. maximum number of |
|
13 |
#' iterations). |
|
14 |
#' @param ... Currently not used. |
|
15 |
#' @return |
|
16 |
#' Returns a [`list`] with two components `x` and `y`. |
|
17 |
#' @references |
|
18 |
#' Eilers, P. H. C. & Boelens, H. F. M. (2005). *Baseline Correction with |
|
19 |
#' Asymmetric Least Squares Smoothing*. |
|
20 |
#' @seealso [signal_correct()] |
|
21 |
#' @example inst/examples/ex-baseline-asls.R |
|
22 |
#' @author P. H. C. Eilers and H. F. M. Boelens (original Matlab code) |
|
23 |
#' @docType methods |
|
24 |
#' @family baseline estimation methods |
|
25 |
#' @aliases baseline_asls-method |
|
26 |
setGeneric( |
|
27 |
name = "baseline_asls", |
|
28 | 2x |
def = function(x, y, ...) standardGeneric("baseline_asls") |
29 |
) |
|
30 | ||
31 |
#' Linear Baseline Estimation |
|
32 |
#' |
|
33 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
34 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
35 |
#' @param points A [`numeric`] vector specifying the data points to be used in |
|
36 |
#' the fitting process (in `x` unit). |
|
37 |
#' @param ... Currently not used. |
|
38 |
#' @return |
|
39 |
#' Returns a [`list`] with two components `x` and `y`. |
|
40 |
#' @seealso [signal_correct()] |
|
41 |
#' @example inst/examples/ex-baseline-linear.R |
|
42 |
#' @author N. Frerebeau |
|
43 |
#' @docType methods |
|
44 |
#' @family baseline estimation methods |
|
45 |
#' @aliases baseline_linear-method |
|
46 |
setGeneric( |
|
47 |
name = "baseline_linear", |
|
48 | 2x |
def = function(x, y, ...) standardGeneric("baseline_linear") |
49 |
) |
|
50 | ||
51 |
#' Polynomial Baseline Estimation |
|
52 |
#' |
|
53 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
54 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
55 |
#' @param d An [`integer`] giving the degree of the polynomial. Must be less |
|
56 |
#' than the number of unique points. |
|
57 |
#' @param tolerance A [`numeric`] scalar giving the tolerance of difference |
|
58 |
#' between iterations. |
|
59 |
#' @param stop An [`integer`] giving the stopping rule (i.e. maximum number of |
|
60 |
#' iterations). |
|
61 |
#' @param ... Currently not used. |
|
62 |
#' @return |
|
63 |
#' Returns a [`list`] with two components `x` and `y`. |
|
64 |
#' @seealso [signal_correct()] |
|
65 |
#' @references |
|
66 |
#' Lieber, C. A. and Mahadevan-Jansen, A. (2003). Automated Method for |
|
67 |
#' Subtraction of Fluorescence from Biological Raman Spectra. *Applied |
|
68 |
#' Spectroscopy*, 57(11): 1363-67. \doi{10.1366/000370203322554518}. |
|
69 |
#' @example inst/examples/ex-baseline-polynomial.R |
|
70 |
#' @author N. Frerebeau |
|
71 |
#' @docType methods |
|
72 |
#' @family baseline estimation methods |
|
73 |
#' @aliases baseline_polynomial-method |
|
74 |
setGeneric( |
|
75 |
name = "baseline_polynomial", |
|
76 | 2x |
def = function(x, y, ...) standardGeneric("baseline_polynomial") |
77 |
) |
|
78 | ||
79 |
#' Rolling Ball Baseline Estimation |
|
80 |
#' |
|
81 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
82 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
83 |
#' @param m An odd [`integer`] giving the window size (i.e. the number of |
|
84 |
#' adjacent points to be used; see [`window_sliding()`]) for |
|
85 |
#' minimization/maximization. |
|
86 |
#' @param s An odd [`integer`] giving the window size (i.e. the number of |
|
87 |
#' adjacent points to be used; see [`window_sliding()`]) for smoothing. |
|
88 |
#' @param ... Currently not used. |
|
89 |
#' @note |
|
90 |
#' There will be \eqn{(m - 1) / 2} points both at the beginning and at the end |
|
91 |
#' of the data series for which a complete \eqn{m}-width window cannot be |
|
92 |
#' obtained. To prevent data loss, progressively wider/narrower windows are |
|
93 |
#' used at both ends of the data series. |
|
94 |
#' @return |
|
95 |
#' Returns a [`list`] with two components `x` and `y`. |
|
96 |
#' @seealso [signal_correct()] |
|
97 |
#' @references |
|
98 |
#' Kneen, M. A. and Annegarn, H. J. (1996). Algorithm for Fitting XRF, SEM and |
|
99 |
#' PIXE X-Ray Spectra Backgrounds. *Nuclear Instruments and Methods in Physics |
|
100 |
#' Research Section B: Beam Interactions with Materials and Atoms*, |
|
101 |
#' 109/110: 209-213. \doi{10.1016/0168-583X(95)00908-6}. |
|
102 |
#' @example inst/examples/ex-baseline-rollingball.R |
|
103 |
#' @author N. Frerebeau |
|
104 |
#' @docType methods |
|
105 |
#' @family baseline estimation methods |
|
106 |
#' @aliases baseline_rollingball-method |
|
107 |
setGeneric( |
|
108 |
name = "baseline_rollingball", |
|
109 | 2x |
def = function(x, y, ...) standardGeneric("baseline_rollingball") |
110 |
) |
|
111 | ||
112 |
#' Rubberband Baseline Estimation |
|
113 |
#' |
|
114 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
115 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
116 |
#' @param noise A length-one [`numeric`] vector giving the noise level. |
|
117 |
#' Only used if `method` is "`rubberband`". |
|
118 |
#' @param spline A [`logical`] scalar: should spline interpolation through the |
|
119 |
#' support points be used instead of linear interpolation? |
|
120 |
#' Only used if `method` is "`rubberband`". |
|
121 |
#' @param ... Extra arguments to be passed to [stats::smooth.spline()]. |
|
122 |
#' @details |
|
123 |
#' A convex envelope of the spectrum is determined and the |
|
124 |
#' baseline is estimated as the part of the convex envelope lying below the |
|
125 |
#' spectrum. Note that the rubber band does not enter the concave regions |
|
126 |
#' (if any) of the spectrum. |
|
127 |
#' @note |
|
128 |
#' `baseline_rubberband()` is slightly modified from C. Beleites' |
|
129 |
#' [hyperSpec::spc.rubberband()]. |
|
130 |
#' @return |
|
131 |
#' Returns a [`list`] with two components `x` and `y`. |
|
132 |
#' @seealso [signal_correct()] |
|
133 |
#' @example inst/examples/ex-baseline-rubberband.R |
|
134 |
#' @author N. Frerebeau |
|
135 |
#' @docType methods |
|
136 |
#' @family baseline estimation methods |
|
137 |
#' @aliases baseline_rubberband-method |
|
138 |
setGeneric( |
|
139 |
name = "baseline_rubberband", |
|
140 | 3x |
def = function(x, y, ...) standardGeneric("baseline_rubberband") |
141 |
) |
|
142 | ||
143 |
#' SNIP Baseline Estimation |
|
144 |
#' |
|
145 |
#' Sensitive Nonlinear Iterative Peak clipping algorithm. |
|
146 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
147 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
148 |
#' @param LLS A [`logical`] scalar: should the LLS operator be applied on `x` |
|
149 |
#' before employing SNIP algorithm? Only used if `method` is "`SNIP`". |
|
150 |
#' @param decreasing A [`logical`] scalar: should a decreasing clipping window |
|
151 |
#' be used? |
|
152 |
#' @param n An [`integer`] value giving the number of iterations. |
|
153 |
#' @param ... Currently not used. |
|
154 |
#' @return |
|
155 |
#' Returns a [`list`] with two components `x` and `y`. |
|
156 |
#' @seealso [signal_correct()] |
|
157 |
#' @references |
|
158 |
#' Morháč, M., Kliman, J., Matoušek, V., Veselský, M. & Turzo, I. (1997). |
|
159 |
#' Background elimination methods for multidimensional gamma-ray spectra. |
|
160 |
#' *Nuclear Instruments and Methods in Physics Research Section A: |
|
161 |
#' Accelerators, Spectrometers, Detectors and Associated Equipment*, 401(1), |
|
162 |
#' p. 113-132. \doi{10.1016/S0168-9002(97)01023-1} |
|
163 |
#' |
|
164 |
#' Morháč, M. & Matoušek, V. (2008). Peak Clipping Algorithms for Background |
|
165 |
#' Estimation in Spectroscopic Data. *Applied Spectroscopy*, 62(1), p. 91-106. |
|
166 |
#' \doi{10.1366/000370208783412762} |
|
167 |
#' |
|
168 |
#' Ryan, C. G., Clayton, E., Griffin, W. L., Sie, S. H. & Cousens, D. R. |
|
169 |
#' (1988). SNIP, a statistics-sensitive background treatment for the |
|
170 |
#' quantitative analysis of PIXE spectra in geoscience applications. |
|
171 |
#' *Nuclear Instruments and Methods in Physics Research Section B: |
|
172 |
#' Beam Interactions with Materials and Atoms*, 34(3), p. 396-402. |
|
173 |
#' \doi{10.1016/0168-583X(88)90063-8} |
|
174 |
#' @example inst/examples/ex-baseline-snip.R |
|
175 |
#' @author N. Frerebeau |
|
176 |
#' @docType methods |
|
177 |
#' @family baseline estimation methods |
|
178 |
#' @aliases baseline_snip-method |
|
179 |
setGeneric( |
|
180 |
name = "baseline_snip", |
|
181 | 6x |
def = function(x, y, ...) standardGeneric("baseline_snip") |
182 |
) |
|
183 | ||
184 |
#' 4S Peak Filling |
|
185 |
#' |
|
186 |
#' Baseline estimation by iterative mean suppression. |
|
187 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
188 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
189 |
#' @param n An [`integer`] value giving the number of iterations. |
|
190 |
#' @param m An odd [`integer`] giving the half window size. |
|
191 |
#' @param by A length-one [`numeric`] vector giving the umber of buckets to |
|
192 |
#' divide `x` into. |
|
193 |
#' @param lambda An [`integer`] giving the smoothing parameter. The larger |
|
194 |
#' `lambda` is, the smoother the curve (see [smooth_whittaker()]). |
|
195 |
#' @param d An [`integer`] specifying the order of the penalty (see |
|
196 |
#' [smooth_whittaker()]). |
|
197 |
#' @param sparse A [`logical`] scalar: should sparse matrices be used for |
|
198 |
#' computation (see [smooth_whittaker()])? If `TRUE`, \pkg{Matrix} is required. |
|
199 |
#' @param ... Currently not used. |
|
200 |
#' @return |
|
201 |
#' Returns a [`list`] with two components `x` and `y`. |
|
202 |
#' @references |
|
203 |
#' Liland, K. H. (2015). 4S Peak Filling - baseline estimation by iterative |
|
204 |
#' mean suppression. *MethodsX*, 2, 135-140. \doi{10.1016/j.mex.2015.02.009}. |
|
205 |
#' @seealso [signal_correct()], [smooth_whittaker()] |
|
206 |
#' @example inst/examples/ex-baseline-peakfilling.R |
|
207 |
#' @author N. Frerebeau |
|
208 |
#' @docType methods |
|
209 |
#' @family baseline estimation methods |
|
210 |
#' @aliases baseline_peakfilling-method |
|
211 |
setGeneric( |
|
212 |
name = "baseline_peakfilling", |
|
213 | 2x |
def = function(x, y, ...) standardGeneric("baseline_peakfilling") |
214 |
) |
|
215 | ||
216 |
# Integrate ==================================================================== |
|
217 |
#' Rectangle Rule |
|
218 |
#' |
|
219 |
#' Approximates the definite integral by using the rectangle rule. |
|
220 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
221 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
222 |
#' @param right A [`logical`] scalar: should the right rule be used instead of |
|
223 |
#' the left rule? |
|
224 |
#' @param ... Currently not used. |
|
225 |
#' @return |
|
226 |
#' Returns a [`list`] with two components `x` and `y`. |
|
227 |
#' @example inst/examples/ex-integrate.R |
|
228 |
#' @author N. Frerebeau |
|
229 |
#' @docType methods |
|
230 |
#' @family integration methods |
|
231 |
#' @aliases integrate_rectangle-method |
|
232 |
setGeneric( |
|
233 |
name = "integrate_rectangle", |
|
234 | 7x |
def = function(x, y, ...) standardGeneric("integrate_rectangle") |
235 |
) |
|
236 | ||
237 |
#' Trapezoidal Rule |
|
238 |
#' |
|
239 |
#' Approximates the definite integral by using the trapezoidal rule. |
|
240 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
241 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
242 |
#' @param ... Currently not used. |
|
243 |
#' @return |
|
244 |
#' Returns a [`list`] with two components `x` and `y`. |
|
245 |
#' @example inst/examples/ex-integrate.R |
|
246 |
#' @author N. Frerebeau |
|
247 |
#' @docType methods |
|
248 |
#' @family integration methods |
|
249 |
#' @aliases integrate_trapezoid-method |
|
250 |
setGeneric( |
|
251 |
name = "integrate_trapezoid", |
|
252 | 2x |
def = function(x, y, ...) standardGeneric("integrate_trapezoid") |
253 |
) |
|
254 | ||
255 |
# Peaks ======================================================================== |
|
256 |
#' Find Peaks |
|
257 |
#' |
|
258 |
#' Finds local maxima in sequential data. |
|
259 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
260 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
261 |
#' @param method A [`character`] string specifying the method to be used for |
|
262 |
#' background noise estimation (see below). |
|
263 |
#' @param SNR An [`integer`] giving the signal-to-noise-ratio for peak detection |
|
264 |
#' (see below). |
|
265 |
#' @param m An odd [`integer`] giving the window size (i.e. the number of |
|
266 |
#' adjacent points to be used). |
|
267 |
#' If `NULL`, 5% of the data points is used as the half window size. |
|
268 |
#' @param ... Extra parameters to be passed to internal methods. |
|
269 |
#' @details |
|
270 |
#' A local maximum has to be the highest one in the given window and has to be |
|
271 |
#' higher than \eqn{SNR \times noise}{SNR * noise} to be recognized as peak. |
|
272 |
#' |
|
273 |
#' The following methods are available for noise estimation: |
|
274 |
#' \describe{ |
|
275 |
#' \item{`MAD`}{Median Absolute Deviation.} |
|
276 |
#' } |
|
277 |
#' |
|
278 |
#' Note that to improve peak detection, it may be helpful to smooth the data |
|
279 |
#' and remove the baseline beforehand. |
|
280 |
#' @note |
|
281 |
#' There will be \eqn{(m - 1) / 2} points both at the beginning and at the end |
|
282 |
#' of the data series for which a complete \eqn{m}-width window cannot be |
|
283 |
#' obtained. To prevent data loss, progressively wider/narrower windows are |
|
284 |
#' used at both ends of the data series. |
|
285 |
#' @return |
|
286 |
#' Returns a [`list`] with two components `x` and `y`. |
|
287 |
#' @note |
|
288 |
#' Adapted from Stasia Grinberg's |
|
289 |
#' [`findPeaks`](https://github.com/stas-g/findPeaks) function. |
|
290 |
#' @example inst/examples/ex-peaks.R |
|
291 |
#' @author N. Frerebeau |
|
292 |
#' @docType methods |
|
293 |
#' @family peaks detection methods |
|
294 |
#' @aliases peaks_find-method |
|
295 |
setGeneric( |
|
296 |
name = "peaks_find", |
|
297 | 2x |
def = function(x, y, ...) standardGeneric("peaks_find") |
298 |
) |
|
299 | ||
300 |
#' Half-Width at Half-Maximum |
|
301 |
#' |
|
302 |
#' Estimates the Half-Width at Half-Maximum (FWHM) for a given peak. |
|
303 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
304 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
305 |
#' @param center A [`numeric`] value giving the peak position in `x` units. |
|
306 |
#' @param ... Currently not used. |
|
307 |
#' @return A [`numeric`] value. |
|
308 |
#' @details |
|
309 |
#' It tries to get the smallest possible estimate. |
|
310 |
#' @example inst/examples/ex-peaks.R |
|
311 |
#' @author N. Frerebeau |
|
312 |
#' @docType methods |
|
313 |
#' @family peaks detection methods |
|
314 |
#' @aliases peaks_fwhm-method |
|
315 |
setGeneric( |
|
316 |
name = "peaks_fwhm", |
|
317 | 2x |
def = function(x, y, ...) standardGeneric("peaks_fwhm") |
318 |
) |
|
319 | ||
320 |
# Replace ====================================================================== |
|
321 |
#' Replace Values Below a Given Threshold |
|
322 |
#' |
|
323 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
324 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
325 |
#' @param threshold A [`numeric`] value or a [`function`] that takes a `numeric` |
|
326 |
#' vector as argument and returns a single `numeric` value. |
|
327 |
#' @param value A [`numeric`] value to replace values below `threshold`. |
|
328 |
#' @param ... Extra parameters to be passed to `threshold`. |
|
329 |
#' @return |
|
330 |
#' Returns a [`list`] with two components `x` and `y`. |
|
331 |
#' @author N. Frerebeau |
|
332 |
#' @docType methods |
|
333 |
#' @family replacement methods |
|
334 |
#' @aliases replace_threshold-method |
|
335 |
setGeneric( |
|
336 |
name = "replace_threshold", |
|
337 | 6x |
def = function(x, y, threshold, ...) standardGeneric("replace_threshold") |
338 |
) |
|
339 | ||
340 |
#' Replace Negative Values |
|
341 |
#' |
|
342 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
343 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
344 |
#' @param value A [`numeric`] value to replace negative values. |
|
345 |
#' @param ... Extra parameters to be passed to `threshold`. |
|
346 |
#' @return |
|
347 |
#' Returns a [`list`] with two components `x` and `y`. |
|
348 |
#' @author N. Frerebeau |
|
349 |
#' @docType methods |
|
350 |
#' @family replacement methods |
|
351 |
#' @aliases replace_negative-method |
|
352 |
setGeneric( |
|
353 |
name = "replace_negative", |
|
354 | 2x |
def = function(x, y, ...) standardGeneric("replace_negative") |
355 |
) |
|
356 | ||
357 |
# Resample ===================================================================== |
|
358 |
#' Bin |
|
359 |
#' |
|
360 |
#' Averages `x` values and applies a function to the corresponding `y` values. |
|
361 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
362 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
363 |
#' @param by An [`integer`] specifying the binning ratio (i.e. the number of |
|
364 |
#' points to be grouped together; see [`window_tumbling()`]). |
|
365 |
#' @param f A [`function`] that takes a `numeric` vector of intensities as |
|
366 |
#' argument and returns a single `numeric` vector. Used to estimate the local |
|
367 |
#' representative value in each bin (defaults to [sum()]; see examples). |
|
368 |
#' @param ... Extra parameters to be passed to `f`. |
|
369 |
#' @return |
|
370 |
#' Returns a [`list`] with two components `x` and `y`. |
|
371 |
#' @author N. Frerebeau |
|
372 |
#' @example inst/examples/ex-resample.R |
|
373 |
#' @docType methods |
|
374 |
#' @family resampling methods |
|
375 |
#' @aliases resample_bin-method |
|
376 |
setGeneric( |
|
377 |
name = "resample_bin", |
|
378 | 2x |
def = function(x, y, ...) standardGeneric("resample_bin") |
379 |
) |
|
380 | ||
381 |
#' Downsample |
|
382 |
#' |
|
383 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
384 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
385 |
#' @param by An [`integer`] specifying the downsampling factor. |
|
386 |
#' @param ... Currently not used. |
|
387 |
#' @return |
|
388 |
#' Returns a [`list`] with two components `x` and `y`. |
|
389 |
#' @author N. Frerebeau |
|
390 |
#' @example inst/examples/ex-resample.R |
|
391 |
#' @docType methods |
|
392 |
#' @family resampling methods |
|
393 |
#' @aliases resample_down-method |
|
394 |
setGeneric( |
|
395 |
name = "resample_down", |
|
396 | 2x |
def = function(x, y, ...) standardGeneric("resample_down") |
397 |
) |
|
398 | ||
399 |
# Upsample |
|
400 |
# |
|
401 |
# @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
402 |
# interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
403 |
# @param by An [`integer`] specifying the upsampling factor. |
|
404 |
# @param ... Currently not used. |
|
405 |
# @details |
|
406 |
# The signal is upsampled by inserting \eqn{n - 1} zeros between samples. |
|
407 |
# @return |
|
408 |
# Returns a [`list`] with two components `x` and `y`. |
|
409 |
# @author N. Frerebeau |
|
410 |
# @example inst/examples/ex-resample.R |
|
411 |
# @docType methods |
|
412 |
# @family resampling methods |
|
413 |
# @aliases resample_interpolate-method |
|
414 |
# setGeneric( |
|
415 |
# name = "resample_up", |
|
416 |
# def = function(x, y, ...) standardGeneric("resample_up") |
|
417 |
# ) |
|
418 | ||
419 |
#' Linearly Interpolate |
|
420 |
#' |
|
421 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
422 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
423 |
#' @param from A length-one [`numeric`] vector giving the starting value of the |
|
424 |
#' sequence where interpolation is to take place. |
|
425 |
#' @param to A length-one [`numeric`] vector giving the end value of the |
|
426 |
#' sequence where interpolation is to take place. |
|
427 |
#' @param by A length-one [`numeric`] vector specifying the increment of the |
|
428 |
#' sequence. |
|
429 |
#' @param ... Extra arguments to be passed to [stats::approx()]. |
|
430 |
#' @return |
|
431 |
#' Returns a [`list`] with two components `x` and `y`. |
|
432 |
#' @author N. Frerebeau |
|
433 |
#' @example inst/examples/ex-resample.R |
|
434 |
#' @docType methods |
|
435 |
#' @family resampling methods |
|
436 |
#' @aliases resample_interpolate-method |
|
437 |
setGeneric( |
|
438 |
name = "resample_interpolate", |
|
439 | 2x |
def = function(x, y, ...) standardGeneric("resample_interpolate") |
440 |
) |
|
441 | ||
442 |
# Rescale ====================================================================== |
|
443 |
#' Normalize intensities by AUC |
|
444 |
#' |
|
445 |
#' Rescales intensities so that the area under the curve (AUC) is equal to 1. |
|
446 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
447 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
448 |
#' @param method A [`character`] string specifying the method for integration. |
|
449 |
#' It must be one of "`rectangle`" or "`trapezoid`". |
|
450 |
#' Any unambiguous substring can be given. |
|
451 |
#' @param ... Currently not used. |
|
452 |
#' @return |
|
453 |
#' Returns a [`list`] with two components `x` and `y`. |
|
454 |
#' @example inst/examples/ex-rescale.R |
|
455 |
#' @author N. Frerebeau |
|
456 |
#' @docType methods |
|
457 |
#' @family normalization methods |
|
458 |
#' @aliases rescale_area-method |
|
459 |
setGeneric( |
|
460 |
name = "rescale_area", |
|
461 | 2x |
def = function(x, y, ...) standardGeneric("rescale_area") |
462 |
) |
|
463 | ||
464 |
#' Rescale intensities to sum to a specified value |
|
465 |
#' |
|
466 |
#' Rescales intensities to sum to a specified value. |
|
467 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
468 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
469 |
#' @param total A legnth-one [`numeric`] vector specifying the output total. |
|
470 |
#' Defaults to 1, i.e. normalizes by total intensity. |
|
471 |
#' @param ... Currently not used. |
|
472 |
#' @return |
|
473 |
#' Returns a [`list`] with two components `x` and `y`. |
|
474 |
#' @example inst/examples/ex-rescale.R |
|
475 |
#' @author N. Frerebeau |
|
476 |
#' @docType methods |
|
477 |
#' @family normalization methods |
|
478 |
#' @aliases rescale_total-method |
|
479 |
setGeneric( |
|
480 |
name = "rescale_total", |
|
481 | 2x |
def = function(x, y, ...) standardGeneric("rescale_total") |
482 |
) |
|
483 | ||
484 |
#' Rescales intensities to have specified minimum and maximum |
|
485 |
#' |
|
486 |
#' Rescales intensities to have specified minimum and maximum. |
|
487 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
488 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
489 |
#' @param min A legnth-one [`numeric`] vector specifying the output minimum. |
|
490 |
#' @param max A legnth-one [`numeric`] vector specifying the output maximum. |
|
491 |
#' @param ... Currently not used. |
|
492 |
#' @return |
|
493 |
#' Returns a [`list`] with two components `x` and `y`. |
|
494 |
#' @example inst/examples/ex-rescale.R |
|
495 |
#' @author N. Frerebeau |
|
496 |
#' @docType methods |
|
497 |
#' @family normalization methods |
|
498 |
#' @aliases rescale_range-method |
|
499 |
setGeneric( |
|
500 |
name = "rescale_range", |
|
501 | 6x |
def = function(x, y, ...) standardGeneric("rescale_range") |
502 |
) |
|
503 | ||
504 |
#' @rdname rescale_range |
|
505 |
#' @aliases rescale_min-method |
|
506 |
setGeneric( |
|
507 |
name = "rescale_min", |
|
508 | 4x |
def = function(x, y, ...) standardGeneric("rescale_min") |
509 |
) |
|
510 | ||
511 |
#' @rdname rescale_range |
|
512 |
#' @aliases rescale_max-method |
|
513 |
setGeneric( |
|
514 |
name = "rescale_max", |
|
515 | 4x |
def = function(x, y, ...) standardGeneric("rescale_max") |
516 |
) |
|
517 | ||
518 |
#' Transform Intensities |
|
519 |
#' |
|
520 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
521 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
522 |
#' @param f A [`function`] that takes a `numeric` vector of intensities as |
|
523 |
#' argument and returns a `numeric` vector. |
|
524 |
#' @param ... Extra arguments to be passed to `f`. |
|
525 |
#' @details |
|
526 |
#' Transformation of intensities can be used to improve the identification of |
|
527 |
#' peaks with a low signal-to-noise ratio. |
|
528 |
#' @return |
|
529 |
#' Returns a [`list`] with two components `x` and `y`. |
|
530 |
#' @example inst/examples/ex-transform.R |
|
531 |
#' @author N. Frerebeau |
|
532 |
#' @docType methods |
|
533 |
#' @family normalization methods |
|
534 |
#' @aliases rescale_transform-method |
|
535 |
setGeneric( |
|
536 |
name = "rescale_transform", |
|
537 | 2x |
def = function(x, y, ...) standardGeneric("rescale_transform") |
538 |
) |
|
539 | ||
540 |
#' Standard Normal Variate (SNV) Transformation |
|
541 |
#' |
|
542 |
#' Subtracts the mean and scales to unit variance. |
|
543 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
544 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
545 |
#' @param ... Currently not used. |
|
546 |
#' @return |
|
547 |
#' Returns a [`list`] with two components `x` and `y`. |
|
548 |
#' @references |
|
549 |
#' Barnes, R. J., Dhanoa, M. S. & Lister, S. J. (1989). Standard Normal Variate |
|
550 |
#' Transformation and De-Trending of Near-Infrared Diffuse Reflectance Spectra. |
|
551 |
#' *Applied Spectroscopy*, 43(5): 772-777. \doi{10.1366/0003702894202201}. |
|
552 |
#' @example inst/examples/ex-normalize.R |
|
553 |
#' @author N. Frerebeau |
|
554 |
#' @docType methods |
|
555 |
#' @family normalization methods |
|
556 |
#' @aliases rescale_snv-method |
|
557 |
setGeneric( |
|
558 |
name = "rescale_snv", |
|
559 | 2x |
def = function(x, y, ...) standardGeneric("rescale_snv") |
560 |
) |
|
561 | ||
562 |
# Signal ======================================================================= |
|
563 |
#' Baseline Correction |
|
564 |
#' |
|
565 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
566 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
567 |
#' @param method A [`character`] string specifying the method for baseline |
|
568 |
#' estimation (see details). Any unambiguous substring can be given. |
|
569 |
#' @param ... Extra arguments to be passed to `baseline_*()` (see details). |
|
570 |
#' @details |
|
571 |
#' Available methods for baseline estimation: |
|
572 |
#' \describe{ |
|
573 |
#' \item{`asls`}{Asymmetric Least Squares Smoothing (see [baseline_asls()]).} |
|
574 |
#' \item{`linear`}{Linear baseline estimation (see [baseline_linear()]).} |
|
575 |
#' \item{`polynomial`}{Polynomial baseline estimation (see |
|
576 |
#' [baseline_polynomial()]).} |
|
577 |
#' \item{`rollingball`}{Rolling ball baseline estimation (see |
|
578 |
#' [baseline_rollingball()]).} |
|
579 |
#' \item{`rubberband`}{Rubberband baseline estimation (see |
|
580 |
#' [baseline_rubberband()]).} |
|
581 |
#' \item{`SNIP`}{Sensitive Nonlinear Iterative Peak clipping algorithm |
|
582 |
#' (see [baseline_snip()]).} |
|
583 |
#' \item{`4S`}{4S Peak Filling (see [baseline_peakfilling()]).} |
|
584 |
#' } |
|
585 |
#' @return |
|
586 |
#' Returns a [`list`] with two components `x` and `y`. |
|
587 |
#' @author N. Frerebeau |
|
588 |
#' @example inst/examples/ex-correct.R |
|
589 |
#' @docType methods |
|
590 |
#' @family signal processing methods |
|
591 |
#' @aliases signal_correct-method |
|
592 |
setGeneric( |
|
593 |
name = "signal_correct", |
|
594 | 2x |
def = function(x, y, ...) standardGeneric("signal_correct") |
595 |
) |
|
596 | ||
597 |
#' Bind |
|
598 |
#' |
|
599 |
#' Combines XY objects. |
|
600 |
#' @param ... Any object that can be interpreted in a suitable way |
|
601 |
#' (see [grDevices::xy.coords()]). |
|
602 |
#' @return |
|
603 |
#' Returns a [`matrix`] of intensities. |
|
604 |
#' @author N. Frerebeau |
|
605 |
#' @example inst/examples/ex-mean.R |
|
606 |
#' @docType methods |
|
607 |
#' @family signal processing methods |
|
608 |
#' @aliases signal_bind-method |
|
609 |
setGeneric( |
|
610 |
name = "signal_bind", |
|
611 | 1x |
def = function(...) standardGeneric("signal_bind") |
612 |
) |
|
613 | ||
614 |
#' Mean Intensities |
|
615 |
#' |
|
616 |
#' @param ... Any object that can be interpreted in a suitable way |
|
617 |
#' (see [grDevices::xy.coords()]). |
|
618 |
#' @return |
|
619 |
#' Returns a [`list`] with two components `x` and `y`. |
|
620 |
#' @author N. Frerebeau |
|
621 |
#' @example inst/examples/ex-mean.R |
|
622 |
#' @docType methods |
|
623 |
#' @family signal processing methods |
|
624 |
#' @aliases signal_mean-method |
|
625 |
setGeneric( |
|
626 |
name = "signal_mean", |
|
627 | 3x |
def = function(...) standardGeneric("signal_mean") |
628 |
) |
|
629 | ||
630 |
#' Subset |
|
631 |
#' |
|
632 |
#' @description |
|
633 |
#' * `signal_select()` allows to subset by values of `x`. |
|
634 |
#' * `signal_slice()` allows to subset by position along `x`. |
|
635 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
636 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
637 |
#' @param subset An [`integer`] vector giving either positive values to keep, |
|
638 |
#' or negative values to drop. The values provided must be either all |
|
639 |
#' positive or all negative (coerced to integer as by [as.integer()]). |
|
640 |
#' @param from,to A [`numeric`] value giving the first and last value (in `x` |
|
641 |
#' unit) to be selected. |
|
642 |
#' @param ... Currently not used. |
|
643 |
#' @return |
|
644 |
#' Returns a [`list`] with two components `x` and `y`. |
|
645 |
#' @author N. Frerebeau |
|
646 |
#' @example inst/examples/ex-subset.R |
|
647 |
#' @docType methods |
|
648 |
#' @family signal processing methods |
|
649 |
#' @name subset |
|
650 |
#' @rdname subset |
|
651 |
NULL |
|
652 | ||
653 |
#' @rdname subset |
|
654 |
#' @aliases signal_select-method |
|
655 |
setGeneric( |
|
656 |
name = "signal_select", |
|
657 | 4x |
def = function(x, y, ...) standardGeneric("signal_select") |
658 |
) |
|
659 | ||
660 |
#' @rdname subset |
|
661 |
#' @aliases signal_slice-method |
|
662 |
setGeneric( |
|
663 |
name = "signal_slice", |
|
664 | 6x |
def = function(x, y, ...) standardGeneric("signal_slice") |
665 |
) |
|
666 | ||
667 |
#' Shift the X Scale |
|
668 |
#' |
|
669 |
#' Shifts the `x` scale by a given value. |
|
670 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
671 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
672 |
#' @param lag A [`numeric`] vector specifying the offset. |
|
673 |
#' @param ... Currently not used. |
|
674 |
#' @return |
|
675 |
#' Returns a [`list`] with two components `x` and `y`. |
|
676 |
#' @author N. Frerebeau |
|
677 |
#' @example inst/examples/ex-shift.R |
|
678 |
#' @docType methods |
|
679 |
#' @family signal processing methods |
|
680 |
#' @aliases signal_shift-method |
|
681 |
setGeneric( |
|
682 |
name = "signal_shift", |
|
683 | 2x |
def = function(x, y, lag, ...) standardGeneric("signal_shift") |
684 |
) |
|
685 | ||
686 |
#' Drift Intensities |
|
687 |
#' |
|
688 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
689 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
690 |
#' @param lag A [`numeric`] vector specifying the offset or any object that can |
|
691 |
#' be interpreted in a suitable way (see [grDevices::xy.coords()]) |
|
692 |
#' @param subtract A [`logical`] scalar: should `lag` be subtracted to `y`? |
|
693 |
#' @param ... Currently not used. |
|
694 |
#' @return |
|
695 |
#' Returns a [`list`] with two components `x` and `y`. |
|
696 |
#' @author N. Frerebeau |
|
697 |
#' @example inst/examples/ex-drift.R |
|
698 |
#' @docType methods |
|
699 |
#' @family signal processing methods |
|
700 |
#' @aliases signal_drift-method |
|
701 |
setGeneric( |
|
702 |
name = "signal_drift", |
|
703 | 8x |
def = function(x, y, lag, ...) standardGeneric("signal_drift") |
704 |
) |
|
705 | ||
706 |
# Smooth ======================================================================= |
|
707 |
#' Rectangular Smoothing |
|
708 |
#' |
|
709 |
#' Unweighted sliding-average or rectangular Smoothing. |
|
710 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
711 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
712 |
#' @param m An odd [`integer`] giving the window size (i.e. the number of |
|
713 |
#' adjacent points to be used; see [`window_sliding()`]). |
|
714 |
#' @param ... Currently not used. |
|
715 |
#' @details |
|
716 |
#' It replaces each point in the signal with the average of \eqn{m} adjacent |
|
717 |
#' points. |
|
718 |
#' @note |
|
719 |
#' There will be \eqn{(m - 1) / 2} points both at the beginning and at the end |
|
720 |
#' of the data series for which a complete \eqn{m}-width window cannot be |
|
721 |
#' obtained. To prevent data loss, progressively wider/narrower windows are |
|
722 |
#' used at both ends of the data series. |
|
723 |
#' @return |
|
724 |
#' Returns a [`list`] with two components `x` and `y`. |
|
725 |
#' @author N. Frerebeau |
|
726 |
#' @example inst/examples/ex-smooth.R |
|
727 |
#' @docType methods |
|
728 |
#' @family smoothing methods |
|
729 |
#' @aliases smooth_rectangular-method |
|
730 |
setGeneric( |
|
731 |
name = "smooth_rectangular", |
|
732 | 2x |
def = function(x, y, ...) standardGeneric("smooth_rectangular") |
733 |
) |
|
734 | ||
735 |
#' Triangular Smoothing |
|
736 |
#' |
|
737 |
#' Weighted sliding-average or triangular smoothing. |
|
738 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
739 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
740 |
#' @param m An odd [`integer`] giving the window size (i.e. the number of |
|
741 |
#' adjacent points to be used; see [`window_sliding()`]). |
|
742 |
#' @param ... Currently not used. |
|
743 |
#' @details |
|
744 |
#' It replaces each point in the signal with the weighted mean of \eqn{m} |
|
745 |
#' adjacent points. |
|
746 |
#' @note |
|
747 |
#' There will be \eqn{(m - 1) / 2} points both at the beginning and at the end |
|
748 |
#' of the data series for which a complete \eqn{m}-width window cannot be |
|
749 |
#' obtained. To prevent data loss, progressively wider/narrower windows are |
|
750 |
#' used at both ends of the data series. |
|
751 |
#' @return |
|
752 |
#' Returns a [`list`] with two components `x` and `y`. |
|
753 |
#' @author N. Frerebeau |
|
754 |
#' @example inst/examples/ex-smooth.R |
|
755 |
#' @docType methods |
|
756 |
#' @family smoothing methods |
|
757 |
#' @aliases smooth_triangular-method |
|
758 |
setGeneric( |
|
759 |
name = "smooth_triangular", |
|
760 | 2x |
def = function(x, y, ...) standardGeneric("smooth_triangular") |
761 |
) |
|
762 | ||
763 |
#' Loess Smoothing |
|
764 |
#' |
|
765 |
#' Smoothes intensities by loess fitting. |
|
766 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
767 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
768 |
#' @param span An [`integer`] specifying the degree of smoothing (see |
|
769 |
#' [stats::loess()]). |
|
770 |
#' @param ... Extra arguments to be passed to [stats::loess()]. |
|
771 |
#' @return |
|
772 |
#' Returns a [`list`] with two components `x` and `y`. |
|
773 |
#' @author N. Frerebeau |
|
774 |
#' @example inst/examples/ex-smooth.R |
|
775 |
#' @docType methods |
|
776 |
#' @family smoothing methods |
|
777 |
#' @aliases smooth_loess-method |
|
778 |
setGeneric( |
|
779 |
name = "smooth_loess", |
|
780 | 2x |
def = function(x, y, ...) standardGeneric("smooth_loess") |
781 |
) |
|
782 | ||
783 |
#' Savitzky-Golay Filter |
|
784 |
#' |
|
785 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
786 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
787 |
#' @param m An odd [`integer`] giving the window size (i.e. the number of |
|
788 |
#' adjacent points to be used). |
|
789 |
#' @param p An [`integer`] giving the degree of the polynomial to be used. |
|
790 |
#' @param ... Currently not used. |
|
791 |
#' @details |
|
792 |
#' This method is based on the least-squares fitting of polynomials to |
|
793 |
#' segments of \eqn{m} adjacent points. |
|
794 |
#' @note |
|
795 |
#' There will be \eqn{(m - 1) / 2} points both at the beginning and at the end |
|
796 |
#' of the data series for which a complete \eqn{m}-width window cannot be |
|
797 |
#' obtained. To prevent data loss, the original \eqn{(m - 1) / 2} points at |
|
798 |
#' both ends of the data series are preserved. |
|
799 |
#' @return |
|
800 |
#' Returns a [`list`] with two components `x` and `y`. |
|
801 |
#' @references |
|
802 |
#' Gorry, P. A. (1990). General Least-Squares Smoothing and Differentiation by |
|
803 |
#' the Convolution (Savitzky-Golay) Method. *Analytical Chemistry*, 62(6), |
|
804 |
#' p. 570-573. \doi{10.1021/ac00205a007}. |
|
805 |
#' |
|
806 |
#' Savitzky, A. & Golay, M. J. E. (1964). Smoothing and Differentiation of |
|
807 |
#' Data by Simplified Least Squares Procedures. *Analytical Chemistry*, |
|
808 |
#' 36(8), p. 1627-1639. \doi{10.1021/ac60214a047}. |
|
809 |
#' @author N. Frerebeau |
|
810 |
#' @example inst/examples/ex-smooth.R |
|
811 |
#' @docType methods |
|
812 |
#' @family smoothing methods |
|
813 |
#' @aliases smooth_savitzky-method |
|
814 |
setGeneric( |
|
815 |
name = "smooth_savitzky", |
|
816 | 2x |
def = function(x, y, ...) standardGeneric("smooth_savitzky") |
817 |
) |
|
818 | ||
819 |
#' Whittaker Smoothing |
|
820 |
#' |
|
821 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
822 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
823 |
#' @param lambda An [`integer`] giving the smoothing parameter. The larger |
|
824 |
#' `lambda` is, the smoother the curve. |
|
825 |
#' @param d An [`integer`] specifying the order of the penalty. |
|
826 |
#' @param sparse A [`logical`] scalar: should sparse matrices be used for |
|
827 |
#' computation? If `TRUE`, \pkg{Matrix} is required. |
|
828 |
#' @param ... Currently not used. |
|
829 |
#' @return |
|
830 |
#' Returns a [`list`] with two components `x` and `y`. |
|
831 |
#' @references |
|
832 |
#' Eilers, P. H. C. (2003). A Perfect Smoother. *Analytical Chemistry*, |
|
833 |
#' 75(14): 3631-36. \doi{10.1021/ac034173t}. |
|
834 |
#' @author N. Frerebeau |
|
835 |
#' @example inst/examples/ex-smooth.R |
|
836 |
#' @docType methods |
|
837 |
#' @family smoothing methods |
|
838 |
#' @aliases smooth_whittaker-method |
|
839 |
setGeneric( |
|
840 |
name = "smooth_whittaker", |
|
841 | 5x |
def = function(x, y, ...) standardGeneric("smooth_whittaker") |
842 |
) |
|
843 | ||
844 |
#' Penalized Likelihood Smoothing |
|
845 |
#' |
|
846 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
847 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
848 |
#' @param lambda An [`integer`] giving the smoothing parameter. The larger |
|
849 |
#' `lambda` is, the smoother the curve. |
|
850 |
#' @param d An [`integer`] specifying the order of the penalty. |
|
851 |
#' @param SE A [`logical`] scalar: should standard errors be returned? |
|
852 |
#' @param progress A [`logical`] scalar: should a progress bar be displayed? |
|
853 |
#' @param ... Currently not used. |
|
854 |
#' @return |
|
855 |
#' Returns a [`list`] with two components `x` and `y`. |
|
856 |
#' @note |
|
857 |
#' \pkg{Matrix} is required. |
|
858 |
#' @references |
|
859 |
#' de Rooi, J. J., van der Pers, N. M., Hendrikx, R. W. A., Delhez, R., |
|
860 |
#' Böttger A. J. and Eilers, P. H. C. (2014). Smoothing of X-ray diffraction |
|
861 |
#' data and Ka2 elimination using penalized likelihood and the composite link |
|
862 |
#' model. *Journal of Applied Crystallography*, 47: 852-860. |
|
863 |
#' \doi{10.1107/S1600576714005809} |
|
864 |
#' @author J. J. de Rooi *et al.* (original R code). |
|
865 |
#' @example inst/examples/ex-xrd-ka2.R |
|
866 |
#' @docType methods |
|
867 |
#' @family smoothing methods |
|
868 |
#' @aliases smooth_likelihood-method |
|
869 |
setGeneric( |
|
870 |
name = "smooth_likelihood", |
|
871 | ! |
def = function(x, y, ...) standardGeneric("smooth_likelihood") |
872 |
) |
|
873 | ||
874 |
# Strip ======================================================================== |
|
875 |
#' Strip XRD ka2 |
|
876 |
#' |
|
877 |
#' @param x,y A [`numeric`] vector. If `y` is missing, an attempt is made to |
|
878 |
#' interpret `x` in a suitable way (see [grDevices::xy.coords()]). |
|
879 |
#' @param lambda An [`integer`] giving the smoothing parameter. The larger |
|
880 |
#' `lambda` is, the smoother the curve. |
|
881 |
#' @param wave A length-two [`numeric`] vector giving the characteristic |
|
882 |
#' wavelengths of the anode material (defaults to copper). |
|
883 |
#' @param tau A length-one [`numeric`] vector giving the ratio between |
|
884 |
#' \eqn{\alpha}1 and \eqn{\alpha}2 line intensities (defaults to 1/2). |
|
885 |
#' @param nseg A length-one [`numeric`] vector specifying the number of equally |
|
886 |
#' sized segments for B-spline basis matrix computation. |
|
887 |
#' @param progress A [`logical`] scalar: should a progress bar be displayed? |
|
888 |
#' @param ... Currently not used. |
|
889 |
#' @return |
|
890 |
#' Returns a [`list`] with two components `x` and `y`. |
|
891 |
#' @note |
|
892 |
#' \pkg{Matrix} is required. |
|
893 |
#' @author J. J. de Rooi *et al.* (original R code). |
|
894 |
#' @references |
|
895 |
#' de Rooi, J. J., van der Pers, N. M., Hendrikx, R. W. A., Delhez, R., |
|
896 |
#' Böttger A. J. and Eilers, P. H. C. (2014). Smoothing of X-ray diffraction |
|
897 |
#' data and Ka2 elimination using penalized likelihood and the composite link |
|
898 |
#' model. *Journal of Applied Crystallography*, 47: 852-860. |
|
899 |
#' \doi{10.1107/S1600576714005809} |
|
900 |
#' @example inst/examples/ex-xrd-ka2.R |
|
901 |
#' @docType methods |
|
902 |
#' @family specialized tools |
|
903 |
#' @aliases ka2_strip_penalized-method |
|
904 |
setGeneric( |
|
905 |
name = "ka2_strip_penalized", |
|
906 | ! |
def = function(x, y, ...) standardGeneric("ka2_strip_penalized") |
907 |
) |
|
908 | ||
909 |
# Windows ====================================================================== |
|
910 |
#' Sliding Windows |
|
911 |
#' |
|
912 |
#' @param n An [`integer`] giving the length of the data series (will be coerced |
|
913 |
#' with [`as.integer()`] and hence truncated toward zero). |
|
914 |
#' @param m An odd [`integer`] giving the window size, i.e. the number of |
|
915 |
#' adjacent points to be used (will be coerced with [`as.integer()`] and hence |
|
916 |
#' truncated toward zero). |
|
917 |
#' @param i A vector [`integer`] specifying the indices of the data points for |
|
918 |
#' which windows should be returned. If `NULL` (the default), windows are |
|
919 |
#' evaluated for each data point. |
|
920 |
#' @description |
|
921 |
#' There will be \eqn{(m - 1) / 2} points both at the beginning and at the end |
|
922 |
#' of the data series for which a complete \eqn{m}-width window cannot be |
|
923 |
#' obtained. To prevent data loss, progressively wider/narrower windows are |
|
924 |
#' evaluated at both ends of the data series. |
|
925 |
#' @param ... Currently not used. |
|
926 |
#' @return |
|
927 |
#' Returns a length-\eqn{n} [`list`] of [`integer`] vectors (indices of the |
|
928 |
#' data points in each window). |
|
929 |
#' @author N. Frerebeau |
|
930 |
#' @example inst/examples/ex-windows.R |
|
931 |
#' @docType methods |
|
932 |
#' @family moving windows |
|
933 |
#' @aliases window_sliding-method |
|
934 |
setGeneric( |
|
935 |
name = "window_sliding", |
|
936 | 14x |
def = function(n, m, ...) standardGeneric("window_sliding") |
937 |
) |
|
938 | ||
939 |
#' Tumbling Windows |
|
940 |
#' |
|
941 |
#' @param n An [`integer`] giving the length of the data series (will be coerced |
|
942 |
#' with [`as.integer()`] and hence truncated toward zero). |
|
943 |
#' @param m An [`integer`] giving the window size, i.e. the number of |
|
944 |
#' adjacent points to be used (will be coerced with [`as.integer()`] and hence |
|
945 |
#' truncated toward zero). |
|
946 |
#' @param drop A [`logical`] scalar: if `m` is not a multiple of `n`, should the |
|
947 |
#' last data points be removed so that all windows have the same length? |
|
948 |
#' @param ... Currently not used. |
|
949 |
#' @return |
|
950 |
#' Returns a [`list`] of [`integer`] vectors (indices of the data points in |
|
951 |
#' each window). |
|
952 |
#' @author N. Frerebeau |
|
953 |
#' @example inst/examples/ex-windows.R |
|
954 |
#' @docType methods |
|
955 |
#' @family moving windows |
|
956 |
#' @aliases window_tumbling-method |
|
957 |
setGeneric( |
|
958 |
name = "window_tumbling", |
|
959 | 4x |
def = function(n, m, ...) standardGeneric("window_tumbling") |
960 |
) |
1 |
# RESAMPLE |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
# Bin ========================================================================== |
|
6 |
#' @export |
|
7 |
#' @rdname resample_bin |
|
8 |
#' @aliases resample_bin,numeric,numeric-method |
|
9 |
setMethod( |
|
10 |
f = "resample_bin", |
|
11 |
signature = signature(x = "numeric", y = "numeric"), |
|
12 |
definition = function(x, y, by, f = mean, ...) { |
|
13 | 1x |
assert_length(y, length(x)) |
14 | ||
15 | 1x |
k <- length(x) %% by |
16 | 1x |
if (k != 0) { |
17 | 1x |
x <- utils::tail(x, -k) |
18 | 1x |
y <- utils::tail(y, -k) |
19 |
} |
|
20 | 1x |
i <- cut(seq_along(x), breaks = length(x) / by, labels = FALSE) |
21 | ||
22 | 1x |
mid <- tapply(X = x, INDEX = i, FUN = mean, simplify = FALSE) |
23 | 1x |
bin <- tapply(X = y, INDEX = i, FUN = f, ..., simplify = FALSE) |
24 | ||
25 | 1x |
xy <- list(x = unlist(mid), y = unlist(bin)) |
26 | 1x |
xy |
27 |
} |
|
28 |
) |
|
29 | ||
30 |
#' @export |
|
31 |
#' @rdname resample_bin |
|
32 |
#' @aliases resample_bin,ANY,missing-method |
|
33 |
setMethod( |
|
34 |
f = "resample_bin", |
|
35 |
signature = signature(x = "ANY", y = "missing"), |
|
36 |
definition = function(x, y, by, f = sum) { |
|
37 | 1x |
xy <- grDevices::xy.coords(x) |
38 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, by = by, f = f) |
39 |
} |
|
40 |
) |
|
41 | ||
42 |
# Downsample =================================================================== |
|
43 |
#' @export |
|
44 |
#' @rdname resample_down |
|
45 |
#' @aliases resample_down,numeric,numeric-method |
|
46 |
setMethod( |
|
47 |
f = "resample_down", |
|
48 |
signature = signature(x = "numeric", y = "numeric"), |
|
49 |
definition = function(x, y, by) { |
|
50 | 1x |
assert_length(y, length(x)) |
51 | ||
52 | 1x |
i <- seq(from = 1, to = length(x), by = by) |
53 | 1x |
xy <- list(x = x[i], y = y[i]) |
54 | 1x |
xy |
55 |
} |
|
56 |
) |
|
57 | ||
58 |
#' @export |
|
59 |
#' @rdname resample_down |
|
60 |
#' @aliases resample_down,ANY,missing-method |
|
61 |
setMethod( |
|
62 |
f = "resample_down", |
|
63 |
signature = signature(x = "ANY", y = "missing"), |
|
64 |
definition = function(x, y, by) { |
|
65 | 1x |
xy <- grDevices::xy.coords(x) |
66 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, by = by) |
67 |
} |
|
68 |
) |
|
69 | ||
70 |
# Interpolate ================================================================== |
|
71 |
#' @export |
|
72 |
#' @rdname resample_interpolate |
|
73 |
#' @aliases resample_interpolate,numeric,numeric-method |
|
74 |
setMethod( |
|
75 |
f = "resample_interpolate", |
|
76 |
signature = signature(x = "numeric", y = "numeric"), |
|
77 |
definition = function(x, y, from, to, by, ...) { |
|
78 | 1x |
assert_length(y, length(x)) |
79 | ||
80 |
## New x scale |
|
81 | 1x |
x_scale <- seq(from = from, to = to, by = by) |
82 | ||
83 |
## Interpolate |
|
84 | 1x |
new_data <- stats::approx(x = x, y = y, xout = x_scale, ...) |
85 | ||
86 | 1x |
xy <- list(x = new_data$x, y = new_data$y) |
87 | 1x |
xy |
88 |
} |
|
89 |
) |
|
90 | ||
91 |
#' @export |
|
92 |
#' @rdname resample_interpolate |
|
93 |
#' @aliases resample_interpolate,ANY,missing-method |
|
94 |
setMethod( |
|
95 |
f = "resample_interpolate", |
|
96 |
signature = signature(x = "ANY", y = "missing"), |
|
97 |
definition = function(x, y, from, to, by, ...) { |
|
98 | 1x |
xy <- grDevices::xy.coords(x) |
99 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, from = from, to = to, by = by, ...) |
100 |
} |
|
101 |
) |
1 |
# SLIDING WINDOWS |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
#' @export |
|
6 |
#' @rdname window_sliding |
|
7 |
#' @aliases window_sliding,integer,integer-method |
|
8 |
setMethod( |
|
9 |
f = "window_sliding", |
|
10 |
signature = signature(n = "integer", m = "integer"), |
|
11 |
definition = function(n, m, i = NULL) { |
|
12 |
## Validation |
|
13 | 7x |
assert_odd(m) |
14 | ||
15 |
## Index |
|
16 | 7x |
k <- (m - 1) / 2 |
17 | 7x |
x <- if (is.null(i)) seq_len(n) else i |
18 | ||
19 | 7x |
left <- x - k |
20 | 7x |
left <- ifelse(left > 0, left, 1) |
21 | ||
22 | 7x |
right <- x + k |
23 | 7x |
right <- ifelse(right < n, right, n) |
24 | ||
25 | 7x |
mapply( |
26 | 7x |
FUN = seq, |
27 | 7x |
from = left, |
28 | 7x |
to = right, |
29 | 7x |
MoreArgs = list(by = 1), |
30 | 7x |
SIMPLIFY = FALSE |
31 |
) |
|
32 |
} |
|
33 |
) |
|
34 | ||
35 |
#' @export |
|
36 |
#' @rdname window_sliding |
|
37 |
#' @aliases window_sliding,numeric,numeric-method |
|
38 |
setMethod( |
|
39 |
f = "window_sliding", |
|
40 |
signature = signature(n = "numeric", m = "numeric"), |
|
41 |
definition = function(n, m, i = NULL) { |
|
42 | 7x |
n <- as.integer(n) |
43 | 7x |
m <- as.integer(m) |
44 | 7x |
methods::callGeneric(n, m, i = i) |
45 |
} |
|
46 |
) |
|
47 | ||
48 |
#' @export |
|
49 |
#' @rdname window_tumbling |
|
50 |
#' @aliases window_tumbling,integer,integer-method |
|
51 |
setMethod( |
|
52 |
f = "window_tumbling", |
|
53 |
signature = signature(n = "integer", m = "integer"), |
|
54 |
definition = function(n, m, drop = FALSE) { |
|
55 | 2x |
k <- n %% m |
56 | 2x |
if (drop && k != 0) { |
57 | 1x |
n <- n - k |
58 |
} |
|
59 | ||
60 | 2x |
x <- seq_len(n) |
61 | 2x |
i <- cut(x, breaks = n / m, labels = FALSE) |
62 | 2x |
split(x, f = i) |
63 |
} |
|
64 |
) |
|
65 | ||
66 |
#' @export |
|
67 |
#' @rdname window_tumbling |
|
68 |
#' @aliases window_tumbling,numeric,numeric-method |
|
69 |
setMethod( |
|
70 |
f = "window_tumbling", |
|
71 |
signature = signature(n = "numeric", m = "numeric"), |
|
72 |
definition = function(n, m, drop = FALSE) { |
|
73 | 2x |
n <- as.integer(n) |
74 | 2x |
m <- as.integer(m) |
75 | 2x |
methods::callGeneric(n, m, drop = drop) |
76 |
} |
|
77 |
) |
1 |
# SIGNAL INTEGRATION |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
#' @export |
|
6 |
#' @rdname integrate_rectangle |
|
7 |
#' @aliases integrate_rectangle,numeric,numeric-method |
|
8 |
setMethod( |
|
9 |
f = "integrate_rectangle", |
|
10 |
signature = signature(x = "numeric", y = "numeric"), |
|
11 |
definition = function(x, y, right = FALSE) { |
|
12 | 4x |
assert_length(y, length(x)) |
13 | ||
14 | 4x |
h <- if (right) utils::tail(y, -1) else utils::head(y, -1) |
15 | 4x |
sum(h * diff(x)) |
16 |
} |
|
17 |
) |
|
18 | ||
19 |
#' @export |
|
20 |
#' @rdname integrate_rectangle |
|
21 |
#' @aliases integrate_rectangle,ANY,missing-method |
|
22 |
setMethod( |
|
23 |
f = "integrate_rectangle", |
|
24 |
signature = signature(x = "ANY", y = "missing"), |
|
25 |
definition = function(x, right = FALSE) { |
|
26 | 3x |
xy <- grDevices::xy.coords(x) |
27 | 3x |
methods::callGeneric(x = xy$x, y = xy$y, right = right) |
28 |
} |
|
29 |
) |
|
30 | ||
31 |
#' @export |
|
32 |
#' @rdname integrate_trapezoid |
|
33 |
#' @aliases integrate_trapezoid,numeric,numeric-method |
|
34 |
setMethod( |
|
35 |
f = "integrate_trapezoid", |
|
36 |
signature = signature(x = "numeric", y = "numeric"), |
|
37 |
definition = function(x, y) { |
|
38 | 1x |
assert_length(y, length(x)) |
39 | ||
40 | 1x |
sum((utils::head(y, -1) + utils::tail(y, -1)) * diff(x) / 2) |
41 |
} |
|
42 |
) |
|
43 | ||
44 |
#' @export |
|
45 |
#' @rdname integrate_trapezoid |
|
46 |
#' @aliases integrate_trapezoid,ANY,missing-method |
|
47 |
setMethod( |
|
48 |
f = "integrate_trapezoid", |
|
49 |
signature = signature(x = "ANY", y = "missing"), |
|
50 |
definition = function(x) { |
|
51 | 1x |
xy <- grDevices::xy.coords(x) |
52 | 1x |
methods::callGeneric(x = xy$x, y = xy$y) |
53 |
} |
|
54 |
) |
1 |
# REPLACE |
|
2 |
#' @include AllGenerics.R |
|
3 |
NULL |
|
4 | ||
5 |
# Threshold ==================================================================== |
|
6 |
#' @export |
|
7 |
#' @rdname replace_threshold |
|
8 |
#' @aliases replace_threshold,numeric,numeric,function-method |
|
9 |
setMethod( |
|
10 |
f = "replace_threshold", |
|
11 |
signature = signature(x = "numeric", y = "numeric", threshold = "function"), |
|
12 |
definition = function(x, y, threshold, value = 0, ...) { |
|
13 | 1x |
assert_length(y, length(x)) |
14 | ||
15 | 1x |
threshold <- threshold(y, ...) |
16 | 1x |
methods::callGeneric(x, y, threshold = threshold, value = value) |
17 |
} |
|
18 |
) |
|
19 | ||
20 |
#' @export |
|
21 |
#' @rdname replace_threshold |
|
22 |
#' @aliases replace_threshold,ANY,missing,function-method |
|
23 |
setMethod( |
|
24 |
f = "replace_threshold", |
|
25 |
signature = signature(x = "ANY", y = "missing", threshold = "function"), |
|
26 |
definition = function(x, threshold, value = 0, ...) { |
|
27 | 1x |
xy <- grDevices::xy.coords(x) |
28 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, threshold = threshold, |
29 | 1x |
value = value) |
30 |
} |
|
31 |
) |
|
32 | ||
33 |
#' @export |
|
34 |
#' @rdname replace_threshold |
|
35 |
#' @aliases replace_threshold,numeric,numeric,numeric-method |
|
36 |
setMethod( |
|
37 |
f = "replace_threshold", |
|
38 |
signature = signature(x = "numeric", y = "numeric", threshold = "numeric"), |
|
39 |
definition = function(x, y, threshold, value = 0, ...) { |
|
40 | 3x |
assert_length(y, length(x)) |
41 | ||
42 | 3x |
i <- y < threshold |
43 | 3x |
y[i] <- value |
44 | ||
45 | 3x |
xy <- list(x = x, y = y) |
46 | 3x |
xy |
47 |
} |
|
48 |
) |
|
49 | ||
50 |
#' @export |
|
51 |
#' @rdname replace_threshold |
|
52 |
#' @aliases replace_threshold,ANY,missing,numeric-method |
|
53 |
setMethod( |
|
54 |
f = "replace_threshold", |
|
55 |
signature = signature(x = "ANY", y = "missing", threshold = "numeric"), |
|
56 |
definition = function(x, threshold, value = 0, ...) { |
|
57 | 1x |
xy <- grDevices::xy.coords(x) |
58 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, threshold = threshold, |
59 | 1x |
value = value) |
60 |
} |
|
61 |
) |
|
62 | ||
63 |
# Negative ===================================================================== |
|
64 |
#' @export |
|
65 |
#' @rdname replace_negative |
|
66 |
#' @aliases replace_negative,numeric,numeric-method |
|
67 |
setMethod( |
|
68 |
f = "replace_negative", |
|
69 |
signature = signature(x = "numeric", y = "numeric"), |
|
70 |
definition = function(x, y, value = 0) { |
|
71 | 1x |
replace_threshold(x, y, threshold = 0, value = value) |
72 |
} |
|
73 |
) |
|
74 | ||
75 |
#' @export |
|
76 |
#' @rdname replace_negative |
|
77 |
#' @aliases replace_negative,ANY,missing-method |
|
78 |
setMethod( |
|
79 |
f = "replace_negative", |
|
80 |
signature = signature(x = "ANY", y = "missing"), |
|
81 |
definition = function(x, value = 0) { |
|
82 | 1x |
xy <- grDevices::xy.coords(x) |
83 | 1x |
methods::callGeneric(x = xy$x, y = xy$y, value = value) |
84 |
} |
|
85 |
) |