<jupyter_start><jupyter_text>Operations Research 2021 TA Session 1: Introduction to Python Lawrence Baker Type some code here, like "2 + 17", then press the little play button that appears on the left when you mouse over the textbox, to check you can get this notebook to run:<jupyter_code>3 + 5<jupyter_output><empty_output><jupyter_text>Outline of TA sessionsWe can't learn python in five TA sessions, but we can learn how to use python for a few specific tasks. Hopefully this gives you enough base skills to either pursue OJT in python or the confidence and interest to take some more detailed courses (e.g. through Datacamp). Post-quals is a great time to do this.These sessions are aimed at those just picking up python, but who have either some technical background or a drive to pick up a new skill. It's also cool if you already know python. (You don't have to come to the TA sessions you don't need. If you don't attend you lose the privilege of asking for help for that week). Sessions1.   **An introduction to python (and colab)**2.   Linear algebra & unconstrained convex optimization problems3.   Constraints and integer programming4.   *TBD - Networks? Dataframes? What do you want?*5.   An introduction to machine learning  My background in coding*   Not classically trained - first encountered coding (and python) in my Master's program.*   Used python extensively for my master's thesis, which was [later published](https://www.nature.com/articles/s41598-020-67864-z)*   Since starting at RAND have mainly used R, but there is a significant python community.Python (and R) opened a lot of doors for me and I think are fascinating in a way that Stata and AMPL are not. If you can think of something you want to code, you can do it in python.  What is python?Python is:* **Object-oriented:** We define a set of objects, each has a state, and we interact with that state using methods and functions* **Interpreted:** We don't need to compile our code, it just runs (albeit slower than a compiled language)* **Pythonic:** Much of the time python code looks similar to plain language. If we want to loop through each item in a list and print it, then the syntax is: ```for item in list:    print(item)```* **Where identation matters:** Python syntax is readable and part of the way it achieves this is through removing brackets. It does this by using indentation. In the above code, the for loop is defined by the identation.* **Flexible:** There is almost nothing we can't do with python* **On the cutting edge**: While newer languages may be faster, python is used for a great deal of contemporary coding, whether that's machine learning or data science* **Well supported:** Python has at least 200,000 packages and an incredibly active stack overflow community.  How to solve problems in python1.  Work out what you should google (use docs if you need a specific function)2.  Google it3.  Read through all the top answers and the corresponding disucssions4.  If you don't understand why it works, then use docs5.  ProfitIf this doesn't work, then ask others. If no one else knows, ask me. If you find yourself asking me more than ~1% of the time, then you need to get better at googling.  What is Google Colab and why are we using it?Google Colab allows us to run python in a browser. As Google Docs is to word, Google Colab is to Jupyter Notebooks (a popular IDE). There are several advantages to Colab:*  It's a notebook, which allows us to write code and markdown text together, format our code into chuunks and easily look at variable outputs without running our entire program. You can find [a guide to markdown here](https://colab.research.google.com/notebooks/markdown_guide.ipynb).*  We don't have to install Python! This can be an arduous process and I don't want to deal with it. You should install python (specifically, Anaconda) in your own time, [see instructions](https://problemsolvingwithpython.com/01-Orientation/01.03-Installing-Anaconda-on-Windows/). For these sessions we will all use Colab.*  We run our code in the cloud, without relying on our CPUs*  All our packages are up to date*  Sharing and interacting is easy!  A brief introduction to the bits of python we needThis will not be exhaustive, but I will cover things which either* We need for the homework* I feel are so important I want to mention them anyway  Assignment and Typing<jupyter_code># Assignment is just the equals sign
a = 3

# Assignment generates no output, if you want to check a variable value then call the variable alone
a
a = 3
# Note that only the most recent output is displayed if you rely on the automatic output
a 
a + 3
a * 17
# Python is dynamically typed, which means you can change the type of a variable as you go
b = 67
b = "sixty seven"

b
# Python is also strongly typed, a variable will stay the same type unless you explictly change it
# Here c is a string
c = "4"

# Python falls over when we try to add a string an integer
c + 2
# Here c is an integer
c = 4

c + 2<jupyter_output><empty_output><jupyter_text>Basic Operations  Arithmatic<jupyter_code># Most basic operations work how you expect
2 + 5
2-13
48*2
54/3
# This is modulus, it's means finding the remainder after division
23 % 7
# Floor division is the companion to modulus, it gives the result of the divsion without the remainder
23 // 7
((23 // 7) * 7) + 23 % 7<jupyter_output><empty_output><jupyter_text>Raising something to the power does not have the expected notation, we use '**'. For some reason '^' is a bitwise XOR<jupyter_code>3**2<jupyter_output><empty_output><jupyter_text>Assignment<jupyter_code># We already looked at the most basic assignment
d = 5

# You can combine assignment with arthmatic
# This means d = d + 2
d += 2

d<jupyter_output><empty_output><jupyter_text>Comparison<jupyter_code>#Equality
2 == 3
'cats' == 'cats'
'cats' == 'dogs'
#Not equal
'cats' != 'dogs'
# Greater than
17 > 5
# Less than or equal to (written in the order it's said)
17 <= 17<jupyter_output><empty_output><jupyter_text>Logical<jupyter_code># And
True & False
# or
True | False
# xor
True ^ False
True ^ True
('cats' == 'dogs') | ('cats' == 'cats')<jupyter_output><empty_output><jupyter_text>Printing<jupyter_code>e = 62
f = 'elephants'

# This command prints to the console
print('When I grow up, I want   ', e, f)

# Even if we output something below the print statement, we still see the print statement output
f<jupyter_output><empty_output><jupyter_text>Basic Operations: Exercise1. Find the remainder of:> $$\frac{154*(23 - 8)}{\frac{16}{3} + 3}$$>Assign the numerator and denominators to separate variables first2. Assign two names and an verb to variables (three in total)>Print the sentence: {name1} likes to {activity} {name2}<jupyter_code>numerator = (154 * (23-8))
denominator = ((16/3)+ 3)

numerator % denominator
name1 = 'brien'
name2 = 'bart'
activity = 'high-five'

print(name1, 'likes to', activity, name2)<jupyter_output>brien likes to high-five bart<jupyter_text>Data Structures and methodsHow can we hold data? I will add a couple more data structures after we introduce packages, for a more complete list of base python data structures [check out the documentation](https://docs.python.org/3/tutorial/datastructures.html)  Lists<jupyter_code>#Define a list
favourite_numbers = [13456, 2, 0, 1, 24, name1, 'cat']

favourite_numbers
# The first element is at position 0
favourite_numbers[0]
# Get the fourth element
i = 3
favourite_numbers[i]
# Get the 1st, 2nd, and 3rd elements
favourite_numbers[0:3]
# Change the forth element - lists are mutable (we can change elements)
favourite_numbers[3] = 16
favourite_numbers<jupyter_output><empty_output><jupyter_text>MethodsLists are a type of object (they are a class). Objects have functions associated with them, these are called methods (more on functions later).Basically the methods are one way to interact with the object to edit it or find out about it's properties.We call a method like this:```object.method()```These brackets could specify some additional variables for the method to use, but often methods don't require any inputs except for the object itself, which is why they are empty.Many methods will edit an object in place, which means we are changing it even without using assignment.<jupyter_code>fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']

fruits
#Lets flip it
fruits.reverse()
fruits
#How many Kiwis do we have?
fruits.count('kiwi')
#Sort in alphabetical order
fruits.sort()
fruits
#Add avocado to the end of the list, we can do this over and over
fruits.append('avocado')
fruits
#Sort then remove (and return) the last list element
fruits.sort()
fruits.pop()
fruits<jupyter_output><empty_output><jupyter_text>DictionariesDictionaries are a set of key-value pairs. For instance you could have a dictionary of how much you like different foods:* Fried chicken: 10* Chocolate: 9* Sprouts: 1Dictionaries are less flexible than lists, but we like them because the time complexity of a search is constant. This means no matter how big a dictionary gets it always takes the same amount of time to search. Very hand for large amounts of data. We have to rely on the keys to search the dict, not the order of the data (as in a list).<jupyter_code>food_dict = {'Fried Chicken' : 10, 'Chocolate' : 9}
food_dict
#Return a value
food_dict['Chocolate']
#Add a value
food_dict['Sprouts'] = 1

food_dict
#Remove a value
del food_dict['Sprouts']

food_dict
#Check for a value
'Chocolate' in food_dict
'Fork' in food_dict<jupyter_output><empty_output><jupyter_text>Data Structures: Exercise1. Make a list of 3 elements2. Add an element to the end3. Reassign the second element to a new value4. Flip the list<jupyter_code>my_list = [1, "2", 'three']
my_list.append('4!')
my_list[1] = 162
my_list.reverse()
my_list<jupyter_output><empty_output><jupyter_text>Control Structures  ifif statements allow us to only execute code if a statement is met. Using else and elif clauses we can also check for other conditions if the first one is not met.<jupyter_code>g = 9

if g > 10:
  print("Wow did g get bigger over the summer?!")
g = 453

if g > 10:
  print("Wow did g get bigger over the summer?!")
else:
  print("g is so tiny")
g = 12

if g > 10:
  print("Wow did g get bigger over the summer?!")
elif g > 5:
  print("Is anyone as mediocre as g?")
else:
  print("g is so tiny")<jupyter_output>Wow did g get bigger over the summer?!<jupyter_text>forWe can use for loops to iterate over an iterable. For our purposes, the iterable is usually a list.<jupyter_code>fruits
for fruit in fruits:
  print('I like juicy', fruit)<jupyter_output>I like juicy apple
I like juicy apple
I like juicy avocado
I like juicy avocado
I like juicy avocado
I like juicy avocado
I like juicy banana
I like juicy banana
I like juicy kiwi
I like juicy orange<jupyter_text>Often we want to iterate through a set of numbers, say to do something 100 times. One way to do this is to use range, where``` range(m, n, s)```Produces the sequence:$$m, m + s, m + 2s, ..., n-s$$<jupyter_code>for num in range(10, 40, 3):
  print(num)
squares = []

for num in range(1, 11, 1):
  square = num**2
  squares.append(square)

squares<jupyter_output><empty_output><jupyter_text>Control Structures: Exercise1. for each of the numbers between 1 and 21 inclusive, print the number. If it's a multiple of 3 print fizz instead, if it's a multiple of 5 print buzz instead. If it's both a multiple of 3 and 5 print fizzbuzz instead.<jupyter_code>for num in range(1, 22, 1):
  string = ""
  if num % 3 == 0:
    string += 'fizz'
  if num % 5 == 0:
    string += 'buzz'
  
  if string == "":
    print(num)
  else:
    print(string)<jupyter_output>1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz
fizz<jupyter_text>FunctionsFunctions are blocks of code that you create to do a specific task, which you can then call again. We have already been calling functions, we can call functions on their own, or we can assign their ouput.```function_name(inputs)a = function_with_output(other inputs)```We've already been using functions, for instance```print("I like printing strings")```or ```range(start, end, step)```The powerful thing about functions is that we can write our own. The basic function syntax looks like this:```def my_func(func_input_1, func_input_2):  """A description of the function"""    *** Stuff you want in your function ***  return(func_result)``` Lets try writing our own function that takes a number as input and returns the square.<jupyter_code>def my_square(num):
  square = num * num
  return square<jupyter_output><empty_output><jupyter_text>When we run the code above, the function is stored in memory, and we can call it again. If you don't run the code above, then python won't know what you are referring to. Notice that python deals with types fairly well and that the error message is instructive<jupyter_code>my_square(3)
my_square(-1.54546345343)
my_square("triangle")<jupyter_output><empty_output><jupyter_text>Functions can also take multiple arguments, or optional arguments. Mandatory arguments must come before optional, to specify an optional argument, use assignment within the function definition. You can specify arguments either in order, or by using the defined names in the function call.The function below assumes an order of arguments (we could remedy this within the function). Specifying what arguments you are calling in the function is a way around this.<jupyter_code>def check_pythagorean(min, mid, max):
  if max**2 == (mid**2 + min**2):
    print("Pythagoras would be proud")
  else:
    print("Why betray a man dead for 2500 years?")
check_pythagorean(3, 4, 5)
check_pythagorean(5, 4, 3)
check_pythagorean(max = 5, mid = 4, min = 3)<jupyter_output>Pythagoras would be proud<jupyter_text>Variables defined within functions aren't available outside (unless we explictly return them), so we are free to use generic names.Functions will often have some error handling within them to stop edge cases. When we want the function to stop we can use 'return' with no variable specified.<jupyter_code>def find_primes(max, min = 0):
  if max <= min:
    print("Make sure that the maximum is larger than the minimum")
    return

  if min < 2:
    min = 2
  
  if max > 1000:
    print("Do you think we have all day?")
    return
  
  #Here the real function starts
  prime_list = []

  #Factors always come in pairs, e.g. 2 and 50 multiply to 100
  #The largest small factor is the square root of the maximum

  for num in range(min, max):
    is_prime = True
    for i in range(2, int(num**0.5)+1, 1):
      if num%i == 0:
        is_prime = False
    if is_prime:
      prime_list.append(num)

  return(prime_list)
    
find_primes(10)
find_primes(min = 60, max = 100)
find_primes(10000)<jupyter_output>Do you think we have all day?<jupyter_text>PackagesPackages provide us with ready access to functions others have written and that, for popular packages, are generally very well written and efficient.We will use a package when we get onto convex optimization so we don't have to code this ourselves. A few packages worth knowing:*   **numpy** (pronounced num-pie): mathematical functions and arrays*   **pandas** (which we won't use but you should know about): dataframes*   **sklearn**: basic machine learning (more on this in future sessions)*   **matplotlib**: foundation for all plotting*   **seaborn**: prettier default plots that play well with dataframes  ImportingWe import packages as a call. Once we import a package then we can refer to it's functions. You only need to import packages once each session. Normally we will do this at the very top of the code.<jupyter_code>import numpy

numpy.sqrt(2)<jupyter_output><empty_output><jupyter_text>Sometimes we will want to use a nickname for a package if we are going to refer to it a lot.<jupyter_code>import numpy as np
np.sqrt(6)<jupyter_output><empty_output><jupyter_text>You can also import a specific function, which means you don't need to refer to it, though I think this is bad practice, because then it's unclear where the function is from.<jupyter_code>from numpy import sqrt

sqrt(3)<jupyter_output><empty_output><jupyter_text>Often packages will be huge and have many levels, so it's good practice only import what you need.<jupyter_code>import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>Dataframes A quick demo to show you what dataframes (which we don't use in OR, but are great for data analysis) look like in python<jupyter_code>import pandas as pd

# load the dataset (this is already present in a package, which is why you don't need to download anything)
ca_df = pd.read_csv("https://download.mlcc.google.com/mledu-datasets/california_housing_train.csv", sep=",")

ca_df.head()
ca_df.describe()<jupyter_output><empty_output><jupyter_text>Note that shape is an *attribute*, not a method, which is why it doesn't have the '()' after. It can be hard to know sometimes whether something is a method or an attribute (try both).<jupyter_code>ca_df.shape<jupyter_output><empty_output><jupyter_text>PlottingWe'll talk more about plotting later, but for now I want to give you a very basic layout for a simple plot.<jupyter_code>#Create some data using a numpy array
x = np.arange(0,  10, 0.01)
y1 = x**2
y2 = 10*np.sin(2*x)+30

# A very basic plot
plt.plot(x, y1)
plt.plot(x, y2)
plt.show()
# A slightly nicer plot

#Define the plot size (allows you to do multiple plots too)
fig, ax = plt.subplots(1, 1, figsize = (9, 6))

#Add data, use labels for legend
plt.plot(x, y1, label = "big one", color = 'dodgerblue')
plt.plot(x, y2, label = "wiggles", color = 'red')

#Set axis limits
plt.xlim(0,10)
plt.ylim(0,100)

#Set  titles
plt.title("A beautiful plot", fontsize=20)
plt.ylabel("y values", fontsize=15)
plt.xlabel("x values", fontsize=15)

#Add legend
plt.legend(fontsize = 12)

#Tell it to 'print' the plot
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08067030397505846, 'fraction_numerical': 0.018372118917715178, 'mean_word_length': 4.759217697980122, 'pattern_counts': {'":': 1, '<': 110, '<?xml version=': 0, '>': 116, 'https://': 5, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '28', 'dir': 'jupyter-structured-clean-dedup', 'id': '664463', 'license': 'MIT', 'n_tokens_mistral': 5721, 'n_tokens_neox': 5324, 'n_words': 2536, 'path': '01_OR_An_introduction_to_Python.ipynb', 'repo_name': 'lawbaker/PythonOR2021', 'size': '17962'}