我有一个像这一个简单的data.frame:
id group idu value
1 1 1_1 34
2 1 2_1 23
3 1 3_1 67
4 2 4_2 6
5 2 5_2 24
6 2 6_2 45
1 3 1_3 34
2 3 2_3 67
3 3 3_3 76
Run Code Online (Sandbox Code Playgroud)
从我想要检索每个组的第一个条目的子集; 就像是:
id group idu value
1 1 1_1 34
4 2 4_2 6
1 3 1_3 34
Run Code Online (Sandbox Code Playgroud)
id不是唯一的,所以方法不应该依赖它.
我可以实现避免循环吗?
dput()
数据的:
structure(list(id = c(1L, 2L, 3L, 4L, 5L, 6L, 1L, 2L, 3L), group = c(1L,
1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L), idu = structure(c(1L, 3L, 5L,
7L, 8L, 9L, 2L, 4L, 6L), .Label = c("1_1", "1_3", "2_1", "2_3",
"3_1", "3_3", "4_2", "5_2", "6_2"), class = "factor"), value = c(34L,
23L, 67L, 6L, 24L, 45L, 34L, 67L, 76L)), .Names = c("id", "group",
"idu", "value"), class = "data.frame", row.names = c(NA, -9L))
Run Code Online (Sandbox Code Playgroud)
had*_*ley 10
使用Gavin的百万行df:
DF3 <- data.frame(id = sample(1000, 1000000, replace = TRUE),
group = factor(rep(1:1000, each = 1000)),
value = runif(1000000))
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_")))
Run Code Online (Sandbox Code Playgroud)
我认为最快的方法是重新排序数据框,然后使用duplicated
:
system.time({
DF4 <- DF3[order(DF3$group), ]
out2 <- DF4[!duplicated(DF4$group), ]
})
# user system elapsed
# 0.335 0.107 0.441
Run Code Online (Sandbox Code Playgroud)
相比之下,我的电脑上Gavin的紧固拉伸+分割方法为7秒.
通常,在处理数据帧时,最快的方法通常是生成所有索引,然后执行单个子集.
根据OP的评论更新
如果在数百万行上执行此操作,则所提供的所有选项都将很慢.以下是100,000行虚拟数据集的一些比较时序:
set.seed(12)
DF3 <- data.frame(id = sample(1000, 100000, replace = TRUE),
group = factor(rep(1:100, each = 1000)),
value = runif(100000))
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_")))
> system.time(out1 <- do.call(rbind, lapply(split(DF3, DF3["group"]), `[`, 1, )))
user system elapsed
19.594 0.053 19.984
> system.time(out3 <- aggregate(DF3[,-2], DF3["group"], function (x) x[1]))
user system elapsed
12.419 0.141 12.788
Run Code Online (Sandbox Code Playgroud)
我放弃了一百万行.更快,不管你信不信,是:
out2 <- matrix(unlist(lapply(split(DF3[, -4], DF3["group"]), `[`, 1,)),
byrow = TRUE, nrow = (lev <- length(levels(DF3$group))))
colnames(out2) <- names(DF3)[-4]
rownames(out2) <- seq_len(lev)
out2 <- as.data.frame(out2)
out2$group <- factor(out2$group)
out2$idu <- factor(paste(out2$id, out2$group, sep = "_"),
levels = levels(DF3$idu))
Run Code Online (Sandbox Code Playgroud)
输出(有效)相同:
> all.equal(out1, out2)
[1] TRUE
> all.equal(out1, out3[, c(2,1,3,4)])
[1] "Attributes: < Component 2: Modes: character, numeric >"
[2] "Attributes: < Component 2: target is character, current is numeric >"
Run Code Online (Sandbox Code Playgroud)
(out1
(或out2
)和out3
(aggregate()
版本)之间的差异就在组件的rownames中.)
时间:
user system elapsed
0.163 0.001 0.168
Run Code Online (Sandbox Code Playgroud)
关于100,000行问题,以及这一百万行问题:
set.seed(12)
DF3 <- data.frame(id = sample(1000, 1000000, replace = TRUE),
group = factor(rep(1:1000, each = 1000)),
value = runif(1000000))
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_")))
Run Code Online (Sandbox Code Playgroud)
有时间的
user system elapsed
11.916 0.000 11.925
Run Code Online (Sandbox Code Playgroud)
使用矩阵版本(生成out2
)可以更快地完成其他版本处理100,000行问题的百万行.这只是表明使用矩阵确实很快,而我的do.call()
版本中的瓶颈就是rbind()
将结果放在一起.
万行问题时间安排完成:
system.time({out4 <- matrix(unlist(lapply(split(DF3[, -4], DF3["group"]),
`[`, 1,)),
byrow = TRUE,
nrow = (lev <- length(levels(DF3$group))))
colnames(out4) <- names(DF3)[-4]
rownames(out4) <- seq_len(lev)
out4 <- as.data.frame(out4)
out4$group <- factor(out4$group)
out4$idu <- factor(paste(out4$id, out4$group, sep = "_"),
levels = levels(DF3$idu))})
Run Code Online (Sandbox Code Playgroud)
原版的
如果你的数据在DF
,那么:
do.call(rbind, lapply(with(DF, split(DF, group)), head, 1))
Run Code Online (Sandbox Code Playgroud)
会做你想做的事:
> do.call(rbind, lapply(with(DF, split(DF, group)), head, 1))
idu group
1 1 1
2 4 2
3 7 3
Run Code Online (Sandbox Code Playgroud)
如果有新数据,DF2
我们会得到:
> do.call(rbind, lapply(with(DF2, split(DF2, group)), head, 1))
id group idu value
1 1 1 1_1 34
2 4 2 4_2 6
3 1 3 1_3 34
Run Code Online (Sandbox Code Playgroud)
但是对于速度,我们可能想要子集而不是使用head()
,我们可以通过不使用来获得一点with()
,例如:
do.call(rbind, lapply(split(DF2, DF2$group), `[`, 1, ))
> system.time(replicate(1000, do.call(rbind, lapply(split(DF2, DF2$group), `[`, 1, ))))
user system elapsed
3.847 0.040 4.044
> system.time(replicate(1000, do.call(rbind, lapply(split(DF2, DF2$group), head, 1))))
user system elapsed
4.058 0.038 4.111
> system.time(replicate(1000, aggregate(DF2[,-2], DF2["group"], function (x) x[1])))
user system elapsed
3.902 0.042 4.106
Run Code Online (Sandbox Code Playgroud)