tests/testthat/test-depth.R

grid <- round(seq(0, 10, length.out = 11), 3)
# use (0.1 * grid + 1) so curves don't all coincide at t=0,
# avoiding platform-dependent tie-breaking in depth computations
lin <- -3:3 * tfd(0.1 * grid + 1, grid)
parallel <- -3:3 + tfd(0 * grid, grid)
names(lin) <- names(parallel) <- 1:7

spike_regular <- c(parallel, tfd(100 * (grid == 10), grid))
spike_irregular <- c(
  -3:3 + tfd(c(0 * grid, 0), c(grid, 20)),
  tfd(100 * (c(grid, 20) == 20), c(grid, 20))
)
na <- 1 * NA + lin[1]

lin_irreg <- {
  m <- as.matrix(lin)
  m[cbind(2:7, 2:7)] <- NA
  tfd(m, evaluator = tf_approx_linear)
}

lin_b <- tfb(lin, verbose = FALSE) |> suppressWarnings()

test_that("MBD works", {
  ranks <- c(1.5, 3.5, 5.5, 7, 5.5, 3.5, 1.5)
  names(ranks) <- names(lin)
  expect_equal(rank(tf_depth(lin, depth = "MBD")), ranks)
  expect_equal(rank(tf_depth(parallel, depth = "MBD")), ranks)
  expect_equal(rank(tf_depth(lin_irreg, depth = "MBD")), ranks)
  expect_equal(rank(tf_depth(lin_b, depth = "MBD")), ranks)
  # weighting by interval length:
  # increases importance of last point -> lower depth
  expect_gt(
    tail(tf_depth(spike_regular), 1),
    tail(tf_depth(spike_irregular), 1)
  )
})

test_that("FM works", {
  # FM should give central curve the highest depth (1 = most central)
  expect_equal(which.max(tf_depth(lin, depth = "FM")), c("4" = 4))
  expect_equal(which.max(tf_depth(parallel, depth = "FM")), c("4" = 4))
  # FM works with irregular and basis representations
  expect_equal(which.max(tf_depth(lin_irreg, depth = "FM")), c("4" = 4))
  expect_equal(which.max(tf_depth(lin_b, depth = "FM")), c("4" = 4))
  # FM values in [0, 1]
  fm_vals <- tf_depth(lin, depth = "FM")
  expect_true(all(fm_vals >= 0 & fm_vals <= 1))
  # FM ranking: central curve deepest for parallel data
  expect_equal(which.max(tf_depth(parallel, depth = "FM")), c("4" = 4))
  # weighting by interval length
  expect_gt(
    tail(tf_depth(spike_regular, depth = "FM"), 1),
    tail(tf_depth(spike_irregular, depth = "FM"), 1)
  )
})

test_that("FM handles ties consistently", {
  identical_curves <- matrix(c(1, 1, 1, 1, 1, 1), nrow = 3, byrow = TRUE)
  expect_equal(tf_depth(identical_curves, depth = "FM"), c(0, 0, 0))

  tied_curves <- matrix(c(0, 0, 0, 0, 1, 1), nrow = 3, byrow = TRUE)
  expect_equal(tf_depth(tied_curves, depth = "FM"), c(2 / 3, 2 / 3, 0))
  expect_equal(
    tf_depth(tied_curves[c(3, 1, 2), , drop = FALSE], depth = "FM"),
    c(0, 2 / 3, 2 / 3)
  )
})

test_that("FSD works", {
  # FSD gives symmetric depths for parallel lines (within floating-point tolerance)
  fsd_par <- unname(tf_depth(parallel, depth = "FSD"))
  expect_equal(fsd_par[1], fsd_par[7], tolerance = 1e-10)
  expect_equal(fsd_par[2], fsd_par[6], tolerance = 1e-10)
  expect_equal(fsd_par[3], fsd_par[5], tolerance = 1e-10)
  expect_gt(fsd_par[4], fsd_par[3])
  # central curve has highest depth (1 = most central)
  expect_equal(which.max(tf_depth(lin, depth = "FSD")), c("4" = 4))
  expect_equal(which.max(tf_depth(parallel, depth = "FSD")), c("4" = 4))
  # works with irregular and basis representations
  expect_equal(which.max(tf_depth(lin_irreg, depth = "FSD")), c("4" = 4))
  expect_equal(which.max(tf_depth(lin_b, depth = "FSD")), c("4" = 4))
  # FSD values in [0, 1]
  fsd_vals <- tf_depth(lin, depth = "FSD")
  expect_true(all(fsd_vals >= 0 & fsd_vals <= 1))
  # weighting by interval length
  expect_gt(
    tail(tf_depth(spike_regular, depth = "FSD"), 1),
    tail(tf_depth(spike_irregular, depth = "FSD"), 1)
  )
})

test_that("RPD works", {
  withr::local_seed(4217)
  # central curve has highest depth (1 = most central)
  expect_equal(which.max(tf_depth(lin, depth = "RPD")), c("4" = 4))
  # central curve deepest for parallel lines
  expect_equal(which.max(tf_depth(parallel, depth = "RPD")), c("4" = 4))
  # RPD values in (0, 1]
  rpd_vals <- tf_depth(lin, depth = "RPD")
  expect_true(all(rpd_vals > 0 & rpd_vals <= 1))
  # works with irregular and basis representations
  expect_equal(which.max(tf_depth(lin_irreg, depth = "RPD")), c("4" = 4))
  expect_equal(which.max(tf_depth(lin_b, depth = "RPD")), c("4" = 4))
  # RPD detects shape outliers that MBD misses:
  skip_on_cran()
  set.seed(42)
  t_grid <- seq(0, 1, length.out = 101)
  n_curves <- 50
  normal_mat <- matrix(0, n_curves, length(t_grid))
  for (i in seq_len(n_curves)) {
    normal_mat[i, ] <- cumsum(rnorm(length(t_grid), sd = 0.1))
  }
  outlier_mat <- matrix(sin(20 * pi * t_grid) * 0.5, nrow = 1)
  all_curves <- tfd(rbind(normal_mat, outlier_mat), arg = t_grid)
  rpd_depths <- tf_depth(
    all_curves,
    depth = "RPD",
    n_projections = 10000L,
    n_projections_beta = 1000L
  )
  mbd_depths <- tf_depth(all_curves, depth = "MBD")
  # RPD should rank the shape outlier lower (less central) than MBD does
  expect_lt(rank(rpd_depths)[n_curves + 1], rank(mbd_depths)[n_curves + 1])
})


test_that("matrix na.rm drops incomplete observations", {
  x <- matrix(c(0, NA, 1, 1, 2, 2), nrow = 3, byrow = TRUE)

  expect_equal(tf_depth(x, depth = "FM", na.rm = TRUE), c(1, 0))
  expect_equal(tf_depth(x, depth = "FSD", na.rm = TRUE), c(0.5, 0.5))

  withr::local_seed(4217)
  expect_equal(
    tf_depth(
      x,
      depth = "RPD",
      na.rm = TRUE,
      n_projections = 100L,
      n_projections_beta = 20L
    ),
    c(0.5, 0.5)
  )
})

test_that("median works", {
  expect_true(is.na(median(c(na, lin))))
  expect_identical(median(c(na, lin), na.rm = TRUE), median(lin))
  expect_message(median(lin[1:2]), "maximal depth")
})

Try the tf package in your browser

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

tf documentation built on April 7, 2026, 5:07 p.m.