Nothing
# the fold-balancing objective shared by cv_spatial (random selection) and
# cv_cluster(balance = TRUE); see .balance_score / .balance_folds in utils.R
pa <- read.csv(system.file("extdata", "species.csv", package = "blockCV"))
pa_sf <- sf::st_as_sf(pa, coords = c("x", "y"), crs = 7845)
# build a records table for a given fold vector (helper for the unit tests)
records_for <- function(folds, response, k){
tt <- .records_table(k, response)
for(p in seq_len(k)){
test_set <- which(folds == p)
train_set <- which(folds != p)
tt <- .records_table_row(tt, p, train_set, test_set, response)
}
tt
}
test_that(".balance_score works for the single-column presence-background case", {
# a length-1 test_cols must not trip the diag()-of-a-scalar identity gotcha
resp <- .column_response(pa, "occ")
tt <- records_for((seq_len(nrow(pa)) %% 5L) + 1L, resp, k = 5)
score <- .balance_score(tt, "test_1", k = 5)
expect_length(score, 2)
expect_true(all(is.finite(score)))
# a perfectly even split scores zero on both components
even <- .records_table(4, .column_response(data.frame(occ = rep(0:1, each = 8)), "occ"))
even[, c("test_0", "test_1")] <- 2 # 2 of each class in every one of 4 folds
zero <- .balance_score(even, c("test_0", "test_1"), k = 4)
expect_equal(zero, c(0, 0))
})
test_that("the objective beats the old min/sd rule on the packaged data", {
resp <- .column_response(pa, "occ")
k <- 5
n <- nrow(pa)
test_cols <- grep("^test", names(.records_table(k, resp)), value = TRUE)
# a shared pool of candidate assignments scored by both rules
set.seed(123)
tables <- lapply(seq_len(200), function(i){
records_for(sample(rep_len(seq_len(k), n)), resp, k)
})
# old rule: ratchet up the minimum cell, ratchet down the sd of *all* cells,
# requiring both to improve at once (the behaviour being replaced)
old_pick <- NA_integer_; min_num <- 0; max_sd <- Inf
for(i in seq_along(tables)){
obj <- tables[[i]]
if(min(obj) >= min_num && stats::sd(unlist(obj)) < max_sd){
old_pick <- i; min_num <- min(obj); max_sd <- stats::sd(unlist(obj))
}
}
# new rule: lexicographic argmin on (eligible empty cells, imbalance)
scores <- vapply(tables, .balance_score, numeric(2), test_cols = test_cols, k = k)
new_pick <- order(scores[1, ], scores[2, ])[1]
imbalance <- function(tt) .balance_score(tt, test_cols, k)[2]
tvar <- function(tt) sum(apply(as.matrix(tt[, test_cols, drop = FALSE]), 2, stats::var))
# (b) the chosen split is at least as balanced as the old rule's pick
expect_lte(imbalance(tables[[new_pick]]), imbalance(tables[[old_pick]]))
expect_lte(tvar(tables[[new_pick]]), tvar(tables[[old_pick]]))
})
test_that("cv_cluster(balance = TRUE) minimises eligible empty test-class cells", {
set.seed(42)
cl <- cv_cluster(
x = pa_sf,
column = "occ",
k = 5,
balance = TRUE,
report = FALSE,
progress = FALSE
)
rec <- cl$records
test_cols <- grep("^test", names(rec), value = TRUE)
tt <- as.matrix(rec[, test_cols, drop = FALSE])
n_c <- colSums(tt)
# every class with at least k records appears in every test fold for this data
fillable <- tt[, n_c >= 5, drop = FALSE]
expect_true(all(fillable > 0))
})
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.