Search code examples
rmachine-learningtrain-test-split

Keep same ratios between groups in training and test datasets


For a machine learning project, I would like to split my data into train and test sets keeping the fraction of a particular group consistent among the sets. I have created a dummy data.frame of 40 rows to explain myself. Here, for the group "Region", 20% of the data is "North America" , 50% "Europe, 20% Asia and 10% Oceania. I want to end up with a random subset, e.g. 25% of the entire data, in which the percentage composition of the group "Region" remains unchanged.

In other words, I want to start with this:

    City    County  Region
1   Shangai China   Asia
2   Tokyo   Japan   Asia
3   Osaka   Japan   Asia
4   Hanoi   Vietnam Asia
5   Beijing China   Asia
6   Sapporo Japan   Asia
7   Tottori Japan   Asia
8   Saigon  Vietnam Asia
9   Rome    Italy   Europe
10  Paris   France  Europe
11  Lisbon  Portugal    Europe
12  Berlin  Germany Europe
13  Madrid  Spain   Europe
14  Vienna  Austria Europe
15  Naples  Italy   Europe
16  Nice    France  Europe
17  Porto   Portugal    Europe
18  Frankfurt   Germany Europe
19  Sevilla Spain   Europe
20  Salzburg    Austria Europe
21  Barcelona   Spain   Europe 
22  Amsterdam   Netherlands Europe 
23  Bern    Switzerland Europe 
24  Milan   Italy   Europe 
25  San Sebastian   Spain   Europe 
26  Rotterdam   Netherlands Europe 
27  Zurich  Switzerland Europe 
28  Turin   Italy   Europe 
29  Ney York City   US  North America
30  Toronto Canada  North America
31  Mexico City Mexico  North America
32  Atlanta US  North America
33  Chicago US  North America
34  Atlanta US  North America
35  Vancouver   Canada  North America
36  Guadalajara Mexico  North America
37  Sydney  Australia   Oceania
38  Wellington  New Zealand Oceania
39  Melbourne   Australia   Oceania
40  Auckland    New Zealand Oceania

And end with this (random selection of rows is important to me):

    City    County  Region
1   New York    US  North America
2   Mexico City Mexico  North America
3   Amsterdam   Netherlands Europe 
4   Madrid  Spain   Europe
5   Lisbon  Portugal    Europe
6   Rome    Italy   Europe
7   Paris   France  Europe
8   Tokyo   Japan   Asia
9   Osaka   Japan   Asia
10  Wellington  New Zealand Oceania

Solution

  • The createDataPartition() function from the caret package can be used to assign observations to training and test groups while preserving the percentage distribution within each class of a split variable. We'll illustrate its use with the AlzheimerDisease data from Applied Predictive Modeling.

    library(caret)
    library(AppliedPredictiveModeling)
    set.seed(90125)
    data(AlzheimerDisease)
    adData = data.frame(diagnosis,predictors)
    inTrain = createDataPartition(adData$diagnosis, p = .6)[[1]]
    training = adData[ inTrain,]
    testing = adData[-inTrain,]
    

    We'll now generate tables for the dependent variables in each data frame, and the Impaired percentage in each is slightly less than 38%.

    > table(training$diagnosis)
    
    Impaired  Control 
          55      146 
    > table(testing$diagnosis)
    
    Impaired  Control 
          36       96 
    > 55/146
    [1] 0.3767123
    > 36/96
    [1] 0.375
    > 
    

    Using data from the original post

    If we take a 75% sample from the data provided with the question, we can partition into a training data frame of 30 rows and a testing frame of 10 rows.

    # OP data
    textFile <- "id|City|County|Region
    1|Shangai|China|Asia
    2|Tokyo|Japan|Asia
    3|Osaka|Japan|Asia
    4|Hanoi|Vietnam|Asia
    5|Beijing|China|Asia
    6|Sapporo|Japan|Asia
    7|Tottori|Japan|Asia
    8|Saigon|Vietnam|Asia
    9|Rome|Italy|Europe
    10|Paris|France|Europe
    11|Lisbon|Portugal|Europe
    12|Berlin|Germany|Europe
    13|Madrid|Spain|Europe
    14|Vienna|Austria|Europe
    15|Naples|Italy|Europe
    16|Nice|France|Europe
    17|Porto|Portugal|Europe
    18|Frankfurt|Germany|Europe
    19|Sevilla|Spain|Europe
    20|Salzbourg|Austria|Europe
    21|Barcelona|Spain|Europe
    22|Amsterdam|Netherlands|Europe
    23|Bern|Switzerland|Europe
    24|Milan|Italy|Europe
    25|SanSebastian|Spain|Europe
    26|Rotterdam|Netherlands|Europe
    27|Zurich|Switzerland|Europe
    28|Turin|Italy|Europe
    29|New York City|US|North America
    30|Toronto|Canada|North America
    31|Mexico City|Mexico|North America
    32|Atlanta|US|North America
    33|Chicago|US|North America
    34|Atlanta|US|North America
    35|Vancouver|Canada|North America
    36|Guadalajara|Mexico|North America
    37|Syndey|Australia|Oceania
    38|Wellington|New Zealand|Oceania
    39|Melbourn|Australia|Oceania
    40|Auckland|New Zealand|Oceania"
    
    data <- read.table(text = textFile,header = TRUE,sep = "|", 
                       stringsAsFactors = FALSE)
    set.seed(901250)
    inTrain = createDataPartition(data$Region, p = .75)[[1]]
    training = data[ inTrain,]
    testing = data[-inTrain,]
    

    When we print a table of the test data, we see that Region is distributed as requested in the question: 20% Asia, 50% Europe, 20% North America, and 10% Oceania.

    > table(testing$Region)
    
            Asia       Europe NorthAmerica      Oceania 
               2            5            2            1 
    > 
    

    Finally, we'll print the testing data frame.

    > testing
       id        City      County        Region
    2   2       Tokyo       Japan          Asia
    8   8      Saigon     Vietnam          Asia
    9   9        Rome       Italy        Europe
    17 17       Porto    Portugal        Europe
    19 19     Sevilla       Spain        Europe
    21 21   Barcelona       Spain        Europe
    22 22   Amsterdam Netherlands        Europe
    32 32     Atlanta          US North America
    36 36 Guadalajara      Mexico North America
    38 38  Wellington New Zealand       Oceania
    >