for matlab 2013 is there a function that compares between three values within tolerance?

iam inquiring if there is a matlab function that can compare between 3 values in 3 vectors within a tolerance and return just one value if there are at least 2 values are equal within this tolerance? for example if i have
x = [.2 .15 0.3]
i need to compare between the three values within tolerance .05 so it will return true for the values of [.2 .15] and for example returns the average of them so is there any function can do this ?

Answers (1)

There is no such function. The following somewhat involved construct will do the trick:
x = [.2 .15 0.3];
tol = 0.05+1*eps;
M = abs(repmat(x,[3,1])-repmat(x',[1,3])<=tol);
[i,j] = find(triu(M-diag([1,1,1])));
if ~isnan(j)
y = x;
isclose = false(1,3);
isclose([i,j]) = true;
y([i,j]) = mean(y([i,j]));
end
The vector isclose is true for the elemnets of x that are close. y is equal to x with those elements replaced by their average.

4 Comments

It is more convenient if we make this into a (slightly more general) function:
function [xclose,xmid] = closeto(x,tol)
% Find entries in three element vector that differ by less than tol
% For longer vectors all elements with value close to another element
% will be treated the same
% Output:
% xclose: local vector. True for elements that are close to another
% element
% xmid: x, where all elements close to another element are replace
% by their avaraged
n = length(x);
x = x(:)'; % Make sure x is row vector
M = abs(repmat(x,[n,1])-repmat(x',[1,n]))<=tol+10*eps;
[i,j] = find(M-eye(n));
xclose = false(size(x));
xmid = x;
if ~isnan(j)
index_close = union(i,j);
xclose(index_close) = true;
meanvalue = mean(x(index_close));
xmid(index_close) = meanvalue;
end
I'm not sure what the purpose of the if ~isnan(j) test since find never outputs nan values. It will either return finite values or empty.
In R2016b or later, M can be calculated more simply with:
M = abs(x - x.') <= tol; %I don't understand the purpose of the 10*eps
In earlier versions, the same can be written as
M = abs(bsxfun(@minus, x, x.')) <= tol;
which should be faster and more memory efficient than repmat.
Overall, a simpler implementation would be:
function [xclose, x] = closeto(x,tol)
% Find entries in vector that differ by less than absolute tol
% Output:
% xclose: local vector. True for elements that are close to another
% element
% xmid: x, where all elements close to another element are replace
% by their averaged
validateattributes(x, {'numeric'}, {'vector'}, 1);
validateattributes(tol, {'numeric'}, {'scalar'}, 2);
inrange = abs(bsxfun(@minus, x, x.')) <= tol;
xclose = any(inrange, 1) | any(inrange, 2);
x(xclose) = mean(x(xclose));
end
Note: I've renamed the xmid output to x to allow for in-place optimisation.
Note 2: I don't see the point of adding a tolerance to the tolerance but if you do that added tolerance needs to be relative not absolute (i.e use eps(tol) not plain eps). For any value above 32, adding 10*eps doesn't add anything. Any value less than roughly 4e-31 is so small compared to 10*eps that tol+10*eps is just 10*eps effectively ignoring the tolerance.
Guillaume,
Your solution is elegant, but it does not work properly, at least not in Matlab 2012b. It crashes in the line
xclose = any(inrange, 1) | any(inrange, 2)
because the two vectors have different orientations. If I fix this and test it on the original example I get:
>> x = [.2 .15 0.3];
>> [xclose, x] = closeto_g(x,0.05)
xclose =
1 1 1
x =
0.2167 0.2167 0.2167
which is not what we want, because the diagonal elements in inrange are of course all 1. Fixing this by subtracting eye(length(x)), I get:
>> [xclose, x] = closeto_g(x,0.05)
xclose =
0 0 0
Hence the 100*eps. With these modifications, your version is admittedly more elegant than mine:
function [xclose, x] = closeto_g(x,tol)
% Find entries in vector that differ by less than absolute tol
% Output:
% xclose: local vector. True for elements that are close to another
% element
% x: x, where all elements close to another element are replace
% by their averaged
validateattributes(x, {'numeric'}, {'vector'}, 1);
validateattributes(tol, {'numeric'}, {'scalar'}, 2);
inrange = abs(bsxfun(@minus, x, x.')) <= tol+100*eps - eye(length(x));
xclose = any(inrange, 1) | any(inrange, 2)';
x(xclose) = mean(x(xclose));
end
P.S: Of course ~isnan should be ~isempty. It works, though.
it crashes. You mean it errors. Yes, I overlooked the difference in direction of the two vectors. It's easily fixed by a transpose. However, I just realised that you don't even need to check both rows and columns of the inrange matrix since it's symmetrical, so:
inrange = abs(bsxfun(@minus, x, x.')) <= tol;
xclose = any(inrange & ~eye(size(inrange)); %the ~eye to remove the diagonal
You are right about the diagonal needing to be removed.
I'm not sure if my edit to add my note 2 above came after or before your comment, but adding an absolute tolerance is the wrong approach.
For any value above 32 (respectively 256) adding 10*eps (resp. 100*eps) is adding nothing as your absolute tolerance is smaller than eps(32) (resp. eps(100)).
For any value below about 4e-31 (resp. 3e-30) adding 10*eps (resp. 100*eps) results in exactly 10*eps (resp. 100*eps), effectively ignoring the tolerance input.
I don't think adding any tolerance on top of the tolerance is the right approach. You just have to accept that with double numbers, computers cannot store .2 and .15 exactly. The exact value of .2 is slightly above .2, the exact value of .15 is slightly below .15, and hence their difference is greater than .05 (which is also stored as a value slightly greater than .05)

Sign in to comment.

Categories

Tags

Asked:

on 5 Sep 2017

Commented:

on 24 Oct 2017

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!