5
$\begingroup$

I have a matrix and want to extract rows according to some criterion. Then I have other matrices and want to extract the same rows. For example, if my criterion is to find rows where all the elements are smaller than some number, I can write

m = Table[i j/10., {i, 5}, {j, 3}]

which returns

{
 {0.1, 0.2, 0.3},
 {0.2, 0.4, 0.6},
 {0.3, 0.6, 0.9},
 {0.4, 0.8, 1.2},
 {0.5, 1., 1.5}
}

Then

Select[m, Max[#] < 1. &]

selects the first three rows of m. Alternatively,

m[[{1, 2, 3}, All]]

also extracts the same first three rows. So if I had the list {1,2,3}, I could use that list to extract the corresponding rows from any number of other objects. To find the list, I tried

Position[m, Max[#] < 1. &]

but it returns the empty list. Why doesn't this work? Is there a better approach? Also, for my real application, execution speed is potentially important.

Update: I am impressed at how many different ways to do this exist! I was also interested in how fast the approaches run. I ran a test on a slightly different task (closer to my application, although my initial list isn't random!).

m = RandomReal[{-1, 1}, {1000, 20}];
lst1 = Position[(Max[Abs[#]] < 0.9) & /@ m, True] // Flatten // 
   AbsoluteTiming;
lst2 = ResourceFunction["SelectIndices"][m, (Max[Abs[#]] < 0.9) &] // 
    Flatten // AbsoluteTiming;
lst3 = Flatten@
    Position[
     Flatten[ResourceFunction[
         "ThroughOperator"][{(Max[Abs[#]] < 0.9) &}] /@ m], True] // 
   AbsoluteTiming;
lst4 = ResourceFunction["SelectPositions"][m, (Max[Abs[#]] < 0.9) &] //
    Flatten // AbsoluteTiming; 
lst5 = 
 Position[m, _?(AllTrue[(Max[Abs[#]] < 0.9) &]), {1}, 
   Heads -> False] // AbsoluteTiming;
lst6 = Flatten[
    Table[Position[m, Select[AllTrue[# < 1 &]][m][[i]]], {i, 1, 
      Length@Select[AllTrue[(Max[Abs[#]] < 0.9) &]][m]}]] // 
   AbsoluteTiming;
{lst1[[1]], lst2[[1]], lst3[[1]], lst4[[1]], lst5[[1]], lst6[[1]]}

returns

{0.002089, 0.002717, 0.003723, 0.007242, 0.009424, 1.09962}

Sorry for obsessing, but here's a new winner (replaces anon. function with explicit one):

(Position[LessThan[0.9] /@ (Max /@ Abs[m]), True] // Flatten // 
   AbsoluteTiming)[[1]]

returns 0.001294. By the way, if the number of rows is smaller than 1000, the ordering of which is fastest changes. (Try 100 and 10.) But method 1 and this variant seem always to be the fastest.

$\endgroup$
1
  • $\begingroup$ Might want to use ResourceFunction["SelectPositions"] or ResourceFunction["SelectIndices"] to get the locations of interest, then use those in Part or Extract. $\endgroup$ Feb 5 at 1:12

4 Answers 4

7
$\begingroup$
lst = Position[(Max[#]<1)&/@m,True]

(* {{1}, {2}, {3}} *)

Using Extract

 Extract[m,lst]
 (* {
     {0.1, 0.2, 0.3}, 
     {0.2, 0.4, 0.6}, 
     {0.3, 0.6, 0.9}
    } *)
     

Using Part

 m[[Flatten@lst]]

(* {
     {0.1, 0.2, 0.3}, 
     {0.2, 0.4, 0.6}, 
     {0.3, 0.6, 0.9}
    } *)

And:

m//#[[Flatten@Position[(Max[#]<1)&/@m,False]]]&

(* {
    {0.4, 0.8, 1.2}, 
    {0.5, 1., 1.5}
   } *) 
$\endgroup$
1
  • 1
    $\begingroup$ This was the most useful to me (and runs very fast). Thanks! It's also a good reminder to me to look at the Function Repository. I will check out those other functions, too. $\endgroup$ Feb 5 at 15:33
7
$\begingroup$

My suggested solution is based on

So if I had the list {1,2,3}, I could use that list to extract the corresponding rows from any number of other objects. To find the list, I tried I tried Position[m, Max[#] < 1. &] but it returns the empty list.

I will borrow something from @Syed's answer. Observe that while

Position[m, Select[AllTrue[# < 1 &]][m]]

does not give you anything, the following

Position[m, Select[AllTrue[# < 1 &]][m][[1]]]

gives

1

Wrap a nice Table around it and Flatten it

Flatten[Table[
  Position[m, Select[AllTrue[# < 1 &]][m][[i]]], {i, 1, 
   Length@Select[AllTrue[# < 1 &]][m]}]]

123

Edit another way to get the {1,2,3} is to use the resource function called ThroughOperator that can do that. This is a development thanks to @Sjoerd Smit.

Flatten@Position[
  Flatten[ResourceFunction["ThroughOperator"][{Max[#] < 1 &}] /@ m], 
  True]

123

$\endgroup$
6
$\begingroup$

Using Select:

m = Table[i j/10., {i, 5}, {j, 3}]
Select[AllTrue[# < 1 &]][m]

Using Pick:

Define a helper function:

f[k_List] := Max[k] < 1
Pick[m, f /@ m]

Using DeleteCases:

DeleteCases[m, _?(AnyTrue[# > 1 &])]

Using Position/Extract:

As Position expects a pattern:

pos = Position[m, _?(Max@# < 1 &), {1}]

OR

pos = Position[m, _?(AllTrue[# < 1 &]), {1}, Heads -> False]

give you the positions that you can extract values from other matrices; e.g.,

Extract[m, pos]

Result:

{{0.1, 0.2, 0.3}, {0.2, 0.4, 0.6}, {0.3, 0.6, 0.9}}

$\endgroup$
2
  • $\begingroup$ The only issue here is that, without an index (such as lst), I do not see how to extract the columns from multiple matrices. If I had only one matrix to extract from, these would be fine. $\endgroup$ Feb 5 at 15:34
  • 1
    $\begingroup$ Thanks, I didn't read the question carefully enough, it seems. I have updated the answer that uses a pattern for extracting the said positions.. $\endgroup$
    – Syed
    Feb 5 at 16:21
3
$\begingroup$

Using GroupBy and Lookup:

Lookup[GroupBy[m, Max@# < 1 &], True]
(*{{0.1, 0.2, 0.3}, {0.2, 0.4, 0.6}, {0.3, 0.6, 0.9}}*)
Lookup[GroupBy[m, Max@# < 1 &], False]
(*{{0.4, 0.8, 1.2}, {0.5, 1., 1.5}}*)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.