# How to make multiple if statements?

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.

• Maybe Piecewise？ Apr 21, 2015 at 0:35
• Also can use Which. Apr 21, 2015 at 0:36
• The above, and of course you can nest If. Apr 21, 2015 at 5:30

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:

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}]


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}]][]


• defeat conceded :) Apr 21, 2015 at 1:23
• @LLlAMnYP lol. :) Apr 21, 2015 at 1:24
• 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 Apr 21, 2015 at 1:26
• Not the question that was asked but a good treatment of a possible application. +1 Apr 21, 2015 at 1:27
• @LLlAMnYP why yes it is. Apr 21, 2015 at 1:28

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 =
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


pwF[thresholds, values] /@ RandomReal[1, 10]
(*  {7.7, 19.1, 7.7, 19.1, 3, 7.7, 1, 19.1, 19.1, 7.7} *)

• darn, you just preempted me with that edit... Apr 21, 2015 at 1:08
• Sorry @Mr.Wizard :)
– kglr
Apr 21, 2015 at 1:10
• No problem; you've got my vote already. :-) Apr 21, 2015 at 1:14

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.

• beat you to it ;-p Apr 21, 2015 at 1:25
• @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...
– ciao
Apr 21, 2015 at 1:27
• 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. :-) Apr 21, 2015 at 1:30
• Would the Nearest method be similar to what Michael did here?: (30103) Apr 21, 2015 at 1:31
• @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.
– ciao
Apr 21, 2015 at 1:38