<jupyter_start><jupyter_text>Conditions and Loops  Why do I need to know this?Programming, at it's heart, is about being able to reduce the amount of work it takes to accomplish a task. In manufacturing, this would look like building a machine that can accomplish portions of a task with less human effort. By reducing the amount of work that we have to put into a process, we free ourselves up to focus on the tasks that are not so easily accomplished by anyone besides a competent human being (us). Think of a program as really tiny machinery. Your goal is to make a machine (in code) that can accomplish routine tasks for you. When you're done, you get to focus on other important tasks that demand your attention. For example, you might want to spend your time working on a forecast model, but first you need to check to make sure that all values in your time series are valid. You COULD just open up your data, and look at every record to make sure that each fits the rules that valid data should follow. Or you could write a program that can check every line for you, and either alert you to problems, or possibly fix them without you even having to be involved. Then, all of your time is freed for the forecasting model.Last week we talked about some of the types of information that we can store in Python. This week, we will start to learn about the tools that are baked into Python that allow us to begin the process of automation.  ConditionsWe have used conditions a few times already, but it is time to talk seriously about conditions and logical statements in Python. If you haven't noticed this yet, you will soon note that Python (and all other programming languages) are VERY literal. Things that we don't intend will occur whenever we assume that a computer will interpret an ambiguous statement in a single anticipated (by humans) way. Eventually, you will get used to being very careful when describing conditions in your code, and these problems will become less of an issue.In Python, we have keywords that we can use to express different kinds of logic. Let's get started by talking about `if` statements. `if` statements function in Python pretty much the same way that they do in English: you state a condition, and then explain what should happen if that condition is met. You can then list alternative outcomes with the conditions that should be met prior to implementing those contingency plans. Finally, you can list what to do in any other outcomes that were not explicity described. Let's try this out:<jupyter_code>x = 150

if x > 100:
    print("Whoa, that's a big one!")<jupyter_output><empty_output><jupyter_text>When we utilize conditions like the example above, every statement depends on the result of the evaluation of a **logical statement**. In this case, the **logical statement** is `x > 100`. This statement has a result that is a boolean value, as we can see if we ask Python to evaluate it as a standalone statement:<jupyter_code>x > 100<jupyter_output><empty_output><jupyter_text>When we use `if` statements, we will *always* need logical statements. We can write the logical statement inline, like we just did, or we can evaluate the truth of any prior statement. Let's look at another helpful example of an `if` statement using existing values:<jupyter_code>x = 150
y = 200

xBigger = x > y
xBig = x > 100

if xBig & xBigger:
    print("That is a REALLY big number")
elif xBig:
    print("x is big, but not THAT big")
else:
    print("x isn't really that big")<jupyter_output><empty_output><jupyter_text>Here we take two numbers (`x` and `y`), and use two logical statements (`x > y` and `x > 100`) to evaluate several possible outcomes. First, we check if both conditions are true using the statement `xBig & xBigger`. If both are `True`, then we print the statement assigned to the `if` block.Next, we incorporate the keyword `elif`. `elif` is a keyword that *continues* an `if` statement. It tells Python to evaluate it's truthiness if the condition above it fails. In other words, the `elif` statement is only evaluated in this case when `xBig & xBigger` is `False`. If the `elif` statement is evaluated as `True`, then its assigned print statement is executed and we skip the `else` block that follows.If the `elif` statement is *also* `False`, then we reach the `else` statement, which is automatically executed and prints the final print statement. `else` indicates that the code should associated with it should be run when all previous logical statements in the block are evaluated as `False`. It will not run if *any* of the previous statements evaluate as `True`.When writing logical blocks, we always write our code in the following order: 1) Write an `if` statement, and its corresponding code block 2) If needed, write as many `elif` statements as needed, along with their corresponding code blocks. **NOTE**: `elif` statements will be evaluated from top to bottom in the code, and once a single `elif` statement is evaluated as `True`, none of the subsequent `elif` statements will be evaluated. Put your `elif` statements in the order that you want them to be prioritized! 3) If needed, write an `else` statement that will apply to all events not covered by `if` or `elif` statements **Solve it**: (from *The Python Workbook*)The following table lists the sound level in decibels for several common noises.| Noise | Decibel Level (dB) ||---|---|| Jackhammer | 130 || Gas Lawnmower | 106 || Alarm Clock | 70 || Quiet Room | 40 |In the cell below, write code that reads a sound level in decibels from the user. If the userenters a decibel level that matches one of the noises in the table then your program should display a message containing only that noise. If the user enters a number of decibels between the noises listed then your program should display a message indicating which noises the level is between. Ensure that your program also generates reasonable output for a value smaller than the quietest noise in the table, and for a value larger than the loudest noise in the table.<jupyter_code>x = int(input("Enter a noise level (in dB): "))

