Confounded
Confounded

Reputation: 522

MATLAB index within groups

Given a vector A of group numbers (such as the one returned by findgroups), how to return a vector B of the same length containing indices of elements within groups of A?

For example, if A = [1 1 1 2 2 2 1 1 2 2] then B = [1 2 3 1 2 3 4 5 4 5].

Add 1

My own solution to this is

s = splitapply(@(x) {x, [1:numel(x)]'}, [1:numel(A)]', A(:))
B(vertcat(s{:,1})) = vertcat(s{:,2})

but it seems somewhat convoluted.

Upvotes: 2

Views: 198

Answers (4)

rahnema1
rahnema1

Reputation: 15837

A solution using sort and accumarray:

[s is]=sort(A);
idx = accumarray(s(:),1,[],@(x){1:numel(x)});
B(is)=[idx{:}];

Another solution using the image processing toolbox:

p=regionprops(A,'PixelIdxList');
B = zeros(size(A));
for k = 1: numel(p)
    B(p(k).PixelIdxList) = 1:numel(p(k).PixelIdxList);
end

Upvotes: 3

gnovice
gnovice

Reputation: 125854

Here's a solution that may not look as compact, but it's quite fast since it uses cumsum and indexing:

mA = max(A);
nA = numel(A);
ind = false(mA, nA);
ind(mA.*(0:(nA-1))+A) = true;
B = cumsum(ind, 2);
B = B(ind).';

And here are some timing results for the solutions thus far:

A = [1 1 1 2 2 2 1 1 2 2];

rahnema1: 6.51343e-05
    Luis: 3.00891e-05
     OmG: 2.36826e-05
 gnovice: 4.93539e-06  % <---

A = randi(20, 1, 1000);

rahnema1: 0.000274138
    Luis: 0.000257126
     OmG: 0.000233348
 gnovice: 9.95673e-05  % <---

A = randi(20, 1, 10000);

rahnema1: 0.00162955
    Luis: 0.00163943
     OmG: 0.00126571
 gnovice: 0.00107134  % <---

My solution is the fastest for the above test cases (moderate size, moderate number of unique values). For larger cases, other solutions gain the edge. The solution from rahnema1 seems to do better as the number of unique values in A increases, whereas the basic for loop from OmG does better when the number of elements in A increases (with relatively fewer unique values):

>> A = randi(200, 1, 100000);

rahnema1: 0.0108024  % <---
    Luis: 0.0931876
     OmG: 0.0427542
 gnovice: 0.0815516

>> A = randi(20, 1, 1000000);

rahnema1: 0.131256
    Luis: 0.171415
     OmG: 0.106548  % <---
 gnovice: 0.124446

Upvotes: 2

Luis Mendo
Luis Mendo

Reputation: 112659

Here's a way that avoids loops:

s = bsxfun(@eq, A(:).', unique(A(:))); % Or, in recent versions, s = A==unique(A).';
t = cumsum(s,2);
B = reshape(t(s), size(A));

Upvotes: 1

OmG
OmG

Reputation: 18838

A solution can be using a loop to replace the values in A:

c = unique(A);
B= A;
for (idx = c)
    f = find(A == idx);
    B(f) = 1:length(f);
 end

Upvotes: 1

Related Questions