tests/testthat/test-mvgls-functions.R

# Unit tests for fitMvglsAndExtractGIC and fitMvglsAndExtractBIC functions

library(testthat)
library(ape)
library(mvMORPH)

# Helper function to create test data
create_test_data <- function(n_tips = 20, n_traits = 2, min_tips_high = 5) {
  # Generate a random phylogenetic tree
  set.seed(123)  # For reproducibility
  base_tree <- rcoal(n_tips)
  
  # Generate painted trees using the custom function
  painted_trees <- generatePaintedTrees(base_tree, min_tips = min_tips_high)
  
  # Select the first painted tree for testing
  painted_tree <- painted_trees[[1]]
  
  # Generate simulated trait data
  trait_data <- mvSIM(painted_tree, nsim = n_traits, model = "BMM", param = list(sigma = 0.1, theta = 0))
  
  # Ensure trait_data is a matrix with proper row names
  if (!is.matrix(trait_data)) {
    trait_data <- as.matrix(trait_data)
  }
  rownames(trait_data) <- painted_tree$tip.label
  
  return(list(painted_tree = painted_tree, trait_data = trait_data))
}

# Group: fitMvglsAndExtractGIC behavior
# Test: fitMvglsAndExtractGIC works with valid inputs (smoke test with valid inputs)
test_that("fitMvglsAndExtractGIC works with valid inputs", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Test the function
  result <- fitMvglsAndExtractGIC(painted_tree, trait_data)
  
  # Check that result is a list with correct elements
  expect_type(result, "list")
  expect_named(result, c("model", "GIC"))
  
  # Check that model is of correct class (mvgls object)
  expect_s3_class(result$model, "mvgls")
  
  # Check that GIC is a numeric value
  expect_type(result$GIC$GIC, "double")
  expect_length(result$GIC, 7)
  expect_false(any(is.na(result$GIC)))
})


# Test: fitMvglsAndExtractGIC handles multiple traits (edge-case input)
test_that("fitMvglsAndExtractGIC handles multiple traits", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data with multiple traits
  test_data <- create_test_data(n_tips = 25, n_traits = 10)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Test the function
  result <- fitMvglsAndExtractGIC(painted_tree, trait_data)
  
  # Check results
  expect_type(result, "list")
  expect_named(result, c("model", "GIC"))
  expect_s3_class(result$model, "mvgls")
  expect_type(result$GIC$GIC, "double")
})

# Test: fitMvglsAndExtractGIC throws error for non-matrix trait_data (expects error)
test_that("fitMvglsAndExtractGIC throws error for non-matrix trait_data", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- as.data.frame(test_data$trait_data)  # Convert to data.frame
  
  # Test that error is thrown
  expect_error(
    fitMvglsAndExtractGIC(painted_tree, trait_data),
    "trait_data must be a matrix."
  )
})

# Test: fitMvglsAndExtractGIC does not work on univariate data
test_that("fitMvglsAndExtractGIC does not work on univariate data", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 1)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data

  # Test that error is thrown
  expect_error(
    fitMvglsAndExtractGIC(painted_tree, trait_data),
    "mvgls can be used only with multivariate datasets. See \"gls\" function in \"nlme\" or \"phylolm\" package instead."
  )
})

# Test: fitMvglsAndExtractGIC throws error for mismatched row names (expects error)
test_that("fitMvglsAndExtractGIC throws error for mismatched row names", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Modify row names to create mismatch
  rownames(trait_data)[1] <- "wrong_name"
  
  # Test that error is thrown
  expect_error(
    fitMvglsAndExtractGIC(painted_tree, trait_data),
    "Row names of trait_data must exactly match the tip labels of the tree."
  )
})

# Group: fitMvglsAndExtractBIC behavior
# Test: fitMvglsAndExtractBIC works with valid inputs (smoke test with valid inputs)
test_that("fitMvglsAndExtractBIC works with valid inputs", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Test the function
  result <- fitMvglsAndExtractBIC(painted_tree, trait_data)
  
  # Check that result is a list with correct elements
  expect_type(result, "list")
  expect_named(result, c("model", "BIC"))
  
  # Check that model is of correct class (mvgls object)
  expect_s3_class(result$model, "mvgls")
  
  # Check that BIC is a numeric value
  expect_type(result$BIC$BIC, "double")
  expect_length(result$BIC, 4)
  expect_false(any(is.na(result$BIC)))
})


