带有容差的返回唯一元素

17
在Matlab中,有一个unique命令,可以返回数组中唯一的行。这是非常方便的命令。
但问题是我不能为它分配公差--在双精度下,我们总是必须在精度范围内比较两个元素。是否有一个内置的命令,可以返回在某个公差范围内的唯一元素?

3
或许你希望将你的问题进一步升华,尝试聚类? - Mikhail Poda
7个回答

13

随着R2015a版本的推出,这个问题终于有了简单的答案(详见我的另一个回答)。 对于早期版本的发布,存在一个内置(未记录在文档中)函数:_mergesimpts。该名称的组成可以猜测为“合并相似点”。

该函数的语法如下:

xMerged = builtin('_mergesimpts',x,tol,[type])

数据数组xN-by-D的,其中N是点的数量,D是维度的数量。每个维度的公差由D元素行向量tol指定。可选输入参数type是一个字符串(默认值为'first''average'),表示如何合并相似元素。

输出xMerged将是M-by-D的,其中M≤N它已排序

1D数据示例:

>> x = [1; 1.1; 1.05];             % elements need not be sorted
>> builtin('_mergesimpts',x,eps)   % but the output is sorted
ans =
    1.0000
    1.0500
    1.1000

合并类型:

>> builtin('_mergesimpts',x,0.1,'first')
ans =
    1.0000  % first of [1, 1.05] since abs(1 - 1.05) < 0.1
    1.1000
>> builtin('_mergesimpts',x,0.1,'average')
ans =
    1.0250  % average of [1, 1.05]
    1.1000
>> builtin('_mergesimpts',x,0.2,'average')
ans =
    1.0500  % average of [1, 1.1, 1.05]

示例,2D数据:

>> x = [1 2; 1.06 2; 1.1 2; 1.1 2.03]
x =
    1.0000    2.0000
    1.0600    2.0000
    1.1000    2.0000
    1.1000    2.0300

所有2D点都是机器精度唯一的:

>> xMerged = builtin('_mergesimpts',x,[eps eps],'first')
xMerged =
    1.0000    2.0000
    1.0600    2.0000
    1.1000    2.0000
    1.1000    2.0300

根据第二个维度的公差进行合并:

>> xMerged = builtin('_mergesimpts',x,[eps 0.1],'first')
xMerged =
    1.0000    2.0000
    1.0600    2.0000
    1.1000    2.0000   % first of rows 3 and 4
>> xMerged = builtin('_mergesimpts',x,[eps 0.1],'average')
xMerged =
    1.0000    2.0000
    1.0600    2.0000
    1.1000    2.0150   % average of rows 3 and 4

基于第一个维度容差合并:

>> xMerged = builtin('_mergesimpts',x,[0.2 eps],'average')
xMerged =
    1.0533    2.0000   % average of rows 1 to 3
    1.1000    2.0300
>> xMerged = builtin('_mergesimpts',x,[0.05 eps],'average')
xMerged =
    1.0000    2.0000
    1.0800    2.0000   % average of rows 2 and 3
    1.1000    2.0300   % row 4 not merged because of second dimension

基于两个维度合并:

>> xMerged = builtin('_mergesimpts',x,[0.05 .1],'average')
xMerged =
    1.0000    2.0000
    1.0867    2.0100   % average of rows 2 to 4

+1 很好!我自己也遇到了这个函数,然后快速搜索就找到了你的解释。这很有用。 - Amro
@Amro 我在尝试弄清楚griddata如何识别“重复”点时发现了它。很遗憾,这个功能没有适当的文档说明。请随意编辑此帖以更正我的错误。我记得有一些不准确或至少缺少一些细节,但从未有时间深入挖掘并使其100%正确。 - chappjc
我认为你已经涵盖了全部内容。唯一可以补充的细节是该函数仅接受非复杂、非稀疏的双精度数据点。 - Amro
@chappjc有没有办法获取_mergesimpts的代码?我制作了一个利用_mergesimpts的Matlab函数,但是Matlab代码生成器无法使用内置函数。有没有办法获取_mergesimpts的Matlab代码? - Abhinav
@chappjc 感谢您的快速回复。您能告诉我为什么这是不可能的吗? - Abhinav
显示剩余6条评论

11
这是一个困难的问题。我甚至会声称在一般情况下它是不可能解决的,因为我所说的传递性问题。假设我们有一个集合中的三个元素{A,B,C}。我将定义一个简单的函数isSimilarTo,如果两个输入在指定的公差范围内,则isSimilarTo(A,B)将返回一个true结果。(请注意,我将在这里说的所有内容在一维和多维中都有意义。)所以如果两个数字被认为是“相似”的,那么我们将选择将它们分组在一起。
假设我们有值{A,B,C},使得isSimilarTo(A,B)为true,并且isSimilarTo(B,C)也为true。即使isSimilarTo(A,C)为false,我们是否应该决定将所有三者分组在一起?
更糟糕的是,转到二维。从一个圆的周长开始,有k个等距离的点。假设选择公差使得任何点都在其直接相邻的点的指定公差范围内,但不是任何其他点。在这种情况下,你如何选择解决哪些点在这个设置中是“唯一”的?
我会声称这个不连贯性问题使得分组问题不可能解决,至少不完美,而且肯定不是以任何有效的方式解决。也许可以尝试基于k-means聚合风格的方法。但这也将非常低效,因为这种方法通常需要事先知道要查找多少组。

