<jupyter_start><jupyter_text>*** Name: [Insert Your Name Here]***  Prelab 3 - Return Statements and Plotting Basics Prelab 3 Contents1. Python Return Statements  * Functions Without Returns  * Return Statements as Code Breaks  * Return Statements for Assigned Output2. Basic Python Plotting<jupyter_code>import numpy as np<jupyter_output><empty_output><jupyter_text>1. Python Return Statements 1.1 Functions Without ReturnsThus far we've not paid much attention to function return statements. As many of you have noticed, you don't HAVE to have a return statement at the end of a function. For example:<jupyter_code>def dummy_func(x=0, y=0):
    """This function takes two optional arguments (x and y) with default values of zero and adds them together 
    to create a new variable z, then prints it."""
    z=x+y
    z
dummy_func()<jupyter_output><empty_output><jupyter_text>There are a  few things to note here. First that the function did not print out z, even though in an ordinary notebook cell (without a function definition) typing the variable name alone usually prints the value stored in it. That's not the case with functions because functions will only return to output what you tell them to. If you want to print the value of something within a function, then you must use a print statement. For example:<jupyter_code>def dummy_func(x=0, y=0):
    """This function takes two optional arguments (x and y) with default values of zero and adds them together 
    to create a new variable z, then prints it."""
    print("x is", x)
    print("y is", y)
    z=x+y
    print("z is", z)
dummy_func(2,3)<jupyter_output><empty_output><jupyter_text>This brings up another subtelty that it's worth reemphasizing here - optional arguments can be specified in a function call in any order ***but*** if you don't specify which variable is which in the function call, it will assume that you are specifying them in the order that you defined them in the function. For example, as I've written the function, you should be able to specify none, one or two arguments in any order. Make sure you understand what each of the following cells are doing before moving on<jupyter_code>dummy_func(1)
dummy_func(y=1)
dummy_func(y=3,x=2)
dummy_func(3,2)<jupyter_output><empty_output><jupyter_text>OK back to the function dummy_func and the idea of return statements. Note that we did not include a return statement in the definition cell. A return statement is ***not necessary*** to delimit the end of a function, though it can be visually useful in that way. In fact, a function will consist of any code that is indented beneath it. If you remove the indentation, any code that you write is no longer considered part of the function. For example:<jupyter_code>def dummy_func(x=0, y=0):
    """This function takes two optional arguments (x and y) with default values of zero and adds them together 
    to create a new variable z, then prints it."""
    print("x is", x)
    print("y is", y)
    z=x+y
    print("z is", z)

print('This is not part of the function and will not be printed when I call it. It will print when I execute this cell')
dummy_func()<jupyter_output><empty_output><jupyter_text>1.2 Return Statements as Code BreaksNow let's try adding a return statment to dummy_func<jupyter_code>def dummy_func(x=0, y=0):
    """This function takes two optional arguments (x and y) with default values of zero and adds them together 
    to create a new variable z, then prints it."""
    print("x is", x)
    print("y is", y)
    z=x+y
    print("z is", z)
    return
dummy_func()<jupyter_output><empty_output><jupyter_text>Note that nothing changed about the output when we added a return to dummy_func. Regardless of where it is embedded in a function, a return statement always tells Python that the function is complete and it should stop execution and return to a command prompt. In this way, it can sometimes be useful inside a function. For example, consider the following function (from Lab 2 solutions)<jupyter_code>def temp_convert(system="C"):
    """
    Description:
    Asks user to input a temperature, and prints it in C, F and K. 
    
    Required Inputs: none
    
    Optional Inputs:
    system - string variable that allows the user to specify the temperature system. 
             Default is C, but F and K are also options.
    """
    
    #ask the user to enter a temperature
    input_temp = input("enter a temperature (set system keyword if not Celsius):")
    
    # default input type is string. Convert it to a Float
    input_temp=float(input_temp)
    
    #Convert all input temperatures to Celsius
    if system == "C":
        input_temp = input_temp
    elif system == "F":
        input_temp = (input_temp-32)*5/9
    elif system == "K":
        input_temp = input_temp - 273
    else:
        #if system keyword is not C, F or K, exit without any output and print a warning
        print("unrecognized system - please specify C, F or K")
        return
    
    #Convert and print the temperatures
    print('Temperature in Celsius is ', str(input_temp))
    temp_f = input_temp*9/5 + 32
    print('Temperature in Farenheit is ', str(temp_f))
    temp_k = input_temp + 273
    print('Temperature in Kelvin is ', str(temp_k))
    return
