Names: [Insert Your Names Here]

Lab 5 - Return Statements and Plotting Basics

Lab 5 Contents

  1. Python Return Statements
    • Functions Without Returns
    • Return Statements as Code Breaks
    • Return Statements for Assigned Output
  2. Basic Python Plotting

In [ ]:
from numpy import *

1. Python Return Statements

1.1 Functions Without Returns

Thus 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:


In [ ]:
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

In [ ]:
dummy_func()

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:


In [ ]:
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)

In [ ]:
dummy_func(2,3)

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


In [ ]:
dummy_func(1)

In [ ]:
dummy_func(y=1)

In [ ]:
dummy_func(y=3,x=2)

In [ ]:
dummy_func(3,2)

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:


In [ ]:
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')

In [ ]:
dummy_func()

1.2 Return Statements as Code Breaks

Now let's try adding a return statment to dummy_func


In [ ]:
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

In [ ]:
dummy_func()

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 3 solutions)


In [ ]:
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

In [ ]:
temp_convert(system="B")

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 #4 solutions).


In [ ]:
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)

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.


In [ ]:
order_please(['Rowan','Tasha'])

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 Output

Return 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:


In [ ]:
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

In [ ]:
dummy_func()

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.


In [ ]:
z

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:


In [ ]:
z = dummy_func()

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


In [ ]:
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

In [ ]:
dummy_func()

And when you want to assign them to variables, you use a similar syntax, though it seems a bit funny.


In [ ]:
x, y, z = dummy_func()

In [ ]:
x

In [ ]:
y

In [ ]:
z

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.


In [ ]:
x, y = dummy_func()

2. Basic Python Plotting

You've already made some plots for this course, however we have not yet manipulated their appearance in any way. For example, in Homework #2, you wrote functions that returned double slit interference patterns that looked like this:

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


In [ ]:
import matplotlib.pyplot as plt
%matplotlib inline

This gives you access to all of pyplot's functions in the usual way of calling modules (plt.functionname). For example:


In [ ]:
x=arange(-10,10,0.01)
y=x**2
plt.plot(x, y)

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


In [ ]:
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)

Note these functions can be set before or after the plot command as long as they're within the same cell.


In [ ]:
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")

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


In [ ]:
plt.plot(x,x**2)
plt.plot(x,x**3)

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


In [ ]:
plt.plot(x,x**2, label='$x^2$')
plt.plot(x,x**3, label='$x^3$')
plt.legend()

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.


In [ ]:
plt.plot(x,x**2, label='$x^2$')
plt.plot(x,x**3, label='$x^3$')
plt.legend(loc="lower right", frameon=False)

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

In [ ]:
# plotting code here

Exercise 3


Exercise 3a

Use LaTeX syntax to write Planck's law (provided in class) in the cell below

Insert Planck's Law here

Exercise 3b

Now, code a function with one required input (temperature) that will return a numpy array called Planck with two columns and 100000 rows. The first column should contain wavelength values between 0 and 100000 nanometers, and the second column should contain the corresponding $B_\lambda$ values. The output flux column should be in units of $W/m^2/nm$. The cell below will import all of the constants that you need to accomplish this.


In [ ]:
import astropy.units as u
from astropy.constants import G, h, c, k_B

In [ ]:
#the cell below should include your planck_func function definition

In [ ]:
# insert test statements here

If you've done this correctly, the following should tell you that the dimensions of the output are (2,100000).


In [ ]:
x = planck_func(1000)
x.shape

Exercise 3c

Use the output of the function to make a plot with appropriate labels (including units) and legend that shows the difference between a 6000K (sun-like) star's blackbody curve and those of stars that are slightly warmer (7000K) and cooler (5000K). Manipulate the x and/or y range of the plot to zoom in on the curves as much as you can while showing all three.

Reminder/Hint: Since the function returns a 2 x 10000 element matrix you will need to use array indices in your plot command. Remember that python array indices start from 0.


In [ ]:
#define your variables to be plotted (outputs from the function you defined) here

In [ ]:
#insert your plot commands here

Exercise 3d

Write a paragraph describing the differences between the areas under these three curves and what that means about their total energy output. Be as quantitative as possible by estimating the area underneath them.

Insert your description here

Exercise 3e

write a new function, planck_norm, that does the same thing as the Planck function, but normalizes the function by the peak value. To do this, divide the flux array by its maximum value. This serves to make all of the curves peak at 1 so that you can easily compare where they peak.


In [ ]:
##new normalized Planck function definition

Make the same plot as you did above, but with normalized flux along the y axis this time.


In [ ]:
#define your variables to be plotted (outputs from the function you defined) here

In [ ]:
#insert your plot commands here

Exercise 3f

Write a paragraph describing what normalization does and why it is useful. What physical properties are corrupted/lost by displaying the normalized flux instead of the true flux, and which are preserved? Include specific descriptions between your plots.

insert normalization paragraph

Exercise 3g

Write a paragraph describing the differences in peak wavelength between these three stars. You should include a precise value for the peak wavelength of each star (Hint: find the index of the maximum flux value and use that to get the corresponding wavelength).


In [ ]:
#you should insert some code here to find the peak wavelength for each star

insert description of peak wavelengths here


In [1]:
from IPython.core.display import HTML
def css_styling():
    styles = open("../custom.css", "r").read()
    return HTML(styles)
css_styling()


Out[1]: