From f0a6978ede7b4c44c90e8e45d44b3cab7f8a5702 Mon Sep 17 00:00:00 2001 From: "Rossum, Bart-Jan van" Date: Mon, 31 May 2021 12:47:19 +0200 Subject: [PATCH] Added tests for detectSerieOut (#79) --- R/detectSerieOut.R | 16 +- inst/tinytest/test_detectSerieOut.R | 237 +++++++++++++++++++++++++++ inst/tinytest/test_detectSingleOut.R | 2 +- serieOut | Bin 0 -> 11999 bytes 4 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 inst/tinytest/test_detectSerieOut.R create mode 100644 serieOut diff --git a/R/detectSerieOut.R b/R/detectSerieOut.R index e69efc8..2f50f68 100644 --- a/R/detectSerieOut.R +++ b/R/detectSerieOut.R @@ -115,8 +115,8 @@ detectSerieOut <- function(corrDat, if (!all(genotypes %in% corrDat[["genotype"]])) { stop("all genotypes should be in corrDat") } - if (!is.numeric(thrCor) || any(thrCor < 0) || any(thrCor > 1)) { - stop("thrCor should be a numerical vector with values between 0 and 1.\n") + if (!is.numeric(thrCor) || any(thrCor < -1) || any(thrCor > 1)) { + stop("thrCor should be a numerical vector with values between -1 and 1.\n") } if (!is.numeric(thrPca) || any(thrPca < 0)) { stop("thrPca should be a numerical vector with positive values.\n") @@ -211,13 +211,13 @@ detectSerieOut <- function(corrDat, formula = type ~ plotId, value.var = "obj.coefficients") ## Remove intercept. - plantDat <- plantDat[-1, ] + plantDat <- plantDat[-1, , drop = FALSE] ## Remove plants with only NA. NAplants <- apply(X = plantDat, MARGIN = 2, FUN = function(plant) { all(is.na(plant)) }) - plantDat <- plantDat[, !NAplants] + plantDat <- plantDat[, !NAplants, drop = FALSE] ## Add geno.decomp as attribute for later use. if (!is.null(geno.decomp)) { attr(plantDat, which = "genoDecomp") <- @@ -271,7 +271,7 @@ detectSerieOut <- function(corrDat, combn(x = colnames(plantDat), m = 2, FUN = function(plants) { ## Fit linear model and extract slope. modForm <- formula(paste(plants, collapse = "~")) - slope <- coef(lm(modForm, data = plantDat))[2] + slope <- abs(coef(lm(modForm, data = plantDat))[2]) if (slope > 1) slope <- 1 / slope return(slope) }, simplify = TRUE) @@ -702,7 +702,7 @@ removeSerieOut <- function(dat = NULL, } if (!is.null(fitSpline)) { if (!inherits(fitSpline, "HTPSpline")) { - stop("dat should be an object of class HTPSpline.\n") + stop("fitSpline should be an object of class HTPSpline.\n") } } if (!inherits(serieOut, "data.frame")) { @@ -720,8 +720,8 @@ removeSerieOut <- function(dat = NULL, } else if (!is.null(fitSpline)) { fitSpline$coefDat[fitSpline$coefDat[["plotId"]] %in% serieOut[["plotId"]], "obj.coefficients"] <- NA - fitSpline$predDat[fitSpline$predDat[["plotId"]] %in% - serieOut[["plotId"]], c("pred.value", "deriv")] <- NA + fitSpline$predDat[fitSpline$predDat[["plotId"]] %in% serieOut[["plotId"]], + c("pred.value", "deriv", "deriv2")] <- NA modDat <- attr(x = fitSpline, which = "modDat") for (trait in traits) { modDat[modDat[["plotId"]] %in% serieOut[["plotId"]], trait] <- NA diff --git a/inst/tinytest/test_detectSerieOut.R b/inst/tinytest/test_detectSerieOut.R new file mode 100644 index 0000000..9073475 --- /dev/null +++ b/inst/tinytest/test_detectSerieOut.R @@ -0,0 +1,237 @@ +### Test detectSingleOut. + +### Test fitSpline + +Sys.setlocale("LC_COLLATE", "C") + +## Read test data from .csv +testDat <- read.csv("testDat.csv", stringsAsFactors = FALSE) +# Set Replicate to 1 for all observations of check1 so geno.decomp can +# actually be used for testing. +testDat[testDat[["Genotype"]] == "check1", "Replicate"] <- 1 + +## Create TP object. +testTP <- createTimePoints(dat = testDat, experimentName = "testExp", + genotype = "Genotype", timePoint = "timepoints", + plotId = "pos", rowNum = "y", colNum = "x") + +## Fit models. +testFitMod <- fitModels(testTP, trait = "t1", quiet = TRUE) +testFitModGD <- fitModels(testTP, trait = "t1", geno.decomp = "Replicate", + quiet = TRUE) + +## Get corrected values. +corr <- getCorrected(testFitMod) +corrGD <- getCorrected(testFitModGD) + +## Fit a splines on the corrected values. +expect_warning(splineRes <- fitSpline(inDat = corr, trait = "t1_corr")) +expect_warning(splineResGD <- fitSpline(inDat = corrGD, trait = "t1_corr")) + +## get coefDat and predDat. +coefDat <- splineRes[["coefDat"]] +predDat <- splineRes[["predDat"]] + +coefDatGD <- splineResGD[["coefDat"]] +predDatGD <- splineResGD[["predDat"]] + +## Check that general checks in detectSingleOut function correctly. +expect_error(detectSerieOut(trait = 1), + "trait should be a character string of length 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = 1), + "corrDat should be a data.frame") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, predDat = 1), + "predDat should be a data.frame") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = 1), + "coefDat should be a data.frame") +expect_error(detectSerieOut(trait = "t2_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat), + "corrDat should at least contain the following columns") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = coefDat, coefDat = coefDat), + "predDat should at least contain the following columns") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = predDat), + "coefDat should at least contain the following columns") + +## Check that checks for genotypes function correctly +expect_warning(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat), + "have less than 3 plotIds and are skipped in the outlier") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = 1), + "genotypes should be a character vector") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "a"), + "all genotypes should be in predDat") +expect_error(detectSerieOut(trait = "t1_corr", + corrDat = corr[corr[["genotype"]] != "G12", ], + predDat = predDat, coefDat = coefDat, + genotypes = "G12"), + "all genotypes should be in corrDat") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, + coefDat = coefDat[coefDat[["genotype"]] != "G12", ], + genotypes = "G12"), + "all genotypes should be in coefDat") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "G12"), + "No genotypes left for performing outlier detection") +expect_silent(serieOut1 <- detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1")) + +## Check that general structure of the output is correct. +expect_inherits(serieOut1, c("serieOut", "data.frame")) +expect_equal(dim(serieOut1), c(7, 4)) +expect_equal(colnames(serieOut1), + c("plotId", "genotype", "reason", "value")) + +## Check that full output content is correct. +expect_equal_to_reference(serieOut1, file = "serieOut") + +## Check that parameter thrCor functions correctly. +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrCor = "a"), + "thrCor should be a numerical vector with values between -1 and 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrCor = 2), + "thrCor should be a numerical vector with values between -1 and 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrCor = 0:1), + "thrCor should be a vector of length 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corrGD, + predDat = predDatGD, coefDat = coefDatGD, + genotypes = "check1", geno.decomp = "geno.decomp", + thrCor = 0:1), + "thrCor should be a named vector, with names matching the levels") + +## Check that parameter thrPca functions correctly. +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrPca = "a"), + "thrPca should be a numerical vector with positive values") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrPca = -1), + "thrPca should be a numerical vector with positive values") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrPca = 0:1), + "thrPca should be a vector of length 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corrGD, + predDat = predDatGD, coefDat = coefDatGD, + genotypes = "check1", geno.decomp = "geno.decomp", + thrPca = 0:1), + "thrPca should be a named vector, with names matching the levels") + + +## Check that parameter thrPca functions correctly. +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrSlope = "a"), + "thrSlope should be a numerical vector with values between 0 and 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrSlope = -1), + "thrSlope should be a numerical vector with values between 0 and 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrSlope = 0:1), + "thrSlope should be a vector of length 1") +expect_error(detectSerieOut(trait = "t1_corr", corrDat = corrGD, + predDat = predDatGD, coefDat = coefDatGD, + genotypes = "check1", geno.decomp = "geno.decomp", + thrSlope = 0:1), + "thrSlope should be a named vector, with names matching the levels") + +# Set all three to extreme values should result in either 0 or many outliers. +serieOut2 <- detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrCor = 0, thrPca = 0, + thrSlope = 1) +expect_equal(dim(serieOut2), c(9, 4)) + +serieOut3 <- detectSerieOut(trait = "t1_corr", corrDat = corr, + predDat = predDat, coefDat = coefDat, + genotypes = "check1", thrCor = -1, thrPca = 180, + thrSlope = 0) +expect_equivalent(serieOut3, data.frame()) + +## Check that detecting outliers functions correctly for models with geno.deco +expect_silent(serieOutGD <- + detectSerieOut(trait = "t1_corr", corrDat = corrGD, + predDat = predDatGD, coefDat = coefDatGD, + genotypes = "check1", geno.decomp = "geno.decomp")) +expect_equal(dim(serieOutGD), c(6, 5)) + +### Check plotting of detectSerieOut results. + +## Check that general checks in plot function correctly. +expect_error(plot(serieOut1, genotypes = "a"), + "a character vector of genotypes used for outlier detection") + +## Check that general output structure is correct. +expect_silent(p <- plot(serieOut1)) +expect_inherits(p, "list") +expect_equal(length(p), 1) +expect_inherits(p[[1]], "gtable") + +## Check that option useTimeNumber functions correctly. +expect_error(plot(serieOut1, useTimeNumber = TRUE, timeNumber = 1), + "timeNumber should be a character string of length 1") +expect_silent(plot(serieOut1, useTimeNumber = TRUE, timeNumber = "timeNumber")) + +## Check that option title functions correctly. +expect_silent(plot(serieOut1, genotypes = "check1", title = "bla")) + +## Check that plotting functions correctly with geno.decomp. +expect_error(plot(serieOutGD, genotypes = "check1", geno.decomp = "2"), + "All selected geno.decomp levels should be in the data") +expect_silent(plot(serieOutGD, genotypes = "check1", geno.decomp = "1")) + +### Check removal of outliers detected by detectSerieOut + +## Check that general checks in plot function correctly. +expect_error(removeSerieOut(dat = 1), + "dat should be a data.frame") +expect_error(removeSerieOut(fitSpline = 1), + "fitSpline should be an object of class HTPSpline") +expect_error(removeSerieOut(dat = corr, fitSpline = splineRes), + "Specify exactly one of dat and fitSpline as inputs") +expect_error(removeSerieOut(dat = corr[, colnames(corr) != "plotId"]), + "dat should at least contain the column plotId") +expect_error(removeSerieOut(dat = corr, serieOut = 1), + "serieOut should be a data.frame") +expect_error(removeSerieOut(dat = corr, + serieOut = serieOut1[, colnames(serieOut1) != "plotId"]), + "serieOut should at least contain the column plotId") + +## Check that outliers are removed from data.frame. +corrOut1 <- removeSerieOut(dat = corr, serieOut = serieOut1) +expect_true(all(is.na(corrOut1[corrOut1[["plotId"]] == "c12r1", "t1_corr"]))) + +corrOut2 <- removeSerieOut(dat = corr, serieOut = serieOut1, + traits = c("t1", "t1_corr")) +expect_true(all(is.na(corrOut2[corrOut2[["plotId"]] == "c12r1", + c("t1", "t1_corr")]))) + +## Check that outliers are removed from fitted spline. +corrOut3 <- removeSerieOut(fitSpline = splineRes, serieOut = serieOut1) +coefOut3 <- corrOut3$coefDat +predOut3 <- corrOut3$predDat +modOut3 <- attr(corrOut3, "modDat") + +expect_true(all(is.na(coefOut3[coefOut3[["plotId"]] == "c12r1", + "obj.coefficients"]))) +expect_true(all(is.na(predOut3[predOut3[["plotId"]] == "c12r1", + c("pred.value", "deriv", "deriv2")]))) +expect_true(all(is.na(modOut3[modOut3[["plotId"]] == "c12r1", "t1_corr"]))) + diff --git a/inst/tinytest/test_detectSingleOut.R b/inst/tinytest/test_detectSingleOut.R index f61e4d9..94aa511 100644 --- a/inst/tinytest/test_detectSingleOut.R +++ b/inst/tinytest/test_detectSingleOut.R @@ -47,7 +47,7 @@ expect_equal(colnames(singleOut), c("plotId", "timePoint", "EffpsII", "yPred", "sd_yPred", "lwr", "upr", "outlier")) -## Check that full output contenct is correct. +## Check that full output content is correct. expect_equal_to_reference(singleOut, file = "singleOut") ## Check that parameter confIntSize functions correctly. diff --git a/serieOut b/serieOut new file mode 100644 index 0000000000000000000000000000000000000000..e3f2264d7fc3c1a80365bb7e1d1d08f8b26bcecc GIT binary patch literal 11999 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 CPzP-Q literal 0 HcmV?d00001