Keep n largest values per column and set the rest to zero in MATLAB without a loop - arrays

I have a matrix of unsorted numbers and I want to keep the n largest (not necessarily unique) values per column and set the rest to zero.
I figured out how to do it with a loop:
a = [4 8 12 5; 9 2 6 18; 11 3 9 7; 8 9 12 4]
k = 2
for n = 1:4
[y, ind] = sort(a(:,n), 'descend');
a(ind(k+1:end),n) = 0;
end
a
which gives me:
a =
4 8 12 5
9 2 6 18
11 3 9 7
8 9 12 4
k =
2
a =
0 8 12 0
9 0 0 18
11 0 0 7
0 9 12 0
However when I try to eliminate the loop, I can't seem to get the indexing right, because this:
a = [4 8 12 5; 9 2 6 18; 11 3 9 7; 8 9 12 4]
k = 2
[y, ind] = sort(a, 'descend');
b = ind(k+1:end,:)
a(b) = 0
which gives me this: (which is not what I wanted to do)
a =
4 8 12 5
9 2 6 18
11 3 9 7
8 9 12 4
k =
2
b =
4 3 3 1
1 2 2 4
a =
0 8 12 5
0 2 6 18
0 3 9 7
0 9 12 4
Am I indexing this wrong? Do I have to use the loop?
I referenced this question to get started but it wasn't exactly what I was trying to do: How to find n largest elements in an array and make the other elements zero in matlab?

You're very close. ind in the sort function gives you the row locations for each column where that particular value would appear in the sorted output. You need to do some additional work if you want to index into the matrix properly and eliminate the entries. You know that for each column of I, that tells you that we need to eliminate those entries from that particular column. Therefore, what I would do is generate column-major linear indices using each column of I to be the rows we need to eliminate.
Try doing this:
a = [4 8 12 5; 9 2 6 18; 11 3 9 7; 8 9 12 4];
k = 2;
[y, ind] = sort(a, 'descend');
%// Change here
b = sub2ind(size(a), ind(k+1:end,:), repmat(1:size(a,2), size(a,1)-k, 1));
a(b) = 0;
We use sub2ind to help us generate our column major indices where the rows are denoted by the values in ind after the kth element and the columns we need are for each column in this matrix. There are size(a,1)-k rows remaining after you truncate out the k values after sorting, and so we generate column values that go from 1 up to as many columns as we have in a and as many rows as there are remaining.
We get this output:
>> a
a =
0 8 12 0
9 0 0 18
11 0 0 7
0 9 12 0