temp_convert(system="B")<jupyter_output><empty_output><jupyter_text>In the example above, a return statement was used mid-function in order to break out of it when a bad temperature system is specified. This returns the prompt to the user BEFORE reaching the print functions below, which are bound to fail. In this way, the return statement acts a bit like a "break" statement but break is only useable inside of loops (for, while) and it does not exit the entire function, but only the loop. For example, consider the following function (also from Lab 2 solutions).<jupyter_code>def order_please(names):
    orders=''
    foods=''
    names.sort(key=len)
    for a in names:
        negg = input(a +": How many eggs would you like?")
        spam = input(a +": Would you like Spam (yes or no)?")
        if spam == "yes":
            spam = ""
            spam_print = "SPAM!"
        elif spam == "no": 
            spam_print = "NO SPAM!"
        else:
            print('unrecognized answer. Please specify yes or no')
            break
        order =a +" wants "+negg + " eggs, and " + spam + " Spam"
        food = a+": "+"egg "*int(negg)+"and "+spam_print
        orders = orders+'\n\n'+order
        foods = foods+'\n'+food 
    print(orders)
    print(foods)<jupyter_output><empty_output><jupyter_text>Like the return statement in the temperature conversion function, break is used here to handle unrecognized input, but here it functions only to end the for loop and not to exit the function. It will still reach the two print statements at the bottom and print any orders that were collected before the bad input. To verify this, exectute the cell below. For Rowan's order, enter 2 for eggs and no for spam. For Tasha's order, enter 3 eggs and yuck for spam. At the end, the function will still print Rowan's order, and skip Tasha's.<jupyter_code>order_please(['Rowan','Tasha'])<jupyter_output><empty_output><jupyter_text>Note though that break will break you out of the for loop entirely. If you enter an unrecognized answer for spam in Rowan's order, it will not then ask Tasha for hers. Verify this by reexecuting the cell above and entering a bad value for spam in Rowan's order.Note too that entering return in place of break in the function above would have stopped the code entirely at that point, and would not have printed the good order before exiting. Try swapping it out in the function definition to verify this.  1.3 Return Statements for Assigned OutputReturn statements are not only useful as code breaks, however. Their main purpose is to return calculated values, arrays, etc. to the user so that they can be referenced in future code cells. So far our code has mostly involved printing things, but sometimes we want to use and manupulate the output of a function and so it needs to be passed back to the user. For example:<jupyter_code>def dummy_func(x=0, y=0):
    """This function takes two optional arguments (x and y) with default values of zero and adds them together 
    to create a new variable z, then prints it."""
    print("x is", x)
    print("y is", y)
    z=x+y
    print("z is", z)
    return z
dummy_func()<jupyter_output><empty_output><jupyter_text>Note in executing the cell above, you now have output (z in this case). Nice, but stil not that useful since it just tells you what it is. The function returns z as output, but does not store anything in a variable called z. So even though you defined z within the function and returned it, python will not recognize the variable. You can see this by executing the cell below, which will return an error.<jupyter_code>z<jupyter_output><empty_output><jupyter_text>This is an important higher-order thing to note about functions - variables only have meaning ***within*** them, not outside them. If I want the function to return whatever I've told it to return (z) ***and*** store it in a variable, I have to tell it so via the following syntax:<jupyter_code>z = dummy_func()<jupyter_output><empty_output><jupyter_text>This time, I've told python that it should take the output of dummy_func and store it in the variable z. Functions can return arbitrary numbers of things as well<jupyter_code>def dummy_func(x=0, y=0):
    """This function takes two optional arguments (x and y) with default values of zero and adds them together 
    to create a new variable z, then prints it."""
    print("x is", x)
    print("y is", y)
    z=x+y
    print("z is", z)
    return x, y, z
