Conditional expressions
Contents
import numpy as np ## It's good practice to put your imports right at the top!
6. Conditional expressions#
Follow along!
Remember that to best make use of this tutorial, it is highly recommended that you make your own notebook and type every piece of code yourself!
At this point we’ve covered how to store data of different types in variables, how to perform simple operations on those variables, and with loops, how to repeat sets of operations. However, there is one last major tool that you will need to add to your coding toolbox before you will really be ab|le to attack a generic problem, and that is the ability to set conditions on when your code should do things. The construction of these conditions, called conditional expressions are what we will cover in this section of the tutorial.
6.1. What is a conditional expression?#
At its most basic, a conditional expression can be thought of as a statement that tests whether something is true or false (in code: True
or False
). However, this definition can be a bit hard to understand until we’ve seen a few examples. In words, consider the following situations:
I have my list of pets, and I want to loop through the list, printing one statement for cats, and another statement for all non-cats. To do this, I can, at each iteration, test if it is TRUE that the current pet is a cat.
I have a set of operations and I only want to use these operations on 2D numpy arrays. So I tell the code to check if it is TRUE that the data is an array and then if it is TRUE that the array is 2D. If these are not true, then the code skips the operations.
I have an array of data and I want to set all elements that are greater than 10 to instead be 0. I can do this by checking if it is TRUE that a given element is greater than 10. If it is, set the element to 0, otherwise (
else
, as we’ll see shortly) leave the element alone.
By the end of this chapter, you will be able to execute all of these scenarios (they will be exercises along the way!).
The key to each, and what was emphasized, was the phrasing of the problem as one in which I need to test whether my data/variables meet a certain condition. Once Python has assessed whether the condition has been met, we can ask it to do different things depending on that outcome.
Finally, let’s consider an introductory example:
temperature = 90
if temperature > 80:
print("It's hot! {} degrees is much too warm!".format(temperature))
else:
print("It's not hot! {} degress is great!".format(temperature))
It's hot! 90 degrees is much too warm!
In this example, try changing the value of temperature and note the change in output.
We’ll explore the syntax of this construction (notice the use of indented blocks again!), but the end result should be somewhat intuitive. We assign temperature a value, then if
that value is greater than 80, we print one thing, else
, we print another.
6.2. Logical Comparisons#
Before we can go much further, we need to spend a moment talking about the different “tests” that we can run to test if a condition is true or false. These are called logical comparisons or logical tests.
The most basic logical comparisons are equalities and inequalities which you should be familiar with from mathematics. These will allow us to compare the values of different variables. However, there are other statements that we can evaluate as either true or false, for example, whether a specific value is present in a list or array or whether a variable is of a specific type. We’ll examine these situations here.
6.2.1. Equalities#
t is often very useful to tell if something is equal to something else. For example, if we want to determine if a variable is 3 (not greater or less than, but exactly). To do this we use the double-equals ==
operator:
a = 5
b = 3
print(a == b)
print(a == 5)
print(b == 5)
print(a == 3)
print(b == 3)
False
True
False
False
True
Note that the equality operator compares the values stored in a variable, not the variable names. It is very, very difficult to do anything with variable names in Python, so this is not a fast way for you to check if a variable name has already been used.
Warning
The second equals sign here is essential! If you remove one, you will end up assigning the value on the right to the value on the left!
As you would hope, equalities work with other data types:
print('eric' == 'eric')
print('Eric' == 'eric') ## Python strings are always case-sensitive
print('5' == 5) ## Remember that quotes make *strings*
print('5' == str(5)) ## A string containing the number 5 is different from the number!
print(list(range(5)) == [0, 1, 2, 3, 4]) ## We can compare lists
print(1/10 == 0.1)
print(1/10 == 0.1000000000000001) ## The accuracy of floating-point numbers is pretty good!
print(np.pi == 22/7)
print(np.pi == 3.1415926)
True
False
False
True
True
True
False
False
False
6.2.2. Inequalities#
There are 5 basic inequalities: not equal !=
, less than <
, greater than >
, less than or equal to <=
, and greater than or equal to >=
. Hopefully these are somewhat intuitive, but if not they should be easy to remember.
The not equal test, !=
, works similarly to the equality operator ==
, but the rest of these operators are really only meant for numerical comparisons. For example, it’s not immediately clear what 'Eric' >= 'Madhav'
might logically mean - but you should try it and see what Python says!
a = 5
b = 3
print(a < b)
print(a <= b)
print(a < 5)
print(a <= 5)
print(a > b)
print(b > 3)
print(b >= 3)
False
False
False
True
True
False
True
Note in the above example the difference between <
and >
and the “or equal to” operators <=
and >=
. This can be made more explicit with floats:
print(1/10 < 0.1)
print(1/10 <= 0.1)
print(1/10 < 0.10000000000001)
False
True
True
6.2.3. Testing if an element is in a list or array#
As noted, we can easily ask if something is contained in a collection like a list or array using the in
operator.
In the following example, we’ll also use variable assignment to keep the outcome of our logical test. Here we’ll contain the result of 'dogs' in pets
in the new variable dog_in_pets
and we’ll contain the result of 'bunnies' in pets
in the new variable bun_in_pets
. We can then see that these variables contain either a True
or False
depending on whether 'dogs'
or 'bunnies'
are in pets
, respectively. This is useful if you need to use the same condition multiple times or if, for example, you want to keep track of how often something is true in a loop.
pets = ['dogs', 'cats', 'iguanas', 'birds']
dog_in_pets = 'dogs' in pets ## We can set the result of a comparison to
bun_in_pets = 'bunnies' in pets ## be stored in another variable.
print(f"It is {dog_in_pets} that 'dogs' is in 'pets'.")
print(f"It is {bun_in_pets} that 'bunnies' is in 'pets'.")
It is True that 'dogs' is in 'pets'.
It is False that 'bunnies' is in 'pets'.
6.2.4. Testing if a variable is a specified type#
One thing that is often useful in more complex programs is to have checks to make sure that your inputs (and outputs) are the correct sizes and types. Python has a built-in function called isinstance
, which takes a variable and a type as inputs and returns True
or False
if the variable is of the specified type.
a = 5
b = 'cat'
print(isinstance(a, int))
print()
print(isinstance(a, np.ndarray)) ## Recall this is the "type" of numpy arrays.
print(isinstance(a, list))
print(isinstance(a, float)) ## Put a period after 5 on the first line...
print()
print(isinstance(b, int))
print(isinstance(b, list))
print(isinstance(b, str))
print()
print(isinstance(a, b)) ## READ THE ERROR MESSAGE
True
False
False
False
False
False
True
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[8], line 14
12 print(isinstance(b, str))
13 print()
---> 14 print(isinstance(a, b)) ## READ THE ERROR MESSAGE
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
Alternately, we can directly compare the output of the type
function with the type that we expect it to return. This allows us to also look at the dtype
(data type) of arrays, as well. (Recall that in return for the structure of the numpy array, we lose the ability to have more than one type of data in each location of the array.)
a = 5
b = 'cat'
c = np.array([True, False, False, True, False])
print(type(a) == int) ## a should be an integer
print(type(b) == str) ## b should be a string
print(type(c) == np.ndarray) ## c should be an array
print(c.dtype == bool) ## and the data contained in c should be boolean!
True
True
True
True
6.2.5. Negation of logical statements#
Any logical statement can be negated by using the not
operator before the condition. This is often useful when a condition is more easily constructed to test when something is false (like when using isinstance). As an example:
a = 5
b = 'cat'
print(not a == b) ## a and b are not the same!
print(not isinstance(a, str)) ## a is not a string
print(not isinstance(a, int)) ## is a an integer?
True
True
False
We can also apply this directly to boolean variables, which is useful when you’ve already made a comparison and you want to use the opposite.
a = 5
b = 'cat'
a_is_b = a == b
print(a_is_b)
print(not a_is_b)
False
True
6.2.6. Exercises#
Write a loop that iterates through the numbers 0 to 10 and prints if they are less than 5 and/or greater than 7. (i.e. “It is True that 4 is less than 5, and it is False that it is greater than 7.”) Recall that you can save the result of a comparison to a variable, which can more easily be formatted into a nice string.
Using the remainder operator
%
(gets the remainder of division 10%4 = 2 and 10.1%10 = 0.1) print whether a given integer is even or odd.BONUS: Using the list
types
and the 5x5 array of random numbersrandarr
below, userandint
orchoice
to randomly select an element fromtypes
. Convert the data inrandarr
to that type, then loop throughtypes
, printing whetherrandarr
is a given type at each iteration.import numpy.random as r # Remember to import your modules! types = [int, float, str, bool] randarr = r.rand(5, 5)
6.3. If
-Else
statements#
We now know how two compare two things (in several ways), but as mentioned at the beginning, what if we want to use a logical statement to determine whether to execute other code or not? In Python, this is achieved with if
-else
statements. The syntax, as you will see shortly is, “if condition is True, then do [indented code], else, do [other indented code]”.
As an example, we return to evaluating the temperature:
temperature = 90
if temperature > 80:
print("It's hot! {} degrees is much too warm!".format(temperature))
else:
print("It's not hot! {} degress is great!".format(temperature))
It's hot! 90 degrees is much too warm!
Here, the logical statement temperature > 80
is evaluated after if
(and followed by a colon!). If the statement proves True
, then the following indented code is executed, if it is False
, then do the indented code after the else
(which also has a colon).
It’s worth noting, that if we don’t want to do something if our statement is False
, then we can omit the else
statement altogether. For example, I only complain if it’s too hot, so maybe I only need to write:
temperature = 90
if temperature > 80:
print("It's hot! {} degrees is much too warm!".format(temperature))
It's hot! 90 degrees is much too warm!
Try changing the temperature below 80 and you’ll see that I have no complaints!
6.3.1. Multiple conditions: elif
#
Ok, so actually, I’m pretty particular about what a comfortable temperature is: there is too hot and too cold, so I want to check the temperature against two conditions before I decide to complain or not. There are several different ways that I can do this in Python.
The first is that I can nest if
-else
statements, just like we saw with loops:
temperature = 79
if temperature > 80:
print("It's hot! {} degrees is much too warm!".format(temperature))
else:
if temperature < 78:
print("It's cold! {} degrees is too cool!".format(temperature))
else:
print("It's great! {} degrees is perfect!".format(temperature))
It's great! 79 degrees is perfect!
But from this example, you may see that if I have many conditions, this could get a bit clunky (too many indents!). Instead we use the elif
statement, which is literally a portmanteau of else
and if
. This statement allows us to check another condition and to further diversify our code. We can now restructure the previous code as:
temperature = 79
if temperature < 78:
print("It's cold! {} degrees is too cool!".format(temperature))
elif temperature < 80:
print("It's great! {} degrees is perfect!".format(temperature))
else:
print("It's hot! {} degrees is much too warm!".format(temperature))
It's great! 79 degrees is perfect!
The way that Python executes this code is that it goes through each if
and elif
statement in order and executes any following code if that condition is met. If it doesn’t, then it goes to the next and tries again. So in the previous example, if temperature = 79
, then the first condition is False
, and that section of code is skipped, then the next condition is True
, and the code there is executed. The rest of the structure is then ignored.
6.3.2. if
vs elif
#
It is worth noting again that because an if
statement can be executed on its own, you have to pay attention to whether you need if
statements or elif
statements. Consider the following example, but with the elif
replaced by if
, and we lower the temperature a bit.
temperature = 60
if temperature < 78:
print("It's cold! {} degrees is too cool!".format(temperature))
if temperature < 80:
print("It's great! {} degrees is perfect!".format(temperature))
else:
print("It's hot! {} degrees is much too warm!".format(temperature))
It's cold! 60 degrees is too cool!
It's great! 60 degrees is perfect!
What happened? Well, the first if
was checked, and proved True
, so that statement printed, but then the next line of code was another if
statement! This statement didn’t care that temperature was already checked. It did its own thing and printed that the weather is perfect!
If you want mutually exclusive cases you must use elif
statements, but if you are ok with multiple conditions being true, then you can use solely if
statements.
6.3.3. What is “true” in Python?#
If you think it might seem like an odd moment to raise a philosophical question, you’re right! This is not a philosophical question, but a very practical one. Back when we introduced different data types, at one point we used the bool
function to convert different variables into booleans. If you were paying attention, you may have noticed some interesting results. For example:
print(bool(1)) ## Is the integer 1 true or false?
print(bool(0)) ## Is 0 true or false?
print(bool(-2)) ## What about negative numbers?
print(bool(0.1)) ## Are floats true or false?
print()
print(bool('No')) ## What about strings?
print(bool("True"))
print(bool("False"))
print()
print(bool("")) ## What about *empty* strings?
print()
print(bool([])) ## Empty lists?
print(bool(['a', 'b', 'c'])) ## Non-empty lists?
print()
print(bool([False, False, False])) ## What is the boolean version of a list of Falses?
print()
print(bool(None)) ## 'None' is a special type in Python indicating a
## variable with... *Nothing* inside it.
print()
print(bool(np.array([]))) ## What is the WARNING here?
print(bool(np.arange(10))) ## READ THIS ERROR MESSAGE
True
False
True
True
True
True
True
False
False
True
True
False
False
/var/folders/0z/q4zqhhxd4kv0r6fzjkg8lzfr0000gn/T/ipykernel_72441/816016128.py:20: DeprecationWarning: The truth value of an empty array is ambiguous. Returning False, but in future this will result in an error. Use `array.size > 0` to check that an array is not empty.
print(bool(np.array([]))) ## What is the WARNING here?
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[17], line 21
19 print()
20 print(bool(np.array([]))) ## What is the WARNING here?
---> 21 print(bool(np.arange(10))) ## READ THIS ERROR MESSAGE
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Because of the way that Python turns other data into a True
or False
, we get a lot of flexibility with regards to how we can set conditions. In particular, we can use a numerical data type, like a float or an integer as a condition, where the condition will only be false if the data point is 0. Alternately, we can use lists as conditions, where the condition will only be false if the list is empty, this makes it easy to recognize when a list is empty (although we can always use len(list) == 0
to check) and to perhaps fill it with data, or something else.
It’s also worth noting that while other variables convert to true and false in somewhat varied ways, True
and False
are always converted to numbers as 1
and 0
, respectively.
print(int(True),int(False))
print(float(True), float(False))
1 0
1.0 0.0
6.3.4. Exercises#
Create a list of at least 7 names. Iterate through the list and print the name if it contains an “i” or an “e”, otherwise, add an “ie” to the end of the name and print the new name.
Rewrite the above temperature example with your own temperatures and conditions. Add two more conditions and describe how you feel in those temperature ranges. These conditions can be exclusive or not, depending on how you feel about the weather!
Bonus: Examine the following code and write down what you believe will be output (in order). Uncomment the code and see if it matches what you expect. Does the loop run as many times as you would expect? What happens to names as the loop is iterated? Use your own print statements and modifications to try and understand this. (If you’re puzzled, please ask!)
names = ["Alfred", "Betty", "Charlie", "Danielle", "Eric", "Fred", "Georgia"] # for name in names: # if len(names) > 5: # print("This is a great party!") # else: # print("There are too few people at this party!") # print("\nOh, no! {} has left!".format(names.pop()))
6.4. Conditional expressions in loops#
Now that we’ve learned some basic conditional statements, we can start to combine them with loops to do more sophisticated tasks with iterations and iterables. First, we’ll learn about the while
loop, which will loop back over a set of instructions as long as (while) a logical condition is True
. We’ll also see how we can use conditional expressions to exit loops early using the break
instruction or to skip iterations using the continue
instruction.
6.4.1. while
loops#
As mentioned, a while
loop is a loop that will execute its code indefinitely while a given condition is True
. As an example:
counter = 0
while counter < 10:
print("The counter is " + str(counter))
counter = counter + 1
print("\nThe loop has finished and the counter is " + str(counter) + "!")
The counter is 0
The counter is 1
The counter is 2
The counter is 3
The counter is 4
The counter is 5
The counter is 6
The counter is 7
The counter is 8
The counter is 9
The loop has finished and the counter is 10!
In the above code, we have a variable counter
, which is initially set to 0. Then the while
loop is given the condition counter < 10
, which checks if counter
is less than 10. As long as counter
is, in fact, less than 10, Python will execute the code inside the loop (where “code inside the loop” again means the indented code). In this example, this code consists of a print function and a line where we increment the value of counter
by 1. After the loop has run 10 times, counter
has the value 10 (as you can see at the very end) and so the while
loop ends.
It’s worth noting that the condition given to the while
loop does not need to be as simple as “number greater/less than number”, so while
loops can be complicated. This can make while
loops tricky, as if a programmer is not careful, they might specify a condition which is always met, meaning that the loop will run indefinitely. If you have been using while
loops and your code seems to be running without end, this is the first place I would look to make sure your code is running correctly. One easy way to check this is to restart the kernel (“Kernel -> Restart”) and insert a print
statement somewhere in your loop. If you start printing hundreds of lines then it means the loop is repeating without end!
For some examples of other conditions that can be used in a while
loop consider the following:
mylist = []
while not mylist: ## What is the logical statement here?
mylist.append("element!")
print(mylist, "\n")
counter = -3
while counter:
counter += 1 ## Increment counter by 1
print("The counter is {}".format(counter))
['element!']
The counter is -2
The counter is -1
The counter is 0
This is the main difference between while
loops and for
loops: for
loops will always run a specified number of times, whether it’s to the end of a range
or through all the elements of a list. while
loops, on the other hand, can run indefinitely. This flexibility is sometimes necessary, but also requires extra care.
6.4.2. The pass
operation#
It is often the case that we want to use conditions in loops to activate or deactivate pieces of code, or to exit a loop altogether. To do this, we can use the operators pass
, continue
, and break
.
The pass
operation can technically be placed anywhere in Python code, and can be thought of as a placeholder for code that does nothing. That is, when the Python interpreter encounters pass
it says to itself “pass along, nothing to do here.” This is useful when you are building code and need to use indentation syntaxes like loops or if
-else
blocks but haven’t finished the parts that go in those areas.
for itr in range(10):
pass ## Note to self: insert computation script here when it's ready!
if True:
pass ## Note to self: insert other clever code here!
In the examples above, the pass
allows for the code to run even though nothing is happening in the indented space. Try removing the pass
lines to see the error message.
6.4.3. The continue
operation#
The continue
operation is similar to pass
, but is more specifically designed for loops in that it tells the loop to skip to the next iteration of the loop, ignoring any instructions after the continue
. This is a sometimes useful way to allow for conditions to skip over irrelevant pieces of code quickly. For example, in the following code we can skip over the print statement for even-valued indices.
for ii in range(10):
if ii % 2 == 0: # What condition is this?
continue
print("I got here and ii = {}".format(ii))
I got here and ii = 1
I got here and ii = 3
I got here and ii = 5
I got here and ii = 7
I got here and ii = 9
6.4.4. The break
operation#
The break
operator is very useful for when you are building your code and need to catch obvious errors. The break
operator tells the current loop to stop immediately. For example:
counter = 0
while True: # This is a legal thing to do!
if counter > 15:
print("Breaking loop: this is an illegally large value for counter!")
break
counter += 1 # This increments counter by 1
if counter % 2 == 0:
continue
print("I got here and counter = {}".format(counter))
I got here and counter = 1
I got here and counter = 3
I got here and counter = 5
I got here and counter = 7
I got here and counter = 9
I got here and counter = 11
I got here and counter = 13
I got here and counter = 15
Breaking loop: this is an illegally large value for counter!
In this example, we used the break
operator to stop the loop when the counter
got too large. This is often useful when you’re testing code, where it is good practice to make sure your loop can run one time (then just a few times) before running it many times.
6.4.5. Exercises#
In the previous example move the line
counter += 1
after theif
statement. Run the cell and examine what happens. Hint: recall that you can interrupt the kernel from the “Kernel” menu and use print statements liberally to examine how counter is (or isn’t) changing.Finish the following code to use only
idx
andcounter
to transformmyarr
from all zeros to resemblenp.arange(10) = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
. Use descriptiveprint
statements to narrate the process. You may modify the conditions under which eachwhile
loop is broken, but you may not change the way thatidx
andcounter
are incremented.myarr = np.zeros(10) idx = 0 while True: counter = 0 while True: counter += 1 break idx += 1 break
6.5. Conditional statements involving arrays#
6.5.1. Broadcasting logical comparisons#
As we noted in the previous section on loops, and when we introduced arrays, it is often desirable to work with arrays because we can apply operations directly to the entire array via broadcasting. Thankfully, logical statements work similarly to other operations on arrays, with a few important distinctions. First let’s look at what happens when we compare an array to a number:
import numpy.random as r
myarr = r.rand(3, 6)
print(myarr)
print(myarr > 0.5)
[[0.08099159 0.27064954 0.49054002 0.18253671 0.1402055 0.40644463]
[0.51184581 0.73683917 0.27855969 0.61262233 0.6782449 0.83985689]
[0.35846118 0.94836025 0.27106495 0.54431791 0.01011715 0.62664959]]
[[False False False False False False]
[ True True False True True True]
[False True False True False True]]
Here, we can see that the result of the comparison is an array of booleans corresponding to whether the element of myarr
at that location satisfies the logical statement. In this case, all the elements that are greater than 0.5 return an element of True
, while the others return False
. Since True
gets converted to 1 when considered numerically by Python (see 5.3.3 above), we can easily recover the number of elements greater than 0.5 using np.sum
.
bigger_than_half = myarr > 0.5 ## Remember that we can save the output of conditional expressions
# print(bigger_than_half.astype(int)) ## Uncomment to see what np.sum is adding up!
num_bigger = np.sum(bigger_than_half)
print("There are {} elements larger than 0.5 in `myarr`".format(num_bigger))
There are 8 elements larger than 0.5 in `myarr`
6.5.2. Using booleans as a “mask”#
Perhaps one of the most useful applications of conditional expressions is to generate boolean arrays called masks that allow for the fast selection of elements from an array that meet a given criteria. For example, in the previous subsection, we showed how we could identify which elements of a random array are larger than 0.5, but what if I wanted to do something to all those elements, like set them to zero. Instead of having to loop through the array and test each element, I can use the boolean array resulting from the logical statement as a mask to operate only on the elements corresponding to True
. What this looks like is this:
myarr = r.rand(3, 6)
print(myarr, "\n")
mask_half = myarr >= 0.5
print(myarr[mask_half], "\n") # What is printed out here? What shape is it?
myarr[mask_half] = 0. # Note the syntax here, I use the [] because I am
print(myarr) # *selecting* the elements that are True, then I am
# assigning those elements of the array to be 0.
[[0.17608591 0.96230883 0.44593884 0.66338464 0.04196926 0.07725917]
[0.61468097 0.07478669 0.61603476 0.10657135 0.12402941 0.10685813]
[0.36994977 0.15166714 0.80126445 0.90932842 0.17669661 0.53733836]]
[0.96230883 0.66338464 0.61468097 0.61603476 0.80126445 0.90932842
0.53733836]
[[0.17608591 0. 0.44593884 0. 0.04196926 0.07725917]
[0. 0.07478669 0. 0.10657135 0.12402941 0.10685813]
[0.36994977 0.15166714 0. 0. 0.17669661 0. ]]
Note the syntax of the above construction. We first create the mask, which I advise you to look at, and then we use it as a selector using the []
syntax to indicate that we would like to select the elements where the mask is True
. We can then use this construction to perform operations (above we showed assignment) on just the masked elements. Here we told Python to set all the values bigger than 0.5 to 0.
myarr = r.rand(3, 5)
print(myarr)
## Find values of myarr bigger than 0.5
mask_half = myarr >= 0.5
print()
## Set all values bigger than 0.5 to 12 + sin(values)
myarr[mask_half] = 12 + np.sin(myarr[mask_half])
## Find values of myarr < 1
mask_small = myarr < 1
## Set these values equal to their logarithm
myarr[mask_small] = np.log(myarr[mask_small])
print(myarr)
[[0.72041664 0.85819222 0.98797626 0.49271964 0.34650592]
[0.13921482 0.81879753 0.96842667 0.75081606 0.05846969]
[0.32887086 0.44439145 0.24766344 0.77395776 0.6341893 ]]
[[12.65969785 12.75666186 12.83491386 -0.70781494 -1.05985537]
[-1.97173706 12.73032495 12.82399529 12.68223563 -2.83924674]
[-1.11209013 -0.81104945 -1.39568455 12.6989711 12.59252465]]
6.5.3. Finding where (at what index) a condition is met#
It is also often useful to identify where, in an indexing sense, a certain condition is met within an array. For example, if we have a time-series measurement of some quantity, maybe we aren’t interested in knowing whether a given instant had a measurement above some threshold, but we want to know when the measurement is above the threshold. In this case, we would want to identify where the measurement meets our criteria, and then look at the corresponding location in our time-array.
We can do this in several ways. First, following the example of the timeseries, we can generate a mask as in the previous subsection, and then apply that mask to the time-array.
import matplotlib.pyplot as plt
times = np.arange(0, 6, 0.01) ## 0 to 6 seconds in 0.01 second steps?
signal = np.sin(times)
noise = 0.2 * r.rand(len(times))
data = signal + noise
plt.plot(times, data) ## We'll learn about plotting soon!
times_when_sin_large = times[data > 1.1] # Apply mask directly to times
print(times_when_sin_large)
[1.16 1.18 1.33 1.34 1.36 1.37 1.38 1.4 1.45 1.47 1.5 1.54 1.57 1.58
1.61 1.62 1.63 1.64 1.65 1.69 1.76 1.78 1.8 1.81 1.85 1.89 1.91]
Alternately, we can use the functions np.nonzero
or np.where
to directly obtain the indices at which a condition is true.
inds1 = np.where(data>1.1)
print(inds1, "\n")
inds2 = np.nonzero(data>1.1)
print(inds2)
_ = plt.scatter(times, data, s=2)
_ = plt.scatter(times[inds1], data[inds2], c='r', s=5, zorder=2)
(array([116, 118, 133, 134, 136, 137, 138, 140, 145, 147, 150, 154, 157,
158, 161, 162, 163, 164, 165, 169, 176, 178, 180, 181, 185, 189,
191]),)
(array([116, 118, 133, 134, 136, 137, 138, 140, 145, 147, 150, 154, 157,
158, 161, 162, 163, 164, 165, 169, 176, 178, 180, 181, 185, 189,
191]),)
Note the structure of the output of nonzero
and where
; they put an array of indices inside a tuple (which is another data structure like lists).
Once we have these indices, we can then use them, as shown in the figure, to select only the data that correspond to our condition. In this case, we used the indices in inds1
to place red dots on the measurements with data
> 1.1.
6.5.4. Exercises#
Create a “timeseries” similar to that in the example except using
np.cos
instead of sine. Usewhere
ornonzero
to find when (at what time) the maximum and minimum of the timeseries is achieved. Then look up the functionsnp.argmax
andnp.argmin
and try those.Create a timeseries using the formula \(y = \cos(t)\times \sin(t)\), where \(t\in[0, 2\pi]\). Choose a timestep sufficient to get reasonable resolution of the function. Approximate the slope of the function using a simple forward difference (calculate the slope between each pair of neighboring points). Find the location(s) of the largest positive and negative slopes. (BONUS): try using a central difference or other higher order methods (coefficients here) and see if the locations of the slopes change. What happens if you add noise as in the examples above?
Consider the following dataset. Find the mean, median, and mode of the data that have a value greater than 0.
import numpy.random as r mu = 0.5 data = r.poisson(mu, size=20000)
6.6. Multiple logical comparisons#
6.6.1. The and
and or
operators#
Often, we need data to meet multiple conditions; maybe we need to look at data that is within a certain range (e.g. larger than a
, but smaller than b
). Rather than using multiple if
-else
structures, we can use the logical operators and
and or
to accomplish this.
If you are not familiar with logical and
and or
, they work as follows: a and b
returns True
only if both a
and b
are true, otherwise it returns False
. On the other hand, a or b
returns True
if either a
or b
is true (or if both of them are true), and returns False
only if both are false. As an example:
a = True
b = False
print(a and b)
print(a or b)
print() ## This just makes an empty line.
cat = 'cat'
dog = 'dog'
catdog = cat + dog
print(('cat' in cat) and ('cat' in dog))
print(('cat' in cat) or ('cat' in dog))
print(('cat' in cat) and ('cat' in catdog))
print(('cat' in cat) or ('cat' not in dog)) # Remember how to NEGATE things
False
True
False
True
True
True
6.6.2. Bitwise logical operators#
If we want to use boolean arrays to create a more complex mask for an array, we can use bitwise operators, which will operate element-wise on our arrays. The bitwise operator for logical and is denoted &
, and logical or is |
. The logical negation operator is ~
. As an example:
nums = np.arange(30)
## Get the numbers larger than 10
bigger_than_10 = nums > 10
## and less than 20
less_than_20 = nums < 20
## Using the "and" to get the indices that are in the range (10, 20)
in_range = bigger_than_10 & less_than_20
print(nums[in_range])
## We can use the negation and or operators to get the rest!
not_in_range = (~bigger_than_10) | (~less_than_20)
print(nums[not_in_range])
also_in_range = bigger_than_10 and less_than_20 ## What is the error message?
[11 12 13 14 15 16 17 18 19]
[ 0 1 2 3 4 5 6 7 8 9 10 20 21 22 23 24 25 26 27 28 29]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[31], line 16
13 not_in_range = (~bigger_than_10) | (~less_than_20)
14 print(nums[not_in_range])
---> 16 also_in_range = bigger_than_10 and less_than_20 ## What is the error message?
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Note
Note that we use parentheses to contain different logical statements. The use of parentheses to denote the order of operations persists with logical comparisons. That is, it is much easier to understand (a < 10) and (b > 20)
than it is a < 10 and b > 20
. In the first statement, it is clear that you are first comparing a
to 10 and b
to 20, then comparing those results. In the second statement, it is not clear what is being compared first. Parentheses are useful for making code readable and reducing mistakes.
6.6.3. any
and all
#
Following the suggestion of the error message at the end of the previous example, let us examine the functions any
and all
. These functions look at a mask (boolean array) and return True
or False
if any/all of the elements of the array are True, depending on which function you are using.
These are often very useful for quality checking your data. For example, let’s say that you have performed a calculation and you think that the resulting array should contain only positive numbers. You could then use all(data > 0)
to decide whether to continue or to print a warning. On the other hand, let’s say you’re running a random simulation where a certain outcome is not guaranteed, you might only continue your program if some of your data meet that condition, i.e. any(data == outcome)
returns True
.
nums = r.rand(10)
are_any_larger = any(nums > 0.5)
print(f"It is {are_any_larger} that some of 'nums' are bigger than 0.5.")
are_all_larger = all(nums > 0.5)
print(f"It is {are_all_larger} that all of 'nums' are bigger than 0.5.")
It is True that some of 'nums' are bigger than 0.5.
It is False that all of 'nums' are bigger than 0.5.
6.6.4. Exercises#
Use the noisy sine wave data from from the earlier example and determine whether any or all of the signal is less than 0 for times greater than 3.
Consider
myarr = r.rand(3, 6)
. Using a loop and anif
-else
block, print out the elements smaller than 0.65 and set them equal to 12.