4

What I'm trying to do: given a 2D matrix, get the column indices of the elements in each row that satisfy some particular condition.

For example, say my matrix is

M = [16 2 3 13; 5 11 10 8; 9 7 6 12; 4 14 15 1]

and my condition is M>6. Then my desired output would be something like

Indices = {[1 4]'; [2 3 4]'; [1 2 4]'; [2 3]';}

After reading the answers to this similar question I came up with this partial solution using find and accumarray:

[ix, iy] = find(M>6);
Indices = accumarray(ix,iy,[],@(iy){iy});

This gives very nearly the results I want -- in fact, the indices are all right, but they're not ordered the way I expected. For example, Indices{2} = [2 4 3]' instead of [2 3 4]', and I can't understand why. There are 3 occurrences of 2 in ix, at indices 3, 6, and 9. The corresponding values of iy at those indices are 2, 3, and 4, in that order. What exactly is creating the observed order? Is it just arbitrary? Is there a way to force it to be what I want, other than sorting each element of Indices afterwards?

Community
  • 1
  • 1
Curtis H.
  • 78
  • 1
  • 7

2 Answers2

2

Here's one way to solve it with arrayfun -

idx = arrayfun(@(x) find(M(x,:)>6),1:size(M,1),'Uni',0)

Display output wtih celldisp(idx) -

idx{1} =
     1     4
idx{2} =
     2     3     4
idx{3} =
     1     2     4
idx{4} =
     2     3

To continue working with accumarray, you can wrap iy with sort to get your desired output which doesn't look too pretty maybe -

Indices = accumarray(ix,iy,[],@(iy){sort(iy)})

Output -

>> celldisp(Indices)
Indices{1} =
     1
     4
Indices{2} =
     2
     3
     4
Indices{3} =
     1
     2
     4
Indices{4} =
     2
     3
Divakar
  • 218,885
  • 19
  • 262
  • 358
  • Beautiful! I didn't know about arrayfun. I'm still a little curious why my attempted approach doesn't quite work, but this seems like the perfect solution to my actual problem. – Curtis H. Nov 14 '14 at 18:26
  • 1
    @CurtisH. Your apporach is good; it's just that `accumarray` does not preserve order – Luis Mendo Nov 14 '14 at 18:42
  • @CurtisH. I think you can wrap `iy` with `sort`. Added code for that. – Divakar Nov 14 '14 at 18:47
  • @Divakar That seems to work too, but your `arrayfun` solution is substantially faster, at least on my actual data. – Curtis H. Nov 14 '14 at 19:15
  • 2
    @CurtisH. That's good to know! I think `accumarray` based approaches are efficient when you are getting numeric outputs from them, otherwise when working with `anonymous functions` its performance drops significantly, I don't have numbers to prove that though. This same story continues with `bsxfun + anonymous functions` for which luckily I do have numbers [here](http://stackoverflow.com/questions/26592765/generating-arrays-using-bsxfun-with-anonymous-function-and-for-elementwise-subtr/26593369#comment41803858_26593369). – Divakar Nov 14 '14 at 19:24
2

accumarray is not guaranteed to preserve order of each chunk of its second input (see here, and also here). However, it does seem to preserve it when the first input is already sorted:

[iy, ix] = find(M.'>6); %'// transpose and reverse outputs, to make ix sorted
Indices = accumarray(ix,iy,[],@(iy){iy}); %// this line is the same as yours

produces

Indices{1} =
     1
     4

Indices{2} =
     2
     3
     4

Indices{3} =
     1
     2
     4

Indices{4} =
     2
     3
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147