寻找向量中唯一值的索引的高效R代码

12

假设我有向量 vec <- c("D","B","B","C","C")

我的目标是得到一个长度为 length(unique(vec)) 的列表,其中列表中的每个元素 i 返回一个由索引组成的向量,这些索引指示在 vec 中出现的位置 unique(vec)[i]

例如,对于 vec 的这个列表将返回:

exampleList <- list()
exampleList[[1]] <- c(1) #Since "D" is the first element
exampleList[[2]] <- c(2,3) #Since "B" is the 2nd/3rd element.
exampleList[[3]] <- c(4,5) #Since "C" is the 4th/5th element.

我尝试了以下的方法,但速度太慢了。我的示例很大,因此我需要更快的代码:

vec <- c("D","B","B","C","C")
uniques <- unique(vec)
exampleList <- lapply(1:3,function(i) {
    which(vec==uniques[i])
})
exampleList

如果有人在这里寻找唯一索引作为向量(而不是列表),那么它就是:order(vec)[!duplicated(sort(vec))],其中 vec <- c("D","B","B","C","C") - stevec
4个回答

7
更新: 在 R 版本 >= 3.1.0 中,行为 DT[, list(list(.)), by=.] 有时会导致错误的结果。在当前的开发版本中,data.table v1.9.3 的 commit #1280 中已经修复了此问题。来自NEWS的消息:

  • DT[, list(list(.)), by=.] 在 R >=3.1.0 中也返回正确的结果。该 bug 是由于 R v3.1.0 中最近(受到欢迎的)更改中,list(.) 不会导致 复制。Closes#481

使用data.table比使用tapply快约15倍:

library(data.table)

vec <- c("D","B","B","C","C")

dt = as.data.table(vec)[, list(list(.I)), by = vec]
dt
#   vec  V1
#1:   D   1
#2:   B 2,3
#3:   C 4,5

# to get it in the desired format
# (perhaps in the future data.table's setnames will work for lists instead)
setattr(dt$V1, 'names', dt$vec)
dt$V1
#$D
#[1] 1
#
#$B
#[1] 2 3
#
#$C
#[1] 4 5

速度测试:

vec = sample(letters, 1e7, T)

system.time(tapply(seq_along(vec), vec, identity)[unique(vec)])
#   user  system elapsed 
#   7.92    0.35    8.50 

system.time({dt = as.data.table(vec)[, list(list(.I)), by = vec]; setattr(dt$V1, 'names', dt$vec); dt$V1})
#   user  system elapsed 
#   0.39    0.09    0.49 

@Arun 很好的观点,谢谢!有些令人惊讶的是它并没有影响时间 - 我想这只是太便宜了可以复制。 - eddi
有趣的是...使用您的data.table代码,我在我的电脑上得到了不同的结果:dt变成了> dt vec V1 1: D 4,5 2: B 4,5 3: C 4,5(即V1的所有元素都相等于4:5)。 - lebatsnok
@lebatsnok,你正在使用哪个版本的data.table?(我正在使用1.9.3) - eddi
@lebatsnok 对我来说在1.9.2上运行良好-尝试清除会话。 - eddi
1
@lebatsnok,这是由于最近的R-3.1.0行为所致。list(.)不再复制(欢迎变化)。本应该被捕获。似乎这个问题没有测试。现在的解决方法是:dt[, list(copy(list(.I))), by=vec]dt[, list(list(copy(.I))), by=vec] - Arun
显示剩余5条评论

7
split(seq_along(vec), vec)

这种方法比tapply方法更快且更简短:

vec = sample(letters, 1e7, T)
system.time(res1 <- tapply(seq_along(vec), vec, identity)[unique(vec)])
#   user  system elapsed 
#  1.808   0.364   2.176 
system.time(res2 <- split(seq_along(vec), vec))
#   user  system elapsed 
#  0.876   0.152   1.029 

巴啦..我没有注意到这已经在上面的评论中提出了类似的方法: split(seq_along(vec), vec) - alexis_laz 3小时前 - lebatsnok
1
唯一可行的方法是使用数字而不是字母。 - marsei

5
您可以使用tapply来完成这个任务:
vec <- c("D", "B", "B", "C", "C")
tapply(seq_along(vec), vec, identity)[unique(vec)]
# $D
# [1] 1
# 
# $B
# [1] 2 3
# 
# $C
# [1] 4 5

identity函数将其参数作为结果返回,通过使用unique(vec)进行索引,可以确保您以与原始向量中元素相同的顺序获取它。


这个函数的速度非常快,有点令人难以置信。 - user2763361
我应该如何让它侧重于元素的排序而不是字母表的顺序呢?我的用例需要元素排序而不是字母表的排序(例如,vec <- c("C","B") 应该返回 $C [1] 1 $B [1] 2 而不是相反)。 - user2763361
我在原帖中添加了一个我正在寻找的示例。 - user2763361
@user2763361 我已经更新了,使其与原始向量的顺序相匹配。 - josliber
5
一个类似的方法可以是:split(seq_along(vec), vec) - alexis_laz
@alexis_laz 很好的观点 - 而且 microbenchmark 似乎认为这大约快了2倍。 - josliber

1
为了保持josilber的答案的顺序,只需通过创建的uniques向量对结果进行索引:
vec <- c("D","B","B","C","C")

uniques <- unique(vec)

tapply(seq_along(vec), vec, identity)[uniques]

# $D
# [1] 1
#
# $B
# [1] 2 3
#
# $C
# [1] 4 5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接