Search code examples
matlabmatrixaccumarray

Using accumarray to output a matrix


MATLAB's accumarray is unbelievably powerful in many applications. My issue, is that my accumarray function to apply on my inputs has three outputs, and accumarray can only handle scalar outputs. For example, I'd like to do something like this:

subs = [1;2;4;2;4;5];
vals = [1;1;1;2;5;1];
accumarray(subs, vals, [], @(x)[min(x),mean(x),max(x)],0)

and have accumarray return:

1.0000    1.0000         0    1.0000    1.0000
1.0000    1.5000         0    3.0000    1.0000
1.0000    2.0000         0    5.0000    1.0000

I suppose I could just run accumarray three times, but my function is slow, and running accumarray once would be vastly faster than running it three times. Am I just hosed here?


Solution

  • You can cheat make the anonymous function output a cell array instead of a single value. This way, accumarray will give you a cell array of matrices. You can then concatenate all of the matrices to a single matrix when you're done. Take note that your proposed anonymous function has the min, max and mean as a row vector but your expected result is a column vector. Therefore I've transposed this inside your anonymous function.

    The gotcha we have to take into account is the fill value. The fill value you specified needs to be a scalar. Therefore, you can cheat by leaving this out, but then your output will now contain empty matrices in the cell instead of the row of the result being filled with 0. A way around this is to find all cells that are empty, replace them with a row of zeros, then piece it all together when you're done. To figure out which rows of the accumarray output are going to be empty, you can use cellfun combined with isempty so we can see which elements in the result are empty. A neater way to do this would be to first pre-allocate a matrix of zeros then only populate the rows that correspond to the non-empty locations in the output of accumarray to finish it off:

    subs = [1;2;4;2;4;5];
    vals = [1;1;1;2;5;1];
    out = accumarray(subs, vals, [], @(x){[min(x),mean(x),max(x)].'});
    ind_empty = cellfun('isempty', out);
    out_final = zeros(3, numel(out));
    out_final(:, ~ind_empty) = cat(2, out{:});
    

    Take note of the use of cat which is concatenating matrices together in a specified dimension. Doing out{:} produces what is known as a comma-separated list so it is equivalent to taking each column of the accumarray output and putting them as individual arguments into cat so that we would eventually piece all of the columns together into a single matrix, but I'm slicing into the output in such a way where we're only populating those locations that were not empty.

    With your test inputs, I match what you get:

    >> out_final
    
    out_final =
    
        1.0000    1.0000         0    1.0000    1.0000
        1.0000    1.5000         0    3.0000    1.0000
        1.0000    2.0000         0    5.0000    1.0000
    

    However, if I can be honest - If you know for sure that you're going to only have three values to bin into accumarray, it may be faster to simply call it three times then concatenate everything when you're done. I would argue that it's more readable and it makes it very clear what you're doing. Doing it the way I did with the cell array above requires that you really know how MATLAB works.