Nothing
## filter_ma() ===========================================
test_that("filter_ma() returns expected smoothed values", {
x <- c(1, 2, 3, 4, 5)
result <- filter_ma(x, width = 3, verbose = FALSE, na.rm = TRUE)
expect_equal(result, c(NA, 2, 3, 4, NA))
result <- filter_ma(x, width = 3, verbose = FALSE, partial = TRUE)
expect_equal(result, c(1.5, 2, 3, 4, 4.5))
# Width-based with single width (floor(1/2) = 0, so just x itself)
result <- filter_ma(x, width = 1, verbose = FALSE)
expect_equal(result, x)
## filter_moving_average alias for filter_ma
result <- filter_moving_average(
x,
width = 3,
verbose = FALSE,
partial = FALSE
)
expect_equal(result, c(NA, 2, 3, 4, NA))
result <- filter_moving_average(
x,
width = 3,
verbose = FALSE,
partial = TRUE
)
expect_equal(result, c(1.5, 2, 3, 4, 4.5))
})
test_that("filter_ma() handles custom time vectors", {
x <- c(10, 20, 30, 40, 50, 60)
t <- c(0, 1, 3, 6, 10, 11)
# Span-based with time gaps
result <- filter_ma(x, t, span = 2, verbose = FALSE)
expect_equal(result, c(15, 15, 30, 40, 55, 55))
})
test_that("filter_ma() handles NA values correctly", {
x <- c(1, NA, 3, 4, 5)
result <- filter_ma(
x,
width = 3,
partial = FALSE,
na.rm = FALSE,
verbose = FALSE
)
expect_equal(result, c(rep(NA, 3), 4, NA))
result <- filter_ma(
x,
width = 3,
partial = TRUE,
na.rm = TRUE,
)
expect_equal(result, c(1, 2, 3.5, 4, 4.5))
result <- filter_ma(
x,
width = 3,
partial = FALSE, ## min 3 valid obs at edges
na.rm = TRUE, ## mean excludes NA
)
expect_equal(result, c(NA, 2, 3.5, 4, NA))
result <- filter_ma(
x,
width = 3,
partial = TRUE,
na.rm = FALSE,
verbose = FALSE
)
expect_equal(result, c(rep(NA, 3), 4, 4.5))
x <- c(1, NA, NA, NA, NA, 6, 7, 8)
result <- filter_ma(x, width = 3, partial = TRUE, na.rm = TRUE)
expect_equal(result, c(1, 1, NA, NA, 6, 6.5, 7, 7.5))
})
test_that("filter_ma() validates x as numeric", {
expect_error(
filter_ma(c("a", "b", "c"), width = 3, verbose = FALSE),
"x.*numeric"
)
})
test_that("filter_ma() validates t as numeric", {
expect_error(
filter_ma(
1:5,
t = letters[1:5],
width = 3,
verbose = FALSE
),
"t.*numeric"
)
})
test_that("filter_ma() validates x and t have same length", {
expect_error(
filter_ma(1:5, t = 1:3, width = 3, verbose = FALSE),
"equal length"
)
})
test_that("filter_ma() validates width & span constraints", {
x <- 1:10
## width or span required
expect_error(
filter_ma(x, verbose = FALSE),
"width.*or.*span.*must be defined"
)
# Width must be positive integer
expect_error(
filter_ma(x, width = -1, verbose = FALSE),
"width.*integer"
)
## width = 0
expect_error(
filter_ma(x, width = 0, verbose = FALSE),
"width.*integer"
)
## width > length(x)
expect_true(all(
filter_ma(x, width = 21, partial = TRUE) == mean(x)
))
# Span must be positive
expect_error(
filter_ma(x, span = -1, verbose = FALSE),
"span.*numeric"
)
## span = 0
expect_equal(filter_ma(x, span = 0, verbose = FALSE), x)
## span = 0.5 takes nearest 1 sample
expect_equal(filter_ma(x, span = 0.5, verbose = FALSE), x)
## span > length(x)
expect_true(all(
filter_ma(x, span = 21, partial = TRUE) == mean(x)
))
})
test_that("filter_ma() warns when both width and span provided", {
## should produce warning
expect_message(
filter_ma(1:5, width = 3, span = 2),
"width.*span"
)
old_verbose <- getOption("mnirs.verbose")
on.exit(options(mnirs.verbose = old_verbose), add = TRUE)
options(mnirs.verbose = FALSE)
## should not produce warning
expect_silent(
result <- filter_ma(1:5, width = 3, span = 2)
)
})
test_that("filter_ma() handles edge cases", {
# Single value
expect_equal(
filter_ma(5, width = 1, verbose = FALSE),
5,
ignore_attr = TRUE
)
# Two values
expect_equal(
filter_ma(c(1, 2), width = 1, verbose = FALSE),
c(1, 2)
)
# All NA
expect_error(
filter_ma(c(NA, NA, NA), width = 3, verbose = FALSE),
"x.*numeric"
)
})
test_that("filter_ma with insufficient width returns NA with warning", {
t <- 1:10
x <- c(1, NA, 3, NA, 5, NA, 7, NA, 9, NA)
expect_all_equal(filter_ma(x, t, width = 2), NA_real_) |>
expect_warning("Set.*na.rm = TRUE")
x <- c(NA, 1, NA)
expect_error(
filter_ma(x, width = 3, na.rm = TRUE),
"Insufficient.*samples"
)
})
## filter_butter() =========================================
test_that("filter_butter validates inputs correctly", {
expect_error(
filter_butter(x = "not_numeric", order = 1, W = 0.1),
"x.*numeric"
)
expect_error(
filter_butter(x = 1:10, order = 0, W = 0.1),
"n.*integer"
)
expect_error(
filter_butter(x = 1:10, order = -1, W = 0.1),
"n.*integer"
)
expect_error(
filter_butter(x = 1:10, order = 1.5, W = 0.1),
"n.*integer"
)
expect_error(
filter_butter(x = 1:10, order = 1, W = 1.5),
"W.*numeric"
)
expect_error(
filter_butter(x = 1:10, order = 1, W = 0),
"W.*numeric"
)
expect_error(
filter_butter(x = 1:10, order = 1, W = c(0.1, 0.3), type = "low"),
"W.*numeric"
)
})
test_that("filter_butter returns correct output structure", {
x <- rnorm(100)
result <- filter_butter(x, order = 2, W = 0.1)
expect_type(result, "double")
expect_length(result, length(x))
expect_false(anyNA(result))
})
test_that("filter_butter handles different edge options", {
set.seed(42)
x <- sin(2 * pi * 1:50 / 10) + rnorm(50, 0, 0.5)
result_none <- filter_butter(x, order = 2, W = 0.1, edges = "none")
result_rev <- filter_butter(x, order = 2, W = 0.1, edges = "rev")
result_rep1 <- filter_butter(x, order = 2, W = 0.1, edges = "rep1")
expect_length(result_none, length(x))
expect_length(result_rev, length(x))
expect_length(result_rep1, length(x))
# Results should differ at edges
expect_false(identical(result_none, result_rev))
expect_false(identical(result_none, result_rep1))
expect_false(identical(result_rev, result_rep1))
})
test_that("filter_butter handles NA values", {
x_with_na <- c(1:5, NA, 7:10)
# NA propagates through filtering
expect_error(
filter_butter(x_with_na, order = 1, W = 0.1, na.rm = FALSE),
"x.*NA"
)
result <- filter_butter(x_with_na, order = 1, W = 0.1, na.rm = TRUE)
expect_true(is.na(result[6]))
# All NA input
expect_error(
filter_butter(rep(NA_real_, 5), order = 1, W = 0.1, na.rm = TRUE),
"x.*valid.*numeric"
)
# Leading/trailing NAs
x_edge_na <- c(NA, NA, 3:8, NA, NA)
result_edge <- filter_butter(x_edge_na, order = 1, W = 0.1, na.rm = TRUE)
expect_true(all(is.na(result_edge[c(1:2, 9:10)])))
expect_length(result_edge, length(x_edge_na))
})
test_that("filter_butter handles different filter types", {
set.seed(123)
x <- rnorm(100)
low <- filter_butter(x, order = 2, W = 0.1, type = "low")
high <- filter_butter(x, order = 2, W = 0.9, type = "high")
stop <- filter_butter(x, order = 2, W = c(0.3, 0.7), type = "stop")
pass <- filter_butter(x, order = 2, W = c(0.3, 0.7), type = "pass")
expect_length(low, length(x))
expect_length(high, length(x))
expect_length(stop, length(x))
expect_length(pass, length(x))
## wrong W elements
expect_error(
filter_butter(x, order = 2, W = c(0.3, 0.7), type = "low"),
"W.*1-element.*numeric"
)
expect_error(
filter_butter(x, order = 2, W = 0.9, type = "pass"),
"W.*2-element.*numeric"
)
})
test_that("filter_butter handles edge cases", {
# Very short vector
x_short <- c(1, 2, 3)
result <- filter_butter(x_short, order = 1, W = 0.5)
expect_length(result, 3)
# Single value
x_single <- 5
result_single <- filter_butter(x_single, order = 1, W = 0.5)
expect_length(result_single, 1)
# Constant signal
x_const <- rep(10, 50)
result_const <- filter_butter(x_const, order = 1, W = 0.5)
expect_true(all(abs(result_const - 10) < 1e-10))
})
test_that("filter_butter smooths noisy signal", {
set.seed(999)
sin_wave <- sin(2 * pi * 1:100 / 20)
x <- seq_along(sin_wave)
noisy <- sin_wave + rnorm(100, 0, 0.3)
filtered <- filter_butter(noisy, order = 2, W = 0.2)
# Filtered signal should be smoother (lower variance)
expect_lt(var(diff(filtered)), var(diff(noisy)))
})
test_that("filter_butter works visually", {
skip("visual check for filter_butter")
set.seed(999)
sin_wave <- sin(2 * pi * 1:100 / 20)
x <- seq_along(sin_wave)
noisy <- sin_wave + rnorm(100, 0, 0.3)
filtered <- filter_butter(noisy, order = 2, W = 0.2)
ggplot2::ggplot(tibble::tibble()) +
ggplot2::aes(x = x) +
ggplot2::geom_line(ggplot2::aes(y = sin_wave, colour = "sin")) +
ggplot2::geom_line(ggplot2::aes(y = noisy, colour = "noisy")) +
ggplot2::geom_line(ggplot2::aes(y = filtered, colour = "filtered"))
})
## moxy_data for filter_mnirs() ==============================================
moxy_data <- read_mnirs(
file_path = example_mnirs("moxy_ramp.xlsx"),
nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"),
time_channel = c(time = "hh:mm:ss"),
verbose = FALSE
) |>
resample_mnirs(method = "linear", verbose = FALSE)
## Input validation tests =======================================
test_that("filter_mnirs validates input data", {
expect_error(
filter_mnirs(data.frame(x = 1:10)),
"`data`.*data.*frame"
)
})
test_that("filter_mnirs method aliases work", {
result_ma <- filter_mnirs(
moxy_data, method = "moving_average", width = 5, verbose = FALSE
)
result_spl <- filter_mnirs(
moxy_data, method = "smooth_spline", verbose = FALSE
)
expect_equal(
filter_mnirs(
moxy_data, method = "moving average", width = 5, verbose = FALSE
),
result_ma
)
expect_equal(
filter_mnirs(
moxy_data, method = "Moving Average", width = 5, verbose = FALSE
),
result_ma
)
expect_equal(
filter_mnirs(
moxy_data, method = "MA", width = 5, verbose = FALSE
),
result_ma
)
expect_equal(
filter_mnirs(moxy_data, verbose = FALSE),
result_spl
)
expect_equal(
filter_mnirs(moxy_data, method = "spline", verbose = FALSE),
result_spl
)
expect_equal(
filter_mnirs(moxy_data, method = "smooth spline", verbose = FALSE),
result_spl
)
expect_equal(
filter_mnirs(moxy_data, method = "Smooth Spline", verbose = FALSE),
result_spl
)
})
test_that("filter_mnirs validates method argument", {
expect_error(
filter_mnirs(moxy_data, method = "invalid", verbose = FALSE),
"should be one of"
)
})
## Smooth spline tests ==============================================
test_that("smooth_spline filters data correctly", {
result <- filter_mnirs(moxy_data, method = "smooth_spline", verbose = FALSE)
expect_s3_class(result, "mnirs")
expect_equal(nrow(result), nrow(moxy_data))
expect_true(all(c("time", "smo2_left", "smo2_right") %in% names(result)))
# Filtered data should be smoother (lower variance)
expect_lt(var(result$smo2_left), var(moxy_data$smo2_left))
expect_lt(var(result$smo2_right), var(moxy_data$smo2_right))
})
test_that("smooth_spline respects spar parameter", {
result_low <- filter_mnirs(
moxy_data,
method = "smooth_spline",
spar = 0.1,
verbose = FALSE
)
result_high <- filter_mnirs(
moxy_data,
method = "smooth_spline",
spar = 0.9,
verbose = FALSE
)
# Higher spar = more smoothing
expect_lt(var(result_high$smo2_left), var(result_low$smo2_left))
expect_lt(var(result_high$smo2_right), var(result_low$smo2_right))
})
test_that("smooth_spline handles NAs", {
moxy_data <- read_mnirs(
file_path = example_mnirs("moxy_ramp.xlsx"),
nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"),
time_channel = c(time = "hh:mm:ss"),
verbose = FALSE
) |>
resample_mnirs(method = "none", verbose = FALSE)
result <- filter_mnirs(
moxy_data,
method = "smooth_spline",
na.rm = TRUE,
verbose = FALSE
)
expect_true(anyNA(result$smo2_left))
expect_true(anyNA(result$smo2_right))
expect_equal(
which(is.na(result$smo2_right)),
which(is.na(moxy_data$smo2_right))
)
expect_error(
filter_mnirs(
moxy_data, method = "smooth_spline", na.rm = FALSE,
verbose = FALSE
),
"NA"
)
})
test_that("smooth_spline errors with irregular samples", {
moxy_data <- read_mnirs(
file_path = example_mnirs("moxy_ramp.xlsx"),
nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"),
time_channel = c(time = "hh:mm:ss"),
verbose = FALSE
)
expect_error(
filter_mnirs(
moxy_data, method = "smooth_spline", verbose = FALSE
),
"irregular samples"
)
})
test_that("smooth_spline validates spar parameter", {
expect_error(
filter_mnirs(
moxy_data, method = "smooth_spline", spar = -1,
verbose = FALSE
),
"spar.*numeric"
)
expect_error(
filter_mnirs(
moxy_data, method = "smooth_spline", spar = c(0.1, 0.5),
verbose = FALSE
),
"spar.*numeric"
)
})
## Butterworth tests ==============================================
test_that("butterworth low-pass filter works", {
result <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
verbose = FALSE
)
expect_s3_class(result, "mnirs")
expect_equal(nrow(result), nrow(moxy_data))
# Low-pass should reduce high-frequency variance
expect_lt(var(diff(result$smo2_left)), var(diff(moxy_data$smo2_left)))
expect_lt(var(diff(result$smo2_right)), var(diff(moxy_data$smo2_right)))
})
test_that("butterworth accepts fc instead of W", {
sr <- attr(moxy_data, "sample_rate")
result_W <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
verbose = FALSE
)
result_fc <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
fc = 0.1 * sr * 0.5,
verbose = FALSE
)
expect_equal(result_W$smo2_left, result_fc$smo2_left)
})
test_that("butterworth prefers W when both W and fc specified", {
expect_message(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
fc = 5
),
".*W.*fc"
)
})
test_that("butterworth errors without W or fc", {
expect_error(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
verbose = FALSE
),
"W.*fc.*must be defined"
)
})
test_that("butterworth errors for invalid fc", {
expect_error(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
fc = 100,
verbose = FALSE
),
"must be between.*0.*half"
)
})
test_that("butterworth validates filter type", {
expect_error(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "invalid",
W = 0.1,
verbose = FALSE
),
"should be one of"
)
})
test_that("butterworth handles different filter types", {
result_low <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
verbose = FALSE
)
result_high <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "high",
order = 2,
W = 0.1,
verbose = FALSE
)
result_stop <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "stop",
order = 2,
W = c(0.05, 0.15),
verbose = FALSE
)
result_pass <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "pass",
order = 2,
W = c(0.05, 0.15),
verbose = FALSE
)
expect_s3_class(result_low, "mnirs")
expect_s3_class(result_high, "mnirs")
expect_s3_class(result_stop, "mnirs")
expect_s3_class(result_pass, "mnirs")
})
test_that("butterworth validates W length for stop/pass filters", {
expect_error(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "stop",
order = 2,
W = 0.5,
verbose = FALSE
),
"W.*numeric"
)
expect_error(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "pass",
order = 2,
fc = 5,
verbose = FALSE
),
"fc.*numeric"
)
})
test_that("butterworth handles NAs with na.rm = TRUE", {
moxy_data <- read_mnirs(
file_path = example_mnirs("moxy_ramp.xlsx"),
nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"),
time_channel = c(time = "hh:mm:ss"),
verbose = FALSE
) |>
resample_mnirs(method = "none", verbose = FALSE)
result <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
na.rm = TRUE,
verbose = FALSE
)
expect_true(anyNA(result$smo2_left))
expect_true(anyNA(result$smo2_right))
expect_equal(
which(is.na(result$smo2_right)),
which(is.na(moxy_data$smo2_right))
)
expect_error(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
na.rm = FALSE,
verbose = FALSE
),
"NA"
)
})
## Moving average tests ==============================================
test_that("moving_average with width works", {
result <- filter_mnirs(
moxy_data,
method = "moving_average",
width = 10,
verbose = FALSE
)
expect_s3_class(result, "mnirs")
expect_equal(nrow(result), nrow(moxy_data))
# Should smooth the data
expect_lt(
var(diff(result$smo2_left), na.rm = TRUE),
var(diff(moxy_data$smo2_left), na.rm = TRUE)
)
expect_lt(
var(diff(result$smo2_right), na.rm = TRUE),
var(diff(moxy_data$smo2_right), na.rm = TRUE)
)
})
test_that("moving_average with span works", {
result <- filter_mnirs(
moxy_data,
method = "moving_average",
span = 1,
verbose = FALSE
)
expect_s3_class(result, "mnirs")
expect_equal(nrow(result), nrow(moxy_data))
})
test_that("moving_average with larger width = more smoothing", {
result_narrow <- filter_mnirs(
moxy_data,
method = "moving_average",
width = 5,
verbose = FALSE
)
result_wide <- filter_mnirs(
moxy_data,
method = "moving_average",
width = 20,
verbose = FALSE
)
expect_lt(
var(result_wide$smo2_left, na.rm = TRUE),
var(result_narrow$smo2_left, na.rm = TRUE)
)
})
## Channel selection tests ==============================================
test_that("filter_mnirs respects nirs_channels argument", {
result <- filter_mnirs(
moxy_data,
nirs_channels = "smo2_right",
method = "smooth_spline",
spar = 0.5,
verbose = FALSE
)
# channel1 should be filtered
expect_false(identical(result$smo2_right, moxy_data$smo2_right))
expect_lt(var(diff(result$smo2_right)), var(diff(moxy_data$smo2_right)))
expect_true(identical(result$smo2_left, moxy_data$smo2_left))
expect_equal(var(diff(result$smo2_left)), var(diff(moxy_data$smo2_left)))
expect_equal(attr(result, "nirs_channels"), "smo2_right")
expect_false(isTRUE(all.equal(
attr(result, "nirs_channels"),
attr(moxy_data, "nirs_channels")
)))
})
## Metadata preservation tests ==============================================
test_that("filter_mnirs preserves and updates metadata", {
original_meta <- attributes(moxy_data)
result <- filter_mnirs(moxy_data, method = "smooth_spline", verbose = FALSE)
result_meta <- attributes(result)
expect_equal(result_meta$nirs_channels, original_meta$nirs_channels)
expect_equal(result_meta$time_channel, original_meta$time_channel)
})
test_that("butterworth updates sample_rate in metadata", {
result <- filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
sample_rate = 10,
verbose = FALSE
)
expect_equal(attr(result, "sample_rate"), 10)
expect_false(attr(result, "sample_rate") == attr(moxy_data, "sample_rate"))
})
## verbose output tests ==============================================
test_that("verbose output works", {
expect_message(
filter_mnirs(moxy_data, method = "smooth_spline", verbose = TRUE),
"spar ="
) |>
expect_message("spar =")
expect_silent(
filter_mnirs(moxy_data, method = "smooth_spline", verbose = FALSE)
)
expect_message(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
fc = 0.1,
verbose = TRUE
),
"W.*overrides.*fc"
)
expect_silent(
filter_mnirs(
moxy_data,
method = "butterworth",
type = "low",
order = 2,
W = 0.1,
fc = 0.1,
verbose = FALSE
)
)
})
## Integration tests ==============================================
test_that("filter_mnirs works with pipe", {
result <- moxy_data |>
filter_mnirs(method = "smooth_spline", verbose = FALSE)
expect_s3_class(result, "mnirs")
expect_equal(nrow(result), nrow(moxy_data))
expect_lt(var(diff(result$smo2_left)), var(diff(moxy_data$smo2_left)))
})
test_that("multiple filtering operations can be chained", {
result <- moxy_data |>
filter_mnirs(
method = "butterworth",
type = "high",
order = 1,
W = 0.01,
verbose = FALSE
) |>
filter_mnirs(method = "smooth_spline", spar = 0.3, verbose = FALSE)
expect_s3_class(result, "mnirs")
expect_equal(nrow(result), nrow(moxy_data))
})
test_that("filter_mnirs works visually on Moxy data", {
skip("visual check for filter_mnirs() on Moxy data")
data <- read_mnirs(
file_path = example_mnirs("moxy_ramp.xlsx"),
nirs_channels = c(smo2 = "SmO2 Live(2)"),
time_channel = c(time = "hh:mm:ss"),
verbose = FALSE
) |>
resample_mnirs(method = "linear", verbose = FALSE) |>
dplyr::mutate(
dplyr::across(
smo2,
\(.x) filter_butter(.x, 2, 0.05),
.names = "{.col}_filtfilt"
)
)
data_filt <- filter_mnirs(
data,
nirs_channels = "smo2",
time_channel = NULL,
sample_rate = NULL,
method = "moving_average", #c("smooth_spline", "butterworth", "moving_average"),
# spar = 0.5,
# type = c("low"),
# order = 2,
# W = 0.05,
# fc = NULL,
width = NULL,
span = 30,
# na.rm = FALSE,
verbose = TRUE
)
## visual check
plot(data) +
ggplot2::scale_colour_manual(
breaks = c("smo2", "filt_vec", "filt_mnirs"),
values = palette_mnirs()
) +
ggplot2::geom_line(
ggplot2::aes(y = smo2_filtfilt, colour = "filt_vec"),
linewidth = 1
) +
ggplot2::geom_line(
data = data_filt,
ggplot2::aes(y = smo2, colour = "filt_mnirs")
)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.