2
$\begingroup$

I know Mathematica's if format is

If[test, then result, else alternative]

For example, this

y:=If[RandomReal[]<0.2, 1, 3.14]

would take a random real number between $0$ and $1$, and evaluate it. If it's less than $0.2$, it'll map y to $1$, otherwise, it'll map y to $3.13$.

I would like to extend this to multiple intervals, short of writing out multiply nested If statements, how can this be done? Can this be done automatically?

For example, if x := RandomReal[], I want to map to 1, if x < 0.1, 3 if 0.1 <= x < 0.2, 19.1 if 0.2 <= x < 0.34, or 7.7 if x >= 0.34.

$\endgroup$
  • $\begingroup$ Maybe Piecewise? $\endgroup$ – Apple Apr 21 '15 at 0:35
  • 1
    $\begingroup$ Also can use Which. $\endgroup$ – bbgodfrey Apr 21 '15 at 0:36
  • $\begingroup$ The above, and of course you can nest If. $\endgroup$ – Sjoerd C. de Vries Apr 21 '15 at 5:30
6
$\begingroup$

There are several functions and methods available which different strengths and limitations that can guide your choice. Among them:

Which

Here combined with Function and Slot to pass a single RandomReal[] value among the tests:

y := Which[
       # < 0.1,         1,
       0.1 <= # < 0.2,  3,
       0.2 <= # < 0.34, 19.1,
       True,            7.7
     ] & @ RandomReal[]

Which, like If, is a flow control construct. True is used to create a default case. Related to it is:

Switch

The syntax of Switch allows us to do without the Function since the first argument is only evaluated once, but we must introduce Pattern and Condition:

y := Switch[RandomReal[],
       x_ /; x < 0.1,          1,
       x_ /; 0.1 <= x < 0.2,   3, 
       x_ /; 0.2 <= x < 0.34,  19.1,
       _,                      7.7
     ]

Here the pattern _ (see Blank) is used to create the default case. Switch is also a flow control function, unlike:

Piecewise

Piecewise is intended as a mathematical function and it therefore often behaves better within a mathematical framework; if the expression is going to be manipulated mathematically it is probably your best starting point. It has an optional nice looking "2D" input form:

enter image description here

