Matlab: how to find an enclosing grid cell index for multiple points - arrays

I am trying to allocate (x, y) points to the cells of a non-uniform rectangular grid. Simply speaking, I have a grid defined as a sorted non-equidistant array
xGrid = [x1, x2, x3, x4];
and an array of numbers x lying between x1 and x4. For each x, I want to find its position in xGrid, i.e. such i that
xGrid(i) <= xi <= xGrid(i+1)
Is there a better (faster/simpler) way to do it than arrayfun(#(x) find(xGrid <= x, 1, 'last'), x)?

You are looking for the second output of histc:
[~,where] = histc(x, xGrid)
This returns the array where such that xGrid(where(i)) <= x(i) < xGrid(where(i)+1) holds.
Example:
xGrid = [2,4,6,8,10];
x = [3,5,6,9,11];
[~,where] = histc(x, xGrid)
Yields the following output:
where =
1 2 3 4 0
If you want xGrid(where(i)) < x(i) <= xGrid(where(i)+1), you need to do some trickery of negating the values:
[~,where] = histc(-x,-flip(xGrid));
where(where~=0) = numel(xGrid)-where(where~=0)
This yields:
where =
1 2 2 4 0
Because x(3)==6 is now counted for the second interval (4,6] instead of [6,8) as before.

Using bsxfun for the comparisons and exploiting find-like capabilities of max's second output:
xGrid = [2 4 6 8]; %// example data
x = [3 6 5.5 10 -10]; %// example data
comp = bsxfun(#gt, xGrid(:), x(:).'); %'// see if "x" > "xGrid"
[~, result] = max(comp, [], 1); %// index of first "xGrid" that exceeds each "x"
result = result-1; %// subtract 1 to find the last "xGrid" that is <= "x"
This approach gives 0 for values of x that lie outside xGrid. With the above example values,
result =
1 3 2 0 0

See if this works for you -
matches = bsxfun(#le,xGrid(1:end-1),x(:)) & bsxfun(#ge,xGrid(2:end),x(:))
[valid,pos] = max(cumsum(matches,2),[],2)
pos = pos.*(valid~=0)
Sample run -
xGrid =
5 2 1 6 8 9 2 1 6
x =
3 7 14
pos =
8
4
0
Explanation on the sample run -
First element of x, 3 occurs last between ...1 6 with the criteria of xGrid(i) <= xi <= xGrid(i+1) at the backend of xGrid and that 1 is at the eight position, so the first element of the output pos is 8. This continues for the second element 7, which is found between 6 and 8 and that 6 is at the fourth place in xGrid, so the second element of the output is 4. For the third element 14 which doesn't find any neighbours to satisfy the criteria xGrid(i) <= xi <= xGrid(i+1) and is therefore outputted as 0.

If x is a column this might help
xg1=meshgrid(xGrid,1:length(x));
xg2=ndgrid(x,1:length(xGrid));
[~,I]=min(floor(abs(xg1-xg2)),[],2);
or a single line implementation
[~,I]=min(floor(abs(meshgrid(xGrid,1:length(x))-ndgrid(x,1:length(xGrid)))),[],2);
Example: xGrid=[1 2 3 4 5], x=[2.5; 1.3; 1.7; 4.8; 3.3]
Result:
I =
2
1
1
4
3

Related

Shuffle array while spacing repeating elements

I'm trying to write a function that shuffles an array, which contains repeating elements, but ensures that repeating elements are not too close to one another.
This code works but seems inefficient to me:
function shuffledArr = distShuffle(myArr, myDist)
% this function takes an array myArr and shuffles it, while ensuring that repeating
% elements are at least myDist elements away from on another
% flag to indicate whether there are repetitions within myDist
reps = 1;
while reps
% set to 0 to break while-loop, will be set to 1 if it doesn't meet condition
reps = 0;
% randomly shuffle array
shuffledArr = Shuffle(myArr);
% loop through each unique value, find its position, and calculate the distance to the next occurence
for x = 1:length(unique(myArr))
% check if there are any repetitions that are separated by myDist or less
if any(diff(find(shuffledArr == x)) <= myDist)
reps = 1;
break;
end
end
end
This seems suboptimal to me for three reasons:
1) It may not be necessary to repeatedly shuffle until a solution has been found.
2) This while loop will go on forever if there is no possible solution (i.e. setting myDist to be too high to find a configuration that fits). Any ideas on how to catch this in advance?
3) There must be an easier way to determine the distance between repeating elements in an array than what I did by looping through each unique value.
I would be grateful for answers to points 2 and 3, even if point 1 is correct and it is possible to do this in a single shuffle.
I think it is sufficient to check the following condition to prevent infinite loops:
[~,num, C] = mode(myArr);
N = numel(C);
assert( (myDist<=N) || (myDist-N+1) * (num-1) +N*num <= numel(myArr),...
'Shuffling impossible!');
Assume that myDist is 2 and we have the following data:
[4 6 5 1 6 7 4 6]
We can find the the mode , 6, with its occurence, 3. We arrange 6s separating them by 2 = myDist blanks:
6 _ _ 6 _ _6
There must be (3-1) * myDist = 4 numbers to fill the blanks. Now we have five more numbers so the array can be shuffled.
The problem becomes more complicated if we have multiple modes. For example for this array [4 6 5 1 6 7 4 6 4] we have N=2 modes: 6 and 4. They can be arranged as:
6 4 _ 6 4 _ 6 4
We have 2 blanks and three more numbers [ 5 1 7] that can be used to fill the blanks. If for example we had only one number [ 5] it was impossible to fill the blanks and we couldn't shuffle the array.
For the third point you can use sparse matrix to accelerate the computation (My initial testing in Octave shows that it is more efficient):
function shuffledArr = distShuffleSparse(myArr, myDist)
[U,~,idx] = unique(myArr);
reps = true;
while reps
S = Shuffle(idx);
shuffledBin = sparse ( 1:numel(idx), S, true, numel(idx) + myDist, numel(U) );
reps = any (diff(find(shuffledBin)) <= myDist);
end
shuffledArr = U(S);
end
Alternatively you can use sub2ind and sort instead of sparse matrix:
function shuffledArr = distShuffleSparse(myArr, myDist)
[U,~,idx] = unique(myArr);
reps = true;
while reps
S = Shuffle(idx);
f = sub2ind ( [numel(idx) + myDist, numel(U)] , 1:numel(idx), S );
reps = any (diff(sort(f)) <= myDist);
end
shuffledArr = U(S);
end
If you just want to find one possible solution you could use something like that:
x = [1 1 1 2 2 2 3 3 3 3 3 4 5 5 6 7 8 9];
n = numel(x);
dist = 3; %minimal distance
uni = unique(x); %get the unique value
his = histc(x,uni); %count the occurence of each element
s = [sortrows([uni;his].',2,'descend'), zeros(length(uni),1)];
xr = []; %the vector that will contains the solution
%the for loop that will maximize the distance of each element
for ii = 1:n
s(s(:,3)<0,3) = s(s(:,3)<0,3)+1;
s(1,3) = s(1,3)-dist;
s(1,2) = s(1,2)-1;
xr = [xr s(1,1)];
s = sortrows(s,[3,2],{'descend','descend'})
end
if any(s(:,2)~=0)
fprintf('failed, dist is too big')
end
Result:
xr = [3 1 2 5 3 1 2 4 3 6 7 8 3 9 5 1 2 3]
Explaination:
I create a vector s and at the beggining s is equal to:
s =
3 5 0
1 3 0
2 3 0
5 2 0
4 1 0
6 1 0
7 1 0
8 1 0
9 1 0
%col1 = unique element; col2 = occurence of each element, col3 = penalities
At each iteration of our for-loop we choose the element with the maximum occurence since this element will be harder to place in our array.
Then after the first iteration s is equal to:
s =
1 3 0 %1 is the next element that will be placed in our array.
2 3 0
5 2 0
4 1 0
6 1 0
7 1 0
8 1 0
9 1 0
3 4 -3 %3 has now 5-1 = 4 occurence and a penalities of -3 so it won't show up the next 3 iterations.
at the end every number of the second column should be equal to 0, if it's not the minimal distance was too big.

MATLAB store indices maximum in logical matrix

Lets say I have a 4 dimensional matrix, from which I would like to retrieve the maximum values over the 2nd and 3rd dimension.
A = rand(4, 4, 4, 4);
[max_2, in_2] = max(A, [], 2);
[max_3, in_3] = max(max_2, [], 3);
How could I use ind_2 and ind_3 to obtain a logical 4 dimensional matrix, where a 1 entry means this entry is maximum in the 2nd and 3rd dimension?
I would use this approach:
A = rand(4, 4, 4, 4); % example data
B = permute(A, [1 4 2 3]); % permute dims 2 and 3 to the end
B = reshape(B, size(A,1), size(A,4), []); % collapse last two dims
C = bsxfun(#eq, B, max(B, [], 3)); % maximize over collapsed last dim
C = reshape(C, size(A,1), size(A,4), size(A,2), size(A,3)); % expand dims back
C = permute(C, [1 3 4 2]); % permute dims back. This is the final result
Here's an approach working with linear indices and uses argmax indices from max function, so it would only consider the first argmax in case of ties for the max value -
% Get size parameters
[m,n,p,q] = size(A);
% Reshape to merge second and third dims
[~, in_23] = max(reshape(A,m,[],q), [], 2);
% Get linear indices equivalent that could be mapped onto output array
idx1 = reshape(in_23,m,q);
idx2 = bsxfun(#plus,(1:m)', m*n*p*(0:q-1)) + (idx1-1)*m;
% Initialize output array an assign 1s at linear indices from idx2
out = false(m,n,p,q);
out(idx2) = 1;
Explanation with a sample
1) Input array :
>> A
A(:,:,1,1) =
9 8
9 1
A(:,:,2,1) =
2 9
8 1
A(:,:,1,2) =
1 7
8 1
A(:,:,2,2) =
8 5
9 7
2) Reshape array for a better visualization :
>> reshape(A,m,[],q)
ans(:,:,1) =
9 8 2 9
9 1 8 1
ans(:,:,2) =
1 7 8 5
8 1 9 7
3) The question is to take max value from each of the rows. For that, we had idx2 as the linear indices :
>> idx2
idx2 =
1 13
2 14
Looking back at the reshape version, thus we chose (bracketed ones) -
>> reshape(A,m,[],q)
ans(:,:,1) =
[9] 8 2 9
[9] 1 8 1
ans(:,:,2) =
1 7 [8] 5
8 1 [9] 7
So, looking closely, we see that for the first row, we had two 9s, but we are choosing the first one only.
4) Finally, we are assigning these into the output array initialized as logical zeros :
>> out
out(:,:,1,1) =
1 0
1 0
out(:,:,2,1) =
0 0
0 0
out(:,:,1,2) =
0 0
0 0
out(:,:,2,2) =
1 0
1 0

