Conditional Statements#

There are some situations where we want to proceed with a task or perform an action dependent on whether a certain condition, or perhaps conditions, have been satisfied. For example, suppose we want to go on a walk outside if it is sunny. The action of going on a walk depends on whether it is sunny. In general, conditional statements are statements where an action or event occurs dependent on if a condition is true. We will investigate how to express these types of statements in Python.

Conditional statements have the form of an “if-then” statement: if statement P, the hypothesis, occurs, then statement Q, the conclusion, also occurs.

How do we write this in code? We utilize the if expression in Python.

The statement below will execute the indented block conclusion, if the hypothesis is true; otherwise, if hypothesis is not true, then the indented block is ignored:

if hypothesis:
    conclusion

Perhaps we want to output whether a number is even. There is more than one method to code this! We could:

  • Extract the last digit and see if it equals 0, 2, 4, 6 or 8.

  • Check the remainder when divided by 2 to see if it is 0.

  • Take our input \(n\) and check if \((-1)^n == 1\).

  • Convert it to a binary number – (a sequence of 0s and 1s that can uniquely represent each number) – and check the last bit.

The approach we will use is to check if the remainder when divided by 2 is 0. If the remainder is 0, then the input is divisible by 2 and thus even!

Recall we use the % operator to compute the remainder. For example 2 % 3 == 2, as 3 goes into 2 exactly 0 times with remainder 2. We also have 3 % 3 == 0 as 3 divides itself with no remainder, and 4 % 3 == 1 as 4 goes into 3 once with a remainder of 1.

And we’ll use the following if expression to implement a function that tests if the input is even.

def test_even(input_number):
    '''This function does not return anything, but rather
    prints a statement about if the input is even'''
    if (input_number % 2) == 0:
        print("The number", input_number, "is even.")

If the input to this function is even, the string will be printed; otherwise, this function does nothing.

test_even(4)
The number 4 is even.

We can revise this function to also include information on odd integers. If we divide a number by 2 and get a remainder of 1, then the number is odd.

Our revised function below takes an input integer then evaluates the first condition: Is the remainder 0? If so, the corresponding string is printed. The next condition is then evaluated: Is the remainder 1? If so, the corresponding string is printed.

Notice that both conditions are always checked in our test_parity function. Note also that the functions defined in this section do not have a return statement, but rather print their output. This is to showcase that if statements are always checked, regardless of the truth value of a previous statement. Recall a function automatically terminates when return is called, thus to illustrate if statements we are using print in our functions.

def test_parity(input_number):
    '''Prints if the input is even or odd'''
    if (input_number % 2) == 0: 
        print("The number", input_number, "is even.")
    if (input_number % 2) == 1: 
        print("The number", input_number, "is odd.")  
test_parity(5)
The number 5 is odd.

Elif statement#

In the case above where we have more than one conditional statement, we can utilize an elif statement. Used in combination with an if expression, an elif statement is only checked if all previous statements evaluate to False. This allows us to check if our first condition is true, otherwise we move to evaluate the truth value of the next statement. In words this says “If hypothesis_1 is True then evaluate, else if hypothesis_2 is True then evaluate, …etc”, where the first ‘True’ is evaluated.

The format of an elif statement is:

if hypothesis_1:
    conclusion_1
elif hypothesis_2:
    conclusion_2
... 
elif hypothesis_n:
    conclusion_n

In an elif statement, once a condition is true, the remaining condition or conditions are not evaluated.

Let’s revise the function test_parity to use an elif statement in a new function: test_parity_revised.

def test_parity_revised(input_number):
    '''Prints if the input is even or odd'''
    if (input_number % 2) == 0: 
        print("The number", input_number, "is even.")
    elif (input_number % 2) == 1: 
        print("The number", input_number, "is odd.") 
test_parity_revised(9)
The number 9 is odd.
test_parity_revised(8)
The number 8 is even.

When we input 8 to the test_parity_revised function, the first if statement evaluates to True. At this point in the function, the remaining elif will not execute thus saving computation time.

Now our function can take any integer and output whether or not that value is even or odd.

But what if our user enters the number 5.7? What if they enter a fraction or an irrational number?? We can make test_parity_revised more complete by indicting what to do in these cases.

Else statement#

Since we have a condition that checks all even numbers and a condition that checks all odd numbers, we want to group all other possibilities into one category – neither even nor odd.

We can do this with the else statement. The else statement takes one of the following forms.

We could have exactly two possible conclusions which is formatted:

if hypothesis_1:
    conclusion_1
else:
    conclusion_2

Or we could have \(n+1\) conclusions for a chosen \(n\) in which case we have the format:

if hypothesis_1:
    conclusion_1
elif hypothesis_2:
    conclusion_2
... 
elif hypothesis_n:
    conclusion_n
else:
    conclusion    

Similar to an elif statement whose condition is true, once the Python interpreter has reached the else statement, no more conditions are evaluated – it is, after all, the last option. At this point, all other conditions have been evaluated and none executed. Thus, there is no condition or hypothesis associated with the else statement. If it is reached, the conclusion of the else statement is executed.

Let’s alter the function test_partity_revised to use an else statement, in a new function: test_parity_final.

def test_parity_final(input_number):
    '''Returns a number's parity '''
    if (input_number % 2) == 0: 
        print("The number", input_number, "is even.")
    elif (input_number % 2) == 1: 
        print("The number", input_number, "is odd.")        
    else: 
        print("The number", input_number, "is neither even nor odd.") 

