4
$\begingroup$

I have two lists, v1 and v2. Each element of the lists may be positive or negative. I want to create a list v3 whose entry i is v1[[i]] if v1[[i]] is posive, but otherwise is v2[[i]].

The trivial way to implement this is to use a For-loop with If. Is there any faster way?

I tried using logical operator:

vP = Boole[Positive[v1]]; 
vN = Boole[Negative[v2]];
v3 = vP v1 + vN v2;

I tought that using logical operations would be faster than going through indices, but instead it is slower. What would be a fast way to do this?

$\endgroup$
6
  • $\begingroup$ I think you mean vN=Boole[Negative[v1]]; $\endgroup$ Jan 24, 2014 at 1:58
  • $\begingroup$ Also, please note that Positive@0 == Negative@0 == False $\endgroup$ Jan 24, 2014 at 2:02
  • $\begingroup$ vP v1+ (1-vP) v2 ? $\endgroup$
    – george2079
    Jan 24, 2014 at 2:05
  • $\begingroup$ @belisarius. Yes, I mean that. Thanks. $\endgroup$
    – sam84
    Jan 24, 2014 at 16:29
  • $\begingroup$ You got good answers, but I feel that a bit more explanation is in order. You are on the right track with the approach you tried. Doing calculations on complete arrays instead of looping through array elements is usually very fast, and it's called "vectorization". The reason why it wasn't fast in this case is that you were working with symbols rather than numbers: True and False. To get optimal performance from Mathematica you need to use homogeneous numerical arrays, which can be packed. Arrays with symbols in them ... $\endgroup$
    – Szabolcs
    Jan 24, 2014 at 22:43

3 Answers 3

10
$\begingroup$
v1 = RandomChoice[{-1, 1}, 100];
v2 = RandomChoice[{-1, 1}, 100];
vL = UnitStep[v1];
vL*v1 + (1 - vL)*v2

Using PatoCriollo's comparison (and now updated to include rasher and Mr. Wizard's methods):

n = 10^6;
v1 = RandomChoice[{-1, 1}, n];
v2 = RandomChoice[{-1, 1}, n];

(bp = Boole[Positive[v1]];
test1 = bp v1 + (1 - bp) v2); // Timing

test2 = With[{c = Clip[v1, {0, 0}, {0, 1}]}, c (v1 - v2) + v2]; // Timing

test3 = Table[If[Positive[v1[[i]]], v1[[i]], v2[[i]]], {i, Length@v1}]; // Timing

(vL = UnitStep[v1];
test4 = vL*v1 + (1 - vL)*v2;) // Timing

(vL = UnitStep[v1];
test5 = vL (v1 - v2) + v2;) // Timing

(vL = UnitStep[v1];
test6 = vL Subtract[v1, v2] + v2;) // Timing

(pos = SparseArray[UnitStep[-v1]]["AdjacencyLists"];
  test7 = v1;
  test7[[pos]] = v2[[pos]];) // Timing

{0.479009, Null}
{0.185765, Null}
{0.192930, Null}
{0.100705, Null}
{0.102183, Null}
{0.078348, Null}
{0.087175, Null}

test1 == test2 == test3 == test4 == test5 == test6 == test7
True

To summarize: the UnitStep method seems the fastest, the Clip and Table methods are about the same, while Boole takes up the rear. All the methods give the same answer. Interestingly, when we change RandomChoice to RandomReal, the Clip and the UnitStep methods are about the same speed (and both are faster than Table and Boole). Using Simon's trick of substituting Subtract[v1,v2] for v1-v2 also speeds up the UnitStep calculations by a modest, but measurable amount. Mr. Wizard's sparse array approach seems to be competitive with, but slightly slower than the UnitStep.

$\endgroup$
9
  • $\begingroup$ This is what OP's v3 does, right (with belisarius' typo fix) $\endgroup$
    – rm -rf
    Jan 24, 2014 at 2:11
  • $\begingroup$ Nope. Gives incorrect result. $\endgroup$
    – ciao
    Jan 24, 2014 at 2:46
  • $\begingroup$ @rasher -- All four methods give the same answer. $\endgroup$
    – bill s
    Jan 24, 2014 at 2:47
  • $\begingroup$ No, they don't. Yours is incorrectly giving v2 entries where v1 is zero. Zero is not positive... $\endgroup$
    – ciao
    Jan 24, 2014 at 2:50
  • 1
    $\begingroup$ You can get a bit more speed by rearranging the expression to remove one of the multiplications: vL (v1 - v2) + v2, and then a bit more by using Subtract: vL Subtract[v1, v2] + v2 $\endgroup$ Jan 24, 2014 at 9:00
6
$\begingroup$

Here is a method using SparseArray properties to find positions (indexes), then Part to make the replacements. It may be a bit more general than the mathematical methods and it appears to be roughly as efficient on my system (running version 7). Note that unless it is acceptable to count zero as positive it is inappropriate to use UnitStep[v1] -- instead use UnitStep[-v1] to watershed in the other direction.

Sample data:

SeedRandom[7]
MatrixForm[ {v1, v2} = RandomInteger[{-5, 5}, {2, 15}] ]

$\left( \begin{array}{ccccccccccccccc} -1 & 2 & -1 & 5 & 3 & 3 & 0 & -2 & -1 & 0 & -1 & 0 & 0 & -5 & 4 \\ 5 & 4 & -2 & 5 & 1 & -3 & 2 & 5 & 1 & -3 & -4 & -2 & -4 & -2 & -4 \end{array} \right)$

pos = SparseArray[UnitStep[-v1]]["AdjacencyLists"];
v3 = v1;
v3[[pos]] = v2[[pos]];
v3
{5, 2, -2, 5, 3, 3, 2, 5, 1, -3, -4, -2, -4, -2, 4}
$\endgroup$
2
  • $\begingroup$ Clean. I like it, +1 $\endgroup$
    – ciao
    Jan 24, 2014 at 11:40
  • $\begingroup$ @rasher Thank you. I wish Position were as fast; then it would be even cleaner. $\endgroup$
    – Mr.Wizard
    Jan 24, 2014 at 23:15
5
$\begingroup$
With[{c = Clip[v1, {0, 0}, {0, 1}]}, c (v1 - v2) + v2]
$\endgroup$

Your Answer

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

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