Reputation: 63
I am trying to figure out how I can take only first 5 rows by a group without replacement in another variable value. For example, if the existing data table (or frame) looks like this:
id V1
1 101
1 102
1 103
1 104
1 105
1 106
1 107
1 108
1 109
1 110
2 101
2 103
2 105
2 107
2 108
2 109
2 110
2 111
2 112
2 101
3 104
3 105
3 107
3 108
3 109
3 110
3 101
3 102
3 103
3 104
But I just want to get first 5 rows for each group but without replacement in V1 values across the groups. So the result table I want is...:
id V1
1 101
1 102
1 103
1 104
1 105
2 107
2 108
2 109
2 110
2 111
3 NA
I have been trying to do this using for loop by going through each id one at a time ....taking first 5 rows for each id and excluding the following rows with V1 values in the previous ids. But as my data is really big (the number of ids is over a million), it takes forever for the for loop to go through all the ids.
Is there anyone smarter than me to help me to find a better, more efficient and clever way to deal with this problem? Thanks much!
Upvotes: 4
Views: 147
Reputation: 1421
Still working on it. This is what I came up with (notice that since id = 3 has only duplicate values it will not be shown at the end). One can Change that. I am not sure about the Performance. Will see if I can come up with something smarter...
df = data.frame (id = c (1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,
2,2,2,2,2,3,3,3,3,3,3,3,3,3,3),
V1 = c(101,102, 103,104,105,106,107,108,109,110,101,
103,105,107,108,109,110,111,112,101,104,
105,107,108,109,110,101,102,103,104))
df2 <- df
for (i in unique(df$id)) {
dfsel <- data.frame(df2 %>% group_by(id) %>% filter(row_number() <= 5 & id == i))
df3 <- df2[!(df2$V1 %in% dfsel$V1) & df2$id != i,]
df2 <- rbind(dfsel,df3)
}
df2[with (df2, order(id)),]
result is
id V1
1 101
1 102
1 103
1 104
1 105
2 107
2 108
2 109
2 110
2 111
EDITED: found another way. Probably not really smarter but I had fun :) One should check Performance, did not have time to think about it properly.
Here is the code
dd <- split(df$V1, df$id)
maxdf <- data.frame(mx = rep(0,length(dd)))
maxdf[1,1] <- dd[[1]][5]
dd[[1]][dd[[1]] > maxdf[1,1]] <- NA
n <- unique(df$id)[2:length(unique(df$id))]
for (i in n) {
dd[[i]][dd[[i]] <= maxdf[i-1,1]] <- NA
maxdf[i,1] <- dd[[i]][!is.na(dd[[i]])][5]
dd[[i]][dd[[i]] > maxdf[i,1]] <- NA
}
df <- stack(dd)
names(df) <- c("V1","id")
df <- df[!is.na(df$V1),]
PS: Solution below is still much more elegant :)
Upvotes: 1
Reputation: 70266
Here's an option in three steps:
# create a vector to store set values
x <- numeric()
# compute the values by id and update x in the process
res <- lapply(split(df$V1, df$id), function(y) {
y <- head(setdiff(y, x), 5)
x <<- union(x, y)
if(!length(y)) NA else y
})
# combine the result to data.frame
stack(res)
# values ind
#1 101 1
#2 102 1
#3 103 1
#4 104 1
#5 105 1
#6 107 2
#7 108 2
#8 109 2
#9 110 2
#10 111 2
#11 NA 3
Upvotes: 4