Suppose A is a 3-D matrix as below (2 rows-2 columns-2 pages).
A(:,:,1)=[1,2;3,4];
A(:,:,2)=[5,6;7,8];
I want to have a vector, say "a", whose inputs are the average of diagonal elements of matrices on each page. So in this simple case, a=[(1+4)/2;(5+8)/2].
But I have difficulties in matlab to do so. I tried the codes below but failed.
mean(A(1,1,:),A(2,2,:))
You can use "partially linear indexing" in the two dimensions that define the diagonal, as follows:
Since partially linear indexing can only be applied on trailing dimensions, you first need to apply permute to rearrange dimensions, so that the first and second dimensions become second and third.
Now you leave the first dimension untouched, linearly-index the diagonals in the second and third dimensions (which effectly reduces those two dimensions to one), and apply mean along the (combined) second dimension.
Code:
B = permute(A, [3 1 2]); %// step 1: permute
result = mean(B(:,1:size(A,1)+1:size(A,1)*size(A,2)), 2); %// step 2: index and mean
In your example,
A(:,:,1)=[1,2;3,4];
A(:,:,2)=[5,6;7,8];
this gives
result =
2.5000
6.5000
You can use bsxfun for a generic solution -
[m,n,r] = size(A)
mean(A(bsxfun(#plus,[1:n+1:n^2]',[0:r-1]*m*n)),1)
Sample run -
>> A
A(:,:,1) =
8 4 1
7 6 3
1 5 8
A(:,:,2) =
1 7 6
8 5 2
1 2 7
A(:,:,3) =
6 2 8
1 1 6
1 4 5
A(:,:,4) =
8 1 6
1 5 1
9 2 7
>> [m,n,r] = size(A);
>> sum(A(bsxfun(#plus,[1:n+1:n^2]',[0:r-1]*m*n)),1)
ans =
22 13 12 20
>> mean(A(bsxfun(#plus,[1:n+1:n^2]',[0:r-1]*m*n)),1)
ans =
7.3333 4.3333 4 6.6667
Related
For example, I have a matrix A (Figure 1). When the variable n = 2, I want it to be transformed to the matrix B. The red rectangle shows the transformation rule of every column. According to this rule, when the n = 3, it can become the matrix C.
I have written a script using a for loop method, but it is a waste of time when the matrix A is very large (e.g. 11688* 140000). Is there an efficient way to solve this problem?
Figure 1:
Here is a way using reshape and implicit expansion:
result = reshape(A((1:size(A,1)-n+1) + (0:n-1).', :), n, []);
For example assume that n = 3. Implicit expansion is used to extract indices of rows:
row_ind = (1:size(A,1)-n+1) + (0:n-1).';
The following matrix is created:
1 2
2 3
3 4
Extract the desired rows of A:
A_expanded = A(row_ind, :)
When the matrix row_ind is used as an index it behaves like a vector:
1
2
1 2 3
2 3 -> 2
3 4 3
4
A_expanded =
3 5 7
6 8 9
2 6 3
6 8 9
2 6 3
1 2 1
Now A_expanded can be reshaped to the desired size:
result = reshape(A_expanded, n, []);
>>result =
3 6 5 8 7 9
6 2 8 6 9 3
2 1 6 2 3 1
If you have the Image Processing Toolbox you can use im2col as follows:
result = im2col(A, [n 1], 'sliding');
I have a vector like this:
h = [1,2,3,4,5,6,7,8,9,10,11,12]
And I want to repeat every third element like so:
h_rep = [1,2,3,3,4,5,6,6,7,8,9,9,10,11,12,12]
How do I accomplish this elegantly in MATLAB? The actual arrays are huge, so ideally I don't want to write a for loop. Is there a vectorized way to do this?
One way to do this would be to use the recent repelem function that was released in version R2015b where you can repeat each element in a vector a certain amount of times. In this case, specify a vector where every third element is a 2 with the rest of the values being a 1 as the number of times to repeat the corresponding element, then use the function:
N = numel(h);
rep = ones(1, N);
rep(3:3:end) = 2;
h_rep = repelem(h, rep);
Using your example: h = 1 : 12, we thus get:
>> h_rep
h_rep =
1 2 3 3 4 5 6 6 7 8 9 9 10 11 12 12
If repelem is not available to you, then a clever use of cumsum may help. Basically, note that for every three elements, the next one is a copy of the previous element. If we had an indicator vector of [1 1 1 0] where 1 is the position that we want to copy and 0 tells us to copy the last value, using cumulative sum or cumsum on repeated versions of this vector - exactly 1 + (numel(h) / 4) will give us exactly where we would need to index into h. Therefore, create a vector of ones that is the length of h added with 1 + (numel(h) / 4 to ensure that we make space for the duplicate elements, then make sure every fourth element is set to 0 before applying the cumsum:
N = numel(h);
rep = ones(1, N + 1 + (N / 4));
rep(4:4:end) = 0;
rep = cumsum(rep);
h_rep = h(rep);
Thus:
>> h_rep
h_rep =
1 2 3 3 4 5 6 6 7 8 9 9 10 11 12 12
One last suggestion (thanks to user #bremen_matt) would be to reshape your vector into a matrix so that it has 3 rows, duplicate the last row, then reshape the resulting duplicated matrix back to a single vector:
h_rep = reshape(h, 3, []);
h_rep = reshape([h_rep; h_rep(end,:)], 1, []);
We again get:
>> h_rep
h_rep =
1 2 3 3 4 5 6 6 7 8 9 9 10 11 12 12
Of course the obvious caveat with the above code is that the length of vector h is evenly divisible by 4.
(Modified according to rayryeng's correct observations)...
Another solution is to play around with the reshape function. If you reshape the matrix to a 3xn matrix first...
B = reshape(h,3,[])
And then copy the last row
B = [B;B(end,:)]
And finally vectorize the solution...
B(:).'
You can use just indexing:
h = [1,2,3,4,5,6,7,8,9,10,11,12]; % initial data
n = 3; % step for repetition
h_rep = h(ceil(n/(n+1):n/(n+1):end));
An index-based approach (using sort):
h_rep = h(sort([1:numel(h) 3:3:numel(h)]));
Or a slightly shorter syntax...
h_rep = h(sort([1:end 3:3:end]));
I think this will do it:
h = [1,2,3,4,5,6,7,8,9,10,11,12];
h0=kron(h,[1 1])
h_rep=h0(mod(1:length(h0),2)==0 | mod(1:length(h0),3)==2)
Answer:
1 2 3 3 4 5 6 6 7 8 9 9 10 11 12 12
Explanation:
After duplicating every element, you select only those that you wants. You can extend this idea to duplicate second and third. etc..
How can I remove elements in a matrix, that aren't all in a straight line, without going through a row at a time in a for loop?
Example:
[1 7 3 4;
1 4 4 6;
2 7 8 9]
Given a vector (e.g. [2,4,3]) How could I remove the elements in each row (where each number in the vector corresponds to the column number) without going through each row at a time and removing each element?
The example output would be:
[1 3 4;
1 4 4;
2 7 9]
It can be done using linear indexing at follows. Note that it's better to work down columns (because of Matlab's column-major order), which implies transposing at the beginning and at the end:
A = [ 1 7 3 4
1 4 4 6
2 7 8 9 ];
v = [2 4 3]; %// the number of elements of v must equal the number of rows of A
B = A.'; %'// transpose to work down columns
[m, n] = size(B);
ind = v + (0:n-1)*m; %// linear index of elements to be removed
B(ind) = []; %// remove those elements. Returns a vector
B = reshape(B, m-1, []).'; %'// reshape that vector into a matrix, and transpose back
Here's one approach using bsxfun and permute to solve for a 3D array case, assuming you want to remove indexed elements per row across all 3D slices -
%// Inputs
A = randi(9,3,4,3)
idx = [2 4 3]
%// Get size of input array, A
[M,N,P] = size(A)
%// Permute A to bring the columns as the first dimension
Ap = permute(A,[2 1 3])
%// Per 3D slice offset linear indices
offset = bsxfun(#plus,[0:M-1]'*N,[0:P-1]*M*N) %//'
%// Get 3D array linear indices and remove those from permuted array
Ap(bsxfun(#plus,idx(:),offset)) = []
%// Permute back to get the desired output
out = permute(reshape(Ap,3,3,3),[2 1 3])
Sample run -
>> A
A(:,:,1) =
4 4 1 4
2 9 7 5
5 9 3 9
A(:,:,2) =
4 7 7 2
9 6 6 9
3 5 2 2
A(:,:,3) =
1 7 5 8
6 2 9 6
8 4 2 4
>> out
out(:,:,1) =
4 1 4
2 9 7
5 9 9
out(:,:,2) =
4 7 2
9 6 6
3 5 2
out(:,:,3) =
1 5 8
6 2 9
8 4 4
This question pops up quite often in one form or another (see for example here or here). So I thought I'd present it in a general form, and provide an answer which might serve for future reference.
Given an arbitrary number n of vectors of possibly different sizes, generate an n-column matrix whose rows describe all combinations of elements taken from those vectors (Cartesian product) .
For example,
vectors = { [1 2], [3 6 9], [10 20] }
should give
combs = [ 1 3 10
1 3 20
1 6 10
1 6 20
1 9 10
1 9 20
2 3 10
2 3 20
2 6 10
2 6 20
2 9 10
2 9 20 ]
The ndgrid function almost gives the answer, but has one caveat: n output variables must be explicitly defined to call it. Since n is arbitrary, the best way is to use a comma-separated list (generated from a cell array with ncells) to serve as output. The resulting n matrices are then concatenated into the desired n-column matrix:
vectors = { [1 2], [3 6 9], [10 20] }; %// input data: cell array of vectors
n = numel(vectors); %// number of vectors
combs = cell(1,n); %// pre-define to generate comma-separated list
[combs{end:-1:1}] = ndgrid(vectors{end:-1:1}); %// the reverse order in these two
%// comma-separated lists is needed to produce the rows of the result matrix in
%// lexicographical order
combs = cat(n+1, combs{:}); %// concat the n n-dim arrays along dimension n+1
combs = reshape(combs,[],n); %// reshape to obtain desired matrix
A little bit simpler ... if you have the Neural Network toolbox you can simply use combvec:
vectors = {[1 2], [3 6 9], [10 20]};
combs = combvec(vectors{:}).' % Use cells as arguments
which returns a matrix in a slightly different order:
combs =
1 3 10
2 3 10
1 6 10
2 6 10
1 9 10
2 9 10
1 3 20
2 3 20
1 6 20
2 6 20
1 9 20
2 9 20
If you want the matrix that is in the question, you can use sortrows:
combs = sortrows(combvec(vectors{:}).')
% Or equivalently as per #LuisMendo in the comments:
% combs = fliplr(combvec(vectors{end:-1:1}).')
which gives
combs =
1 3 10
1 3 20
1 6 10
1 6 20
1 9 10
1 9 20
2 3 10
2 3 20
2 6 10
2 6 20
2 9 10
2 9 20
If you look at the internals of combvec (type edit combvec in the command window), you'll see that it uses different code than #LuisMendo's answer. I can't say which is more efficient overall.
If you happen to have a matrix whose rows are akin to the earlier cell array you can use:
vectors = [1 2;3 6;10 20];
vectors = num2cell(vectors,2);
combs = sortrows(combvec(vectors{:}).')
I've done some benchmarking on the two proposed solutions. The benchmarking code is based on the timeit function, and is included at the end of this post.
I consider two cases: three vectors of size n, and three vectors of sizes n/10, n and n*10 respectively (both cases give the same number of combinations). n is varied up to a maximum of 240 (I choose this value to avoid the use of virtual memory in my laptop computer).
The results are given in the following figure. The ndgrid-based solution is seen to consistently take less time than combvec. It's also interesting to note that the time taken by combvec varies a little less regularly in the different-size case.
Benchmarking code
Function for ndgrid-based solution:
function combs = f1(vectors)
n = numel(vectors); %// number of vectors
combs = cell(1,n); %// pre-define to generate comma-separated list
[combs{end:-1:1}] = ndgrid(vectors{end:-1:1}); %// the reverse order in these two
%// comma-separated lists is needed to produce the rows of the result matrix in
%// lexicographical order
combs = cat(n+1, combs{:}); %// concat the n n-dim arrays along dimension n+1
combs = reshape(combs,[],n);
Function for combvec solution:
function combs = f2(vectors)
combs = combvec(vectors{:}).';
Script to measure time by calling timeit on these functions:
nn = 20:20:240;
t1 = [];
t2 = [];
for n = nn;
%//vectors = {1:n, 1:n, 1:n};
vectors = {1:n/10, 1:n, 1:n*10};
t = timeit(#() f1(vectors));
t1 = [t1; t];
t = timeit(#() f2(vectors));
t2 = [t2; t];
end
Here's a do-it-yourself method that made me giggle with delight, using nchoosek, although it's not better than #Luis Mendo's accepted solution.
For the example given, after 1,000 runs this solution took my machine on average 0.00065935 s, versus the accepted solution 0.00012877 s. For larger vectors, following #Luis Mendo's benchmarking post, this solution is consistently slower than the accepted answer. Nevertheless, I decided to post it in hopes that maybe you'll find something useful about it:
Code:
tic;
v = {[1 2], [3 6 9], [10 20]};
L = [0 cumsum(cellfun(#length,v))];
V = cell2mat(v);
J = nchoosek(1:L(end),length(v));
J(any(J>repmat(L(2:end),[size(J,1) 1]),2) | ...
any(J<=repmat(L(1:end-1),[size(J,1) 1]),2),:) = [];
V(J)
toc
gives
ans =
1 3 10
1 3 20
1 6 10
1 6 20
1 9 10
1 9 20
2 3 10
2 3 20
2 6 10
2 6 20
2 9 10
2 9 20
Elapsed time is 0.018434 seconds.
Explanation:
L gets the lengths of each vector using cellfun. Although cellfun is basically a loop, it's efficient here considering your number of vectors will have to be relatively low for this problem to even be practical.
V concatenates all the vectors for easy access later (this assumes you entered all your vectors as rows. v' would work for column vectors.)
nchoosek gets all the ways to pick n=length(v) elements from the total number of elements L(end). There will be more combinations here than what we need.
J =
1 2 3
1 2 4
1 2 5
1 2 6
1 2 7
1 3 4
1 3 5
1 3 6
1 3 7
1 4 5
1 4 6
1 4 7
1 5 6
1 5 7
1 6 7
2 3 4
2 3 5
2 3 6
2 3 7
2 4 5
2 4 6
2 4 7
2 5 6
2 5 7
2 6 7
3 4 5
3 4 6
3 4 7
3 5 6
3 5 7
3 6 7
4 5 6
4 5 7
4 6 7
5 6 7
Since there are only two elements in v(1), we need to throw out any rows where J(:,1)>2. Similarly, where J(:,2)<3, J(:,2)>5, etc... Using L and repmat we can determine whether each element of J is in its appropriate range, and then use any to discard rows that have any bad element.
Finally, these aren't the actual values from v, just indices. V(J) will return the desired matrix.
This question already has answers here:
Element-wise array replication in Matlab
(7 answers)
A similar function to R's rep in Matlab [duplicate]
(4 answers)
Closed 8 years ago.
Let's say, I have:
A=[1 2; 3 4];
I want to use repmat that return:
B = [1 1 2 2; 1 1 2 2; 3 3 4 4; 3 3 4 4]
Kindly need your help. Thank you
I do not know a method using repmat but here is a method using kron
kron([1 2 ; 3 4],[1 1;1 1])
ans =
1 1 2 2
1 1 2 2
3 3 4 4
3 3 4 4
An alternative which uses repmat is
A=[1 2; 3 4];
cell2mat(arrayfun(#(x)repmat(x,2,2),A,'UniformOutput',false))
ans =
1 1 2 2
1 1 2 2
3 3 4 4
3 3 4 4
arrayfun is used to evaluate each element in A using the anonymous function #(x)repmat(x,2,2) which replicates that single element into a 2x2 matrix.
The result of arrayfun is a 2x2 cell array where each element is a 2x2 matrix. We then convert this cell array into a matrix via cell2mat.
Let the data be defined as
A = [1 2; 3 4];
R = 2; %// number of repetitions of each row
C = 2; %// number of repetitions of each column. May be different from R
Two possible approaches are as follows:
The simplest method is to use indexing:
B = A(ceil(1/R:1/R:size(A,1)), ceil(1/C:1/C:size(A,2)));
If you really want to do it with repmat, you need to play with dimensions using permute and reshape: move original dimensions 1, 2 to dimensions 2, 4 (permute); do the repetition along new dimensions 1, 3 (repmat); collapse dimensions 1, 2 into one dimension and 3, 4 into another dimension (reshape):
[r c] = size(A);
B = reshape(repmat(permute(A, [3 1 4 2]), [R 1 C 1]), [r*R c*C]);
Example result for R=2, C=3 (obtained with any of the two approaches):
B =
1 1 1 2 2 2
1 1 1 2 2 2
3 3 3 4 4 4
3 3 3 4 4 4