matlab remove for loop in matrix computation - matlab

I'm working on a problem on Matlab according to Matrix. I think my code could be improved by remove the for loop. But I really don't know how to fix this one. Can anyone help me, please?
the code is:
K = 3;
X = [1 2; 3 4; 5 6; 7 8];
idx = [1;2;3;1];
for i = 1:K
ids = (idx == i);
centroids(i,:) = sum(bsxfun(#times, X, ids))./ sum(ids);
end
in this code, data points X is 4x2. There are K=3 centroids, so the centroids is a matrix of 3x2. This code is part of a K-mean function, which is using data points and their closest centroids to find new position of centroids.
I want to make the code as something without the FOR loop, maybe beginning like this:
ids = bsxfun(#eq, idx, 1:K);
centroids = ..............

You can avoid the bsxfun by using logical indexing, this seems to be a worthwhile performance increase, at least for small matrices X. It is best for small K, and for a small number of rows of X.
K = 3;
X = [1 2; 3 4; 5 6; 7 8];
idx = [1;2;3;1];
centroids=zeros(K,2);
for i = 1:K
ids = (idx == i);
centroids(i,:) = sum(X(ids,:),1)./sum(ids);
end
If X has a large number of rows, this method is fastest:
K = 3;
X = [1 2; 3 4; 5 6; 7 8];
idx = [1;2;3;1];
centroids=zeros(K,2);
t=bsxfun(#eq,idx,1:K);
centroids=bsxfun(#rdivide,t.'*X,sum(t).');
And if K is very large, Luis' accumarray method is fastest.

You could apply accumarray. Note that accumarray only works when X is a column. So, if X has two columns, you can call accumarray twice:
centroids(:,1) = accumarray(idx, X(:,1), [], #mean)
centroids(:,2) = accumarray(idx, X(:,2), [], #mean)
Alternatively, if X contains two columns of real numbers, you can use complex to "pack" the two columns into one complex column, and then unpack the results:
centroids = accumarray(idx, complex(X(:,1),X(:,2)), [], #mean);
centroids = [ real(centroids) imag(centroids)];
If X has an arbitrary number of columns, possibly with complex numbers, you can loop over columns:
centroids = NaN(K, size(X,2)); %// preallocate
for col = 1:size(X,2);
centroids(:,col) = accumarray(idx, X(:,col), [], #mean);
end

Related

extract 3D matrix's columns based on "surface" values - vectorization

I have an NxMxK matrix A
A = [1 2 1; 1 1 2];
A = cat(3, A, [3 3 3; 3 3 3])
A(:,:,1) =
1 2 1
1 1 2
A(:,:,2) =
3 3 3
3 3 3
and I want to create a YxK 2D matrix B where K is the number of elements of A(:,:,1)==2:
k=0;
for ii=1:size(A,1)
for jj=1:size(A,2)
if A(ii,jj)==2
k=k+1;
B(k,:) = A(ii,jj,:);
end
end
end
Is there a way of vectorizing this code?
My attempt was to find the indices of A(:,:,1)==2 and then try to select the whole column but I do not know how to do it:
inds = find(A(:,:,1)==2)
B = A(inds,:) %this line does not make sense
EDIT
Preallocating B helps:
inds=find(A(:,:,1)==2);
B=NaN(numel(inds),size(A,3));
k=0;
for ii=1:size(A,1)
for jj=1:size(A,2)
if A(ii,jj)==2
k=k+1;
B(k,:) = squeeze(A(ii,jj,:));
end
end
end
But still not vectorized.
You can reshape matrix A to a (N*M)xK 2D matrix.
A = [1 2 1; 1 1 2];
A = cat(3, A, [3 3 3; 3 3 3]);
A_ = reshape(A,numel(A(:,:,1)),size(A,3));
B = A_(A_(:,1)==2,:);
Your first attempt at vectorization is almost right. Just don't use find, but use the logical matrix for indexing.
inds = A(:,:,1)==2;
The inds matrix is 2D, not 3D, so we use repmat to repeat its values along the 3rd dimension:
K = size(A,3);
inds = repmat(inds,1,1,K); % or simply cat(3,inds,inds) if K==2
B = A(inds);
The result is a column vector of size Y*K, not a matrix of size YxK, we can use reshape to fix that:
B = reshape(B,[],K);
I guess this answer is similar to Anthony's, except the indexing and the reshaping are reversed. I didn't really notice the similarity until after I wrote it down. I guess also Anthony's is a little shorter. :/

Replace diagonal elements only in rows specified by a vector of row indices? (MATLAB)

I have an N x N matrix, A, and a vector of row indices, v. I want to replace the diagonal elements of A only for the rows in A specified by v without using a for loop.
For example:
N = 10;
A = rand(N,N); %Random N x N matrix
v = [1 4 6 9 10]; %vector of row indices
%What I want to do but without a for loop:
for i = 1:length(v)
A(v(i),v(i)) = 0;
end
%I thought this would work, but it does not:
%A(v,v) = 0;
I feel like there must a one-line method of doing this, but can't seem to figure out what it would be.
Cheers
Use sub2ind:
A(sub2ind(size(A),v,v)) = 0;

how to get an incremental power matrix in matlab

I wanted to compute the following matrix in Matlab:
g=[I
A
.
.
.
A^N]
I used the following program in Matlab:
A=[2 3;4 1];
s=A;
for n=1:1:50
s(n)=A.^n;
end
g=[eye(1,1),s];
I am getting the following error:
In an assignment A(I) = B, the number of elements in B and I must be the same.
Error in s_x_calcu_v1 (line 5)
s(n)=A.^n;
The problem is that you are trying to assign a matrix to a single element. In matlab calling s(n) mean you get the nth element of s, regardless of the dimensions of s. You can use a three dimensional matrix
N = 50;
A=[2 3;4 1];
[nx,ny] = size(A);
s(nx,ny,N) = 0; %makes s a nx x ny x N matrix
for n=1:1:N
s(:,:,n)=A.^n; %Colon to select all elements of that dimension
end
g=cat(3, eye(size(A)) ,s); %Add the I matrix of same size as A
Or a vectorized version
s = bsxfun(#power, A(:), 1:N);
s = reshape(s,2,2,N);
g = cat(3, eye(size(A)) ,s);
And a third solution using cumprod
s = repmat(A(:), [1 N]);
s = cumprod(s,2);
s = reshape(s,2,2,N);
g = cat(3, eye(size(A)) ,s);
Your s array is a 2-by-2 array, you cannot index it to store the result of your compuation at each step of your loop.
For this, the simpler is probably to define s as a cell:
% --- Definitions
A = [2 3;4 1];
N = 50;
% --- Preparation
s = cell(N,1);
% --- Computation
for n=1:N
s{n} = A.^n;
end
Best,
When you loop from 1 to N computing each time A.^n you are doing LOTS of redundant computations! Note that
A.^n = (A.^(n-1)).*A; %//element-wise power
A^n = (A^n) * A; %// matrix power
Therefore,
A = [2 3;4 1];
N = 50;
s = cell(N+1,1);
s{1} = eye(size(A,1));
for ii=1:N
s{ii+1} = s{ii}.*A; %// no powers, just product!
end
g = vertcat( s{:} );
BTW, the same holds if you want to compute matrix power (instead of element-wise powers), all you need is changing to s{ii+1} = s{ii}*A;

Matlab: store array in matrix?

I have many array (n*1 dimension), how can I do something like
matrix = [];
for i = 1:5
for j =1:5
matrix (i,j) = zeros(n,1); % store a given array to a cell of a matrix
end
end
I find Array of Matrices in MATLAB
But this is store matrices into array, not the otherwise.
Ying Xiong's suggestion is what you want if the vectors are of different lengths. But assuming the number of elements is constant (which they seem to be) you may also use a 3-dimensional array, where each (i,j) element contains a vector in the third dimension, like this:
rows = 5; cols = 5; n = 10; %// Dimensions
matrix = zeros(rows, cols, n); %// Initialize matrix
vector = 1:n; %// Just an example
for ii = 1:rows %// Bad practice to use i as a variable name
for jj = 1:cols %// Bad practice to use j as a variable name
matrix(ii,jj,:) = vector; %// Assignment
end
end
Now each index (i,j) contains the vectors you want, for instance:
squeeze(matrix(1,1,:))
ans =
1
2
3
4
5
6
7
8
9
10
Having all values in a single matrix can be a good thing if you want to do similar operations on all elements, as vectorized approaches are usually very fast in MATLAB. You might want to check out permute, reshape and functions like bsxfun.
Note that you might be able to vectorize the loops, but without knowing the specifics, that's impossible to know.
You need to use cell array.
n = 10;
matrix = cell(5,5);
for i = 1:5
for j = 1:5
matrix{i,j} = zeros(n,1);
end
end

random sampling from a 3-way data cube

imagine a 2 x 2 x 2 three-way data cube:
data = [1 2; 3 4];
data(:,:,2) = [5 6; 7 8]
I wish to generate a row-column slice from this cube (i.e. a 2x2 matrix) in which each element of the slice is obtained by randomly sampling its 3-mode fiber (i.e. an nth mode fiber is a vector running along the nth mode/dimension/way. There are 4 3-mode fibers in this cube, one of them is f1 = [1 5], another one is f2 = [2 6] and so on). For example, one slice could turn out to be:
slice = [5 2; 3 4]
a different sampling might lead to the slice:
slice = [1 2; 7 8]
Is there a quick way to do this?
I tried using slice = datasample(data,1,3) but this function randomly picks a row-column slice from the cube (i.e. either, slice = [1 2; 3 4] or [5 6; 7 8]).
If I understand correctly, then it's quite simple actually. You have four 3-mode fibers and you wish to construct a 2x2 matrix where each element was sampled from the corresponding fiber.
So, you need to sample 4 times (one for each fiber) one element out of 2 (each fiber has two elements):
>> [h w fiberSize] = size(data); % make the solution more general
>> fIdx = randsample( fiberSize, h*w ); % sample with replacements
After sampling we construct the slice, for simplicity I'll "flatten" the 3D data into a 2D matrix
>> fData = reshape( data, [], fiberSize );
>> slice = fData( sub2ind( [h*w fiberSize], 1:(h*w), fIdx ) );
>> slice = reshape( slice, [h w] ); % shape the slice
The following solution is valid for any cube size. No toolbox required.
N = size(data,1); %//length of side of cube
r = randi(N,1,N^2)-1; %//this is the random sampling
data_permuted = permute(data,[3 1 2]); %//permute so that sampling is along first dim
slice = data_permuted((1:N:N^3)+r); %//sample using linear indexing
slice = reshape(slice.',N,N); %//reshape into a matrix
Try this solution for any nmode (e.g. nmode=3 is the 3-mode):
data = cat(3,[1 2; 3 4],[5 6; 7 8]);
nmode = 3;
Dn = size(data,nmode);
modeSampleInds = randi(Dn,1,numel(data)/Dn); % here is the random sample
dp = reshape(permute(data,[nmode setdiff(1:ndims(data),nmode)]), Dn, []);
outSize = size(data); outSize(nmode)=1;
slice = reshape(dp(sub2ind(size(dp),modeSampleInds,1:size(dp,2))),outSize)
Note that this does not require the Statistics Toolbox. It is also completely generalized for any matrix size, number of dimension, and "N-mode fiber".
I'd be glad to explain each line if needed.