diff --git a/DESCRIPTION b/DESCRIPTION index 16d3f96..0b36bec 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: PaleoSpec Title: Spectral tools for the ECUS group -Version: 0.2.91 +Version: 0.3 Authors@R: c( person("Thomas", "Laepple", email = "tlaepple@awi.de", role = c("aut", "cre")), person("Thomas", "Muench", email = "tmuench@awi.de", role = c("aut")), diff --git a/NAMESPACE b/NAMESPACE index 1d612d6..0a3a611 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,6 +11,8 @@ export(BinTimeseries) export(ClosestElement) export(ColTransparent) export(DF2Spec) +export(FilterSpec) +export(FilterSpecLog) export(FirstElement) export(GetTransferFunction) export(GetVarFromSpectra) @@ -40,6 +42,7 @@ export(SpecACF) export(SpecInterpolate) export(SpecMTM) export(SubsampleTimeseriesBlock) +export(TrimNA) export(as.spec) export(as_spec_df) export(gg_spec) diff --git a/NEWS.md b/NEWS.md index caf3490..05e83f1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,12 @@ +# PaleoSpec 0.3 + +* Add FilterSpec and FilterSpecLog as replacements / alternatives to LogSmooth + + FilterSpec and FilterSpecLog uses ApplyFilter to (optionally) avoid loosing the + highest and lowest frequencies when smoothing a spectrum. Default behaviour is + method 3: min roughness (reflect and invert ends). Avoids the artefact seen at + high freqs with LogSmooth. + # PaleoSpec 0.2.91 * Return vector of dof from SpecMTM even when all == 2 diff --git a/R/FilterSpec.R b/R/FilterSpec.R new file mode 100644 index 0000000..c8939f3 --- /dev/null +++ b/R/FilterSpec.R @@ -0,0 +1,250 @@ +#' Filter a Power Spectrum Object +#' +#' @param spec A spec object +#' @param keep_low_f Keep filtered (smoothed) low frequencies or replace with unfiltered +#' @inheritParams stats::spec.pgram +#' @inheritParams ApplyFilter +#' @return A spec object (list) +#' @seealso [FilterSpecLog] for filtering with filter widths equal in log-space +#' @export +#' +#' @examples +#' ## Comparison of the four methods - for power spectra, methods 0, 2 or 3 make the most sense +#' library(PaleoSpec) +#' +#' a <- 100 +#' b <- 1 +#' N <- 1e03 +#' set.seed(20230625) +#' ts1 <- SimPLS(N, beta = b, alpha = a) +#' sp1 <- SpecMTM(ts(ts1), bin.width = 1) +#' LPlot(sp1) +#' abline(log10(a), -b, col = "green") + +#' fl <- seq(3, 9, by = 2) +#' sp1_f3_0 <- FilterSpec(sp1, spans = fl, method = 0) +#' sp1_f3_1 <- FilterSpec(sp1, spans = fl, method = 1) +#' sp1_f3_2 <- FilterSpec(sp1, spans = fl, method = 2) +#' sp1_f3_3 <- FilterSpec(sp1, spans = fl, method = 3) +#' sp1_f3_4 <- FilterSpec(sp1, spans = fl, method = 4) +#' +#' LPlot(sp1) +#' LLines(sp1_f3_0, col = "blue") +#' LLines(sp1_f3_1, col = "green", lty = 2) +#' LLines(sp1_f3_2, col = "red", lty = 3) +#' LLines(sp1_f3_3, col = "orange", lty = 4) +#' LLines(sp1_f3_4, col = "gold", lty = 5) +#' +#' ## Comparison of keeping the filtered values in the reflected end portions or not +#' sp1_f3_0T <- FilterSpec(sp1, spans = fl, method = 0, keep_low_f = TRUE) +#' sp1_f3_0F <- FilterSpec(sp1, spans = fl, method = 0, keep_low_f = FALSE) + +#' LPlot(sp1_f3_0F) +#' LLines(sp1_f3_0T, col = "red") + +#' sp1_f3_2T <- FilterSpec(sp1, spans = fl, method = 2, keep_low_f = TRUE) +#' sp1_f3_2F <- FilterSpec(sp1, spans = fl, method = 2, keep_low_f = FALSE) + +#' LPlot(sp1_f3_2F) +#' LLines(sp1_f3_2T, col = "red") +FilterSpec <- function(spec, spans, method = 3, keep_low_f = TRUE) { + if (length(spec$dof) == 1) { + spec$dof <- rep(spec$dof, length(spec$freq)) + } + + dof0 <- spec$dof + + kernel <- stats::kernel("modified.daniell", spans %/% 2) + filter <- kernel[-kernel$m:kernel$m] + + spec_filt <- ApplyFilter(spec$spec, filter = filter, method = method) + + if (keep_low_f == FALSE) { + # replace filtered spec with original in area where freqs have been reflected + i <- 1:ceiling(length(filter) / 2) + spec_filt[i] <- spec$spec[i] + + iend <- length(spec$freq) - (i-1) + + spec_filt[iend] <- spec$spec[iend] + + } + + spec$spec <- as.numeric(spec_filt) + + # degrees of freedom of the kernel + df.kern <- stats::df.kernel(kernel) + + spec$dof <- df.kern * spec$dof / 2 + + if (keep_low_f == FALSE) { + + i <- 1:ceiling(length(filter) / 2) + spec$dof[i] <- dof0[i] + + iend <- length(spec$freq) - (i-1) + spec$dof[iend] <- dof0[iend] + + } + + # Adjust DOF in reflected filter region + if (keep_low_f == TRUE){ + + fl <- length(filter) + i <- 1:ceiling(fl / 2) + iend <- length(spec$freq) - (i-1) + + + if (method %in% c(2,3)){ + scl <- 2 * (fl - (i - 1)) / fl + spec$dof[i] <- spec$dof[i] / scl + spec$dof[iend] <- spec$dof[iend] / scl + + } + + if (method == 0){ + # remove NA portion + spec$freq <- spec$freq[is.na(spec$spec) == FALSE] + spec$dof <- spec$dof[is.na(spec$spec) == FALSE] + spec$shape <- spec$shape[is.na(spec$spec) == FALSE] + spec$spec <- spec$spec[is.na(spec$spec) == FALSE] + } + + } + + spec$shape <- spec$dof / 2 + + spec <- AddConfInterval(spec) + + + return(spec) +} + + + +#' Smooth a Spectrum with Evenly Spaced Bins in Logspace +#' +#' @param spec A spec object +#' @inheritParams LogSmooth +#' @inheritParams ApplyFilter +#' +#' @return A spec object (list) +#' @seealso [LogSmooth()] for an alternative implementation of log spaced filtering +#' @export +#' @examples +#' library(PaleoSpec) +#' +#' # simulate a timeseries with powerlaw power spectrum +#' a <- 100 +#' b <- 1 +#' N <- 1e03 +#' +#' set.seed(20230625) +#' ts1 <- SimPLS(N, beta = b, alpha = a) +#' sp1 <- SpecMTM(ts(ts1), bin.width = 1) +#' LPlot(sp1) +#' abline(log10(a), -b, col = "green") +#' # +#' sp1_f3_0 <- FilterSpecLog(sp1, method = 0) +#' sp1_f3_2 <- FilterSpecLog(sp1, method = 2) +#' +#' LPlot(sp1) +#' LLines(sp1_f3_0, col = "blue") +#' LLines(sp1_f3_2, col = "green", lty = 3) +#' +#' sp1_df0.05 <- FilterSpecLog(sp1) +#' sp1_df0.1 <- FilterSpecLog(sp1, df.log = 0.1) +#' +#' LPlot(sp1) +#' LLines(sp1_df0.05, col = "blue") +#' LLines(sp1_df0.1, col = "red") +#' +#' ## A combination of FilterSpec and FilterSpecLog +#' +#' sp1_FSL <- FilterSpecLog(sp1) +#' sp1_FSL_FS <- FilterSpec(FilterSpecLog(sp1), spans = c(3, 5)) +#' sp1_FS_FSL <- FilterSpecLog(FilterSpec(sp1, spans = c(3, 5))) +#' LPlot(sp1) +#' LLines(sp1_FSL, col = "blue") +#' LLines(sp1_FSL_FS, col = "red") +#' LLines(sp1_FS_FSL, col = "green") +FilterSpecLog <- function(spec, + df.log = 0.05, + spans = NULL, + method = 3, f.res = 10){ + + GetFW <- function(spec, df.log) { + ((exp(df.log) - 1) * max(spec$freq)) / min(spec$freq) + } + + if (length(spec$dof) == 1){ + spec$dof <- rep(spec$dof, length(spec$freq)) + } + + if (is.null(spans)){ + spans <- GetFW(spec, df.log = df.log) + } + + # interpolate spectrum onto equal in log space freq axis + delta_f <- min(spec$freq) + logfreq <- log(spec$freq) + + freq_logspace <- (seq(min(logfreq), max(logfreq)+delta_f, length.out = f.res*length(spec$freq))) + spec_loginterp <- stats::approx(logfreq, spec$spec, xout = freq_logspace, rule = 2)$y + + spans_adj <- spans * f.res + + # DOF of filter + kernel <- stats::kernel("daniell", spans_adj %/% 2) + filter <- kernel[-kernel$m:kernel$m] + df.kern <- stats::df.kernel(kernel) + + # DOF of a boxcar filter the same width + kernal.flat <- stats::kernel("daniell", length(filter) %/% 2) + df.kern.flat <- stats::df.kernel(kernal.flat) + + # modify for non boxcar filters + df.mod <- df.kern / df.kern.flat + + # smooth/filter in log space + spec_filt <- ApplyFilter(spec_loginterp, filter = filter, method = method) + + # re-interpolate back to original freq axis + spec3 <- stats::approx(freq_logspace, spec_filt, xout = logfreq)$y + + # overwrite spec with filtered spec + spec$spec <- spec3 + + # keep old DOF + dof0 <- spec$dof + + # Gets the difference in delta_f for the log and standard freq axis + NpF <- function(freq, fw, df){ + + posdiff <- (exp(log(freq) + df) - freq) + negdiff <- (freq - exp(log(freq) - df)) + + fdiff <- rowMeans(cbind(negdiff, posdiff)) + + 2 * fw * (fdiff/df) * 1/(2*max(freq)) + } + df.logkern <- NpF(spec$freq, length(filter), df = diff(freq_logspace[1:2])) + + spec$dof <- spec$dof + df.mod * df.logkern * spec$dof/2 + spec$shape <- spec$dof/2 + spec$spans <- paste(spans, collapse = ",") + + if (method == 0){ + # remove NA portion + spec$freq <- spec$freq[is.na(spec$spec) == FALSE] + spec$dof <- spec$dof[is.na(spec$spec) == FALSE] + spec$shape <- spec$shape[is.na(spec$spec) == FALSE] + spec$spec <- spec$spec[is.na(spec$spec) == FALSE] + } + + spec <- AddConfInterval(spec) + + + + return(spec) +} diff --git a/R/SpecACF.R b/R/SpecACF.R index 32e6fa5..07a85f2 100644 --- a/R/SpecACF.R +++ b/R/SpecACF.R @@ -217,24 +217,43 @@ mvacf.by.fft <- function(x){ #' Remove leading and trailing rows of all NA #' -#' @param m a numeric matrix -#' -#' @return a numeric matrix -#' @keywords internal +#' @param m a numeric matrix, data.frame or vector +#' @param trim trim leading and trailing rows of "all" NA or containing "any" NA values +#' @return a numeric matrix, data.frame or vector +#' @export #' @examples -#' m <- matrix(c(NA, NA, NA, 1:9, NA,NA,NA, 10:12, NA,NA,NA), ncol = 3, byrow = TRUE) +#' m <- matrix(c(NA, NA, NA, 1, NA, NA, NA, 1, 1, NA, NA, NA, 1:9, NA,NA,NA, 10:12, NA, 1, NA, NA,NA,NA), ncol = 3, byrow = TRUE) #' m -#' PaleoSpec:::TrimNA(m) -TrimNA <- function(m){ +#' TrimNA(m) +#' TrimNA(m, trim = "any") +TrimNA <- function(m, trim = c("all", "any")) { + trim <- match.arg(trim) + + # make it work on vectors + class_m <- class(m) + if (class_m[1] == "numeric") { + m <- cbind(m) + } - empty.row <- is.nan(rowMeans(m, na.rm = TRUE)) - rank.good <- (empty.row == FALSE) * 1:length(empty.row) + if (trim == "all") { + empty.row <- is.nan(rowMeans(m, na.rm = TRUE)) + rank.good <- (empty.row == FALSE) * 1:length(empty.row) + } else if (trim == "any") { + empty.row <- is.na(rowMeans(m)) + rank.good <- (empty.row == FALSE) * 1:length(empty.row) + } first.good <- which.min(empty.row * 1:length(empty.row)) last.good <- which.max(rank.good) - m[first.good:last.good, , drop = FALSE] + m <- m[first.good:last.good, , drop = FALSE] + + # return to a vector + if (class_m[1] == "numeric") { + m <- as.numeric(m) + } + return(m) } diff --git a/README.Rmd b/README.Rmd index b1b0fef..f249fab 100644 --- a/README.Rmd +++ b/README.Rmd @@ -27,8 +27,8 @@ PaleoSpec is an R package to assist the analysis of variance and power spectra. You can install the development version of PaleoSpec from [GitHub](https://github.com/) with: ``` r -# install.packages("devtools") -devtools::install_github("EarthSystemDiagnostics/paleospec") +# install.packages("remotes") +remotes::install_github("EarthSystemDiagnostics/paleospec") ``` ## Usage diff --git a/README.md b/README.md index 1297c73..18ece15 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ You can install the development version of PaleoSpec from [GitHub](https://github.com/) with: ``` r -# install.packages("devtools") -devtools::install_github("EarthSystemDiagnostics/paleospec") +# install.packages("remotes") +remotes::install_github("EarthSystemDiagnostics/paleospec") ``` ## Usage diff --git a/man/FilterSpec.Rd b/man/FilterSpec.Rd new file mode 100644 index 0000000..7863840 --- /dev/null +++ b/man/FilterSpec.Rd @@ -0,0 +1,64 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/FilterSpec.R +\name{FilterSpec} +\alias{FilterSpec} +\title{Filter a Power Spectrum Object} +\usage{ +FilterSpec(spec, spans, method = 3, keep_low_f = TRUE) +} +\arguments{ +\item{spec}{A spec object} + +\item{spans}{vector of odd integers giving the widths of modified + Daniell smoothers to be used to smooth the periodogram.} + +\item{method}{single integer for choosing an endpoint constraint method; +available choices are integers 0-4, see details.} + +\item{keep_low_f}{Keep filtered (smoothed) low frequencies or replace with unfiltered} +} +\value{ +A spec object (list) +} +\description{ +Filter a Power Spectrum Object +} +\examples{ +## Comparison of the four methods - for power spectra, methods 0, 2 or 3 make the most sense +library(PaleoSpec) + +a <- 100 +b <- 1 +N <- 1e03 +set.seed(20230625) +ts1 <- SimPLS(N, beta = b, alpha = a) +sp1 <- SpecMTM(ts(ts1), bin.width = 1) +LPlot(sp1) +abline(log10(a), -b, col = "green") +fl <- seq(3, 9, by = 2) +sp1_f3_0 <- FilterSpec(sp1, spans = fl, method = 0) +sp1_f3_1 <- FilterSpec(sp1, spans = fl, method = 1) +sp1_f3_2 <- FilterSpec(sp1, spans = fl, method = 2) +sp1_f3_3 <- FilterSpec(sp1, spans = fl, method = 3) +sp1_f3_4 <- FilterSpec(sp1, spans = fl, method = 4) + +LPlot(sp1) +LLines(sp1_f3_0, col = "blue") +LLines(sp1_f3_1, col = "green", lty = 2) +LLines(sp1_f3_2, col = "red", lty = 3) +LLines(sp1_f3_3, col = "orange", lty = 4) +LLines(sp1_f3_4, col = "gold", lty = 5) + +## Comparison of keeping the filtered values in the reflected end portions or not +sp1_f3_0T <- FilterSpec(sp1, spans = fl, method = 0, keep_low_f = TRUE) +sp1_f3_0F <- FilterSpec(sp1, spans = fl, method = 0, keep_low_f = FALSE) +LPlot(sp1_f3_0F) +LLines(sp1_f3_0T, col = "red") +sp1_f3_2T <- FilterSpec(sp1, spans = fl, method = 2, keep_low_f = TRUE) +sp1_f3_2F <- FilterSpec(sp1, spans = fl, method = 2, keep_low_f = FALSE) +LPlot(sp1_f3_2F) +LLines(sp1_f3_2T, col = "red") +} +\seealso{ +[FilterSpecLog] for filtering with filter widths equal in log-space +} diff --git a/man/FilterSpecLog.Rd b/man/FilterSpecLog.Rd new file mode 100644 index 0000000..3dc4ea1 --- /dev/null +++ b/man/FilterSpecLog.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/FilterSpec.R +\name{FilterSpecLog} +\alias{FilterSpecLog} +\title{Smooth a Spectrum with Evenly Spaced Bins in Logspace} +\usage{ +FilterSpecLog(spec, df.log = 0.05, spans = NULL, method = 3, f.res = 10) +} +\arguments{ +\item{spec}{A spec object} + +\item{df.log}{width of the smoother in log units} + +\item{method}{single integer for choosing an endpoint constraint method; +available choices are integers 0-4, see details.} +} +\value{ +A spec object (list) +} +\description{ +Smooth a Spectrum with Evenly Spaced Bins in Logspace +} +\examples{ +library(PaleoSpec) + +# simulate a timeseries with powerlaw power spectrum +a <- 100 +b <- 1 +N <- 1e03 + +set.seed(20230625) +ts1 <- SimPLS(N, beta = b, alpha = a) +sp1 <- SpecMTM(ts(ts1), bin.width = 1) +LPlot(sp1) +abline(log10(a), -b, col = "green") +# +sp1_f3_0 <- FilterSpecLog(sp1, method = 0) +sp1_f3_2 <- FilterSpecLog(sp1, method = 2) + +LPlot(sp1) +LLines(sp1_f3_0, col = "blue") +LLines(sp1_f3_2, col = "green", lty = 3) + +sp1_df0.05 <- FilterSpecLog(sp1) +sp1_df0.1 <- FilterSpecLog(sp1, df.log = 0.1) + +LPlot(sp1) +LLines(sp1_df0.05, col = "blue") +LLines(sp1_df0.1, col = "red") + +## A combination of FilterSpec and FilterSpecLog + +sp1_FSL <- FilterSpecLog(sp1) +sp1_FSL_FS <- FilterSpec(FilterSpecLog(sp1), spans = c(3, 5)) +sp1_FS_FSL <- FilterSpecLog(FilterSpec(sp1, spans = c(3, 5))) +LPlot(sp1) +LLines(sp1_FSL, col = "blue") +LLines(sp1_FSL_FS, col = "red") +LLines(sp1_FS_FSL, col = "green") +} +\seealso{ +[LogSmooth()] for an alternative implementation of log spaced filtering +} diff --git a/man/TrimNA.Rd b/man/TrimNA.Rd index 9822353..b480aec 100644 --- a/man/TrimNA.Rd +++ b/man/TrimNA.Rd @@ -4,20 +4,22 @@ \alias{TrimNA} \title{Remove leading and trailing rows of all NA} \usage{ -TrimNA(m) +TrimNA(m, trim = c("all", "any")) } \arguments{ -\item{m}{a numeric matrix} +\item{m}{a numeric matrix, data.frame or vector} + +\item{trim}{trim leading and trailing rows of "all" NA or containing "any" NA values} } \value{ -a numeric matrix +a numeric matrix, data.frame or vector } \description{ Remove leading and trailing rows of all NA } \examples{ -m <- matrix(c(NA, NA, NA, 1:9, NA,NA,NA, 10:12, NA,NA,NA), ncol = 3, byrow = TRUE) +m <- matrix(c(NA, NA, NA, 1, NA, NA, NA, 1, 1, NA, NA, NA, 1:9, NA,NA,NA, 10:12, NA, 1, NA, NA,NA,NA), ncol = 3, byrow = TRUE) m -PaleoSpec:::TrimNA(m) +TrimNA(m) +TrimNA(m, trim = "any") } -\keyword{internal} diff --git a/tests/testthat/test-FilterSpec.R b/tests/testthat/test-FilterSpec.R new file mode 100644 index 0000000..3bef6a2 --- /dev/null +++ b/tests/testthat/test-FilterSpec.R @@ -0,0 +1,163 @@ +# Unit tests by ChatGPT 4 +# They basically only check that errors are not thrown and that the outputs are +# the right "shape" + +library(testthat) +library(PaleoSpec) # Assuming PaleoSpec is the package containing the necessary functions + +context("FilterSpec Function Tests") + +# Helper function to create a mock 'spec' object +create_mock_spec <- function() { + SpecMTM(ts(rnorm(100))) +} + +# Test with standard input +test_that("FilterSpec returns correct structure with standard inputs", { + spec <- create_mock_spec() + spans <- c(3, 5, 7) + result <- FilterSpec(spec, spans, method = 3, keep_low_f = TRUE) + + expect_true(is.list(result)) + expect_equal(length(result$freq), length(spec$freq)) + expect_equal(length(result$spec), length(spec$spec)) + expect_equal(length(result$dof), length(spec$dof)) +}) + +# Test dof length 1 +test_that("FilterSpec copes with dof length 1", { + spec <- create_mock_spec() + + spec$dof <- spec$dof[1] + + spans <- c(3, 5, 7) # Largest span is 7 + + result <- FilterSpec(spec, spans, method = 3, keep_low_f = TRUE) + + expect_true(is.list(result)) + expect_equal(length(result$dof), length(spec$freq)) +}) + + +# Test different methods +test_that("FilterSpec handles different methods correctly", { + spec <- create_mock_spec() + spans <- c(3, 5, 7) + + for (m in 0:4) { + result <- FilterSpec(spec, spans, method = m, keep_low_f = TRUE) + expect_true(is.list(result)) + } +}) + +# Test keep_low_f parameter +test_that("FilterSpec handles keep_low_f parameter correctly", { + spec <- create_mock_spec() + spans <- c(3, 5, 7) + + result_true <- FilterSpec(spec, spans, method = 3, keep_low_f = TRUE) + result_false <- FilterSpec(spec, spans, method = 3, keep_low_f = FALSE) + + expect_true(is.list(result_true)) + expect_true(is.list(result_false)) + expect_false(identical(result_true$spec, result_false$spec)) +}) + + +# Test edge cases (e.g., empty spec, incorrect types) +test_that("FilterSpec handles edge cases appropriately", { + empty_spec <- list(freq = numeric(0), spec = numeric(0), dof = numeric(0), shape = numeric(0)) + spans <- c(3, 5, 7) + + expect_error(FilterSpec(empty_spec, spans)) + expect_error(FilterSpec("not a spec", spans)) + expect_error(FilterSpec(create_mock_spec(), "not numeric")) +}) + +# Test method = 0 specific behaviour +test_that("FilterSpec with method 0 adjusts length of result$freq", { + spec <- create_mock_spec() + spans <- c(3, 5, 7) # Largest span is 7 + + result <- FilterSpec(spec, spans, method = 0, keep_low_f = TRUE) + + expected_length <- length(spec$freq) - (2 * max(spans) - 2) + actual_length <- length(result$freq) + + expect_equal(actual_length, expected_length) +}) + + + +# FilterSpecLog ------ + +library(testthat) +library(PaleoSpec) # Assuming PaleoSpec contains the FilterSpecLog function + +context("FilterSpecLog Function Tests") + + +# Test basic functionality +test_that("FilterSpecLog returns correct structure", { + spec <- create_mock_spec() + result <- FilterSpecLog(spec) + + expect_true(is.list(result)) + expect_equal(length(result$freq), length(spec$freq)) + expect_equal(length(result$spec), length(spec$spec)) + expect_equal(length(result$dof), length(spec$dof)) +}) + +# Test dof length 1 +test_that("FilterSpecLog copes with dof length 1", { + spec <- create_mock_spec() + + spec$dof <- spec$dof[1] + + spans <- c(3, 5, 7) # Largest span is 7 + + result <- FilterSpecLog(spec) + + expect_true(is.list(result)) + expect_equal(length(result$dof), length(spec$freq)) +}) + + +# Test different methods +test_that("FilterSpecLog handles different methods correctly", { + spec <- create_mock_spec() + + for (m in 0:3) { + result <- FilterSpecLog(spec, method = m) + expect_true(is.list(result)) + } +}) + +# Test df.log parameter +test_that("FilterSpecLog handles df.log parameter correctly", { + spec <- create_mock_spec() + result_default <- FilterSpecLog(spec) + result_custom <- FilterSpecLog(spec, df.log = 0.1) + + expect_true(is.list(result_default)) + expect_true(is.list(result_custom)) + expect_false(identical(result_default$spec, result_custom$spec)) +}) + +# Test edge cases +test_that("FilterSpecLog handles edge cases appropriately", { + empty_spec <- list(freq = numeric(0), spec = numeric(0), dof = numeric(0), shape = numeric(0)) + + expect_error(FilterSpecLog(empty_spec)) + expect_error(FilterSpecLog("not a spec")) +}) + +# Test specific behaviour for method = 0 +test_that("FilterSpecLog with method 0 adjusts length of result$freq", { + spec <- create_mock_spec() + + result <- FilterSpecLog(spec, method = 0) + + expect_lt(length(result$freq), length(spec$freq)) +}) + diff --git a/tests/testthat/test-TrimNA.R b/tests/testthat/test-TrimNA.R new file mode 100644 index 0000000..25a2053 --- /dev/null +++ b/tests/testthat/test-TrimNA.R @@ -0,0 +1,34 @@ +context("test TrimNA") + +test_that("TrimNA", { + + library(PaleoSpec) + + # "all" NA + m1 <- matrix(c(1, 2, 3, NA, 2, 3, 1, 2, NA), ncol = 3) + m2 <- matrix(c(1, 2, NA, NA, 2, NA, 1, 2, NA), ncol = 3) + + expect_equal(dim(TrimNA(m1)), c(3,3)) + expect_equal(dim(TrimNA(m2)), c(2,3)) + + + m3 <- c(NA, 1, 2, 3, NA, NA) + + expect_length(TrimNA(m3), 3) + + + expect_s3_class(TrimNA(as.data.frame(m1)), "data.frame") + expect_s3_class(TrimNA(as.data.frame(m2)), "data.frame") + + expect_true(is.matrix(TrimNA(m2))) + + + # "any" NA + + expect_equal(dim(TrimNA(m2, trim = "any")), c(1,3)) + + m4 <- matrix(c(NA, NA, NA, 1, NA, NA, NA, 1, 1, NA, NA, NA, 1:9, NA,NA,NA, 10:12, NA, 1, NA, NA,NA,NA), ncol = 3, byrow = TRUE) + expect_equal(dim(TrimNA(m4, trim = "all")), c(9,3)) + expect_equal(dim(TrimNA(m4, trim = "any")), c(5,3)) + +})