y := \[Piecewise] {
     {1, # < 0.1},
     {3, 0.1 <= # < 0.2},
     {19.1, 0.2 <= # < 0.34},
     {7.7, True}
    } & @ RandomReal[]

Interval and IntervalMemberQ

The three methods above are general; however for the given example there are more specialized approaches. One is to use Interval, though it is important to understand that it represents an interval closed on both ends. Here is one formulation also using Pick; the interval range {-∞, ∞} is used for the default case:

With[
  {intv = 
    Interval /@ Append[Partition[{0, 0.1, 0.2, 0.34}, 2, 1], {-∞, ∞}]},
  y := First @ Pick[{1, 3, 19.1, 7.7}, IntervalMemberQ[intv, RandomReal[]]]
]

Interpolation

Faster when applicable is an InterpolatingFunction as generated by Interpolation:

intfn = Interpolation[{{0.1, 0.2, 0.34, 1*^99}, {1, 3, 19.1, 7.7}}\[Transpose], 
   InterpolationOrder -> 0];

y := intfn @ RandomReal[]

In the example above I had to use an arbitrary "large number" 1*^99 for the default case as Infinity is not accepted.

A Plot of intfn:

Plot[intfn[x], {x, 0, 1}]

enter image description here

$\endgroup$
6
$\begingroup$

This is not an answer to your question, but I wanted to point out a better way of coding

y:=If[RandomReal[]<0.2,1,3.14]

Use RandomChoice, instead, as you can specify the exact probabilities with which each number is chosen:

h := RandomChoice[{0.2, 0.8} -> {1, 3.14}]

This can be adapted to your second list with something like this

x[rngs_ -> vals_] := First@x[rngs -> vals, 1]
x[rngs_ -> vals_, n_] := Block[{ps},
  ps = Flatten[{#, 1 - Total@#}]& @ Differences @ Prepend[0] @ rngs;
  RandomChoice[ps -> vals, n]
] /; Length@vals - 1 == Length@rngs

x[{0.1, 0.2, 0.34} -> {1, 2, 3, 4}, 4]
(* {4, 1, 1, 4} *)

Obviously, you would not want to recalculate the probability list every time, so you can return a function, instead

z[rngs_ -> vals_, n_:1] := Block[{ps},
  ps = Flatten[{#, 1 - Total@#}]& @ Differences @ Prepend[0] @ rngs;
  With[{plst = ps},
    If[n==1,
      RandomChoice[plst -> vals]&,
      RandomChoice[plst -> vals, n]&
    ]
  ]
] /; Length@vals - 1 == Length@rngs

Which can then be used to construct your y

y := Evaluate[z[{0.1, 0.2, 0.34} -> {1, 2, 3, 4}]][]

and answers your question, apparently.

$\endgroup$
  • $\begingroup$ defeat conceded :) $\endgroup$ – LLlAMnYP Apr 21 '15 at 1:23
  • $\begingroup$ @LLlAMnYP lol. :) $\endgroup$ – rcollyer Apr 21 '15 at 1:24
  • $\begingroup$ Also was thinking of how to specify {0.1, 0.1, 0.14, 0.66} for RandomChoice when given the ranges {0.1, 0.2, 0.34, 1}. That's a long-winded way of writing the inverse function of Accumulate $\endgroup$ – LLlAMnYP Apr 21 '15 at 1:26
  • $\begingroup$ Not the question that was asked but a good treatment of a possible application. +1 $\endgroup$ – Mr.Wizard Apr 21 '15 at 1:27
  • $\begingroup$ @LLlAMnYP why yes it is. $\endgroup$ – rcollyer Apr 21 '15 at 1:28
4
$\begingroup$

Update 2: Using WeightedData, EmpiricalDistribution, Randomvariate

ClearAll[wdF]
wdF[t_, v_, n_: 1] := Module[{d = EmpiricalDistribution[
     WeightedData[v, Differences[Join[{0}, t, {1}]]]]},
  RandomVariate[d, n]]

Examples:

thresholds = {.1, .2, .34};
values = {1, 3, 19.1, 7.7};   

wdF[thresholds, values]
(* {7.7} *)
wdF[thresholds, values, 10]
(* {1, 7.7, 19.1, 7.7, 7.7, 7.7, 7.7, 19.1, 19.1, 1} *)

Update: Folding Ifs

ClearAll[foldedIf]
foldedIf[t_, v_][x_] := Module[{args = 
    Reverse@Thread[{Partition[Join[{-∞}, t], 2, 1], Most@v}]},
  Fold[If[# <= x < #2 & @@ #2[[1]], Evaluate@Last@#2, #] &, Last@v, args]]

Examples:

foldedIf[{t1, t2, t3}, {a, b, c, d}][x]

If[-∞ <= x < t1, a, If[t1 <= x < t2, b, If[t2 <= x < t3, c, d]]]

foldedIf[thresholds, values][w]

If[-∞ <= w < 0.1, 1, If[0.1 <= w < 0.2, 3, If[0.2 <= w < 0.34, 19.1, 7.7]]]

foldedIf[thresholds, values] /@ RandomReal[1, 5]
(* {7.7, 7.7, 19.1, 7.7, 7.7} *)

Two-argument form of Fold:

ClearAll[feldIf]
feldIf[t_, v_][x_] := Fold[If[# <= x < #2 & @@ #2[[1]], Evaluate@Last@#2, #] &, 
  Prepend[Reverse@ Thread[{Partition[Join[{-∞}, t], 2, 1], Most@v}], Last@v]]

feldIf[{t1, t2, t3}, {a, b, c, d}][x]

If[-∞ <= x < t1, a, If[t1 <= x < t2, b, If[t2 <= x < t3, c, d]]]


Original post:

y := With[{rr = RandomReal[]}, 
  Piecewise[{{1, rr < .1}, {3, .1 <= rr < .2}, {19.1, .2 <= rr < .34}}, 7.7]]

Table[y, {10}]
(* {7.7`, 3, 7.7`, 3, 19.1`, 7.7`, 7.7`, 7.7`, 19.1`, 7.7`} *)

A function that constructs a Piecewise function given a list of thresholds and a list of values:

ClearAll[pwF]
pwF[t_, v_][x_] := Module[{args = Join @@@ 
      Thread[{List /@ v, Partition[Join[{0}, t, {Infinity}], 2, 1]}]},
   Piecewise[{#, #2 <= x < #3} & @@@ args]]

Example:

thresholds = {.1, .2, .34};
values = {1, 3, 19.1, 7.7};

pwF[thresholds, values]@x

enter image description here

pwF[thresholds, values] /@ RandomReal[1, 10]
(*  {7.7`, 19.1`, 7.7`, 19.1`, 3, 7.7`, 1, 19.1`, 19.1`, 7.7`} *)
$\endgroup$
  • $\begingroup$ darn, you just preempted me with that edit... $\endgroup$ – Mr.Wizard Apr 21 '15 at 1:08
  • $\begingroup$ Sorry @Mr.Wizard :) $\endgroup$ – kglr Apr 21 '15 at 1:10
  • $\begingroup$ No problem; you've got my vote already. :-) $\endgroup$ – Mr.Wizard Apr 21 '15 at 1:14
3
$\begingroup$

I'd use Interval for something like this.

E.G., a function that takes as arguments the intervals, what a "hit" in the interval should return, and the target:

f1 = Pick[#2, IntervalMemberQ[Interval /@ #1, #3]] &;

f1[{{0, .1}, {.1, .2}, {.2, .3}}, {1, 2, 3}, .23]

(* {3} *)

And using this to build a single argument function for a given set of intervals and returns:

f2 = f1[{{0, .1}, {.1, .2}, {.2, .3}}, {1, 2, 3}, #] &;

test = RandomReal[1/3, 3]
f2 /@ test

(* 

{0.136326, 0.188152, 0.292161}

{{2}, {2}, {3}} 

*)

Take note of how values on boundaries are handled, use Part or First to filter to your needs.

$\endgroup$
  • $\begingroup$ beat you to it ;-p $\endgroup$ – Mr.Wizard Apr 21 '15 at 1:25
  • $\begingroup$ @Mr.Wizard: Ah, LOL - was that added while I was typing, could have sworn when I glanced at answers was surprised no one had interval. WIll happily delete if you'd like... BTW - there's a goofy way with Nearest... $\endgroup$ – ciao Apr 21 '15 at 1:27
  • $\begingroup$ Yes, I added it a couple of minutes after my initial post, and before my Interpolation addendum. Please don't feel the need to delete; sometimes a simple one-subject answer is clearer and preferred by the OP. I was just having some fun with it; my original answer was going to include meta-programming but about a minute before I would have posted kguler's pwF edit came up so I had to thing of something else useful to add. :-) $\endgroup$ – Mr.Wizard Apr 21 '15 at 1:30
  • $\begingroup$ Would the Nearest method be similar to what Michael did here?: (30103) $\endgroup$ – Mr.Wizard Apr 21 '15 at 1:31
  • $\begingroup$ @Mr.Wizard: Nah - just bisect the intervals as Partition[...,2,1] pairs, threading over the desired returns inside the Nearest creation. So anything <=the distance to boundary gets a given value. Not quite the same result as OP question, but I suppose adding an "undefined" return on the front/back bounds addresses that. Like I said, goofy. $\endgroup$ – ciao Apr 21 '15 at 1:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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