话虽如此,我仍然会提供一种妥协方案,有时可以在限制范围内起作用。技巧在于Consolidator,可以在Matlab中心文件交换中找到。我的方法是将输入有效地舍入到指定的公差范围内。这样做后,unique和accumarray的组合使得即使对于一个或多个维度中的大数据集,聚合也可以高效完成。

当公差足够大以至于多个数据块属于同一组时,它们将被舍入为相同的值,舍入步骤可能会偶尔出现错误,这时这种方法就是一个合理的选择。


9
自R2015a版本开始,Matlab终于提供了一个函数来实现这个功能,即uniquetol(在R2015a之前,参见我的另一个回答):

uniquetol是设置容差范围内的唯一值。

    uniquetol类似于unique。其中unique执行精确比较,而uniquetol使用容差进行比较。

语法很简单:

C = uniquetol(A,TOL)使用容差TOL返回A中的唯一值。

语义也很清晰:

C的每个值与A的一个值在容差范围内,但C中的任何两个元素都不在容差范围内。 C按升序排列。如果满足以下条件,则两个值uv在容差范围内:
    abs(u-v) <= TOL*max(A(:),[],1)

它也可以按行操作,并且容差可以通过输入的"DataScale"进行缩放,而不是通过输入数据中的最大值进行缩放。
但是关于解决方案的唯一性有一个重要的说明:

满足“C中的任何两个元素都不在容差范围内”条件的多个有效C输出可能存在。例如,交换A中的列可能会导致返回不同的解决方案,因为输入按列按词典顺序排序。另一个结果是,uniquetol(-A,TOL)可能与-uniquetol(A,TOL)的结果不同。

还有一个新函数ismembertol与上面类似地与ismember相关。

5

我不知道有这样的函数。一个棘手的问题是,如果你的容差是1e-10,而你有一个向量,其值在9e-11处等间隔分布,第一个和第三个条目不相同,但第一个与第二个相同,第二个与第三个相同 - 那么有多少个“唯一”的元素?

解决这个问题的一种方法是将您的值四舍五入到所需的精度,然后对其运行unique。您可以使用round2(http://www.mathworks.com/matlabcentral/fileexchange/4261-round2)来实现这一点,或者使用以下简单方法:

r = rand(100,1); % some random data
roundedData = round(r*1e6)/1e6; % round to 1e-6
uniqueValues = unique(roundedData);

您也可以使用hist命令来做到这一点,只要精度不太高即可:
r = rand(100,1); % create 100 random values between 0 and 1
grid = 0:0.001:1; % creates a vector of uniquely spaced values 
counts = hist(r,grid); % now you know for each element in 'grid' how many values there are
uniqueValues = grid(counts>0); % and these are the uniques

4

我以前遇到过这个问题。 技巧是首先对数据进行排序,然后使用diff函数找到每个项之间的差异。 然后比较差分是否小于容差。

这是我使用的代码:

tol = 0.001
[Y I] = sort(items(:));
uni_mask = diff([0; Y]) > tol;
%if you just want the unique items:
uni_items = Y(uni_mask); %in sorted order
uni_items = items(I(uni_mask));  % in the original order

这种方法不能处理"漂移"的情况...因此像0:0.00001:100这样的输入实际上只会返回一个唯一值。

如果你想要处理"漂移",那么我建议使用histc函数,但是你需要大致猜测一下你愿意拥有多少个条目。

NUM = round(numel(items) / 10); % a rough guess
bins = linspace(min(items), max(items), NUM);
counts = histc(items, bins);
unit_items = bins(counts > 0);

顺便提一下:我是在远离Matlab的文本编辑器中编写的,所以可能会有一些愚蠢的拼写错误或者偏移一个字符的错误。

希望这有所帮助。


0
前几天我被一个MatLab 2010的问题困住了,所以没有round(X,n),也没有mergesimpts(至少我无法让它工作),因此,一个对于我的数据有效的简单解决方案是:

使用默认公差的rat:

unique(cellstr(rat(x)))

其他公差:

unique(cellstr(rat(x,tol)))

0

这很难定义清楚,假设你有一个容差为1。

那么[1; 2; 3; 4]的结果会是什么?

当你有多列时,定义可能会变得更加具有挑战性。

然而,如果你主要担心舍入问题,你可以通过以下两种方法解决大部分问题:

  1. 将所有数字(考虑到你的容差)四舍五入,然后使用unique
  2. 以顶行作为你的唯一集合,使用ismemberf来确定每一行是否唯一,如果是,则将其添加到你的唯一集合中。

第一种方法的弱点在于0.499999999和0.500000000可能不被视为重复项。而第二种方法的弱点在于输入的顺序很重要。


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