在一个向量中找到唯一元素的最后出现索引

18

我有一个无序向量 v,如下所示,我想找到列表中每个唯一元素的最后出现位置的索引。

v <- scan(text="1 2 1 2 1 1 1 3 1 2 2 3 3 3 1 1 1 4 1 1 1 4 1 5 5 6
                6 2 3 3 4 4 2 2 2 2 2 3 3 3 1 4 4 4 3 2 5 5 5 5")
v
# [1] 1 2 1 2 1 1 1 3 1 2 2 3 3 3 1 1 1 4 1 1 1 4 1 5 5 6 6 2 3 3 4 4 2 2 2 2 2 3 3 3 
# [41] 1 4 4 4 3 2 5 5 5 5

预期结果(按顺序为1、2、3、4、5):
41 46 45 44 50

我知道可以使用unique(unlist(v))来查找唯一元素,但是如何找到它们最后出现的索引呢?有任何想法吗?

提前致谢。


3
你所称呼的“列表”看起来像是“向量”。如果你在提问时使用正确的术语,那将有助于影响答案。同时,正如@akrun所指出的,最好能以更易复制的形式分享一些样本数据(dput非常方便)。 - A5C1D2H2I1M1N2O1R2T1
如果元素是唯一的,那么谈论最后一次出现有什么意义呢?你的意思是找到向量中每个(不同的)值的最后一次出现。 - Marc van Leeuwen
10个回答

22

即使数据没有被排序,也可以使用另一种方法:

length(v1)-match(unique(v1),rev(v1))+1

正是我在寻找的答案 :) 完美。非常感谢。 - Joarder Kamal

11
tapply(seq_along(v), v, max)
#  1  2  3  4  5  6 
# 41 46 45 44 50 27 

8

如果vector已经排序,您可以尝试使用rle。提取长度($lengths),然后进行cumsum。正如我之前提到的,如果没有排序,则这种方法行不通(根据您真正想要的结果而定)。基本上,rle通过检查一段连续的元素中有多少个相似来运作。它将在列表中给出lengths和相应的values

cumsum(rle(v1)$lengths)
#[1] 28 37 42 46 50

另一种选择是按向量对序列进行分组,并获取每个组的max值。我想这可能会很慢。
unname(cumsum(tapply(seq_along(v1),v1, FUN=which.max)))    
#[1] 28 37 42 46 50

或者只需检查前一个值是否与当前值相同,然后将 TRUE 插入为最后一个元素,并使用 which 获取 TRUE 的索引。

 which(c(v1[-1]!=v1[-length(v1)],TRUE))
 #[1] 28 37 42 46 50

或者使用match
 c(match(unique(v1),v1)-1, length(v1))[-1]
#[1] 28 37 42 46 50

或者使用findInterval

 findInterval(unique(v1), v1)
 #[1] 28 37 42 46 50

更新

对于新向量v2

max.col(t(sapply(unique(v2), `==`, v2)),'last')
#[1] 41 46 45 44 50 27

或者在将无序向量排序后使用findInterval函数

   f1 <- function(v){
      v1 <- setNames(v, seq_along(v))
      ind <- order(v1)
      as.numeric(names(v1[ind][findInterval(unique(v1), v1[ind])]))
    }     

 f1(v2)
 #[1] 41 46 45 44 50 27

使用@Marat talipov帖子中的示例(z),

 f1(z)
 #[1] 4 5 3

注意:我按照在z中首次出现的唯一元素的顺序得到结果。即1,后跟32。如果需要根据值重新排序,则可以使用order(如@Marat Talipov所述)。但是,在这种情况下,不清楚OP真正想要什么。

数据

v1 <- c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 
 3, 4, 4, 4, 4, 5, 5, 5, 5)

v2 <-  c(1, 2, 1, 2, 1, 1, 1, 3, 1, 2, 2, 3, 3, 3, 1, 1, 1, 4, 1, 1, 
 1, 4, 1, 5, 5, 6, 6, 2, 3, 3, 4, 4, 2, 2, 2, 2, 2, 3, 3, 3, 1, 
 4, 4, 4, 3, 2, 5, 5, 5, 5)

 z <- c(1, 3, 2, 1, 3)

以上所有的都有效!非常感谢 :) 您能否解释一下rle在这里是如何工作的? 实际上,我的列表不会是连续的,它可能像1 1 2 3 3 2 4 5 1 2 1 1 3 3 4 2 5 5这样。 我也会编辑我的问题。 - Joarder Kamal
在这种情况下,您期望的结果是什么? - akrun
我已将列表编辑为无序。感谢解释,这些对理解非常有帮助。 - Joarder Kamal
max.col(t(sapply(unique(v2), ==, v2)),'last') 工作得非常完美,这正是我一直在寻找的。非常感谢。 - Joarder Kamal
3
当您发布示例时,最好模拟数据,否则就会有许多可能的情况、解决方案等等。 - akrun