if x>130:
    print("Louder than a jackhammer")
elif x==130:
    print("As loud as a jackhammer")
elif 106 < x < 130:
    print("Louder than a gas lawnmower, quieter than a jackhammer")
elif x==106:
    print("As loud as a gas lawnmower")
elif 70 < x < 106:
    print("Louder than an alarm clock, quieter than a gas lawnmower")
elif x==70:
    print("As loud as an alarm clock")
elif 40 < x < 70:
    print("Louder than a quiet room, quieter than an alarm clock")
elif x==40:
    print("As loud as a quiet room")
else:
    print("Quieter than a quiet room")<jupyter_output><empty_output><jupyter_text>**Solve it**:In the cell below, write a set of conditions that will check the value of `x`, then print the word for any number from 0 to 10 in Finnish:nolla [0], yksi [1], kaksi [2], kolme [3], neljä [4], viisi [5], kuusi [6], seitsemän [7], kahdeksan [8], yhdeksän [9], and kymmenen [10].If the number is outside of this range, then just print "out of range!"<jupyter_code>x = int(input("Enter a number: "))

if x == 0:
    print("nolla")
elif x==1:
    print("yksi")
elif x==2:
    print("kaksi")
elif x==3:
    print("kolme")
elif x==4:
    print("neljä")
elif x==5:
    print("viisi")
elif x==6:
    print("kuusi")
elif x==7:
    print("seitsemän")
elif x==8:
    print("kahdeksan")
elif x==9:
    print("yhdeksän")
elif x==10:
    print("kymmenen")
else:
    print("out of range!")<jupyter_output>viisi<jupyter_text>Loops Just a minute ago, we thought about a problem where we might need to check each record in a data set for valid entries. If we want to accomplish this kind of task, we need to write code that will check the first line, then the second line, then the third line, etc. We can do this by hand, of course. Imagine a list of entries stored in the variable `forecastData`, where we need to make sure that each record is above 40 but below 50. Checking each value manually for validity might look something like the code below:<jupyter_code>forecastData = [49.5, 40.2, 53.7, 48.9, 51.0]

if (forecastData[0] > 40) & (forecastData[0] < 50):
    print(True)
else:
    print(False)
    
if (forecastData[1] > 40) & (forecastData[1] < 50):
    print(True)
else:
    print(False)
    
if (forecastData[2] > 40) & (forecastData[2] < 50):
    print(True)
else:
    print(False)
    
if (forecastData[3] > 40) & (forecastData[3] < 50):
    print(True)
else:
    print(False)
    
if (forecastData[4] > 40) & (forecastData[4] < 50):
    print(True)
else:
    print(False)<jupyter_output><empty_output><jupyter_text>That is a LOT of redundant code. Does it work? Sure. But what happens when I need to fix it? As I typed those lines, I did some copying and pasting, and then noticed that I made a few typos. At that point, I had to fix the typos and then copy and paste the corrected code again. Not ideal, but functional. Then I did it again! And again, it was frustrating to fix each of the iterations of the code.While convenience is important, it is also much more likely when I am copying and pasting code over and over that I will make mistakes than if I just have to write one version of the code that would work for each of the lines of my list.What we are doing when we write logic like the code above is called **looping**. We are writing code that will iteratively work its way through each specified record, and will **loop** through the same logic for each of those records. Wouldn't it be easier if we could just specify the pattern of behavior we expect, and not have to walk our program through each specific iteration?Yes. Yes it would. For LoopsThe most common type of loop used in Python is a `for` loop. They are convenient and safe, as you will soon learn. Let's write a loop for the problem above.<jupyter_code>forecastData = [49.5, 40.2, 53.7, 48.9, 51.0]

