4
votes

Supose there is a Matrix

A =

 1     3     2    4
 4     2     5    8
 6     1     4    9

and I have a Vector containing the "class" of each column of this matrix for example

v = [1 , 1 , 2 , 3]

How can I sum the columns of the matrix to a new matrix as column vectors each to the column of their class? In this example columns 1 and 2 of A would added to the first column of the new matrix, column 2 to the 3 to the 2nd, column 4 the the 3rd.

Like

SUM =

4    2    4
6    5    8
7    4    9

Is this possible without loops?

4
Don't fear the MATLAB loop! It is quite fast nowadays and often easy to read!knedlsepp
@knedlsepp ... but removes most of the fun :-PLuis Mendo
@LuisMendo: Well, I'm as much into a good vectorized solution as the next guy, but if the for-loop beats all the other approaches, why avoid it? ;-)knedlsepp
so @user3563898 does any of the answer below helped you? Please mark one of them as accepted. Thanks!Benoit_11

4 Answers

4
votes

One of the perfect scenarios to combine the powers of accumarray and bsxfun -

%// Since we are to accumulate columns, first step would be to transpose A
At = A.'  %//'

%// Create a vector of linear IDs for use with ACCUMARRAY later on
idx = bsxfun(@plus,v(:),[0:size(A,1)-1]*max(v))

%// Use ACCUMARRAY to accumulate rows from At, i.e. columns from A based on the IDs
out = reshape(accumarray(idx(:),At(:)),[],size(A,1)).'

Sample run -

A =
     1     3     2     4     6     0
     4     2     5     8     9     2
     6     1     4     9     8     9
v =
     1     1     2     3     3     2
out =
     4     2    10
     6     7    17
     7    13    17
3
votes

An alternative with accumarray in 2D. Generate a grid with the vector v and then apply accumarray:

A = A.';

v = [1 1 2 3]; 

[X, Y] = ndgrid(v,1:size(A,2));

Here X and Y look like this:

X =

     1     1     1
     1     1     1
     2     2     2
     3     3     3


Y =

     1     2     3
     1     2     3
     1     2     3
     1     2     3

Then apply accumarray:

B=accumarray([X(:) Y(:)],A(:)),

SUM = B.'

SUM =

     4     2     4
     6     5     8
     7     4     9

As you see, using [X(:) Y(:)] create the following array:

ans =

     1     1
     1     1
     2     1
     3     1
     1     2
     1     2
     2     2
     3     2
     1     3
     1     3
     2     3
     3     3

in which the vector v containing the "class" is replicated 3 times since there are 3 unique classes that are to be summed up together.

EDIT:

As pointed out by knedlsepp you can get rid of the transpose to A and B like so:

[X2, Y2] = ndgrid(1:size(A,1),v);

B = accumarray([X2(:) Y2(:)],A(:))

which ends up doing the same. I find it a bit more easier to visualize with the transposes but that gives the same result.

2
votes

How about a one-liner?

result = full(sparse(repmat(v,size(A,1),1), repmat((1:size(A,1)).',1,size(A,2)), A));
1
votes

Don't optimize prematurely!

The for loop performs fine for your problem:

out = zeros(size(A,1), max(v));
for i = 1:numel(v)
    out(:,v(i)) = out(:,v(i)) + A(:,i);
end

BTW: With fine, I mean: fast, fast, fast!