7
也可以尝试其他方法。
which(c(diff(tmp), TRUE) == 1)
# [1] 28 37 42 46 50

同样地,或类似地。
which(!!c(diff(tmp), TRUE))

6
您可以尝试使用"data.table"中的.N,像这样:
library(data.table)
data.table(x, y = seq_along(x))[, y[.N], by = x]
#    x V1
# 1: 1 41
# 2: 2 46
# 3: 3 45
# 4: 4 44
# 5: 5 50
# 6: 6 27

在这里,我们基本上创建了一个双列数据表,其中第一列是您的向量,第二列是您的向量的索引位置。.N 告诉我们每个组中有多少行(由by =捕获),因此我们可以直接使用该信息从y中选择值。


更好的方法是,如@Arun推荐的那样,我们可以跳过创建“y”并直接执行:
data.table(x)[, .I[.N], by=x]

样本数据:

x <- c(1, 2, 1, 2, 1, 1, 1, 3, 1, 2, 2, 3, 3, 3, 1, 1, 1, 4, 1, 1, 
  1, 4, 1, 5, 5, 6, 6, 2, 3, 3, 4, 4, 2, 2, 2, 2, 2, 3, 3, 3, 1, 
  4, 4, 4, 3, 2, 5, 5, 5, 5)

5
抱歉说,但是“被接受的答案以及其他几个假定能够处理无序向量的答案都提供了不正确的解决方案”。
[编辑2]
这个答案已经成为一件争议的事情,应该如何对待“正确”或“错误”的答案。在此,我将所需的输出解释为,解决方案应该是一个未命名的向量,其元素按递增顺序排序。结果发现,可能存在其他解释(请参见下面的评论),虽然它们对我来说看起来不是很明显,但它们肯定有存在的权利,至少直到OP添加更多示例以澄清情况。
基于这一点,更好地说,“那些复制OP样本的答案可能会导致与输出向量中元素的排序有关的不一致结果”。不一致性部分源自于原始OP的问题几次更改,而那些在当前问题状态下完全正常的答案可能不适用于问题的最终状态。我的答案旨在让读者了解这种情况,并建议轻松修复以获得OP问题的解决方案。
最后,我确实意识到我的答案结果是过度笨重的,但考虑到帖子中的混乱程度,我认为澄清未来感兴趣的读者的情况是更好的。
/ [编辑2]
我意外地发现了这个问题,当我开始将不同的解决方案放在一起进行基准研究时。这里提到的一些解决方案不起作用,因为最初的问题暗示输入向量按递增顺序排序,而这并不是事实,因此我在这里不讨论它们。为了作者的样本数据提供正确答案的解决方案被收集在一起,并包装在相应的函数中:
f.duplicated <- function(z) {
  i <- which(!duplicated(z,fromLast=T))
  i[order(z[i])]  
}

f.match.unique.rev <- function(v1) {
  length(v1)-match(unique(v1),rev(v1))+1
}

f.max.col.sapply.unique <- function(v2){
  max.col(t(sapply(unique(v2), `==`, v2)),'last')
}

f.data.table <- function(x) {
  # data.table(x, y = seq_along(x))[, y[.N], by = x]$V1
  setkey(data.table(x, y = seq_along(x)), x)[, y[.N], by = x]$V1
}

f.tapply.seq_along.max <- function(v) {
  tapply(seq_along(v), v, max)
}

f.sapply.split.seq_along.max <- function(v) {
  sapply(split(seq_along(v), v), max)
}

然后,我编写了一个小函数来比较这些结果:
compare.results <- function(z) {
  d <- rbind(
    f.duplicated(z),
    f.match.unique.rev(z),
    f.max.col.sapply.unique(z),
    f.data.table(z),
    f.tapply.seq_along.max(z),
    f.sapply.split.seq_along.max(z)
    )
  rownames(d) <- c(
    'f.duplicated',
    'f.match.unique.rev',
    'f.max.col.sapply.unique',
    'f.data.table',
    'f.tapply.seq_along.max',
    'f.sapply.split.seq_along.max'
  )
  d
}

并确保所选解决方案适用于示例数据:

z <- c(1,2,1,2, 1, 1, 1, 3, 1, 2, 2, 3, 3, 3, 1, 1, 1, 4, 1, 1, 1, 4, 1, 5, 5, 6, 6, 2, 3, 3, 4, 4, 2, 2, 2, 2, 2, 3, 3, 3, 1, 4, 4, 4, 3, 2, 5, 5, 5, 5)

compare.results(z)
#                               1  2  3  4  5  6
# f.duplicated                 41 46 45 44 50 27
# f.match.unique.rev           41 46 45 44 50 27
# f.max.col.sapply.unique      41 46 45 44 50 27
# f.data.table                 41 46 45 44 50 27
# f.tapply.seq_along.max       41 46 45 44 50 27
# f.sapply.split.seq_along.max 41 46 45 44 50 27

[问题] 当我使用另一个输入向量1 3 2 1 3时,正确答案为4 3 5,但我发现有些解决方案提供了错误的结果:

