Matlab: initialize empty array of matrices - arrays

I need to create an empty array of matrices, and after fill it with matrices of the same size.
I have made a little script to explain:
result = [];
for i = 0: 4;
M = i * ones(5,5); % create matrice
result = [result,M]; % this would have to append M to results
end
Here result is a matrix of size 5*25 and I need an array of matrices 5*5*4.
I have been researched but I only found this line: result = [result(1),M];

The issue is that [] implicitly concatenates values horizontally (the second dimension). In your case, you want to concatenate them along the third dimension so you could use cat.
result = cat(3, result, M);
But a better way to do it would be to actually pre-allocate your result array using zeros
result = zeros(5, 5, 4);
And then within your loop fill each "slice" of the 3D array with the values.
for k = 0:4
M = k * ones(5,5);
result(:,:,k+1) = M;
end

Related

How do I delete all-zero pages from a 3D matrix in a loop?

How can I delete all-zero pages from a 3D matrix in a loop?
I have come up with the following code, though it is not 'entirely' correct, if at all. I am using MATLAB 2019b.
%pseudo data
x = zeros(3,2,2);
y = ones(3,2,2);
positions = 2:4;
y(positions) = 0;
xy = cat(3,x,y); %this is a 3x2x4 array; (:,:,1) and (:,:,2) are all zeros,
% (:,:,3) is ones and zeros, and (:,:,4) is all ones
%my aim is to delete the arrays that are entirely zeros i.e. xy(:,:,1) and xy(:,:,2),
%and this is what I have come up with; it doesn't delete the arrays but instead,
%all the ones.
for ii = 1:size(xy,3)
for idx = find(xy(:,:,ii) == 0)
xy(:,:,ii) = strcmp(xy, []);
end
end
Use any to find indices of the slices with at least one non-zero value. Use these indices to extract the required result.
idx = any(any(xy)); % idx = any(xy,[1 2]); for >=R2018b
xy = xy(:,:,idx);
I am unsure what you'd expect your code to do, especially given you're comparing strings in all-numerical arrays. Here's a piece of code which does what you desire:
x = zeros(3,2,2);
y = ones(3,2,2);
positions = 2:4;
y(positions) = 0;
xy = cat(3,x,y);
idx = ones(size(xy,3),1,'logical'); % initialise catching array
for ii = 1:size(xy,3)
if sum(nnz(xy(:,:,ii)),'all')==0 % If the third dimension is all zeros
idx(ii)= false; % exclude it
end
end
xy = xy(:,:,idx); % reindex to get rid of all-zero pages
The trick here is that sum(xy(:,:,ii),'all')==0 is zero iff all elements on the given page (third dimension) are zero. In that case, exclude it from idx. Then, in the last row, simply re-index using logical indexing to retain only pages whit at least one non-zero element.
You can do it even faster, without a loop, using sum(a,[1 2]), i.e. the vectorial-dimension sum:
idx = sum(nnz(xy),[1 2])~=0;
xy = xy(:,:,idx);

Sub-setting symmetric matrices along the diagonal

