Creating matrix of maximum values indices in MATLAB - matlab

Using MATLAB, I have an array of values of size 8 rows x N columns. I need to create a matrix of the same size, that counts maximum values in each column and puts 1 in the cell that contains maximum value, and 0 elsewhere.
A little example. Lets assume we have an array of values D:
D =
0.0088358 0.0040346 0.40276 0.0053221
0.017503 0.011966 0.015095 0.017383
0.14337 0.38608 0.16509 0.15763
0.27546 0.25433 0.2764 0.28442
0.01629 0.0060465 0.0082339 0.0099775
0.034521 0.01196 0.016289 0.021012
0.12632 0.13339 0.11113 0.10288
0.3777 0.19219 0.005005 0.40137
Then, the output matrix for such matrix D would be:
0 0 1 0
0 0 0 0
0 1 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
1 0 0 1
Is there a way to do it without catching vector of indices from max function and then putting ones in the right place using for loop?

A one-line answer:
M = D==repmat(max(D),size(D,1),1)
or more elegantly:
M = bsxfun(#eq, D, max(D))
Update:
According to the comments, if you want to be on the safe side and catch the accidental non-unique maximums, add the following statement:
M( cumsum(M)>1 ) = false
which will ensure that in the case of multiple maximums, only the first to occur has a corresponding one in the output matrix (this is equivalent to the behavior of the max() function's returned index).

There are probably better ways to do it, my first approach is:
D = rand(8,4)
[val, sub] = max(D)
ind = sub2ind( size(D), sub, 1:4 )
res = false( size(D) )
res( ind ) = true

I have written an extension to the original problem that can handle arbitrary multidimension array and search for maximum along any specified dimension.
I used it to solve for the Nash equilibrium in game theory. Hope others will find it helpful.
A = rand([3 3 2]);
i = 1; % specify the dimension of A through which we find the maximum
% the following codes find the maximum number of each column of A
% and create a matrix M of the same size with A
% which puts 1 in the cell that contains maximum value, and 0 elsewhere.
[Amax pos] = max(A, [], i);
% pos is a now 1x3x3 matrix (the ith dimension is "shrinked" by the max function)
sub = cell(1, ndims(A));
[sub{:}] = ind2sub(size(pos), (1:length(pos(:)))');
sub{i} = pos(:);
ind = sub2ind(size(A), sub{:});
M = false(size(A));
M(ind) = true;
Example:
A(:,:,1) =
0.0292 0.4886 0.4588
0.9289 0.5785 0.9631
0.7303 0.2373 0.5468
A(:,:,2) =
0.5211 0.6241 0.3674
0.2316 0.6791 0.9880
0.4889 0.3955 0.0377
M(:,:,1) =
0 0 0
1 1 1
0 0 0
M(:,:,2) =
1 0 0
0 1 1
0 0 0

Related

Merge two matrices in matlab

I have two matrices. One is of size 1,000,000 x 9 and the other is 500,000 x 9.
The columns have the same meaning and the first 7 columns have the function of a key. Correspondingly, the last two columns have data character. There are many overlapping key values in both of the matrices and I would like to have a big matrix to compare the values. This big matrix should be of dimension 1,000,000 x 11.
For example:
A = [0 0 0 0 0 0 0 10 20; 0 0 0 0 0 0 1 30 40];
B = [0 0 0 0 0 0 0 50 60];
A merged matrix would look like this:
C = [0 0 0 0 0 0 0 10 20 50 60; 0 0 0 0 0 0 1 30 40 0 0];
As you can see, the first row of C has columns 8, 9 from matrix A and columns 10,11 from matrix B. The second row uses the columns 8, 9 from matrix A and 0,0 for the last to columns because there is no corresponding entry in matrix B.
I have accomplished this task theoretically, but it is very, very slow. I use loops a lot. In any other programming language, I would sort both tables, would iterate both of the tables in one big loop keeping two pointers.
Is there a more efficient algorithm available in Matlab using vectorization or at least a sufficiently efficient one that is idiomatic/short?
(Additional note: My largest issue seems to be the search function: Given my matrix, I would like to throw in one column vector 7x1, let's name it key to find the corresponding row. Right now, I use bsxfun for that:
targetRow = data( min(bsxfun(#eq, data(:, 1:7), key), [], 2) == 1, :);
I use min because the result of bsxfun is a vector with 7 match flags and I obviously want all of them to be true. It seems to me that this could be bottleneck of a Matlab algorithm)
Maybe with ismember and some indexing:
% locates in B the last ocurrence of each key in A. idxA has logicals of
% those keys found, and idxB tells us where in B.
[idxA, idxB] = ismember(A(:,1:7), B(:,1:7),'rows');
C = [ A zeros(size(A, 1), 2) ];
C(idxA, 10:11) = B(idxB(idxA), 8:9); % idxB(idxA) are the idxB != 0
I think this does what you want, only tested with your simple example.
% Initial matrices
A = [0 0 0 0 0 0 0 10 20;
0 0 0 0 0 0 1 30 40];
B = [0 0 0 0 0 0 0 50 60];
% Stack matrices with common key columns, 8&9 or 10&11 for data columns
C = [[A, zeros(size(A,1),2)]; [B(:,1:7), zeros(size(B,1),2), B(:,8:9)]];
% Sort C so that matching key rows will be consecutive
C = sortrows(C,1:7);
% Loop through rows
curRow = 1;
lastRow = size(C,1) - 1;
while curRow < lastRow
if all(C(curRow,1:7) == C(curRow+1,1:7))
% If first 7 cols of 2 rows match, take max values (override 0s)
% It may be safer to initialise the 0 columns to NaNs, as max will
% choose a numeric value over NaN, and it allows your data to be
% negative values.
C(curRow,8:11) = max(C(curRow:curRow+1, 8:11));
% Remove merged row
C(curRow+1,:) = [];
% Decrease size counter for matrix
lastRow = lastRow - 1;
else
% Increase row counter
curRow = curRow + 1;
end
end
Answer:
C = [0 0 0 0 0 0 0 10 20 50 60
0 0 0 0 0 0 1 30 40 0 0]

How to find a non-zero number between two zeros in a cell array in matlab

I have a cell array (11000x500) with three different type of elements.
1) Non-zero doubles
2) zero
3) Empty cell
I would like to find all occurances of a non-zero number between two zeros.
E.g. A = {123 13232 132 0 56 0 12 0 0 [] [] []};
I need the following output
out = logical([0 0 0 0 1 0 1 0 0 0 0 0]);
I used cellfun and isequal like this
out = cellfun(#(c)(~isequal(c,0)), A);
and got the follwoing output
out = logical([1 1 1 0 1 0 1 0 0 1 1 1]);
I need help to perform the next step where i can ignore the consecutive 1's and only take the '1's' between two 0's
Could someone please help me with this?
Thanks!
Here is a quick way to do it (and other manipulations binary data) using your out:
out = logical([1 1 1 0 1 0 1 0 0 1 1 1]);
d = diff([out(1) out]); % find all switches between 1 to 0 or 0 to 1
len = 1:length(out); % make a list of all indices in 'out'
idx = [len(d~=0)-1 length(out)]; % the index of the end each group
counts = [idx(1) diff(idx)]; % the number of elements in the group
elements = out(idx); % the type of element (0 or 1)
singles = idx(counts==1 & elements==1)
and you will get:
singles =
5 7
from here you can continue and create the output as you need it:
out = false(size(out)); % create an output vector
out(singles) = true % fill with '1' by singles
and you get:
out =
0 0 0 0 1 0 1 0 0 0 0 0
You can use conv to find the elements with 0 neighbors (notice that the ~ has been removed from isequal):
out = cellfun(#(c)(isequal(c,0)), A); % find 0 elements
out = double(out); % cast to double for conv
% elements that have more than one 0 neighbor
between0 = conv(out, [1 -1 1], 'same') > 1;
between0 =
0 0 0 0 1 0 1 0 0 0 0 0
(Convolution kernel corrected to fix bug found by #TasosPapastylianou where 3 consecutive zeros would result in True.)
That's if you want a logical vector. If you want the indices, just add find:
between0 = find(conv(out, [1 -1 1], 'same') > 1);
between0 =
5 7
Another solution, this completely avoids your initial logical matrix though, I don't think you need it.
A = {123 13232 132 0 56 0 12 0 0 [] [] []};
N = length(A);
B = A; % helper array
for I = 1 : N
if isempty (B{I}), B{I} = nan; end; % convert empty cells to nans
end
B = [nan, B{:}, nan]; % pad, and collect into array
C = zeros (1, N); % preallocate your answer array
for I = 1 : N;
if ~any (isnan (B(I:I+2))) && isequal (logical (B(I:I+2)), logical ([0,1,0]))
C(I) = 1;
end
end
C = logical(C)
C =
0 0 0 0 1 0 1 0 0 0 0 0

Unique vectors in cell based on tolerance

I'm trying to make a search algorithm which finds the unique columns of a cell based on a tolerance level. The unique function of MATLAB (R2012a), does not provide a tolerance input. Below is the code which I have so far; I have limited myself to checking uniqueness based on the first identity (j=1) for now, however, this needs to be updated later.
The output is: I obtain a store cell which contains all the vector expect the duplicates of [0;1;0]. However other duplicate are maintained (e.g. [1;0;-0.4])
clear all; close all; clc;
%%
tolerance=1e-6;
U_vector{1} = [0 1 0 1 1 0 1 0 1 1;
1 0 1 0 0 1 0 1 0 0;
0 -0.4238 0 0.4238 -0.4238 0 0.4238 0 0.8161001 -0.8161];
for i = 1:1:size(U_vector,2)
k=1;
store{i}(:,k) = U_vector{i}(:,k);
for j=1;%:1:(size(U_vector{i},2))
for m=j:1:(size(U_vector{i},2))
if (abs(U_vector{i}(:,j)-U_vector{i}(:,m)) >= tolerance)
k=k+1;
store{i}(:,k) = U_vector{i}(:,m);
end
end
end
end
There's an undocumented function to merge similar points, which works on rows too:
>> u = [0 1 0 1 1 0 1 0 1 1;
1 0 1 0 0 1 0 1 0 0;
0 -0.4238 0 0.4238 -0.4238 0 0.4238 0 0.8161001 -0.8161];
>> uMerged = builtin('_mergesimpts',u.',0.3).'
uMerged =
0 1.0000 1.0000 1.0000 1.0000
1.0000 0 0 0 0
0 -0.8161 -0.4238 0.4238 0.8161
Just get u = U_vector{1}; in your case, then pack the result in a cell too (out{1} = uMerged;).
Also, the function can take a vector tolerance indicating a tolerance for each column. From the command line message from this function:
Tolerance must be a scalar or a vector with the same number of columns as the first input 'X'.
So this works too:
uMerged = builtin('_mergesimpts',u.',[eps eps 0.3]).'
BTW: There will probably be an official function for this in the future, but we're not allowed to discuss :).
You do not need so many nested loops. This works with the sample you provided.
It uses a working table which is reduced as duplicates are found.
for ii = 1:1:size(U_vector,2)
A = U_vector{ii} ; %// create a working copy of the current table
store{ii} = [] ; %// initialize the result cell array
endOfTable = false ;
while ~endOfTable
store{ii}(:,end+1) = A(:,1) ; %// save the first column of the table
idx = logical( sum( abs( bsxfun(#minus,A(:,2:end),A(:,1))) >= tolerance ) ) ; %// find the indices of the columns not within the tolerance
A = A(:, [false idx] ) ; %// remove the duplicate columns in A
if size(A,2) < 2 ; endOfTable = true ; end %// exit loop if we reached the last column
end
%// store last column if it remained unmatched
if size(A,2) == 1
store{ii}(:,end+1) = A(:,1) ;
end
end
Which output with your data:
>> store{1}
ans =
0 1.0000 1.0000 1.0000 1.0000
1.0000 0 0 0 0
0 -0.4238 0.4238 0.8161 -0.8161
What about this?!:
u = cell2mat(U_vector{1});
i=1;
while i<=size(u,2)
test=repmat(u(:,i),1,size(u,2)); % compare matrix entries to current column i
differentCols = ~all(same); % column indices that are not equal to column i
differentCols(i)=1; % ensure one such column stays in u
u=u(:,differentCols); % new u-> keep different columns
i=i+1; % next column
end
u % print u
Seems to work for me, but no guarantees.

How to count the number of 1's from the total matrix

I have code like below:
N=10;
R=[1 1 1 1 1 0 0 0 0 0;1 1 1 1 1 1 1 1 1 1];
p=[0.1,0.2,0.01];
B = zeros(N , N);
B(1:N,1:N) = eye(N);
C=[B;R];
for q=p(1:length(p))
Rp=C;
for i=1:N
if(rand < p)
Rp(i,:) = 0;
end
end
end
from this code I vary the value of p. So for different value of p, i am getting different Rp. Now I want to get the total number of "1"'s from each Rp matrix. it means may be for p1 I am getting Rp1=5, for p2, Rp=4.
For example
Rp1=[1 0 0 0 0;0 1 0 0 0;0 0 0 0 0],
Rp2=[1 0 0 0 0;0 1 0 0 0;1 0 0 0 0],
Rp3=[0 0 0 0 0;0 1 0 0 0;0 0 0 0 0],
So total result will be 2,3,1.
I want to get this result.
If the matrix contains only 0 and 1 you are trying to count the nonzero values and there is a function for that called nnz
n = nnz(Rp);
As I mentioned in the comments you should replace
if(rand < p)
with
if(rand < q)
Then you can add the number of nonzero values to a vector like
r = [];
for q=p(1:length(p))
Rp=C;
for i=1:N
if(rand < p)
Rp(i,:) = 0;
end
end
r = [r nnz(Rp)];
end
Then r will contain your desired result. There are many ways to improve your code as mentioned in other answers and comments.
Assuming Rp is your matrix, then simply do one of the following:
If your matrix only contains zeros and ones
sum(Rp(:))
Or if your matrix contains multiple values:
sum(Rp(:)==1)
Note that for two dimensional matrices sum(Rp(:)) is the same as sum(sum(Rp))
I think your real question is how to save this result, you can do this by assigning it to an indexed varable, for example:
S(count) = sum(Rp(:));
This will require you to add a count variable that increases with one every step of the loop. It will be good practice (and efficient) to initialize your variable properly before the loop:
S = zeros(length(p),1);
If you need to count the 1's in any matrix M you should be able to do sum(M(:)==1)

How to replace non-zero elements randomly with zero?

I have a matrix including 1 and 0 elements like below which is used as a network adjacency matrix.
A =
0 1 1 1
1 1 0 1
1 1 0 1
1 1 1 0
I want to simulate an attack on the network, so I must replace some specific percent of 1 elements randomly with 0. How can I do this in MATLAB?
I know how to replace a percentage of elements randomly with zeros, but I must be sure that the element that is replaced randomly, is one of the 1 elements of matrix not zeros.
If you want to change each 1 with a certain probability:
p = 0.1%; % desired probability of change
A_ones = find(A); % linear index of ones in A
A_ones_change = A_ones(rand(size(A_ones))<=p); % entries to be changed
A(A_ones_change) = 0; % apply changes in those entries
If you want to randomly change a fixed fraction of the 1 entries:
f = 0.1; % desired fraction
A_ones = find(A);
n = round(f*length(A_ones));
A_ones_change = randsample(A_ones,n);
A(A_ones_change) = 0;
Note that in this case the resulting fraction may be different to that intended, because of the need to round to an integer number of entries.
#horchler's point is a good one. However, if we keep it simple, then you can just multiple your input matrix to a mask matrix.
>> a1=randint(5,5,[0 1]) #before replacing 1->0
a1 =
1 1 1 0 1
0 1 1 1 0
0 1 0 0 1
0 0 1 0 1
1 0 1 0 1
>> a2=random('unif',0,1,5,5) #Assuming frequency distribution is uniform ('unif')
a2 =
0.7889 0.3200 0.2679 0.8392 0.6299
0.4387 0.9601 0.4399 0.6288 0.3705
0.4983 0.7266 0.9334 0.1338 0.5751
0.2140 0.4120 0.6833 0.2071 0.4514
0.6435 0.7446 0.2126 0.6072 0.0439
>> a1.*(a2>0.1) #And the replacement prob. is 0.1
ans =
1 1 1 0 1
0 1 1 1 0
0 1 0 0 1
0 0 1 0 1
1 0 1 0 0
And other trick can be added to the mask matrix (a2). Such as a different freq. distribution, or a structure (e.g. once a cell is replaced, the adjacent cells become less likely to be replaced and so on.)
Cheers.
The function find is your friend:
indices = find(A);
This will return an array of the indices of 1 elements in your matrix A and you can use your method of replacing a percent of elements with zero on a subset of this array. Then,
A(subsetIndices) = 0;
will replace the remaining indices of A with zero.