z <- c(1,3,2,1,3)
compare.results(z)
#                              1 2 3
# f.duplicated                 4 3 5
# f.match.unique.rev           4 5 3  # ***
# f.max.col.sapply.unique      4 5 3  # ***
# f.data.table                 4 3 5
# f.tapply.seq_along.max       4 3 5
# f.sapply.split.seq_along.max 4 3 5

[修复] 我发现f.match.unique.rev(被接受的答案)和 f.max.col.sapply.unique 的问题在于假设唯一元素在数据集中具有递增顺序,这是作者示例中的情况,但不是我的情况。以下是修正后的解决方案:

f.max.col.sapply.unique <- function(v2){
  i <- max.col(t(sapply(unique(v2), `==`, v2)),'last')
  i[order(v2[i])]  
}


f.match.unique.rev <- function(v1) {
  i <- length(v1)-match(unique(v1),rev(v1))+1
  i[order(v1[i])]  
}

[编辑] 我得知原始的f.data.table结果是一个数据表结构,包含两列(xV1),这些信息足以构建出作者期望的格式答案。实际上,在f.data.table中的错误是由于我决定使用列V1作为函数输出引起的。我通过修改代码(见下面的注释)更新了f.data.table,提供了正确的预期格式解决方案,并将旧版本保存为注释。此外,我从我的答案结尾删除了关于f.data.table解决方案的讨论,因为它已经不再需要。


4
仅供参考,OP已经多次更改了数据和所需的输出,我们不需要每小时都来检查是否有新的请求。请注意,这不是我们的职责。 - David Arenburg
@DavidArenburg,我同意动态变化的需求在这篇文章中造成了一团糟。然而,未来的读者将不会意识到这种情况,他们可能想知道一些答案,包括被接受的答案,没有提供他们从问题中期望得到的解决方案。 - Marat Talipov
1
@MaratTalipov 我们不知道 OP 想要如何获取序列。如果它按 z 中出现的顺序排列,那么我的输出是正确的;否则,如果他想根据唯一元素的顺序再次重新排序,那么你的输出将是正确的。 - akrun
1
@MaratTalipov 我认为在这种情况下,称其他解决方案为“不正确”是错误的。 - akrun
我确实阅读了这部分期望结果(按1、2、3、4、5的顺序):,但在这里,顺序与元素的顺序对齐。如果OP提供了像你的例子一样并说“我希望结果是这样的”,那就更好了。根据我们的解释方式,您可以将其他解决方案称为不正确,反之亦然。 - akrun
显示剩余6条评论

4
这里有另一种方法:
z <- c(1,2,1,2, 1, 1, 1, 3, 1, 2, 2, 3, 3, 3, 1, 1, 1, 4, 1, 1, 1, 4, 1, 5, 5, 6, 6, 2, 3, 3, 4, 4, 2, 2, 2, 2, 2, 3, 3, 3, 1, 4, 4, 4, 3, 2, 5, 5, 5, 5)

i <- which(!duplicated(z,fromLast=T))
i[order(z[i])]

duplicated函数返回一个逻辑向量,指示从反向考虑的重复项。这个想法是对这个向量取反以获得唯一元素的逻辑向量,并使用which来获取索引。

更新: 正如评论中所指出的,我的原始答案 which(!duplicated(z,fromLast=T)) 返回的向量与输入向量中元素的递增顺序不对应。为了解决这个问题,我将第一个命令的结果保存为向量i,并根据需要重新排序。


1
对于给定的 t <- c(1, 1, 2, 1, 2, 2, 3, 3, 4, 5, 1, 5, 4),期望的结果应该如下所示,其中元素为 1、2、3、4、5: 11 6 8 13 12 但是上面的代码返回了不同的顺序: 6 8 11 12 13 - Joarder Kamal

4

仅供娱乐,

library(dplyr)  
#you can use new feature `add_rownames()`   
data.frame(x, row=1:length(x)) %>% group_by(x) %>%  summarise(max(row))
#  x max(row)
#1 1       41
#2 2       46
#3 3       45
#4 4       44
#5 5       50
#6 6       27

为了
x <- c(1, 2, 1, 2, 1, 1, 1, 3, 1, 2, 2, 3, 3, 3, 1, 1, 1, 4, 1, 1, 
  1, 4, 1, 5, 5, 6, 6, 2, 3, 3, 4, 4, 2, 2, 2, 2, 2, 3, 3, 3, 1, 
  4, 4, 4, 3, 2, 5, 5, 5, 5)

4

只是为了好玩 - 没有向量化 - 但足以胜任:

sapply(split(seq_along(v), v), max)
# 1  2  3  4  5  6 
#41 46 45 44 50 27 

2

使用grouping函数:

最初的回答:

g <- grouping(v)
g[attr(g, "ends")]
# [1] 41 46 45 44 50 27

1
好棒的内置函数!我不知道那个。谢谢。 - igorkf

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