Matlab - using values from two matrices to alter a third - matlab

I have a matrix A which contains values 0-100. Matrix B (same number of rows, but only two columns) has values 0 or 1 in its second column. Matrix C has the same number of entries as Matrix A, but just 0 or 1.
I'll assume C starts full of zeros. If there is a 1 in the second column of B, then I want to have a 1 in the same row in C, for every position where there is a value in the corresponding position in A. However, I can't work out how to get this. I've tried using the for loop, but it doesn't seem to be doing what I want.
i.e.
A =
10 10 10
10 10 10
0 20 10
B =
1 0
2 0
3 1
I want to have C:
C =
0 0 0
0 0 0
0 1 1
Very grateful for any help.

Find desired rows using B:
>> B(:, 2) == 1
ans =
0
0
1
Find possible values from A:
>> A ~= 0
ans =
1 1 1
1 1 1
0 1 1
Put it all together by anding using bsxfun:
>> bsxfun(#and, B(:, 2) == 1, A ~= 0)
ans =
0 0 0
0 0 0
0 1 1

Related

how to create a vector based on two auxiliary vectors in Matlab

I am creating a Matlab program to handle an array of data that contains finite integers (not necessary to be consecutive). Let says my data array is A, I need to find the unique values of A first, that forms a vector B, and I have to count the number of occurrence of each unique value in A, that gives another vector C. Finally, I need a vector E which has the same length of A but with the value of the ith element of E computed as E(i) = C(k) with k = A(i)
I have the code to create B and C as follows:
A=[5 5 1 1 3 1 3 11 9 6 -2];
[B, ia, ic]=unique(A);
C = accumarray(ic,1);
A could be a large vector and the elements of A could be very different and in a wide range, I wonder if there is any vectorize approach to generate the array E instead of the following loop method
E = zeros(size(A));
for n=1:length(E)
k=find(B==A(n));
E(n) = C(k);
end
As you say, to calculate the values for E, you need to calculate the indices in B which correspond to each element in A, informally where A == B. This obviously won't work because A and B are different sizes. But we can get what we want by thinking about things as a 2D grid. Transpose one of the vectors and equate them.
A == B.'
% or for older versions of MATLAB (won't implicitly expand the different sized variables)
bsxfun(#eq, A, B.')
ans =
7×11 logical array
0 0 0 0 0 0 0 0 0 0 1
0 0 1 1 0 1 0 0 0 0 0
0 0 0 0 1 0 1 0 0 0 0
1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 1 0 0 0
% to help visualise
% A = [5 5 1 1 3 1 3 11 9 6 -2];
% B = [-2 1 3 5 6 9 11];
You can see that, for each column, the position of the 1 tells us the where we can find that element in A in vector B. We can use find to get the row indices of each 1, and use that to calculate E.
[indices, ~] = find(A == B.');
E = C(indices);
Final result
If you want to calculate this with one line of code, you can use do the following
A=[5 5 1 1 3 1 3 11 9 6 -2];
[B, ia, ic]=unique(A);
C = accumarray(ic,1);
E = C(mod(find(B.' == A)-1, length(B))+1).';
% or for older versions of MATLAB
E = C(mod(find(bsxfun(#eq, A, B.'))-1, length(B))+1).';

Acces 3D Matrix with 2D index and 1D vector

I have a 3D matrix A (size m*n*k) where m=latitude, n*longitude and k=time.
I want only specific values from first and second dimension, specified by a logical matrix B (size m*n), and I want only the timesteps specified by vector C (size k).
In the end this should become a 2D matrix D, since the first two dimesions will colapse to one.
What is the most easy approach to do this?
And also is it possible to combine logical with linear indizes here? For example B is logical and C is linear?
Sample code with rand:
A=rand(10,10,10);
B=randi([0 1], 10,10);
C=randi([0 1], 10,1);
D=A(B,C) %This would be my approach which doesnt work. The size of D should be sum(B)*sum(c)
Another example without rand:
A=reshape([1:27],3,3,3);
B=logical([1,0,0;1,0,0;0,0,0]);
C=(1,3); %get data from timestep 1 and 5
D=A(B,C);%What I want to do, but doesnÄt work that way
D=[1,19;2,20];%Result should look like this! First dimension is now all data from dimesion 1 and 2. New dimesion 2 is now the time.
A = rand(4,4,4);
B = randi([0 1], 4,4)
B =
1 1 0 1
1 0 1 1
0 0 1 0
1 0 1 1
>> C = randi([0 1],1,1,4);
>> C(:)
ans =
0
1
1
0
Then use bsxfun or implicit expansion expansion whith .* if newer Matlab version to generate a matrix of logical for you given coordinates.
>> idx = logical(bsxfun(#times,B,C))
idx(:,:,1) =
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
idx(:,:,2) =
1 1 0 1
1 0 1 1
0 0 1 0
1 0 1 1
idx(:,:,3) =
1 1 0 1
1 0 1 1
0 0 1 0
1 0 1 1
idx(:,:,4) =
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
Then your output is D = A(idx). However, note that this D is now an Nx1 array. Where N is number of true elements is B times number of true elements in C. 10x True in B and 2x True in C:
>> size(D)
ans =
20 1
An easy way to do it is to first reshape A into an m*n-by-k matrix, then do your indexing:
result = reshape(A, [], size(A, 3));
result = result(B, C);
In this case C can be either a logical vector or vector of indices.

Is there a fast way to count occurrences of items in a matrix and save them in another matrix without using loops?

I have a time-series matrix X whose first column contains user ID and second column contains the item ID they used at different times:
X=[1 4
2 1
4 2
2 3
3 4
1 1
4 2
5 3
2 1
4 2
5 4];
I want to find out which user used which item how many times, and save it in a matrix Y. The rows of Y represent users in ascending order of ID, and the columns represent items in ascending order of ID:
Y=[1 0 0 1
2 0 1 0
0 0 0 1
0 3 0 0
0 0 1 1]
The code I use to find matrix Y uses 2 for loops which is unwieldy for my large data:
no_of_users = size(unique(X(:,1)),1);
no_of_items = size(unique(X(:,2)),1);
users=unique(X(:,1));
Y=zeros(no_of_users,no_of_items);
for a=1:size(A,1)
for b=1:no_of_users
if X(a,1)==users(b,1)
Y(b,X(a,2)) = Y(b,X(a,2)) + 1;
end
end
end
Is there a more time efficient way to do it?
sparse creates a sparse matrix from row/column indices, conveniently accumulating the number of occurrences if you give a scalar value of 1. Just convert to a full matrix.
Y = full(sparse(X(:,1), X(:,2), 1))
Y =
1 0 0 1
2 0 1 0
0 0 0 1
0 3 0 0
0 0 1 1
But it's probably quicker to just use accumarray as suggested in the comments:
>> Y2 = accumarray(X, 1)
Y2 =
1 0 0 1
2 0 1 0
0 0 0 1
0 3 0 0
0 0 1 1
(In Octave, sparse seems to take about 50% longer than accumarray.)

More efficient way of symmetrizing a square matrix in MATLAB?

I have an "almost symmetric" matrix, which I wish to symmetrize in MATLAB. For example, I wish to symmetrize
>> A = [0 0 1; 2 0 3; 0 3 0]
A =
0 0 1
2 0 3
0 3 0
into
>> B
B =
0 2 1
2 0 3
1 3 0
Safe assumptions are that diagonal entries of A are all zero and that "the bits to change" are always 0. E.g., I changed A(1, 2) and A(3, 1) in the above example, and original values at both locations were 0.
My best attempt based on #Photon's comment (Thanks Photon!) is
>> C = -0.5*(A.'.*A~=0)+1;
>> B = (A+A.').*C
B =
0 2 1
2 0 3
1 3 0
Is there a better (more efficient or faster) way of achieving this?
What about
B = max( A, A.' );
Assuming all entries of A are non-negative.

Computing linear indices and counting non-zero entries of large sparse matrices in row-first order

Say I have a sparse non-rectangular matrix A:
>> A = round(rand(4,5))
A =
0 1 0 1 1
0 1 0 0 1
0 0 0 0 1
0 1 1 0 0
I would like to obtain the matrix B where the non-zero entries of A are replaced by their linear index in row-first order:
B =
0 2 0 4 5
0 7 0 0 10
0 0 0 0 15
0 17 18 0 0
and the matrix C that where the non-zero entries of A are replaced by the order in which they are found in a row-first search:
C =
0 1 0 2 3
0 4 0 0 5
0 0 0 0 6
0 7 8 0 0
I am looking for vectorized solutions for this problem that scale to large sparse matrices.
If I understand what you are asking, a couple of tranpositions should do the trick. The key is that find(A.') will do "row-first" indexing on A, where .' is the short hand for the transpose of a 2D matrix. So:
>> A = round(rand(4,5))
A =
0 1 0 1 1
0 1 0 0 1
0 0 0 0 1
0 1 1 0 0
then
B=A.';
B(find(B)) = find(B);
B=B.';
gives
B =
0 2 0 4 5
0 7 0 0 10
0 0 0 0 15
0 17 18 0 0
Here's a solution that doesn't require any transposing back and forth:
>> B = A; %# Initialize B
>> C = A; %# Initialize C
>> mask = logical(A); %# Create a logical mask using A
>> [r,c] = find(A); %# Find the row and column indices of non-zero values
>> index = c + (r - 1).*size(A,2); %# Compute the row-first linear index
>> [~,order] = sort(index); %# Compute the row-first order with
>> [~,order] = sort(order); %# two sorts
>> B(mask) = index %# Fill non-zero elements of B
B =
0 2 0 4 5
0 7 0 0 10
0 0 0 0 15
0 17 18 0 0
>> C(mask) = order %# Fill non-zero elements of C
C =
0 1 0 2 3
0 4 0 0 5
0 0 0 0 6
0 7 8 0 0
An outline (Matlab isn't on this machine, so verification is delayed):
You can use find() to get the coordinate list. Let T = A'; [r,c] = find(T)
From the coordinate list, you can create both B and C. Let valB = sub2ind([r,c],T) and valC = 1:length(r)
Use the sparse command to create B and C, e.g. B = sparse(r,c,valB), and then transpose, e.g. B = B' (or could do sparse(c,r,valB)).
Or, as #IanHincks suggests, let B = A'; B(find(B)) = find(B). (I'm not sure why .' is recommended, but, again, I don't have Matlab in front of to check.) For C, simply use C(find(C)) = 1:nnz(A). And transpose back, as he suggests.
Personally, I work with coordinate lists all the time, having migrated away from the sparse matrix representation, just to cut out the costs of index lookups.