How to "chop up" matrix in Matlab using combination of logical indexing and slicing? - matlab

I have a matrix M that looks similar to this:
M = [ 1, 2, 3, 0, 0;
1, 2, 0, 0, 0;
2, 3, 4, 5, 0;
4, 5, 6, 0, 0;
1, 2, 3, 4, 5;
]
I'm trying to get a column vector with the rightmost non-zero value of each row in A, but ONLY for the rows that have the first column == 1.
I'm able to calculate a filter for the rows:
r = M( :, 1 ) == 1;
> r = [ 1; 1; 0; 0; 1 ]
And I have a set of indices for "the rightmost non-zero value of each row in M":
> c = [ 3, 2, 4, 3, 5 ]
How do I combine these in a slicing of A in order to get what I'm looking for? I'm looking for something like:
A( r, c )
> ans = [ 3; 2; 5 ]
But doing this gets me a 3x3 matrix, for some reason.

The shortest way I can think of is as follows:
% Get the values of the last non-zero entry per row
v = M(sub2ind(size(M), 1:size(M,1), c))
% Filter out the rows that does not begin with 1.
v(r == 1)

This seems to work (I assume other operations defining r,c have been performed):
M(sub2ind(size(A),find(r==1).',c(r==1))).'
Short interpretation of the problem and solution:
M( r, c )
gives a 3 x 5 matrix (not 3 x 1 as desired) due to mixing of logical and subscript indices. The logical indices in r pick out rows in A with r==1. Meanwhile row array c picks out elements from each row according to the numeric index:
ans =
3 2 0 3 0
0 2 0 0 0
3 2 4 3 5
What you really want are indices into the rightmost nonzero elements in each row starting with 1. The solution uses linear indices (numeric) to get the correct elements from the matrix.

I think this should do the trick. I wonder if there is more elegant way of doing this though.
% get only rows u want, i.e. with first row == 1
M2 = M(r,:);
% get indices of
% "the rightmost non-zero value of each row in M"
% for the rows u want
indicesOfinterest = c(r==1);
noOfIndeciesOfinterest = numel(indicesOfinterest);
% desired output column vector
output = zeros(noOfIndeciesOfinterest, 1);
% iterate through the indeces and select element in M2
% from each row and column indicated by the indice.
for idx = 1:noOfIndeciesOfinterest
output(idx) = M2(idx, indicesOfinterest(idx));
end
output % it is [3; 2 ; 5]

You can use
arrayfun(#(x) M(x,c(x)), find(r))
But unless you need r and c for other purposes, you can use
arrayfun(#(x) M(x,find(M(x,:),1,'last')), find(M(:,1)==1))

Here is a way to do it using linear indexing:
N = M';
lin_index = (0:size(N,1):prod(size(N))-1) + c;
v = N(lin_index);
v(r)

Related

How can I index multiple array segments at once without a loop?

I want to change the value in column 2 based on the values in column 1 in one array (main), using a start and end index from another array (conditions).
In conditions column 1 holds the start index, column 2 the end index.
main = zeros(8, 2);
main(:, 1) = 1:8;
conditions = [2, 3; 6, 8]
main =
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
conditions =
2 3
6 8
I know how to do it using a loop (shown below), but am looking for a faster method.
for ii = 1:size(conditions, 1)
main(main(:, 1) >= conditions(ii, 1) & main(:, 1) <= conditions(ii, 2), 2) = 1;
end
main =
1 0
2 1
3 1
4 0
5 0
6 1
7 1
8 1
Doing main(main(:, 1) >= conditions(:, 1) & main(:, 1) <= conditions(:, 2), 2) = 1 results in the error Matrix dimensions must agree.
Is there a non-loop method?
Your attempt is almost correct. If you transpose the conditions, then you'll be comparing a column of main with a row of conditions, leading to MATLAB doing implicit singleton expansion, giving a matrix output. This matrix can then be collapsed using any.
main = zeros(8, 2);
main(:, 1) = 1:8;
conditions = [2, 3; 6, 8];
index = (main(:,1) >= conditions(:, 1).') & (main(:, 1) <= conditions(:, 2).');
index = any(index,2);
main(index,2) = 1;
(I've separated out the code into 3 lines for clarity, but of course they can all be a single line.)
Note that for versions of MATLAB prior to R2016b, this code won't work, you'll need to use bsxfun instead:
index = bsxfun(#ge,main(:,1),conditions(:, 1).') & bsxfun(#le,main(:, 1),conditions(:, 2).');
NOTE: This is a solution for integers only as the original question only presented the integer case.
First, figure out how many elements there are included in the interval
dCon = diff(conditions,[],2)+1;
Then construct an increasing sequence of indexes to the maximum number of elements (this list would be enormous for the float case, and thus this solution does not, feasibly/efficiently, extend to floats)
idx0 = repmat(1:max(dCon),length(dCon),1);
Cap off the indexes that are too large
idx0(idx0>dCon)=1;
Now add the starting point
idx = idx0 + conditions(:,1)-1;
now idx contains all the numbers you want to change. Use ismember to find all elements in main and change them to 1.
main(ismember(main(:,1),idx(:)),2)=1;
EDIT: This is the full example with the vector from Gnovice in the comments
main = zeros(10, 2);
main(:, 1) = [1; 2; 2; 2; 3; 3; 4; 6; 6; 8];
conditions = [2, 3; 6, 8]
dCon = diff(conditions,[],2)+1;
idx0 = repmat(1:max(dCon),length(dCon),1);
idx0(idx0>dCon)=1;
idx = idx0 + conditions(:,1)-1;
main(ismember(main(:,1),idx(:)),2)=1;

N-dimensional identity tensor in Matlab

The function eye does not support N-dimensional arrays.
I would like to create a matrix such that
I(i,j,:,:)=eye(3,3)
in a vectorial fashion, without having to loop over i and j.
What is the best way to do this? I haven't been able to find anything in the documentation.
You can use repmat to repeat eye into the 3rd and 4th dimensions, and use shiftdim to shift the dimensions
% for i = 1 to M, and j = 1 to N
k = shiftdim( repmat( eye(3,3), 1, 1, M, N ), 2 );
The output is nasty, because MATLAB doesn't display >2D data very well, but here are a couple of tests:
% Test that a given i-j pair gives eye(3,3) in the 3rd and 4th dimension
isequal( k(1,2,:,:), reshape( eye(3,3), 1, 1, 3, 3 ) ); % = 1, passed
% Test I-j slices are equal and i/j are oriented correctly. Test with M ~= N
isequal( k( 1, 1, :, : ), k( M, N, :, : ) ); % = 1, passed
And here is the actual output of a slice
% Below is equivalent to eye(3,3) in the 3rd and 4th dimensions
k(3,4,:,:)
ans(:,:,1,1) =
1
ans(:,:,2,1) =
0
ans(:,:,3,1) =
0
ans(:,:,1,2) =
0
ans(:,:,2,2) =
1
ans(:,:,3,2) =
0
ans(:,:,1,3) =
0
ans(:,:,2,3) =
0
ans(:,:,3,3) =
1

How to compare every elements in an array with every other element without repetition?

I want data to be processed as follows.
Eg.
say data x(i)=[1 2 3 5 2 1].
Comparisons should be elements INDEX [1 to 2, 1 to 3, 1 to 4, 1 to 5, 1 to 6, 2 to 3,2 to 4,2 to 5,2 to 6,3 to 4....]
following the above logic
hence elements values of distance = [1, 2 , 3 , 4 , 5 , 1 , 2, 3, 4, 1, 2, 3, 1, 2, 1].
hence elements values of difference = [1, 2 , 4 , 1 , 0 , 1 , 3, 0, 1, 2, 1, 2, 3, 4, 1].
I have written the following code for the same but i notice that the final matrix 'b' that i want is always changing size when it should be constant. I welcome any suggestions
clc;
close all;
clear all;
% read data set
I= imread('img2.bmp');
G=double(rgb2gray(I));
%choose 2 random numbers
n = 1;
s = [1 960];
k = round(rand(n,1)*range(s)+min(s));
for i = 1:length(k)
% choose a particular row from a matrix
row_no=k(i);
%G=R(row_no,:);
% compare every element with its neigbour to create distance and difference matrix
x1=row_no;
x2=row_no;
for y1 = 1:length(G)%size(G,2)
for y2 =1:length(G) %1:size(G,2)
distance_Mat(y1,y2) = round(sqrt((y2-y1)^2 + (x2-x1)^2));
difference_Mat(y1,y2) = 1*(G(x1,y1) - G(x2,y2))^2;
end
end
%% now remove repeating comparisons
b=horzcat(distance_Mat(:),(difference_Mat(:)));
[UniXY,Index]=unique(b,'rows');
DupIndex=setdiff(1:size(b,1),Index);
b(DupIndex,:)=[];
%calculate the cumulative sums and store it in different colums of data matrix
A1 = cumsum(b);
data(:,1)=A1;
end
If you have the stats toolbox then
distance_Mat = squareform(pdist(x'));
only does each comparison once and then mirrors the data. You can get just the lower half by
tril(distance_Mat,-1);
If you don't have the toolbox then try this:
I = tril(ones(numel(x)),-1);
[r,c] = find(I);
distance_Mat = zeros(numel(x));
distance_Mat(logical(I)) = round(sqrt((x(r)-x(c)).^2)

How to intersect two or more matrices?

Let say I have some number of matrices with non-negative entries, N matrices for example of equal sizes MxM. For example, I have 3 matrices as follow:
A1=[2, 2, 0;
2, 2, 0;
0, 2, 0];
A2=[4, 0, 4;
4, 3, 0;
0, 0, 1];
A3=[2, 0, 0;
1, 0, 3;
3, 4, 3];
I want to find the intersection of A1, A2, and A3 in matlab. That means I want to get the follow matrix:
B=[-1, 2, 4;
-1, -1, 3;
3, -1, -1];
If the intersection of the (i, j) element of the N matrices, i.e., the elements A1(i, j), A2(i, j), A3(i, j), is at most one nonzero number then B(i,j) equals that number. Else if the intersection is at least two numbers I output -1 as I showed in the previous example.
How can I do this in matlab without loops?
First, concatenate to a single 3D-Matrix:
A=cat(3,A1,A2,A3)
Then count the non-zero elements, which gives the location of the -1-Elements:
L=(sum(A~=0,3)>1)
Finally, There where L=1 we want a -1, otherwise we want the single element at that location, which is the sum because the others are zero:
L*-1+(1-L).*sum(A,3)
I would do it something like so:
First, count up the non-zero elements at each location across all matrices:
foo = (A1 ~= 0) + (A2 ~= 0) + (A3 ~= 0);
Next, find those which have more than one non-zero element, and set them to -1 in the result:
B = zeros(3,3);
B(foo > 1) = -1;
Finally, find the non-zero elements (since in the elements we care about there is only one non-zero element, this is the same as the sum across matrices)
sumA = A1 + A2 + A3;
And add them into the result matrix
B(foo == 1) = sumA(foo == 1);

Matlab: Applying threshold to one dimension in a matrix

I have a matrix M(x,y). I want to apply a threshold in all values in x, such that if x
Example:
M = 1, 2;
3, 4;
5, 6;
If t = 5 is applied on the 1st dimension, the result will be
R = 0, 2;
0, 4;
5, 6;
One way (use M(:,1) to select the first column; M(:,1)<5 returns row indices for items in the first column that are lest than 5))-
> R = M;
> R(M(:,1)<5,1) = 0
R =
0 2
0 4
5 6
Another -
R = M;
[i,j]=find(M(:,1)<5); % locate rows (i) and cols (j) where M(:,1) < 5
% so j is just going to be all 1
% and i has corresponding rows
R(i,1)=0;
To do it in a matrix of arbitrary dimensions:
thresh_min = 5;
M(M < thresh_min) = 0;
The statement M < thresh_min returns indices of M that are less than thresh_min. Then, reindexing into M with these indices, you can set all of these valuse fitting your desired criterion to 0 (or whatever else).