从列表中查找唯一值

35
假设您有一组数值列表。
x <- list(a=c(1,2,3), b = c(2,3,4), c=c(4,5,6))

我想从所有列表元素中找到唯一的值。目前为止,以下代码已经解决了这个问题。

unique(unlist(x))

有人知道更有效的方法吗?我有一个巨大的列表,有很多值,希望能够加速处理。

1个回答

48

Marek建议的这个解决方案是原始问题的最佳答案。请参阅下面的讨论以了解其他方法以及为什么Marek的方法最有用。

> unique(unlist(x, use.names = FALSE))
[1] 1 2 3 4 5 6

讨论

更快的解决方案是首先在您的x组件上计算unique(),然后对这些结果进行最终的unique()。如果列表的组件具有相同数量的唯一值(如下面两个示例所示),则此方法才能起作用。例如:

首先展示您的版本,然后是我的双重唯一方法:

> unique(unlist(x))
[1] 1 2 3 4 5 6
> unique.default(sapply(x, unique))
[1] 1 2 3 4 5 6

我们必须调用 unique.default,因为存在一个针对 uniquematrix 方法,它保持一个边缘固定;这是可以的,因为矩阵可以被视为向量。

Marek在对这个答案的评论中指出,unlist 方法速度慢的原因可能是列表中的 names。Marek的解决方案是利用 unlistuse.names 参数,如果使用该参数,则得到的解决方案比上面的双重去重版本更快。 对于Roman帖子中的简单 x,我们得到

> unique(unlist(x, use.names = FALSE))
[1] 1 2 3 4 5 6

即使组件之间唯一元素的数量不同,Marek的解决方案也能够奏效。

下面是一个更大的示例,其中列出了三种方法的时间:

## Create a large list (1000 components of length 100 each)
DF <- as.list(data.frame(matrix(sample(1:10, 1000*1000, replace = TRUE), 
                                ncol = 1000)))

以下是使用 DF 两种方法的结果:

> ## Do the three approaches give the same result:
> all.equal(unique.default(sapply(DF, unique)), unique(unlist(DF)))
[1] TRUE
> all.equal(unique(unlist(DF, use.names = FALSE)), unique(unlist(DF)))
[1] TRUE
> ## Timing Roman's original:
> system.time(replicate(10, unique(unlist(DF))))
   user  system elapsed 
  12.884   0.077  12.966
> ## Timing double unique version:
> system.time(replicate(10, unique.default(sapply(DF, unique))))
   user  system elapsed 
  0.648   0.000   0.653
> ## timing of Marek's solution:
> system.time(replicate(10, unique(unlist(DF, use.names = FALSE))))
   user  system elapsed 
  0.510   0.000   0.512

这表明,将双重unique应用于各个组件,然后对这些较小的唯一值集合应用unique(),要比直接对整个列表DF使用unique()更快;但是,这种加速纯粹是由于列表DF上的names。如果我们告诉unlist不要使用names,那么Marek的解决方案比双重unique稍微快一点。因为Marek的解决方案正确地使用了正确的工具,而且比变通方法更快,所以它是首选解决方案。

使用双重unique的一个大问题是,它只在每个输入列表(DFx)的每个组件具有相同数量的唯一值时才有效,就像这里的两个示例一样。在这种情况下,sapply将结果简化为矩阵,从而允许我们应用unique.default。如果输入列表的组件具有不同数量的唯一值,则双重unique解决方案将失败。


1
检查 system.time(replicate(10, unique(unlist(DF,FALSE,FALSE))))。它更快。 - Marek
就这样。 你只需要使用unique(unlist(DF, use.names = TRUE))即可获得实质性的加速。我知道数据框中的名称是减速的源头,但我没有想到这会成为这里的问题。就我所见,由于“DF”和“x”仅具有单个级别的组件,因此没有任何递归发生。你应该将它作为答案发布,因为它直接使用了正确的工具。 - Gavin Simpson
2
@ucfagls 你是指 unique(unlist(DF, use.names = FALSE)) 吗?如果你喜欢的话,请在你的回答中包含它 ;) - Marek
我现在已经将Marek的解决方案添加到我的答案中,并附上了时间。关于这些时间为什么会改变的说明:这些新的时间是在我的工作站上完成的,而原始电子邮件中的时间是在家里的一台更新的机器上完成的。 - Gavin Simpson
我原以为s(l)apply家族可以独立地处理每个列表元素。但是在上面的代码中,sapply如何比较列表元素之间的值? - Roman Luštrik
显示剩余4条评论

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