Here's one using bsxfun -
%// Get descending sorting indices per column
[~, ind] = sort(a,1, 'descend')
%// Get linear indices that are to be set to zeros and set those in a to 0s
rem_ind = bsxfun(#plus,ind(n+1:end,:),[0:size(a,2)-1]*size(a,1))
a(rem_ind) = 0
Sample run -
a =
4 8 12 5
9 2 6 18
11 3 9 7
8 9 12 4
n =
2
ind =
3 4 1 2
2 1 4 3
4 3 3 1
1 2 2 4
rem_ind =
4 7 11 13
1 6 10 16
a =
0 8 12 0
9 0 0 18
11 0 0 7
0 9 12 0

Related

Equalize to zero a different element of a matrix for each row without loop

I have an IxS matrix called numerateur and I would like to set one element per row to zero. However, the column to which this element belongs changes for each row. I can do it with a loop but would like to use a more efficient method.
You can find my loop below:
choice is an Ix1 vector containing, for each row, the column number of the element I want to set to zero in numerateur.
for i = 1:I
rank1 = choice(i);
numerateur(i,rank1) = 0;
end
You need to convert the column indices to linear indices:
ind = (1:size(numerateur,1)) + (choice(:).'-1)*size(numerateur,1);
numerateur(ind) = 0;
Example:
>> numerateur = randi(9,5,7)
numerateur =
8 7 8 3 2 8 4
7 8 6 3 6 1 3
8 9 5 5 5 9 5
4 4 2 7 4 9 9
1 6 7 3 8 7 9
>> choice = [2; 3; 6; 5; 1];
>> ind = (1:size(numerateur,1)) + (choice(:).'-1)*size(numerateur,1);
>> numerateur(ind) = 0;
>> numerateur
numerateur =
8 0 8 3 2 8 4
7 8 0 3 6 1 3
8 9 5 5 5 0 5
4 4 2 7 0 9 9
0 6 7 3 8 7 9
Use implicit expansion to find logical indices:
numerateur(choice == 1:size(numerator,2)) = 0;
Alternatively you can use sparse matrix to generate logical indices:
numerateur(sparse(1:I,choice,true,I,S)) = 0;

Finding the distribution of length of islands in a 2D array?

I will explain my question using an example. Imagine you have a 2D matrix like below:
5 4 3 8 0 0
5 4 2 9 1 0
5 6 2 7 2 0
5 4 7 9 0 0
5 6 7 1 2 0
By islands I mean column groups of same elements (except zeros).
I would like to find the histogram of length of islands except those consisting of zero elements.
This matrix has
island-length occurrence
5 1
2 3
1 12
How can I realize this task using Matlab ?
Maybe there are shorter possibilities, but this will do - and it is fully vectorized:
A = [5 4 3 8 0 0
5 4 2 9 1 0
5 6 2 7 2 0
5 4 7 9 0 0
5 6 7 1 2 0]
%// pad zeros to first line of A
X(2:size(A,1)+1,:) = A;
%// differences of X
dX = diff(X)
%// cumulative sum of "logicalized" differences
cs = cumsum(logical(dX(:)))
%// filter out zeros
cs = cs(logical(A(:)))
%// count occurances
aa = accumarray(cs,1)
%// unique occurances
uaa = unique(aa)
%// count unique occurances
occ = hist(aa,uaa).'
%// accumarray may introduce new zeros, filter out
mask = logical(uaa)
%// output
out = [occ(mask) uaa(mask)]
out =
12 1
3 2
1 5
Needed a slight modification to one of my old snippets to filter the zeros. Here you go:
% Your Matrix
A = [ 5 4 3 8 0 0;
5 4 2 9 1 0;
5 6 2 7 2 0;
5 4 7 9 0 0;
5 6 7 1 2 0];
% Find Edges (Ends of Islands)
B = diff(A);
B = [ones(1,size(A,2));B~=0;ones(1,size(A,2))];
% At each column, find distances between island edges, filter out zero islands.
R = cell(size(A,2),1);
for i = 1:size(A,2)
[C ~] = find(B(:,i));
Ac = A(C(1:end-1),i);
D = diff(C);
D(Ac==0)=[];
R{i} = D;
end
% Find histogram of island lengths
R = R(find(~cellfun(#isempty,R)),1);
R = cell2mat(R);
[a,~,c] = unique(R);
out = [a, accumarray(c,ones(size(R)))];

How to remove extra duplicated elements in each row of a matrix in matlab?

Let's say I have a matrix
A = [2 3 2 5 6 7 2;
1 2 5 4 5 6 7;
7 5 3 9 8 1 2];
How do I remove 2s and keep one 2 in the first row and keep only one 5 in the second row?
The result can't be a matrix anymore, because each row will have a different length. You can obtain the result as a cell array of row vectors as follows:
B = mat2cell(A, ones(size(A,1),1)); %// convert matrix to cell array of its rows
B = cellfun(#(x) unique(x,'stable'), B, 'uniformoutput', 0); %// stably remove duplicates
For your example matrix
A = [2 3 2 5 6 7 2;
1 2 5 4 5 6 7;
7 5 3 9 8 1 2];
this gives
B{1} =
2 3 5 6 7
B{2} =
1 2 5 4 6 7
B{3} =
7 5 3 9 8 1 2
If you want to find out which values are duplicates within the row, you can do something like this:
[vals, col_idx] = sort(A,2);
idx = bsxfun(#plus,(col_idx-1)*size(A,1), (1:size(A,1))');
is_duplicate(idx(:,2:end)) = vals(:,1:end-1) == vals(:,2:end);
is_duplicate = reshape(is_duplicate, size(A));
is_duplicate =
0 0 1 0 0 0 1
0 0 0 0 1 0 0
0 0 0 0 0 0 0
From there, it depends what outcome you are looking for. You could set the duplicates to NaN or some other value, or you could set them to NaN, but then shift them to the end of the row, using something like the following:
col_idx = cumsum(~is_duplicate, 2);
idx = bsxfun(#plus,(col_idx-1)*size(A,1), (1:size(A,1))');
A_new = nan(size(A));
A_new(idx(~is_duplicate)) = A(~is_duplicate);
A_new =
2 3 5 6 7 NaN NaN
1 2 5 4 6 7 NaN
7 5 3 9 8 1 2

Matlab- moving numbers to new row if condition is met

I have a variable like this that is all one row:
1 2 3 4 5 6 7 8 9 2 4 5 6 5
I want to write a for loop that will find where a number is less than the previous one and put the rest of the numbers in a new row, like this
1 2 3 4 5 6 7 8 9
2 4 5 6
5
I have tried this:
test = [1 2 3 4 5 6 7 8 9 2 4 5 6 5];
m = zeros(size(test));
for i=1:numel(test)-1;
for rows=1:size(m,1)
if test(i) > test(i+1);
m(i+1, rows+1) = test(i+1:end)
end % for rows
end % for
But it's clearly not right and just hangs.
Let x be your data vector. What you want can be done quite simply as follows:
ind = [find(diff(x)<0) numel(x)]; %// find ends of increasing subsequences
ind(2:end) = diff(ind); %// compute lengths of those subsequences
y = mat2cell(x, 1, ind); %// split data vector according to those lenghts
This produces the desired result in cell array y. A cell array is used so that each "row" can have a different number of columns.
Example:
x = [1 2 3 4 5 6 7 8 9 2 4 5 6 5];
gives
y{1} =
1 2 3 4 5 6 7 8 9
y{2} =
2 4 5 6
y{3} =
5
If you are looking for a numeric array output, you would need to fill the "gaps" with something and filling with zeros seem like a good option as you seem to be doing in your code as well.
So, here's a bsxfun based approach to achieve the same -
test = [1 2 3 4 5 6 7 8 9 2 4 5 6 5] %// Input
idx = [find(diff(test)<0) numel(test)] %// positions of row shifts
lens = [idx(1) diff(idx)] %// lengths of each row in the proposed output
m = zeros(max(lens),numel(lens)) %// setup output matrix
m(bsxfun(#le,[1:max(lens)]',lens)) = test; %//'# put values from input array
m = m.' %//'# Output that is a transposed version after putting the values
Output -
m =
1 2 3 4 5 6 7 8 9
2 4 5 6 0 0 0 0 0
5 0 0 0 0 0 0 0 0

Rearranging an array using for loop in Matlab

I have a 1 x 15 array of values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
I need to rearrange them into a 3 x 5 matrix using a for loop:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
How would I do that?
I'm going to show you three methods. One where you need to have a for loop, and two others when you don't:
Method #1 - for loop
First, create a matrix that is 3 x 5, then keep track of an index that will go through your array. After, create a double for loop that will help you populate the array.
index = 1;
array = 1 : 15; %// Array we wish to access
matrix = zeros(3,5); %// Initialize
for m = 1 : 3
for n = 1 : 5
matrix(m,n) = array(index);
index = index + 1;
end
end
matrix =
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
Method #2 - Without a for loop
Simply put, use reshape:
matrix = reshape(1:15, 5, 3).';
matrix =
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
reshape will take a vector and restructure it into a matrix so that you populate the matrix by columns first. As such, we want to put 1 to 5 in the first column, 6 to 10 in the second and 11 to 15 in the third column. Therefore, our output matrix is in fact 5 x 3. When you see this, this is actually the transposed version of the matrix we want, which is why you do .' to transpose the matrix back.
Method #3 - Another method without a for loop (tip of the hat goes to Luis Mendo)
You can use vec2mat, and specify that you need to have 5 columns worth for your matrix:
matrix = vec2mat(1:15, 5);
matrix =
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
vec2mat takes a vector and reshapes it into a matrix of as many columns as you specify in the second parameter. In this case, we need 5 columns.
For the sake of (bsx)fun, here is another option...
bsxfun(#plus,1:5,[0:5:10]')
ans =
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
less readable, maybe faster, but who cares if it is such a small of an array...
A = [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ] ;
A = reshape( A' , 3 , 5 ) ;
A' = 1 2 3 4 5
6 7 8 9 10
11 12 13 14 15

Resources