R caret createFolds vs. createMultiFolds discrepancies

Viewed 634

I create the folds for a cross-validation with caret.

I discovered a discrepancy between the functions createFolds and createMultiFolds. It seems to me that createFolds is without replacement, which is the correct version according to my understanding. createMultiFolds has two flaws, first it uses replacement and second it has much more observations per fold than expected.

Does anyone know why these discrepancies occur, or do I have to specify it differently? In the end I would like to use a repeated cross-validation.

Here is a MWE:

library(caret)
data(mtcars)

set.seed(123)
folds <- createMultiFolds(y = mtcars$am, k = 5, times = 5)

set.seed(123)
folds <- createFolds(mtcars$am, k = 5)

The output is as follows:

createMultiFolds (only the first 5 folds):

Fold1.Rep1  1  2  3  4  6  7  8  9 10  11  12  13  14  15  16  18  20  22  23  24  25  26  27  29  30  31
Fold2.Rep1  1  2  3  5  6  7  8  9 11  12  14  16  17  18  19  20  21  22  23  24  25  28  29  31  32
Fold3.Rep1  2  4  5  6  7  8  9 10 11  12  13  15  17  18  19  20  21  23  26  27  28  29  30  31  32
Fold4.Rep1  1  2  3  4  5  6  7 10 13  14  15  16  17  18  19  21  22  23  24  25  26  27  28  29  30  32
Fold5.Rep1  1  3  4  5  8  9 10 11 12  13  14  15  16  17  19  20  21  22  24  25  26  27  28  30  31  32

createFolds:

Fold1  5 17 19 21 28 32
Fold2  4 10 13 15 26 27 30
Fold3  1  3 14 16 22 24 25
Fold4  8  9 11 12 20 31
Fold5  2  6  7 18 23 29
2 Answers

If you inspect the source code of createMultiFolds, you will see that it calls createFolds with returnTrain = TRUE. From the documentation,

returnTrain: a logical. When true, the values returned are the sample
          positions corresponding to the data used during training.
          This argument only works in conjunction with ‘list = TRUE’

Therefore, if you modify createFolds appropriately, everything is well:

> library(caret)
> data(mtcars)
> set.seed(123)
> multiFolds <- createMultiFolds(y = mtcars$am, k = 5, times = 2)
> set.seed(123)
> folds1 <- createFolds(mtcars$am, k = 5, returnTrain = TRUE)
> folds2 <- createFolds(mtcars$am, k = 5, returnTrain = TRUE)
> all(multiFolds$Fold1.Rep1 == folds1$Fold1)
[1] TRUE
> all(multiFolds$Fold2.Rep1 == folds1$Fold2)
[1] TRUE
> all(multiFolds$Fold3.Rep1 == folds1$Fold3)
[1] TRUE
> all(multiFolds$Fold4.Rep1 == folds1$Fold4)
[1] TRUE
> all(multiFolds$Fold5.Rep1 == folds1$Fold5)
[1] TRUE
> all(multiFolds$Fold1.Rep2 == folds2$Fold1)
[1] TRUE
> all(multiFolds$Fold2.Rep2 == folds2$Fold2)
[1] TRUE
> all(multiFolds$Fold3.Rep2 == folds2$Fold3)
[1] TRUE
> all(multiFolds$Fold4.Rep2 == folds2$Fold4)
[1] TRUE
> all(multiFolds$Fold5.Rep2 == folds2$Fold5)
[1] TRUE

createMultiFolds has two flaws, first it uses replacement [...]

Where did you get this from? If you’re talking about the 1’s, the first one is part of the name: Fold1.Rep1, Fold2.Rep1, …, Fold{k}.Rep{times}.

As noted in the question, createFolds() splits the data into k folds. However, the output from the function is a list of observation indices that are held out from each fold, not the rows included in each fold. We can see this by creating a table of all the fold data as follows.

set.seed(123)
folds <- createFolds(mtcars$am, k = 5)
table(unlist(folds))

...and the output:

> table(unlist(folds))

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
 1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1 
27 28 29 30 31 32 
 1  1  1  1  1  1 

If we use the returnTrain = TRUE argument with createFolds(), it returns the index of observations included in each fold, as illustrated in the other answer. For k = 5, we expect each observation to be used in 4 of the folds, and confirm this with the following code.

set.seed(123)
folds <- createFolds(mtcars$am, k = 5, returnTrain = TRUE)
table(unlist(folds))

...and the output:

> table(unlist(folds))

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
 4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4 
27 28 29 30 31 32 
 4  4  4  4  4  4 

As noted in the answer, setting returnTrain = TRUE causes createFolds() to return the same output as createMultiFolds() with times = 1. We can illustrate that each observation is used in 4 of the 5 folds as follows.

set.seed(123)
folds1 <- createMultiFolds(y = mtcars$am, k = 5, times = 1)
table(unlist(folds1))

...and the output:

> table(unlist(folds1))

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
 4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4 
27 28 29 30 31 32 
 4  4  4  4  4  4 

We can compare the contents of folds and folds with lapply() and all() as follows.

# compare folds to folds1
lapply(1:5,function(x){
     all(folds1[[x]],folds[[x]])
})

[[1]]
[1] TRUE

[[2]]
[1] TRUE

[[3]]
[1] TRUE

[[4]]
[1] TRUE

[[5]]
[1] TRUE

If we set times = 2, we expect each observation to be included in 8 of the 10 folds.

set.seed(123)
folds <- createMultiFolds(y = mtcars$am, k = 5, times = 2)
table(unlist(folds))

...and the output:

> table(unlist(folds))

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
 8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8 
27 28 29 30 31 32 
 8  8  8  8  8  8

CONCLUSIONS: in both functions caret uses sampling to ensure that each observation is included in the hold out group 1 time across the k folds for each repetition of times =, within the constraint that observations for each value of the dependent variable passed to the function are proportionally distributed in the in sample and out of sample components of each fold.

In the case of a small data set such as mtcars, it's not easy for the algorithm to split effectively, as we can see when we run tables to compare in sample / holdout vs. mtcars$am.

set.seed(123)
folds <- createFolds(mtcars$am, k = 5)
table(unlist(folds))
lapply(folds,function(x){
     holdout <- rep(FALSE,nrow(mtcars))
     holdout[x] <- TRUE
     table(holdout,mtcars$am)
})

$Fold1
       
holdout  0  1
  FALSE 16 10
  TRUE   3  3

$Fold2
       
holdout  0  1
  FALSE 15 10
  TRUE   4  3

$Fold3
       
holdout  0  1
  FALSE 14 11
  TRUE   5  2

$Fold4
       
holdout  0  1
  FALSE 15 11
  TRUE   4  2

$Fold5
       
holdout  0  1
  FALSE 16 10
  TRUE   3  3

Each fold contains 6 or 7 observations in the hold out set, with a minimum of 2 manual transmission cars (am = 1) in each hold out set.

With default arguments, createFolds() returns the indexes of held out observations rather than included observations. createFolds(x,k,returnTrain=TRUE) behaves exactly the same as createMultiFolds(x,k,times=1).

Related