Comparing the output of this function to test_parity_revised, we see that the past functions cannot handle a decimal input like 5.7, whereas test_parity_final can!

test_parity_final(5)
The number 5 is odd.
test_parity_final(5.7)
The number 5.7 is neither even nor odd.
test_parity_revised(5.7)

A note of caution when using the else statement. Since the else statement is executed without checking a condition, you want to be absolutely certain that all desired possibilities are accounted for in the previous condition(s).

For example, if we change test_parity_revised as below to check if a number is even, but assume that everything else must be odd, we get an error when testing something like 5.7 in this function, as 5.7 is not odd!

def test_parity_revised(input_number):
    '''Returns a number's parity '''
    if (input_number % 2) == 0: 
        print("The number", input_number, "is even")
    else: 
        print("The number", input_number, "is odd")

test_parity_revised(5.7)

The number 5.7 is odd.

Whoops!

Example: six-sided die#

We can use random selection in combination with conditional statements to investigate abstract phenomena.

For example, suppose we want to consider how often even versus odd numbers are rolled in 100 rolls of an ideal six-sided die. We can illustrate this using the random choice function in addition to our parity function above.

First, we create a six-sided die, by creating an array containing the numbers 1 - 6.

When we randomly select a choice from this list, we are simulating rolling a die. Repeating this 100 times gives us an array containing the outcomes of 100 tosses.

import numpy as np
die = np.arange(1, 7)

die
array([1, 2, 3, 4, 5, 6])
results_roll = np.random.choice(die, 100)

results_roll
array([1, 2, 4, 3, 4, 2, 2, 6, 3, 6, 5, 3, 3, 6, 4, 1, 2, 4, 2, 5, 6, 5,
       3, 2, 5, 3, 6, 1, 2, 4, 3, 1, 6, 1, 2, 2, 1, 6, 2, 4, 2, 5, 2, 5,
       6, 1, 6, 4, 1, 5, 1, 5, 3, 4, 3, 2, 3, 4, 5, 3, 2, 4, 2, 4, 5, 1,
       5, 4, 3, 5, 3, 2, 4, 2, 2, 4, 5, 1, 3, 2, 3, 1, 5, 6, 1, 2, 6, 2,
       3, 1, 3, 3, 6, 1, 1, 5, 2, 2, 2, 2])

Our random roll generator outputs a number between 1 and 6, but we are only interested in if that number is even or odd. But, the way we’ve written our function, we cannot directly put an entire array through it.

Instead we’ll call np.vectorize, which takes a function as input and returns as output a function that acts on an entire array. That is, it allows for elementwise evaluation of our parity function!

Below we define a new function, vec_parity, which takes an array containing the roll results, that is an array of numbers 1 - 6, and returns an array denoting even or odd rolls.

Note that in the function below we use an if-else statement, because we are assuming the inputs are integer values 1 - 6.

def parity(input_integer):
    '''Assumes integer input
    Returns even or odd'''
    if (input_integer % 2) == 0:
        return "even"
    else:
        return "odd"

    
vec_parity = np.vectorize(parity)

results_parity = vec_parity(results_roll)

results_parity
array(['odd', 'even', 'even', 'odd', 'even', 'even', 'even', 'even',
       'odd', 'even', 'odd', 'odd', 'odd', 'even', 'even', 'odd', 'even',
       'even', 'even', 'odd', 'even', 'odd', 'odd', 'even', 'odd', 'odd',
       'even', 'odd', 'even', 'even', 'odd', 'odd', 'even', 'odd', 'even',
       'even', 'odd', 'even', 'even', 'even', 'even', 'odd', 'even',
       'odd', 'even', 'odd', 'even', 'even', 'odd', 'odd', 'odd', 'odd',
       'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'odd', 'even',
       'even', 'even', 'even', 'odd', 'odd', 'odd', 'even', 'odd', 'odd',
       'odd', 'even', 'even', 'even', 'even', 'even', 'odd', 'odd', 'odd',
       'even', 'odd', 'odd', 'odd', 'even', 'odd', 'even', 'even', 'even',
       'odd', 'odd', 'odd', 'odd', 'even', 'odd', 'odd', 'odd', 'even',
       'even', 'even', 'even'], dtype='<U4')

Our goal is to compare the total number of even rolls out of the total 100 rolls of the die.

Recall we can count all the even rolls in the array by elementwise comparing each result to “even” and summing over the instances of True.

total_even = sum(results_parity == 'even')

total_even
51

Suppose we want to repeat this experiment a few times and record the results. We create a results array containing the total evens in the first 100 rolls and extend this list with each new experiment.

results = np.array([total_even]) 

results
array([51])

We first generate the 100 random rolls of the die using np.random.choice(die, 100).

From this array, the vec_parity function will tell us which rolls were even and which rolls were odd. We sum over the even elements and append this to the current array.

(Recall np.append() does not modify the original list, so the assignment is necessary.)

results = np.append(results, sum(vec_parity(np.random.choice(die, 100)) == 'even'))

results
array([51, 57])

We can run this experiment again!

results = np.append(results, sum(vec_parity(np.random.choice(die, 100)) == 'even'))

results
array([51, 57, 50])

And again!

results = np.append(results, sum(vec_parity(np.random.choice(die, 100)) == 'even'))

results
array([51, 57, 50, 53])

And again?

results = np.append(results, sum(vec_parity(np.random.choice(die, 100)) == 'even'))

results
array([51, 57, 50, 53, 52])

Is there a more automated way? In the next section we explore how we can streamline this process of running an experiment multiple times.