how to use accumarray over a matrix avoiding a loop?
    19 views (last 30 days)
  
       Show older comments
    
    Fernando
 on 12 Feb 2013
  
    
    
    
    
    Edited: Jasper van Casteren
      
 on 8 Feb 2022
            Hi,
I want to sum elements of a matrix according to an index. If instead of the matrix I had a vector, I could use accumarray. However, accumarray does not accept matrices as second argument. Any ideas on how to do this avoiding a loop to sum for each vector separately?
For example:
A=[1;1;2;3;3;3];
B=rand(6,10);
C=accumarray(A,B);
gives the output ??? Error using ==> accumarray Second input VAL must be a vector with one element for each row in SUBS, or a scalar.
I could do this by columns of B
C=zeros(size(unique(A),1),10);
for i=1:10,
C(:,i)=accumarray(A,B(:,i));
end
But if the number of columns of B is large, this will be quite slow and I would like this step to be done quickly as it has to be done as part of a minimization routine, so it is done every time the objective function is evaluated.
Thanks,
0 Comments
Accepted Answer
  Sean de Wolski
      
      
 on 12 Feb 2013
        
      Edited: Sean de Wolski
      
      
 on 12 Feb 2013
  
      You could always use accumarray with two dimensions of subs rather than vals:
A=[1;1;2;3;3;3];
B=rand(6,10);
[xx, yy] = ndgrid(A,1:size(B,2));
C=accumarray([xx(:) yy(:)],B(:));
Frankly, I'll bet the for-loop will be fastest.
More And my suspicions are right depending on how big A and B actually are. I am seeing the for-loop be faster with small arrays and accumarray with two-dimensional subs being faster for larger ones.
arrayfun/cell2mat etc are really slow since they require the conversion to cells which are slow and arrayfun is just slower than a for-loop anyway.
function timeAccumarray
A=[1;1;2;3;3;3];
B=rand(6,10);
[t1,t2,t3] = deal(0);
for ii = 1:100
    tic
    [xx, yy] = ndgrid(A,1:size(B,2));
    C=accumarray([xx(:) yy(:)],B(:));
    t1 = t1+toc;
    tic    
    for jj=size(B,2):-1:1   %dynamically preallocate, avoiding slow unique()
        C2(:,jj)=accumarray(A,B(:,jj));
    end
    t2=t2+toc;
    tic
    C3=cell2mat(arrayfun(@(x) accumarray(A,B(:,x)),1:size(B,2),'un',0));
    t3 = t3+toc;
end
isequal(C,C2,C3)
[t1 t2 t3]
ans = 0.0072 0.0289 0.1381
1 Comment
  Baris Gecer
 on 24 Feb 2017
				
      Edited: Baris Gecer
 on 24 Feb 2017
  
			This helped me a lot.Thanks! The first code was quite fast in my case where the matrix is 4096*128.
More Answers (3)
  Azzi Abdelmalek
      
      
 on 12 Feb 2013
        C=cell2mat(arrayfun(@(x) accumarray(A,B(:,x)),1:size(B,2),'un',0));
0 Comments
  Jasper van Casteren
      
 on 8 Feb 2022
        
      Edited: Jasper van Casteren
      
 on 8 Feb 2022
  
      I have the following problem and solution:
for instance, I have a set of nodes of which I have the indices in A, and a set of measured injections at these nodes. I have multiple injections at the same node. B thus contains the same indices as A, but in random order and non-unique.
How to accumulate the injections in B per node in A?
A = unique node indices for which I need the matrix of injections
B = non-unique node indices for which I have a matrix of injections. All indices in B are also in A
V = matrix of injections for the nodes in B.
I use,
A = unique(randi([1,1000], 300, 1));  % few hundred random node indices
B = A(randi([1, numel(A)], 1000, 1)); % 1000 random measurement locations
nA = numel(A); % for convenience
nB = numel(B); % for convenience
V = randi([10,100], nB, 8760); % 8760 random measurements at 1000 locations
[~, B2A] = ismember(B,A);
printf('%d', any(B2A<1)); % should be zero
tic;
C = full(sparse(B2A, 1:nB, 1, nA, nB) * V);
toc
This produces
'0'
0.013 seconds
This is pretty fast for large matrices.
0 Comments
See Also
Categories
				Find more on Loops and Conditional Statements in Help Center and File Exchange
			
	Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!