# Test: fitMvglsAndExtractBIC handles multiple traits (edge-case input)
test_that("fitMvglsAndExtractBIC handles multiple traits", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data with multiple traits
  test_data <- create_test_data(n_tips = 25, n_traits = 10)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Test the function
  result <- fitMvglsAndExtractBIC(painted_tree, trait_data)
  
  # Check results
  expect_type(result, "list")
  expect_named(result, c("model", "BIC"))
  expect_s3_class(result$model, "mvgls")
  expect_type(result$BIC$BIC, "double")
})

# Test: fitMvglsAndExtractBIC throws error for non-matrix trait_data (expects error)
test_that("fitMvglsAndExtractBIC throws error for non-matrix trait_data", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- as.data.frame(test_data$trait_data)  # Convert to data.frame
  
  # Test that error is thrown
  expect_error(
    fitMvglsAndExtractBIC(painted_tree, trait_data),
    "trait_data must be a matrix."
  )
})

# Test: fitMvglsAndExtractBIC throws error for univariate trait_data (expects error)
test_that("fitMvglsAndExtractBIC throws error for univariate trait_data", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 1)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Test that error is thrown
  expect_error(
    fitMvglsAndExtractBIC(painted_tree, trait_data),
    "mvgls can be used only with multivariate datasets. See \"gls\" function in \"nlme\" or \"phylolm\" package instead."
  )
})

# Test: fitMvglsAndExtractBIC throws error for mismatched row names (expects error)
test_that("fitMvglsAndExtractBIC throws error for mismatched row names", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Modify row names to create mismatch
  rownames(trait_data)[1] <- "wrong_name"
  
  # Test that error is thrown
  expect_error(
    fitMvglsAndExtractBIC(painted_tree, trait_data),
    "Row names of trait_data must exactly match the tip labels of the tree."
  )
})

# Group: cross-function consistency
# Test: GIC and BIC functions produce consistent models
test_that("GIC and BIC functions produce consistent models", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Test both functions
  result_gic <- fitMvglsAndExtractGIC(painted_tree, trait_data)
  result_bic <- fitMvglsAndExtractBIC(painted_tree, trait_data)
  
  # Check that both models have same logLik (since they're fitting the same model)
  expect_equal(logLik(result_gic$model), logLik(result_bic$model))
  
  # Check that both produce valid information criteria
  expect_type(result_gic$GIC$GIC, "double")
  expect_type(result_bic$BIC$BIC, "double")
})

# Group: edge cases
# Test: functions handle small trees
test_that("functions handle small trees", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data with minimum viable tree size
  test_data <- create_test_data(n_tips = 10, n_traits = 2, min_tips_high = 3)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Test both functions
  result_gic <- fitMvglsAndExtractGIC(painted_tree, trait_data)
  result_bic <- fitMvglsAndExtractBIC(painted_tree, trait_data)
  
  # Check results
  expect_type(result_gic, "list")
  expect_type(result_bic, "list")
  expect_s3_class(result_gic$model, "mvgls")
  expect_s3_class(result_bic$model, "mvgls")
})

# Test: functions handle trait data with missing row names
test_that("functions handle trait data with missing row names", {
  skip_if_not_installed("mvMORPH")
  skip_if_not_installed("ape")
  
  # Create test data
  test_data <- create_test_data(n_tips = 20, n_traits = 2)
  painted_tree <- test_data$painted_tree
  trait_data <- test_data$trait_data
  
  # Remove row names
  rownames(trait_data) <- NULL
  
  # Test that error is thrown for both functions
  expect_error(
    fitMvglsAndExtractGIC(painted_tree, trait_data),
    "Row names of trait_data must exactly match the tip labels of the tree."
  )
  
  expect_error(
    fitMvglsAndExtractBIC(painted_tree, trait_data),
    "Row names of trait_data must exactly match the tip labels of the tree."
  )
})

Try the bifrost package in your browser

Any scripts or data that you put into this service are public.

bifrost documentation built on April 17, 2026, 9:07 a.m.