for i in forecastData:
    if (i > 40) & (i < 50):
        print(True)
    else:
        print(False)<jupyter_output><empty_output><jupyter_text>That was way easier! But really, what did we just do?The structure of our `for` loop above is the standard format. It translates to English in the following way: "For every element (call it `i`) in the object `forecastData`, do the following:". We even end it with a colon like we would in English!`i` becomes a **placeholder**, or a variable that will be temporarily assigned each time our loop goes to work. It will use each entry in `forecastData` one time, and while a given entry is being used, it will have the nickname `i`. That way, we can write all the code in our loop to describe what should happen to `i`. Then, in each pass of the loop, `i` takes the next value, and we follow the same set of instructions. In this case, we are just checking that our variables meet the established criteria, and then printing `True` if they do, or `False` if they don't.One awesome side effect of this version of loops is that we can easily update our loop if there is a mistake or change to the pattern, and we only have to do so ONE time! No copying and pasting, and less risk of accidental errors popping up in our code. **Solve it**:Write a for loop that will print out each word in the following list one by one:`["A","Long","Time","Ago","In","A","Galaxy","Far","Far","Away"]`<jupyter_code>wordList = ["A","Long","Time","Ago","In","A","Galaxy","Far","Far","Away"]

for i in wordList:
    print(i)<jupyter_output>A
Long
Time
Ago
In
A
Galaxy
Far
Far
Away<jupyter_text>List ComprehensionsAnother great way to write a for loop is to use **list comprehensions**, or lists containing loop syntax that can generate a list as a result of the loop! It is a compact and effective way to get your concept into list form:<jupyter_code>forecastData = [49.5, 40.2, 53.7, 48.9, 51.0]

# Use a list comprehension to evaluate the conditions from our old for loop
meetsCondition = [(i > 40) & (i < 50) for i in forecastData]

print(meetsCondition)<jupyter_output><empty_output><jupyter_text>In this list comprehension, we are able to use a *single line* to write out our previous `for` loop, and then we can simply print our results by printing out the list itself. List comprehensions can provide us a quick way to make all sorts of lists:<jupyter_code># Make a list of integers from 0 to 9
quickList = [i for i in range(10)]

print("List from 0 to 9", quickList, "\n")

# Make a list that prints all numbers divisible by 3

anotherList = [i for i in range(100) if i%3==0]

print("List of multiples of 3", anotherList)<jupyter_output><empty_output><jupyter_text>As we can see from that second example, we can even include statements that allow us to use `if` statements and control which elements are included within our list comprehension.Comprehensions are frequently used to streamline code, and can make our work much easier to read if a for statement is simply intended to transform data based on a fixed pattern for all elements in an existing list or other iterable. **Solve it**:Write a list comprehension to find all numbers between 0 and 1000 with the number 4 in them, and store the resulting list as the variable `fours`<jupyter_code>fours = [i for i in range(1000) if (i%10==4) or ((i%100-i%10)/10==4) or ((i%1000-i%100-i%10)/100==4)]<jupyter_output><empty_output><jupyter_text>While LoopsAnother kind of loop that is used in some contexts is a `while` loop. `while` loops differ significantly from `for` loops in their intended use: a `for` loop is designed to execute a determined number of iterations, whereas a `while` loop is designed to execute code repeatedly until a **stopping condition** is met. This means that we can allow a `while` loop to repeat indefinitely. A **stopping rule** is the logical statement that is checked before each iteration of a `while` loop. Whenever this condition is evaluated as `True`, then the loop is executed. When the condition is evaluated as `False`, the loop is terminated and the program advances to subsequent commands. This is where the term **infinite loop** comes from! If we forget to update our stopping condition within our `while` loop, then it will never stop, because we never tell it to stop. It is **really** important to remember this when we are writing `while` loops, or we will probably crash our program if not our computer.A fun example of a while loop would be writing a function to prompt the user to add numbers until the sum is above 42:<jupyter_code>total = 0
while (total<=42):
    total += float(input("Please enter a number:"))
    print(total)<jupyter_output><empty_output><jupyter_text>In reality, we would probably use `while` loops for significantly more difficult problems than our toy example. `while` loops are valuable in cases where we are seeking convergence such as dynamic programming, search models, and optimization problems in the context of statistical models. **Solve it**:Write a while loop that takes `x` and subtracts 10 while `x` is still above 100.<jupyter_code>x = 432

while x>100:
    x-=10<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07407899103883173, 'fraction_numerical': 0.0161964819117159, 'mean_word_length': 4.466618287373004, 'pattern_counts': {'":': 0, '<': 65, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '309527', 'license': 'MIT', 'n_tokens_mistral': 4298, 'n_tokens_neox': 4024, 'n_words': 2358, 'path': 'Notebooks/2 - Conditions and Loops.ipynb', 'repo_name': 'dustywhite7/pythonMikkeli', 'size': '15065'}