I have an 8x8 matrix, e.g. A=rand(8,8). What I need to do is subset all 2x2 matrices along the diagonal. That means that I need to save matrices A(1:2,1:2), A(3:4,3:4), A(5:6,5:6), A(7:8,7:8). To better explain myself, the current version that I am using is the following:
AA = rand(8,8);
BB = zeros(8,2);
for i = 1:4
BB(2*i-1:2*i,:) = AA(2*i-1:2*i,2*i-1:2*i);
end
This works fine for small AA matrices and small AA submatrices, however as the size grows significantly (it can get up to even 50,000x50,000) using a for loop like the one above in not viable. Is there a way to achieve the above without the loop? I've thought of other approaches that could perhaps utilize upper and lower triangular matrices, however even these seem to need a loop at some point. Any help is appreciated!!
Here's a way:
AA = rand(8,8); % example matrix. Assumed square
n = 2; % submatrix size. Assumed to divide the size of A
mask = repelem(logical(eye(size(AA,1)/n)), n, n);
BB = reshape(permute(reshape(AA(mask), n, n, []), [1 3 2]), [], n);
This generates a logical mask that selects the required elements, and then rearranges them as desired using reshape and permute.
Here is an alternative that doesn't generate a full matrix to select the block diagonals, as in Luis Mendo' answer but instead directly generates the indices to these elements. It is likely that this will be faster for very large matrices, as creating the indexing matrix will be expensive in that case.
AA = rand(8,8); % example matrix. Assumed square
n = 2; % submatrix size. Assumed to divide the size of A
m=size(AA,1);
bi = (1:n)+(0:m:n*m-1).'; % indices for elements of one block
bi = bi(:); % turn into column vector
di = 1:n*(m+1):m*m; % indices for first element of each block
BB = AA(di+bi-1); % extract the relevant elements
BB = reshape(BB,n,[]).' % put these elements in the desired order
Benchmark
AA = rand(5000); % couldn't do 50000x50000 because that's too large!
n = 2;
BB1 = method1(AA,n);
BB2 = method2(AA,n);
BB3 = method3(AA,n);
assert(isequal(BB1,BB2))
assert(isequal(BB1,BB3))
timeit(#()method1(AA,n))
timeit(#()method2(AA,n))
timeit(#()method3(AA,n))
% OP's loop
function BB = method1(AA,n)
m = size(AA,1);
BB = zeros(m,n);
for i = 1:m/n
BB(n*(i-1)+1:n*i,:) = AA(n*(i-1)+1:n*i,n*(i-1)+1:n*i);
end
end
% Luis' mask matrix
function BB = method2(AA,n)
mask = repelem(logical(eye(size(AA,1)/n)), n, n);
BB = reshape(permute(reshape(AA(mask), n, n, []), [1 3 2]), [], n);
end
% Cris' indices
function BB = method3(AA,n)
m = size(AA,1);
bi = (1:n)+(0:m:n*m-1).';
bi = bi(:);
di = 0:n*(m+1):m*m-1;
BB = reshape(AA(di+bi),n,[]).';
end
On my computer, with MATLAB R2017a I get:
method1 (OP's loop): 0.0034 s
method2 (Luis' mask matrix): 0.0599 s
method3 (Cris' indices): 1.5617e-04 s
Note how, for a 5000x5000 array, the method in this answer is ~20x faster than a loop, whereas the loop is ~20 faster than Luis' solution.
For smaller matrices things are slightly different, Luis' method is almost twice as fast as the loop code for a 50x50 matrix (though this method still beats it by ~3x).

Grow 3D array in Matlab

Is there a way to grow a 3D array in the third dimension using the end index in a loop in Matlab?
In 2D it can be done like
a = [];
for x = y
a(end + 1, :) = f(x);
end
But in 3D the same thing will not work as a(1,1,end) will try to index a(1,1,1) the first iteration (not a(1,1,0) as one might expect). So I can't do
im = [];
for x = y
im(:, :, end + 1) = g(x);
end
It seems the end of a in third dimension is handled a bit differently than in the first two:
>> a = [];
>> a(end,end,end) = 1
Attempted to access a(0,0,1); index must be a positive integer or logical.
Am I missing something about how end indexing works here?
What you're asking...
If you know the size of g(x), initialize im to an empty 3d-array:
im = zeros(n, m, 0); %instead of im = [];
I think your code should work now.
A better way...
Another note, resizing arrays each iteration is expensive! This doesn't really matter if the array is small, but for huge matrices, there can be a big performance hit.
I'd initialize to:
im = zeros(n, m, length(y));
And then index appropriately. For example:
i = 1;
for x = y
im(:, :, i) = g(x);
i = i + 1;
end
This way you're not assigning new memory and copying over the whole matrix im each time it gets resized!

Matlab: creating 3D arrays as a function of 2D arrays

I want to create 3d arrays that are functions of 2d arrays and apply matrix operations on each of the 2D arrays. Right now I am using for loop to create a series of 2d arrays, as in the code below:
for i=1:50
F = [1 0 0; 0 i/10 0; 0 0 1];
B=F*F';
end
Is there a way to do this without the for loop? I tried things such as:
F(2,2) = 0:0.1:5;
and:
f=1:0.1:5;
F=[1 0 0; 0 f 0; 0 0 1];
to create them without the loop, but both give errors of dimension inconsistency.
I also want to perform matrix operations on F in my code, such as
B=F*F';
and want to plot certain components of F as a function of something else. Is it possible to completely eliminate the for loop in such a case?
If I understand what you want correctly, you want 50 2D matrices stacked into a 3D matrix where the middle entry varies from 1/10 to 50/10 = 5 in steps of 1/10. You almost have it correct. What you would need to do is first create a 3D matrix stack, then assign a 3D vector to the middle entry.
Something like this would do:
N = 50;
F = repmat(eye(3,3), [1 1 N]);
F(2,2,:) = (1:N)/10; %// This is 1/10 to 5 in steps of 1/10... or 0.1:0.1:5
First pre-allocate a matrix F that is the identity matrix for all slices, then replace the middle row and middle column of each slice with i/10 for i = 1, 2, ..., 50.
Therefore, to get the ith slice, simply do:
out = F(:,:,i);
Minor Note
I noticed that what you want to do in the end is a matrix multiplication of the 3D matrices. That operation is not defined in MATLAB nor anywhere in a linear algebra context. If you want to multiply each 2D slice independently, you'd be better off using a for loop. Doing this vectorized with native operations isn't supported in this context.
To do it in a loop, you'd do something like this for each slice:
B = zeros(size(F));
for ii = 1 : size(B,3)
B(:,:,ii) = F(:,:,ii)*F(:,:,ii).';
end
... however, examining the properties of your matrix, the only thing that varies is the middle entry. If you perform a matrix multiplication, all of the entries per slice are going to be the same... except for the middle, where the entry is simply itself squared. It doesn't matter if you multiple one slice by the transpose of the other. The transpose of the identity is still the identity.
If your matrices are going to be like this, you can just perform an element-wise multiplication with itself:
B = F.*F;
This will not work if F is anything else but what you have above.
Creating the matrix would be easy:
N = 50;
S = cell(1,N);
S(:) = {eye(3,3)};
F = cat(3, S{:});
F(2,2,:) = (1:N)/10;
Another (faster) way would be:
N = 50;
F = zeros(3,3,N);
F(1,1,:) = 1;
F(2,2,:) = (1:N)/10;
F(3,3,:) = 1;
You then can get the 3rd matrix (for example) by:
F(:,:,3)

MATLAB: Run Data Matrix through a loop and add results to data matrix

What I ultimately need is to input a 2-column matrix, run it through a bunch of conditions and have an output of the 2 original columns plus an additional three.
My initial data matrix I split into several arrays according to time (the second column) and continue with applying my conditions on each array individually:
A = arrayfun(#(x) M(M(:, 2) == x, :), unique(M(:,2)), 'uniformoutput', false);
n = numel(A);
k = 0;
for i = 1:n % for each # of arrays
matrix = A{i}; % array i
dat = size(matrix);
length = dat(1,1); % length of i array
adductName = zeros(length, 1); % preallocate columns
actualMass = zeros(length, 1);
adductMass = zeros(length, 1);
%... continued with conditions here's an example of one
for r = 1:length % for the length of array i
mass = matrix(1,r);
M = mass-1;
k=k+1;
if any(M == matrix(:, 1)) % if any M matches rest of column 1 in array
adductName(k) = 'M';
actualMass(k) = M;
adductMass(k) = mass;
else
adductName(k) = 'None';
actualMass(k) = 0;
adductMass(k) = 0;
adductName, actualMass and adductMass are the three additional columns I need added in my output
My question is, how do I recombine all of my arrays, A{i}'s along with my additional three columns into one data matrix to be outputted?
You can either use the [ ] operator or explicit calls to horzcat or vertcat to concatenate matrices in different ways. See the Creating and Concatenating Matrices documentation part for further reference.

Resources