6
$\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$ Commented Feb 5, 2023 at 1:12

5 Answers 5

8
$\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$ Commented Feb 5, 2023 at 15:33
8
$\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$
7
$\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$ Commented Feb 5, 2023 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$ Commented Feb 5, 2023 at 16:21
4
$\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$
3
$\begingroup$
m = Table[i j/10., {i, 5}, {j, 3}]; 

Using ReplaceAt (new in 13.1)

ReplaceAt[m, _ :> Nothing, Position[m, x_ /; Max[x] >= 1, 1]] 

Using SequenceSplit (new in 11.3)

Catenate @ SequenceSplit[m, {x_} /; Max[x] > 1] 

Using Cases

Cases[m, x_ /; Max[x] <= 1] 

All return

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

$\endgroup$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.