Search code examples
arraysmatlabvectorizationcellscell-array

Concatenate subcells through one dimension of a cell array without using loops in MATLAB


I have a cell array. Each cell contains a vector of variable length. For example:

example_cell_array=cellfun(@(x)x.*rand([length(x),1]),cellfun(@(x)ones(x,1), num2cell(ceil(10.*rand([7,4]))), 'UniformOutput', false), 'UniformOutput', false)

I need to concatenate the contents of the cells down through one dimension then perform an operation on each concatenated vector generating scalar for each column in my cell array (like sum() for example - the actual operation is complex, time consuming, and not naturally vectorisable - especially for diffent length vecotrs).

I can do this with loops easily (for my concatenated vector sum example) as follows:

[M N]=size(example_cell_array);
result=zeros(1,N);
cat_cell_array=cell(1,N);
for n=1:N
    cat_cell_array{n}=[];
    for m=1:M
        cat_cell_array{n}=[cat_cell_array{n};example_cell_array{m,n}];
    end
end
result=cell2mat(cellfun(@(x)sum(x), cat_cell_array, 'UniformOutput', false))

Unfortunately this is WAY too slow. (My cell array is 1Mx5 with vectors in each cell ranging in length from 100-200)

Is there a simple way to produce the concatenated cell array where the vectors contained in the cells have been concatenated down one dimension?

Something like:

dim=1;
cat_cell_array=(?concatcells?(dim,example_cell_array);

Edit: Since so many people have been testing the solutions: Just FYI, the function I'm applying to each concatenated vector is circ_kappa(x) available from Circular Statistics Toolbox


Solution

  • Some approaches might suggest you to unpack the numeric data from example_cell_array using {..} and then after concatenation pack it back into bigger sized cells to form your cat_cell_array. Then, again you need to unpack numeric data from that concatenated cell array to perform your operation on each cell.

    Now, in my view, this multiple unpacking and packing approaches won't be efficient ones if example_cell_array isn't one of your intended outputs. So, considering all these, let me suggest two approaches here.


    Loopy approach

    The first one is a for-loop code -

    data1 =  vertcat(example_cell_array{:}); %// extract all numeric data for once
    starts = [1 sum(cellfun('length',example_cell_array),1)]; %// intervals lengths
    idx = cumsum(starts); %// get indices to work on intervals basis
    result  = zeros(1,size(example_cell_array,2)); 
    %// replace this with "result(size(example_cell_array,2))=0;" for performance
    for k1 = 1:numel(idx)-1
        result(k1) = sum(data1(idx(k1):idx(k1+1)-1));
    end
    

    So, you need to edit sum with your actual operation.


    Almost-vectorized approach

    If example_cell_array has a lot of columns, my second suggestion would be an almost vectorized approach, though it doesn't perform badly either with a small number of columns. Now this code uses cellfun at the first line to get the lengths for each cell in concatenated version. cellfun is basically a wrapper to a loop code, but this is not very expensive in terms of runtime and that's why I categorized this approach as an almost vectorized one.

    The code would be -

    lens = sum(cellfun('length',example_cell_array),1); %// intervals lengths
    maxlens = max(lens);
    numlens = numel(lens);
    array1(maxlens,numlens)=0;
    array1(bsxfun(@ge,lens,[1:maxlens]')) = vertcat(example_cell_array{:}); %//'
    result = sum(array1,1);
    

    The thing you need to do now, is to make your operation run on column basis with array1 using the mask created by the bsxfun implementation. Thus, if array1 is a M x 5 sized array, you need to select the valid elements from each column using the mask and then do the operation on those elements. Let me know if you need more info on the masking issue.

    Hope one of these approaches would work for you!


    Quick Tests: Using a 250000x5 sized example_cell_array, quick tests show that both these approaches for the sum operation perform very well and give about 400x speedup over the code in the question at my end.