3

I have a list of pairs of values in mathematica, for example List= {{3,1},{5,4}}.

How do I change the first element (3 & 5) if the second element does not reach a threshold. For example, if the second parts are below 2 then i wish the first parts to go to zero. so that list then = {{0,1},{5,4}}. Some of these lists are extremely long so manually doing it is not an option, unfortunately.

skaffman
  • 398,947
  • 96
  • 818
  • 769
Mary
  • 788
  • 6
  • 19
  • 43

4 Answers4

9

Conceptually, the general way is to use Map. In your case, the code would be

In[13]:= lst = {{3, 1}, {5, 4}}

Out[13]= {{3, 1}, {5, 4}}

In[14]:= thr = 2

Out[14]= 2

In[15]:= Map[{If[#[[2]] < thr, 0, #[[1]]], #[[2]]} &, lst]

Out[15]= {{0, 1}, {5, 4}}

The # symbol here stands for the function argument. You can read more on pure functions here. Double square brackets stand for the Part extraction. You can make it a bit more concise by using Apply on level 1, which is abbreviated by @@@:

In[27]:= {If[#2 < thr, 0, #], #2} & @@@ lst

Out[27]= {{0, 1}, {5, 4}}

Note however that the first method is several times faster for large numerical lists. An even faster, but somewhat more obscure method is this:

In[29]:= Transpose[{#[[All, 1]]*UnitStep[#[[All, 2]] - thr], #[[All, 2]]}] &[lst]

Out[29]= {{0, 1}, {5, 4}}

It is faster because it uses very optimized vectorized operations which apply to all sub-lists at once. Finally, if you want the ultimate performance, this procedural compiled to C version will be another factor of 2 faster:

fn = Compile[{{lst, _Integer, 2}, {threshold, _Real}},
  Module[{copy = lst, i = 1},
    For[i = 1, i <= Length[lst], i++,
      If[copy[[i, 2]] < threshold, copy[[i, 1]] = 0]];
    copy], CompilationTarget -> "C", RuntimeOptions -> "Speed"] 

You use it as

In[32]:= fn[lst, 2] 

Out[32]= {{0, 1}, {5, 4}}

For this last one, you need a C compiler installed on your machine.

Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • Perfect, Thank you!! Using Map works great.. very very much appreciated... a lot of time saved.. – Mary Mar 27 '11 at 12:50
  • @Leonid. Thank you. Currently wishing i'd stumbled across it a while ago! – Mary Mar 27 '11 at 13:01
  • 3
    @Leonid I have learned that it is advantageous to read your replies very carefully :-) Purely for my own information, is there any advantage in `{If[#[[2]] < 2, 0, #[[1]]], #[[2]]} & /@ lst` (your method) over `If[#[[2]] < 2, {0, #[[2]]}, #] & /@ lst` ? – 681234 Mar 27 '11 at 15:12
  • 2
    @TomD I think the code you suggest is better than mine. It is more direct and it is also about 20 % faster.What IMO is important is to use `Compile`-able functions in `Map` if you want speed. `Map` attempts to auto - compile the function to be mapped, if the length of the list exceeds the setting `SystemOptions["CompileOptions" -> "MapCompileLength"]`, which defaults to `100`. Both our functions can be `Compiled`, and so are fast. But if you define the same function with patterns and try using it in Map, you immediately have an order of magnitude slowdown on packed lists. Can be a surprise :) – Leonid Shifrin Mar 27 '11 at 16:00
  • @TomD I think you should add `If[#[[2]] < 2, {0, #[[2]]}, #] & /@ lst` to your answer. – Mr.Wizard Mar 27 '11 at 18:24
  • Leonid, I tried changing the setting of "ApplyCompileLength" to 100, but it did not speed up your second method. Why does this setting default to infinity, and why does changing it not help? Why does Map have this optimization and Apply does not? – Mr.Wizard Mar 27 '11 at 18:26
  • Also, in your compiled function, why do you set `i = 1` twice? – Mr.Wizard Mar 27 '11 at 18:29
  • @Mr.Wizard I tried the same, and got the same result. I can only guess that the original list can not remain packed when you use `Apply`, since `Apply` changes the head `List` to something else. Therefore, while you can compile the function, the list will be unpacked and then the optimization does not make sense. But this is only a guess, not a definitive answer. As for why `i=1` - indeed, a duplication.I need it in `Module` declarations to define the type of `i` as integer for the `Compile` type-inferencer (at least, this used to be needed in the past). I prefer to still keep it in the loop. – Leonid Shifrin Mar 27 '11 at 18:48
  • @Leonid Thanks for the very nice explanation. (It is your code, I was merely fiddling with it with a view to a better understanding!) – 681234 Mar 28 '11 at 14:58
3

Another alternative: Apply (@@@, Apply at level 1) and Boole (turns logical values in 1's and 0's):

lst = {{3, 1}, {5, 4}};
{#1 Boole[#2 >= 2], #2} & @@@ lst
Sjoerd C. de Vries
  • 16,122
  • 3
  • 42
  • 94
  • 3
    +1 for elegance. One problem (of which I became aware relatively recently) with level-1 `Apply`-based solutions when used with pure functions and packed arrays is that `Apply` can not utilize auto-compilation (which I mentioned in a comment below my answer). This results in an order of magnitude slowdown w.r.t. the method based on `Map` for really large packed lists, and may look puzzling at first. Of course, all this is only relevant if efficiency is important. – Leonid Shifrin Mar 27 '11 at 16:15
  • @Leonid I didn't know that. Any idea why only Map autocompiles and not Apply? – Sjoerd C. de Vries Mar 27 '11 at 20:21
  • Forget it. I see it's far down in the comments. – Sjoerd C. de Vries Mar 27 '11 at 20:22
2

An alternative approach might be to use substitution rules, and attach a condition (/;)

lst = {{3, 1}, {5, 4}};

lst /. {x_, y_ /; y < 2} -> {0, y}

output:

{{0, 1}, {5, 4}}

681234
  • 4,214
  • 2
  • 35
  • 42
  • 6
    It would be better to use RuleDelayed `:>` in this application. – Mr.Wizard Mar 27 '11 at 17:50
  • @Mr.Wizard. Thanks for pointing that out. Can you provide a brief explanation? – 681234 Mar 28 '11 at 14:51
  • Sure: try setting `y = $Failed;` before running your code above. Most any time that you use named patterns like `y_` in a replacement, you want `:>` to keep the symbols local. Also, you do not need to name `x_`, as a simple `_` will do. Naming is only necessary to define a pattern as the *same* as another, such as `{x_, _, x_}` to match `{1, 6, 1}` but not `{1, 2, 3}`, or of course when referencing it by name. – Mr.Wizard Mar 28 '11 at 15:26
1

Assuming that your matrix is 2x2 and by second elemnt you mean the second row: This should work:

If[A[[2, 1]] < 2 || A[[2, 2]] < 2, A[[2,1]] = 0 ]; A

You may have to change the variables, since your questions is kind of confusing. But that's the idea ;-)

Ping Lu
  • 91
  • 12
  • thanks for this. Also works great. Appreciate all your time, guys. thanks! – Mary Mar 27 '11 at 13:03
  • No problem, but you should definitely go with Leonid's answer, it's by far the fastest when your dealing with big matrices! (my solution was just a dirty-and-quick approach). – Ping Lu Mar 27 '11 at 15:45