04 Conditionals Part 2
In this module we will flesh out conditionals by looking first at some of the ways we can make comparative statements with strings. In doing so, we’ll look at some built-in functionality of strings that are not comparisons per se but aid us a great deal in making comparisons. We’ll finish by looking at hoe we can modify and combine conditionals to make fine-grained sets of conditions for controlling how our program executes.
Comparisons with strings
When we’re working with string data, we obviously need to look beyond our mathematical comparisons
with the exception of one. Let’s make a new script called string_conditionals.py that responds
differently based on the value of a string.
An example program
We’ll use as an example a program that converts strings containing the names of law enforcement angencies involved in some arrest into standardized names. This kind of process is very common working with data like jail intake records and indeed is based on a real project.
The problem we will solve is this: how do we get data managable when it may not initially be internally consistent?
We’ll start by mimicking just part of a handful of arrest records, here just the agency that performed the arrest. Let’s start with a script that defines this fake data but doesn’t actually do anything.
# string_conditionals.py
first_record = "ICE"
second_record = "Minneapolis Police"
third_record = "Immigration and customs enforcement"
fourth_record = "ICE (Chicago AOR)"
fifth_record = "MPD"
sixth_record = "IMMIGRATION AND CUSTOMS ENFORCEMENT"
So we have a bunch of variables. They are all strings, they are all different, and they all refer to one of two agencies: Immigration and Customs Enforcement (ICE) and Minneapolis Police Department (MPD).
Throughout this module we will build a function that will use conditional logic to map these
variables to either "ICE" or "MPD".
Just like with numbers, we can use == to check whether two strings are exactly the same. For now
let’s just do the easy ones, if we pass "ICE" in to the function we should return "ICE" and the
same goes for "MPD". To ensure that our function always returns something we’ll also include a
path in the branching logic that returns "Unknown" if we don’t find a match.
# string_conditionals.py
def clean_arresting_agency(agency: str) -> str:
if agency == "ICE":
return "ICE"
elif agency == "MPD":
return "MPD"
else:
return "Unknown"
first_record = "ICE"
second_record = "ICE (Chicago AOR)"
third_record = "Minneapolis Police"
fourth_record = "Immigration and customs enforcement"
fifth_record = "MPD"
sixth_record = "IMMIGRATION AND CUSTOMS ENFORCEMENT"
first_clean = clean_arresting_agency(first_record)
print(f"The first arrest was made by {first_clean} ({first_record})")
second_clean = clean_arresting_agency(second_record)
print(f"The second arrest was made by {second_clean} ({second_record})")
third_clean = clean_arresting_agency(third_record)
print(f"The third arrest was made by {third_clean} ({third_record})")
fourth_clean = clean_arresting_agency(fourth_record)
print(f"The fourth arrest was made by {fourth_clean} ({fourth_record})")
fifth_clean = clean_arresting_agency(fifth_record)
print(f"The fifth arrest was made by {fifth_clean} ({fifth_record})")
sixth_clean = clean_arresting_agency(sixth_record)
print(f"The sixth arrest was made by {sixth_clean} ({sixth_record})")
uv run string_conditionals.py
The first arrest was made by ICE (ICE)
The second arrest was made by Unknown (ICE (Chicago AOR))
The third arrest was made by Unknown (Minneapolis Police)
The fourth arrest was made by Unknown (Immigration and customs enforcement)
The fifth arrest was made by MPD (MPD)
The sixth arrest was made by Unknown (IMMIGRATION AND CUSTOMS ENFORCEMENT)
Looking at this script, there’s nothing completely new. While we did not use the == operator in
our previous conditionals script, we did discuss it. The only difference is that now we’re using
strings rather than numbers.
However, our little function does not catch most of the cases and as expected only get the answer we’re looking for when we have an exact match going in.
Let’s look at the second arrest where the record is "ICE (Chicago AOR)". We know that is just a
more specific way of saying "ICE" but obviously our function doesn’t see that. One way to fix it
would be to add more conditions in our function like elif agency == "ICE (Chicago AOR)": along
with elif agency == "ICE (St. Paul AOR)":, elif agency == "ICE (Denver AOR)": and so on to
cover our bases. This, however, would be a nightmare if all we want to know is whether its ICE or
not.
This is where Python’s in operator comes in.
The in operator
The in comparison operator basically does what it says on the tin: it checks whether the thing
before it is in the thing after it. Let’s update our function to look not for the “ICE” string
exactly but instead whether or not the substring "ICE" is part of the full arrest record
string.
# string_conditionals.py
def check_arresting_agency(agency: str) -> str:
if "ICE" in agency:
return "ICE"
elif agency == "MPD":
return "MPD"
else:
return "Unknown"
first_record = "ICE"
second_record = "ICE (Chicago AOR)"
third_record = "Minneapolis Police"
fourth_record = "Immigration and customs enforcement"
fifth_record = "MPD"
sixth_record = "IMMIGRATION AND CUSTOMS ENFORCEMENT"
first_clean = clean_arresting_agency(first_record)
print(f"The first arrest was made by {first_clean} ({first_record})")
second_clean = clean_arresting_agency(second_record)
print(f"The second arrest was made by {second_clean} ({second_record})")
third_clean = clean_arresting_agency(third_record)
print(f"The third arrest was made by {third_clean} ({third_record})")
fourth_clean = clean_arresting_agency(fourth_record)
print(f"The fourth arrest was made by {fourth_clean} ({fourth_record})")
fifth_clean = clean_arresting_agency(fifth_record)
print(f"The fifth arrest was made by {fifth_clean} ({fifth_record})")
sixth_clean = clean_arresting_agency(sixth_record)
print(f"The sixth arrest was made by {sixth_clean} ({sixth_record})")
uv run string_conditionals.py
The first arrest was made by ICE (ICE)
The second arrest was made by ICE (ICE (Chicago AOR))
The third arrest was made by Unknown (Minneapolis Police)
The fourth arrest was made by Unknown (Immigration and customs enforcement)
The fifth arrest was made by MPD (MPD)
The sixth arrest was made by Unknown (IMMIGRATION AND CUSTOMS ENFORCEMENT)
Now, we can see that our conditional catches both "ICE" and "ICE (Chicago AOR)". We’ll see the
in operator a lot moving forward and it’s often indispensable when filtering through text.
Combining Comparisons
Our next record on the list is "Minneapolis Police". We want this to be "MPD". One approach
would be to add another elif something like elif agency == "Minneapolis Police": or
elif "Minneapolis" in agency:. The problem with this is that we’d end up with two conditional
paths in our code that do the same thing. This can be confusing and also means that if we want to
change the behavior of the script when we encounter ICE as the arresting agency, we have to change
it in two places.
Instead we can check them both in the same branch by combining conditionals. The most common
combinations are and and or. We call these special keywords that let us combine conditions
boolean operators. A statement with and evaluates to True if the things on either
side of the and are both True. Think about the plain english statement of “DeepMay is cool
and it rocks.” Well thats obviously true, DeepMay is cool and DeepMay rocks.
or is slightly less obvious. A statement with or evaluates to True if at least one of the
things on either side of it is True. Think this time about something like “DeepMay is going to
suck ass or its going to be amazing.” This is also a true statement. It is not true that
DeepMay is going to suck ass but it is true that its going to be amazing so the overall statement
is true.
Let’s look at a specific Python example separate from our script before looking at how they work in the abstract and finally adding this stuff to make our script work better.
A toy example with and and or
We’ll look at a couple of True statements and then break down what is going in in them.
8 < 9 and "May" in "DeepMay"
8 > 9 or "May" in "DeepMay"
Let’s start with the first line. When Python encounters this line, it does something similar to what we do when we consider the order of operations in math.
First, Python will evaluate the comparisons on either side of the and. Eight is less than nine so
8 < 9 evaluates to True. Along the same lines “May” is in “DeepMay” so "May" in "DeepMay"
also evaluates to True. Next, Python looks at the overall statement which we could think of as
True and True. Of course, that also evaluates to True.
The secondly line is mechanically similar. This time, Python sees 8 > 9 and eight is not greater
than nine so it evaluates to False. The "May" in "DeepMay" is identical and again evaluates to
True. Overall Python then sees this statement as False or True and this whole thing evaluates
to True.
and, or, and ^
Let’s abstract a bit from our specific statements to think about “and” and “or” in boolean logic.
“And” basically works the same as it does in english, but “or” is a little different. When we use
“or” in normal language we typically mean “one and not the other” rather than “one or both.” Instead,
the way we use “or” colloquially is what we call exclusive or or xor in programming. While less
common than and or or we can combine statements using xor using the symbol ^.
Just to be really sure we understand how these things work, Consider a couple of combined
conditional statements that look like this left_hand_comparison and right_hand_comparison where
left_hand_comparison and right_hand_comparison are some kind of comparison like x > 7 or
"ICE" in agency that evaluate to a bool.
left_hand_comparison | right_hand_comparison | or | and | xor (^) |
|---|---|---|---|---|
False | False | False | False | False |
True | False | ``True` | False | True |
False | True | True | False | True |
True | True | True | True | False |
The first two columns in the table above show the possible combinations of True and False
values for the statements on either side of our boolean operators. The latter three columns show
the overall value of the statement for each operator.
Inverting a boolean value with not
The last boolean trick we’ll discuss before returning to our name filtering program is the not
operator. not goes before some bool to switch it to the other value—not True is equivalent
to False, not x > 9 is equivalent to x < 9. We will often use the not operator along with
in as in a statement like if not "ICE" in agency:. In that case the code inside the if will be
executed if "ICE" is not a substring of agency
Finishing up the program
We are now equipped with all that we need to make a concise little agency name cleanup function.
First, let’s modify out elif statement where we catch the Minneapolis PD agency names. We’ll use
or to accomplish this without adding branches. For brevity we will omit the parts of the script
outside of the function definition as none of it changes and it is quite verbose.
# string_conditionals.py
def check_arresting_agency(agency: str) -> str:
if "ICE" in agency:
return "ICE"
elif agency == "MPD" or "Minneapolis" in agency:
return "MPD"
else:
return "Unknown"
# ...
uv run string_conditionals.py
The first arrest was made by ICE (ICE)
The second arrest was made by ICE (ICE (Chicago AOR))
The third arrest was made by MPD (Minneapolis Police)
The fourth arrest was made by Unknown (Immigration and customs enforcement)
The fifth arrest was made by MPD (MPD)
The sixth arrest was made by Unknown (IMMIGRATION AND CUSTOMS ENFORCEMENT)
Now we just have two cases that aren’t working. Both are Immigration and Customs Enforcement fully
spelled out but in one case it’s in all caps. Python gives us a handy way to to convert strings to
lowercase with a string method .lower(). While it is not, strictly speaking, a way of comparing
strings, .lower() is so useful when doing so that it demands discussion here.
Especially if we’re working with data we did not generate ourselves, we don’t want to have to worry about how things are capitalized. Its not uncommon to convert all string data to lowercase (unless it is specifically case-sensitive like keys).
For the sake of excersize, let’s make our function robust by doing just that—converting agency to lowercase before checking our conditions.
# string_conditionals.py
def check_arresting_agency(agency: str) -> str:
agency_lower = agency.lower()
if "ice" in agency_lower or "immigration" in agency_lower:
return "ICE"
elif agency_lower == "mpd" or "minneapolis" in agency_lower:
return "MPD"
else:
return "Unknown"
# ...
uv run string_conditionals.py
The first arrest was made by ICE (ICE)
The second arrest was made by ICE (ICE (Chicago AOR))
The third arrest was made by ICE (Minneapolis Police)
The fourth arrest was made by ICE (Immigration and customs enforcement)
The fifth arrest was made by MPD (MPD)
The sixth arrest was made by ICE (IMMIGRATION AND CUSTOMS ENFORCEMENT)
Uh oh, we’ve got a problem. While our function now correctly maps the fully spelled out
“Immigration and Customs Enforcement” it now also incorrectly maps “Minneapolis police” to “ICE”!
If we think about it, it should be obvious why. "ice" is, in fact, a substring of
"Minneapolis police".
While we could revert back to checking against agency names in their original case, if we imagine
that we might have hundreds more agency names of unknown case to parse, that’s not such an
appealing idea. Instead will use another of our boolean operators, and, along with our inversion
operator not.
We can check whether "police" is a substring of agency with "police" in agency. We can invert
that condition with not "police" in agency, and we can combine it with our other conditions with
and.
Let’s think about exactly how we want our ICE condition to work. We know we want to match on
"ice" and "immigration" but that we don’t want to accidentally match on "police". If we find
the match for "immigration" we don’t need to differentiate between "ice" and "police" so we
know that the "immigration" in agency condition should be combined with the rest using or. Now,
we know that ICE won’t have "police" in the name so we’ll need not "police" in agency to be
True along with "ice" in agency. We’ll need to combine them with and.
Above when we broke down how Python parses comparisons combined with boolean operators we mentioned order of operations. Like in math, we can use parentheses to affect the order of operations—statements inside of parentheses will be evaluated before others. Let’s work from the inside out. We’ll need “ice” but not “police” if we are matching on the agency’s acronym rather than the full name.
"ice" in agency and not "police" in agency
The truthiness of that whole statement represents whether or not we found the ice acronym so we
need the whole thing to be evaluated together before we look at the check for the "immigration"
substring.
("ice" in agency and not "police" in agency) or "immigration" in agency
All of this together will fulfill our requirements for the "ICE" branch. Let’s plug it in and
make sure it works.
# string_conditionals.py
def check_arresting_agency(agency: str) -> str:
agency_lower = agency.lower()
if ("ice" in agency_lower and not "police" in agency_lower) or "immigration" in agency_lower:
return "ICE"
elif agency_lower == "mpd" or "minneapolis" in agency_lower:
return "MPD"
else:
return "Unknown"
# ...
uv run string_conditionals.py
The first arrest was made by ICE (ICE)
The second arrest was made by ICE (ICE (Chicago AOR))
The third arrest was made by MPD (Minneapolis Police)
The fourth arrest was made by ICE (Immigration and customs enforcement)
The fifth arrest was made by MPD (MPD)
The sixth arrest was made by ICE (IMMIGRATION AND CUSTOMS ENFORCEMENT)
Finally, we’ve handled all of the cases we need to convert a bunch of messy agency names into two managable ones. As always, we encourage playing around with these things to see how they break down.
Summary
In this module we started by looking at strings and the in operator for matching on substrings.
We looked at how to combine conditional statements with boolean operators to make complex branches
in a program. We looked at a best practice for handling string by converting them to a consistent
case for processing.
Next time we will look at how we can store multiple values in a single variable with lists