Sum up vector values till threshold, then start again

I have a vector a = [1 3 4 2 1 5 6 3 2]. Now I want to create a new vector 'b' with the cumsum of a, but after reaching a threshold, let's say 5, cumsum should reset and start again till it reaches the threshold again, so the new vector should look like this:
b = [1 4 4 2 3 5 6 3 5]
Any ideas?
You could build a sparse matrix that, when multiplied by the original vector, returns the cumulative sums. I haven't timed this solution versus others, but I strongly suspect this will be the fastest for large arrays of a.
% Original data
a = [1 3 4 2 1 5 6 3 2];
% Threshold
th = 5;
% Cumulative sum corrected by threshold
b = cumsum(a)/th;
% Group indices to be summed by checking for equality,
% rounded down, between each cumsum value and its next value. We add one to
% prevent NaNs from occuring in the next step.
c = cumsum(floor(b) ~= floor([0,b(1:end-1)]))+1;
% Build the sparse matrix, remove all values that are in the upper
% triangle.
S = tril(sparse(c.'./c == 1));
% In case you use matlab 2016a or older:
% S = tril(sparse(bsxfun(#rdivide,c.',c) == 1));
% Matrix multiplication to create o.
o = S*a.';
By normalizing the arguments of cumsum with the threshold and flooring you can get grouping indizes for accumarray, which then can do the cumsumming groupwise:
t = 5;
a = [1 3 4 2 1 5 6 3 2];
%// cumulative sum of normalized vector a
n = cumsum(a/t);
%// subs for accumarray
subs = floor( n ) + 1;
%// cumsum of every group
aout = accumarray( subs(:), (1:numel(subs)).', [], #(x) {cumsum(a(x))});
%// gather results;
b = [aout{:}]
One way is to use a loop. You create the first cumulative sum cs, and then as long as elements in cs are larger than your threshold th, you replace them with elements from the cumulative sum on the rest of the elements in a.
Because some elements in a might be larger than th, this loop will be infinite unless we also eliminate these elements too.
Here is a simple solution with a while loop:
a = [1 3 4 2 1 5 6 3 2];
th = 5;
cs = cumsum(a);
while any(cs>th & cs~=a) % if 'cs' has values larger that 'th',
% and there are any values smaller than th left in 'a'
% sum all the values in 'a' that are after 'cs' reached 'th',
% excluding values that are larger then 'th'
cs(cs>th & cs~=a) = cumsum(a(cs>th & cs~=a));
end
Calculate the cumulative sum and replace the indices value obeying your condition.
a = [1 3 4 2 1 5 6 3 2] ;
b = [1 4 4 2 3 5 6 3 5] ;
iwant = a ;
a_sum = cumsum(a) ;
iwant(a_sum<5) = a_sum(a_sum<5) ;

Vectorization- Matlab

Given a vector
X = [1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3]
I would like to generate a vector such
Y = [1 2 3 4 5 1 2 3 4 5 6 1 2 3 4 5]
So far what I have got is
idx = find(diff(X))
Y = [1:idx(1) 1:idx(2)-idx(1) 1:length(X)-idx(2)]
But I was wondering if there is a more elegant(robust) solution?
One approach with diff, find & cumsum for a generic case -
%// Initialize array of 1s with the same size as input array and an
%// intention of using cumsum on it after placing "appropriate" values
%// at "strategic" places for getting the final output.
out = ones(size(X))
%// Find starting indices of each "group", except the first group, and
%// by group here we mean run of identical numbers.
idx = find(diff(X))+1
%// Place differentiated and subtracted values of indices at starting locations
out(idx) = 1-diff([1 idx])
%// Perform cumulative summation for the final output
Y = cumsum(out)
Sample run -
X =
1 1 1 1 2 2 3 3 3 3 3 4 4 5
Y =
1 2 3 4 1 2 1 2 3 4 5 1 2 1
Just for fun, but customary bsxfun based alternative solution -
%// Logical mask with each column of ones for presence of each group elements
mask = bsxfun(#eq,X(:),unique(X(:).')) %//'
%// Cumulative summation along columns and use masked values for final output
vals = cumsum(mask,1)
Y = vals(mask)
Here's another approach:
Y = sum(triu(bsxfun(#eq, X, X.')), 1);
This works as follows:
Compare each element with all others (bsxfun(...)).
Keep only comparisons with current or previous elements (triu(...)).
Count, for each element, how many comparisons are true (sum(..., 1)); that is, how many elements, up to and including the current one, are equal to the current one.
Another method is using the function unique
like this:
[unqX ind Xout] = unique(X)
Y = [ind(1):ind(2) 1:ind(3)-ind(2) 1:length(X)-ind(3)]
Whether this is more elegant is up to you.
A more robust method will be:
[unqX ind Xout] = unique(X)
for ii = 1:length(unqX)-1
Y(ind(ii):ind(ii+1)-1) = 1:(ind(ii+1)-ind(ii));
end

MATLAB Vector of elements x(i) or y(i) with max(abs(x),abs(y))

Sorry for the title, couldn't think of a concise way to phrase the problem. I need to write a MATLAB one-liner that gives you a vector of elements z(i) where z(i) is the element x(i) or y(i) given by max(abs(x(i)),abs(y(i))). I.e, z is the vector whose elements are the ith elements of x or y which has the maximum absolute value.
I have
max(abs(x),abs(y))
But this obviously gives you a vector of the greatest absolute values. This is close to what I want, but I need to get the sign back of the original vector. I'm not sure how to do this on a single line.
Under the assumption that x and y are arrays (not necessarily vectors) of identical dimensions, you can use logical indexing:
(abs(x)>=abs(y)).*x + (abs(x)<abs(y)).*y
For information, abs(x)>=abs(y) is a logical array for which, for all valid indices, the kth component is
1 if x(k) is greater than or equal to y(k),
0 otherwise.
Example:
>> x = [4;7;-1;9;6];
>> y = [5;2;-3;9;3];
>> (abs(x)>=abs(y)).*x + (abs(x)<abs(y)).*y
ans =
5
7
-3
9
6
If you are interested in a generic code that you could use when working with a number of 2D matrices, let's say x, y and p, you can try this -
x = [-2 4 1;
4 -3 2]
y = [8 -3 -5;
-9 1 5]
p = [6 8 6;
7 -1 -2]
mats = cat(3,x,y,p);%// concatenate all identically sized 2D matrices into 3D
[m,n] = size(x);%// get size
[maxval,dim3ind] = max(abs(mats),[],3);%// Max abs values and indices across dim3
mats_sign = sign(mats); %// signum values
out = mats_sign((dim3ind-1)*m*n + bsxfun(#plus,[1:m]',[0:n-1]*m)).*maxval %// output
Output -
x =
-2 4 1
4 -3 2
y =
8 -3 -5
-9 1 5
p =
6 8 6
7 -1 -2
out =
8 8 6
-9 -3 5
So, if you would like to include one more 2D matrix say q into the mix, just edit the first line of code -
mats = cat(3,x,y,p,q);

Resources