0
$\begingroup$

I am working with the following dataset:

dataFrame <- read.table(header=T, text='Students  Year    Score    Flag
1    0    62    0
1    1    66    0
1    2    62    0
1    3    73    0
2    0    70    0
2    1    50    0
2    2    50    0
2    3    70    0
3    0    68    0
3    1    74    0
3    2    68    0
3    3    78    0
4    0    50    0
4    1    50    0
4    2    50    0
4    3    58    0
5    0    63    0
5    1    63    0
5    2    67    0
5    3    70    0
6    0    63    0
6    1    63    0
6    2    63    0
6    3    69    0
7    0    62    0
7    1    63    0
7    2    64    0
7    3    65    0
8    0    67    0
8    1    73    0
8    2    65    0
8    3    75    0
9    0    54    1
9    1    63    1
9    2    50    1
9    3    49    1
9    3    50    1
10    0    60    1
10    1    81    1
10    2    50    1
10    3    49    1')

In the dataset above I have the following variables:

"Students": factor with 10 levels (1,2,...,10)

"Year": factor with 4 levels (0,1,2,3)

The dependent variable "Score"

and a variable "Flag" that idenfies students that had at least 1 "Score" below 50 in the Year 3

In this data set the only two students "Flag" = 1 are the "Student" = 9 and "Student" = 10.

What I would like to achieve is a balanced partition of the data set that takes into account both the variable "Flag" and the variable "Students". For instance in this data set this means to have the "Student" 9 in one partition and the 10 in the other, for instance:

training

Students  Year    Score    Flag
1    0    62    0
1    1    66    0
1    2    62    0
1    3    73    0
2    0    70    0
2    1    50    0
2    2    50    0    
2    3    70    0
3    0    68    0
3    1    74    0
3    2    68    0
3    3    78    0
4    0    50    0
4    1    50    0
4    2    50    0
4    3    58    0
6    0    63    0
6    1    63    0
6    2    63    0
6    3    69    0
10    0    60    1
10    1    81    1
10    2    50    1
10    3    49    1

test

Students  Year    Score    Flag
5    0    63    0
5    1    63    0
5    2    67    0
5    3    70    0
6    0    63    0
6    1    63    0
6    2    63    0
6    3    69    0
7    0    62    0
7    1    63    0
7    2    64    0
7    3    65    0
8    0    67    0
8    1    73    0
8    2    65    0
8    3    75    0
9    0    54    1
9    1    63    1
9    2    50    1
9    3    49    1
9    3    50    1

I have tried the following but doesn't seem to work

library(caret)
trainIndex                  <- createDataPartition( c( dataFrame$Flag , dataFrame$Students ) , 
                                                p     = .6 ,
                                                list  = FALSE ,
                                                times = 1 )


dataFrame[ trainIndex , ]
dataFrame[ -trainIndex , ]

What would be the best way of obtaning what I need?

Many thanks

$\endgroup$
5
  • $\begingroup$ c( dataFrame\$Flag , dataFrame\$Students ) concatenated the two vectors (giving you one of twice the length); what you want is a "rowwise" concatenation. Add a column to your dataframe with something like within(dataFrame, newcol <- paste(as.character(Flag), as.character(Students))). $\endgroup$
    – kasterma
    Commented Feb 8, 2015 at 20:16
  • $\begingroup$ @kasterma thanks for your reply. I don't think your solution is producing what I need: the students 9 and 10 end up in both train and test data sets $\endgroup$
    – user44252
    Commented Feb 8, 2015 at 20:33
  • $\begingroup$ Indeed I misunderstood. I still do (since I don't understand the flag variable and its properties), but the student 9 and 10 problem can be solved by using createDataPartition on the levels of the student variable (as you read it in above Students is not a factor, but you can make it so). trainIdx <- createDataPartition(levels(dataFrame\$Students), .....) Then trainstudentIds = levels(dataFrame\$Students)[trainIdx]. $\endgroup$
    – kasterma
    Commented Feb 9, 2015 at 5:55
  • $\begingroup$ @kasterma thanks again, maybe, if I explain the background it would be a bit more clear what I am trying to achive here. $\endgroup$
    – user44252
    Commented Feb 9, 2015 at 9:31
  • $\begingroup$ In the original data set I have a large cohort of students and I want to predict who is going to fail ("Score" < 50) in the third year ("Year" = 3) by looking at previous performance ("Year" = 0,1,2) and other predictors. The proportion of such failing students is very low and If I randomly partition I might end up having no failing students in train or test.Therefore I need to make sure I pick up a balanced ammount of failures ("Flag"=1) and successes ("Flag"=0) but also I need to insure that the same Student is not scattered in different data sets. $\endgroup$
    – user44252
    Commented Feb 9, 2015 at 9:33

3 Answers 3

0
$\begingroup$

After some back and forth in comments I think I understand

First get a table of students and flags, in this case obtained by

flaggedStudents <- dataFrame[!duplicated(dataFrame$Students),c(1,4)]

Then from the students pick a balanced partition using createDataPartition

trainStudents <- createDataPartition(flaggedStudents$Flag, p=0.6, list=FALSE, times=1)

Then you have the training frame as follows:

dataFrame[dataFrame$Student %in% trainStudents,]

and testing frame:

dataFrame[!dataFrame$Student %in% trainStudents,]

Better would be to "normalize" your whole dataframe. Now you have a flag variable that is per student, repeated in multiple rows, but these rows have a varying score depending on the second column. I wrote that nice and messy, because it is.

An interesting paper describing how you might like your data to be is tidy data by Hadley Wickham (pdf).

$\endgroup$
3
  • $\begingroup$ interesting: your solution seems to be working only if "Students" and "Flag" are forced to factors: if I set.seed( 1 ) and run createDataPartition with the variables as NOT factors I obtain the following students: 2,3,4,5,7,8 (so the flagged students 9 & 10 are kept together). Any idea why? Also, I can't keep the data in the large format (if that was what your where thinking) because some students had to take Year 3 multiple times $\endgroup$
    – user44252
    Commented Feb 9, 2015 at 10:46
  • $\begingroup$ @user44252 "so the flagged students 9 & 10 are kept together" this will happen with probability 0.6 * 0.6 + 0.4 * 0.4 (either both in train class, or both in test class). $\endgroup$
    – kasterma
    Commented Feb 9, 2015 at 11:02
  • $\begingroup$ but then why when "Students" and "Flag" are turned into factors your solution works fine? Anyway, many thanks $\endgroup$
    – user44252
    Commented Feb 9, 2015 at 16:44
0
$\begingroup$

In the spirit of sharing a working example, here is the solution based on the suggestion made above by @kasterma.

The relevant variables must be turned into factors in order to have the code working properly.

dataFrame$Students <- factor( dataFrame$Students )
dataFrame$Flag     <- factor( dataFrame$Flag )

flaggedStudents <- dataFrame[!duplicated(dataFrame$Students),c(1,4)]

trainStudents <- createDataPartition(flaggedStudents$Flag, p=0.6, list=FALSE, times=1)

dataFrame[ dataFrame$Student %in% trainStudents , ]
   dataFrame[!dataFrame$Student %in% trainStudents , ]
$\endgroup$
0
$\begingroup$

The above answer is great! But it does not work when the "Student" (id) values are characters. It can be made to, by adding one line:

trainStudents <-  unique(dataFrame$Students)[trainStudents]

just before running

dataFrame[ dataFrame$Student %in% trainStudents , ]

This updates the numeric identifiers returned by createDataPartition into the actual values of the IDs.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.