Repeated, optionally stratified k-fold cross-validation, implemented
with base R and data.table. split_cv() works
on a list of tables, nest_cv() on a nested table such as
the output of w2l_nest(). Folds are returned as row indices
and, optionally, as training / validation subsets
(data.table or data.frame).
split_cv()# Prepare example data: Convert first 3 columns of iris dataset to long format and split
dt_split <- w2l_split(data = iris, cols = 1:3)
# dt_split is now a list containing 3 data tables for Sepal.Length, Sepal.Width, and Petal.Length
# Example 1: Single cross-validation (no repeats)
split_cv(
data = dt_split, # Input list of split data
v = 3, # Set 3-fold cross-validation
repeats = 1, # Perform cross-validation once (no repeats)
seed = 123 # Reproducible folds
)
#> $Sepal.Length
#> id train_idx validate_idx
#> <char> <list> <list>
#> 1: Fold1 1, 2, 5, 7, 9,10,...[100] 3, 4, 6, 8,15,19,...[50]
#> 2: Fold2 3,4,5,6,7,8,...[100] 1, 2, 9,10,11,14,...[50]
#> 3: Fold3 1,2,3,4,6,8,...[100] 5, 7,12,13,16,17,...[50]
#> train validate
#> <list> <list>
#> 1: <data.table[100x3]> <data.table[50x3]>
#> 2: <data.table[100x3]> <data.table[50x3]>
#> 3: <data.table[100x3]> <data.table[50x3]>
#>
#> $Sepal.Width
#> id train_idx validate_idx
#> <char> <list> <list>
#> 1: Fold1 2, 4, 5, 6, 7,13,...[100] 1, 3, 8, 9,10,11,...[50]
#> 2: Fold2 1,2,3,4,6,8,...[100] 5, 7,13,14,17,21,...[50]
#> 3: Fold3 1,3,5,7,8,9,...[100] 2, 4, 6,15,18,22,...[50]
#> train validate
#> <list> <list>
#> 1: <data.table[100x3]> <data.table[50x3]>
#> 2: <data.table[100x3]> <data.table[50x3]>
#> 3: <data.table[100x3]> <data.table[50x3]>
#>
#> $Petal.Length
#> id train_idx validate_idx
#> <char> <list> <list>
#> 1: Fold1 1, 2, 8, 9,10,11,...[100] 3, 4, 5, 6, 7,12,...[50]
#> 2: Fold2 2,3,4,5,6,7,...[100] 1,10,11,17,18,19,...[50]
#> 3: Fold3 1,3,4,5,6,7,...[100] 2, 8, 9,14,15,20,...[50]
#> train validate
#> <list> <list>
#> 1: <data.table[100x3]> <data.table[50x3]>
#> 2: <data.table[100x3]> <data.table[50x3]>
#> 3: <data.table[100x3]> <data.table[50x3]>
# Returns a list where each element contains:
# - id: fold labels (Fold1, Fold2, Fold3)
# - train_idx / validate_idx: row indices of each fold
# - train / validate: training and validation subsets
# Example 2: Repeated cross-validation
split_cv(
data = dt_split, # Input list of split data
v = 3, # Set 3-fold cross-validation
repeats = 2, # Perform cross-validation twice
seed = 123
)
#> $Sepal.Length
#> id id2 train_idx validate_idx
#> <char> <char> <list> <list>
#> 1: Repeat1 Fold1 1, 2, 5, 7, 9,10,...[100] 3, 4, 6, 8,15,19,...[50]
#> 2: Repeat1 Fold2 3,4,5,6,7,8,...[100] 1, 2, 9,10,11,14,...[50]
#> 3: Repeat1 Fold3 1,2,3,4,6,8,...[100] 5, 7,12,13,16,17,...[50]
#> 4: Repeat2 Fold1 2, 4, 5, 6, 7,13,...[100] 1, 3, 8, 9,10,11,...[50]
#> 5: Repeat2 Fold2 1,2,3,4,6,8,...[100] 5, 7,13,14,17,21,...[50]
#> 6: Repeat2 Fold3 1,3,5,7,8,9,...[100] 2, 4, 6,15,18,22,...[50]
#> train validate
#> <list> <list>
#> 1: <data.table[100x3]> <data.table[50x3]>
#> 2: <data.table[100x3]> <data.table[50x3]>
#> 3: <data.table[100x3]> <data.table[50x3]>
#> 4: <data.table[100x3]> <data.table[50x3]>
#> 5: <data.table[100x3]> <data.table[50x3]>
#> 6: <data.table[100x3]> <data.table[50x3]>
#>
#> $Sepal.Width
#> id id2 train_idx validate_idx
#> <char> <char> <list> <list>
#> 1: Repeat1 Fold1 1, 2, 8, 9,10,11,...[100] 3, 4, 5, 6, 7,12,...[50]
#> 2: Repeat1 Fold2 2,3,4,5,6,7,...[100] 1,10,11,17,18,19,...[50]
#> 3: Repeat1 Fold3 1,3,4,5,6,7,...[100] 2, 8, 9,14,15,20,...[50]
#> 4: Repeat2 Fold1 1, 4, 5, 7, 9,10,...[100] 2, 3, 6, 8,14,20,...[50]
#> 5: Repeat2 Fold2 2,3,6,7,8,9,...[100] 1, 4, 5,12,17,21,...[50]
#> 6: Repeat2 Fold3 1,2,3,4,5,6,...[100] 7, 9,10,11,13,15,...[50]
#> train validate
#> <list> <list>
#> 1: <data.table[100x3]> <data.table[50x3]>
#> 2: <data.table[100x3]> <data.table[50x3]>
#> 3: <data.table[100x3]> <data.table[50x3]>
#> 4: <data.table[100x3]> <data.table[50x3]>
#> 5: <data.table[100x3]> <data.table[50x3]>
#> 6: <data.table[100x3]> <data.table[50x3]>
#>
#> $Petal.Length
#> id id2 train_idx validate_idx
#> <char> <char> <list> <list>
#> 1: Repeat1 Fold1 1,2,4,5,6,8,...[100] 3, 7, 9,15,22,23,...[50]
#> 2: Repeat1 Fold2 1,2,3,4,5,7,...[100] 6, 8,12,14,16,17,...[50]
#> 3: Repeat1 Fold3 3, 6, 7, 8, 9,12,...[100] 1, 2, 4, 5,10,11,...[50]
#> 4: Repeat2 Fold1 1, 2, 5, 8,10,12,...[100] 3, 4, 6, 7, 9,11,...[50]
#> 5: Repeat2 Fold2 3,4,5,6,7,8,...[100] 1, 2,12,14,20,21,...[50]
#> 6: Repeat2 Fold3 1,2,3,4,6,7,...[100] 5, 8,10,15,16,25,...[50]
#> train validate
#> <list> <list>
#> 1: <data.table[100x3]> <data.table[50x3]>
#> 2: <data.table[100x3]> <data.table[50x3]>
#> 3: <data.table[100x3]> <data.table[50x3]>
#> 4: <data.table[100x3]> <data.table[50x3]>
#> 5: <data.table[100x3]> <data.table[50x3]>
#> 6: <data.table[100x3]> <data.table[50x3]>
# Returns a list where each element contains:
# - id: repeat labels (Repeat1, Repeat2)
# - id2: fold labels (Fold1, Fold2, Fold3)
# - train_idx / validate_idx, train / validate
# Example 3: Stratified CV, indices only (memory friendly)
res <- split_cv(dt_split, v = 5, strata = "Species", seed = 1,
materialize = FALSE)
# Rebuild the training set of fold 1 of the first dataset when needed
head(dt_split[[1]][res[[1]]$train_idx[[1]], ])
#> Petal.Width Species value
#> <num> <fctr> <num>
#> 1: 0.2 setosa 5.1
#> 2: 0.2 setosa 4.9
#> 3: 0.2 setosa 4.7
#> 4: 0.2 setosa 4.6
#> 5: 0.2 setosa 5.0
#> 6: 0.4 setosa 5.4nest_cv()# Example: Cross-validation for nested data.table demonstrations
# Setup test data
dt_nest <- w2l_nest(
data = iris, # Input dataset
cols = 1:2 # Nest first 2 columns
)
# Example 1: Basic 2-fold cross-validation (reproducible)
nest_cv(
data = dt_nest, # Input nested data.table
v = 2, # Number of folds (2-fold CV)
seed = 123 # Reproducible folds
)
#> name id train_idx validate_idx
#> <char> <char> <list> <list>
#> 1: Sepal.Length Fold1 1,2,3,5,6,7,...[75] 4, 8, 9,11,12,15,...[75]
#> 2: Sepal.Length Fold2 4, 8, 9,11,12,15,...[75] 1,2,3,5,6,7,...[75]
#> 3: Sepal.Width Fold1 1,2,3,4,6,9,...[75] 5, 7, 8,11,12,13,...[75]
#> 4: Sepal.Width Fold2 5, 7, 8,11,12,13,...[75] 1,2,3,4,6,9,...[75]
#> train validate
#> <list> <list>
#> 1: <data.table[75x4]> <data.table[75x4]>
#> 2: <data.table[75x4]> <data.table[75x4]>
#> 3: <data.table[75x4]> <data.table[75x4]>
#> 4: <data.table[75x4]> <data.table[75x4]>
# Example 2: Repeated 2-fold CV, keeping only the split objects
nest_cv(
data = dt_nest, # Input nested data.table
v = 2, # Number of folds (2-fold CV)
repeats = 2, # Number of repetitions
seed = 123,
materialize = FALSE # No train/validate copies (saves memory)
)
#> name id id2 train_idx
#> <char> <char> <char> <list>
#> 1: Sepal.Length Repeat1 Fold1 1,2,3,5,6,7,...[75]
#> 2: Sepal.Length Repeat1 Fold2 4, 8, 9,11,12,15,...[75]
#> 3: Sepal.Length Repeat2 Fold1 1,2,3,4,6,9,...[75]
#> 4: Sepal.Length Repeat2 Fold2 5, 7, 8,11,12,13,...[75]
#> 5: Sepal.Width Repeat1 Fold1 2, 3, 7, 8, 9,11,...[75]
#> 6: Sepal.Width Repeat1 Fold2 1, 4, 5, 6,10,14,...[75]
#> 7: Sepal.Width Repeat2 Fold1 3, 9,10,14,15,16,...[75]
#> 8: Sepal.Width Repeat2 Fold2 1,2,4,5,6,7,...[75]
#> validate_idx
#> <list>
#> 1: 4, 8, 9,11,12,15,...[75]
#> 2: 1,2,3,5,6,7,...[75]
#> 3: 5, 7, 8,11,12,13,...[75]
#> 4: 1,2,3,4,6,9,...[75]
#> 5: 1, 4, 5, 6,10,14,...[75]
#> 6: 2, 3, 7, 8, 9,11,...[75]
#> 7: 1,2,4,5,6,7,...[75]
#> 8: 3, 9,10,14,15,16,...[75]
# Example 3: data.frame subsets, ready for ASReml-R / lm() / glm()
cv_df <- nest_cv(dt_nest, v = 2, seed = 123, out_type = "df")
class(cv_df$train[[1]]) # "data.frame"
#> [1] "data.frame"
# Example 4: masking-style CV (keep all rows, hide validation phenotypes)
cv_idx <- nest_cv(dt_nest, v = 2, seed = 123, materialize = FALSE)
cv_idx[dt_nest, on = "name", full := i.data] # attach the full nested table
masked <- as.data.frame(cv_idx$full[[1]]) # a copy: dt_nest stays intact
masked$value[cv_idx$validate_idx[[1]]] <- NA
sum(is.na(masked$value)) # validation records to be predicted
#> [1] 75