dummy_func()<jupyter_output><empty_output><jupyter_text>And when you want to assign them to variables, you use a similar syntax, though it seems a bit funny.<jupyter_code>x, y, z = dummy_func()
x
y
z<jupyter_output><empty_output><jupyter_text>Note that when you define a function with multiple return variables and you assign those returned variables into stored variables with = you ***must have an equal number of assigned variables as returned variables***. For example, the following will return an error.<jupyter_code>x, y = dummy_func()<jupyter_output><empty_output><jupyter_text>Exercise 1---------------------Write a function that does the following:   1) Has one required numerical input - the distance to a galaxy   2) Has one optional input - a string specifying the units for that distance, with a default value of "Mpc"   3) If the unit specified is "lyr" (light years), "pc" (parsecs) or "km" (kilometers), converts the value of the numerical input to Megaparsecs   4) If the unit is not one of the above or Mpc, prints a reasonable error message and exits   5) Uses Hubble's law, one version of which is shown below, to compute the redshift (z) of the galaxy. c is the speed of light, d is the distance to the galaxy in Mpc, and $H_0$ is Hubble's constant, 73.5km/sec/Mpc.   $$z=\frac{dH_0}{c}$$ 6) If the computed redshift is greater than 1, print: "The linear form of hubble's law does not provide an accurate distance estimate beyond z=1" and exit    7) Returns the redshift as a variable<jupyter_code>#function definition
#test statement<jupyter_output><empty_output><jupyter_text>2. Basic Python PlottingYou've already made some plots for this course, however we have not yet manipulated their appearance in any way. For example, in Prelab 2, you made plots of the sine function that looked like this: ![](sin.png "sin plot")As you can see, python (unlike many languages) generally does a lovely job with coloring, line thickness etc. with simple plot commands. It does not, however, add titles, axis labels, legends, etc. and these are ***very*** important things to include. From now on, any plots that you make in Labs or homeworks should always, at a minimum, include: axis labels (including units), a plot title, and a legend in any case where there's more than one line on the same plot. There are many useful optional inputs to the plot command that allow you to tweak the appearance of the plot, including: linestyle, color, placement of the legend, etc. So let's learn the basics by plotting some things. So far we have been using the command %pylab inline to allow jupyter to insert inline plots in our notebooks. Now we are going to do this more properly by importing the matplotlib library's plotting module pyplot an then telling the notebook that you still want it to display any plots inline (inside the notebook) with the magic function %matplotlib inline with the following lines<jupyter_code>import matplotlib.pyplot as plt
%matplotlib inline<jupyter_output><empty_output><jupyter_text>This gives you access to all of pyplot's functions in the usual way of calling modules (plt.functionname). For example:<jupyter_code>x=np.arange(-10,10,0.01)
y=x**2
plt.plot(x, y)<jupyter_output><empty_output><jupyter_text>Here are some especially useful pyplot functions, called with plt.functionname(input(s)):* xlim and ylim set the range of the x and y axes, respectively and they have two required inputs - a minimum and maximum for the range. By default, pylab will set axis ranges to encompass all of the values in the x and y arrays that you specified in the plot call, but there are many cases where you might want to "zoom in" on certain regions of the plot. * xlabel and ylabel set the labels for the x and y axes, respectively and have a required string input. * title sets the title of the plot and also requires a string input. Line properties are controlled with optional keywords to the plot function, namely the commands *color*, *linestyle* and *linewidth*. The first two have required string arguments (lists of the options are available here), and the third (linewidth) requires a numerical argument in multiples of the default linewidth (1).  These can be specified either in the call to the function or separately before or after the plot call.See the cell below for an example of all of these at play<jupyter_code>plt.xlim(-5,5)
plt.ylim(0,20)
plt.xlabel("the independent variable (no units)")
plt.ylabel("the dependent variable (no units)")
plt.title("The Quadratic Function")
plt.plot(x, y, color='red',linestyle='--', linewidth=2.5)<jupyter_output><empty_output><jupyter_text>Note these functions can be set before or after the plot command as long as they're within the same cell.<jupyter_code>plt.plot(x, y, color='red',linestyle='--', linewidth=2.5)
plt.xlim(-5,5)
plt.ylim(0,20)
plt.xlabel("the independent variable (no units)")
plt.ylabel("the dependent variable (no units)")
plt.title("The Quadratic Function")<jupyter_output><empty_output><jupyter_text>As you have already encountered in your labs and homeworks, it is often useful to overplot functions on top of one another, which we do with multiple plot commands. In this case, what you need to make a quality graphic is a legend to label which is which. For example, let's plot the cubic function on top of the quadratic. In doing so below, note that we don't actually **have** to specify a separate variable, but that the arguments to a plot command can be combinations of variables<jupyter_code>plt.plot(x,x**2)
plt.plot(x,x**3)<jupyter_output><empty_output><jupyter_text>To add a legend to a plot, you use the pyplot function legend. Perhaps the simplest way to use this function is to assign labels to each line plot with the label keyword, as below, and then call legend with no input. Note that the label keyword requires string input and that you can use LaTeX syntax within those labels<jupyter_code>plt.plot(x,x**2, label='$x^2$')
plt.plot(x,x**3, label='$x^3$')
plt.legend()<jupyter_output><empty_output><jupyter_text>As you can see above, the default for a legend is to place it at the upper right of the plot, even when it obscures the underlying lines and to draw a solid line around it (bounding box). Generally speaking, bounding boxes are rather ugly, so you should nearly always (unless you really want to set the legend apart) use the optional Boolean (True or False) keyword "frameon" to turn this off. Legend also takes the optional keyword loc to set the location of the legend. Loc should be a string, and you can see the full list of options by accessing help for the legend function by hitting shift+tab+tab inside of the plt.legend parentheses below.<jupyter_code>plt.plot(x,x**2, label='$x^2$')
plt.plot(x,x**3, label='$x^3$')
plt.legend(loc="lower right", frameon=False)<jupyter_output><empty_output><jupyter_text>Exercise 2---------------------Make a plot that meets the following criteria:* plots $x^a$ for all integer values of a between 0 and 5* labels the x and y axes appropriately* includes an appropriate descriptive title* zooms in on a region of the plot where you find the differences between the functions to be the most interesting* sets linestyles and colors to be distinct for each function* makes a legend without a bounding box at an appropriate location<jupyter_code># plotting code here
from IPython.core.display import HTML
def css_styling():
    styles = open("../../custom.css", "r").read()
    return HTML(styles)
css_styling()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07438734719888766, 'fraction_numerical': 0.00590927524477145, 'mean_word_length': 4.32449105490438, 'pattern_counts': {'":': 8, '<': 97, '<?xml version=': 0, '>': 97, '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': '24', 'dir': 'jupyter-structured-clean-dedup', 'id': '43953', 'license': 'MIT', 'n_tokens_mistral': 4839, 'n_tokens_neox': 4670, 'n_words': 2454, 'path': 'Prelabs/Prelab3/Prelab3.ipynb', 'repo_name': 'kfollette/AST200-Spring2020', 'size': '17261'}