7.1. Defining functions#

A function is a block of organized, reusable code that we use to perform specific actions. If you’ve worked in other programming languages, you might know them as methods, subroutines, procedures, and other names. Generally, what we’re referring to, regardless of what we call them, are self-contained blocks – or modules – of code that accomplish a specific task.

Why use functions? Once we define a function, we can use it over and over without having to re-write a bunch of code each time. Basically, the intuition here is that if we anticipate we might want to do something – say, subtract the mean and divide by the standard deviation for each observation in a dataset – over and over, rather than have to write out that formula each time, we could instead simply write that formula once inside some specific syntax that defines a function that will carry ouy that formula each time we call it. (Note we “define” a function, rather than “assign” a name, as we did with naming in chapter 3.)

We’ve already seen a kind of function, in fact. In chapter 3 we used built-in functions in Python to do things like import packages or find an absolute value. Under the hood, there’s other code behind these functions, but they’re so common, that we can just write, e.g., import, rather than have to define that function ourselves from scratch.

We also saw that if we want to do something for which there is not a built-in function, we can import a package that will bring with it additional functionality – for example, we imported the package statistics so we could use mean, rather than having to define “mean” from scratch within our own program.

That said, we absolutely could have defined our own function to find the mean – and there may be times when we prefer to do that. Perhaps we don’t want to rely on external packagees, or its actually simpler to just define it ourselves, or we want more control over what’s actually going on under the hood – or, we are purists and prefer to do it all ourselves (as we’ll see as we get to machine learning, writing out programs from scratch can also help build intuition much more effectively than just using “canned” programs we import wholesale). In many cases, there may also simply not be a function that does explicitly what you need to do – or it might be more tedious to go searching to see if there is one rather than simply define it ourselves.

Being able to define our own functions is undeniably useful – necessary, even – for more advanced coding. Here’s how to do it.

Components of a user-defined function#

The below function converts a temperature given in Fahreneheit to Celsius. (Bonus: not only is this a useful example for user-defined functions, but also we will now have a handy tool to support us as we travel in and out of the United States!)

def f_to_c(temp_in_f):
    '''Takes a temperature in Fahrenheit and converts it to Celsius'''
    return ((temp_in_f-32)*(5/9))

Notice if we run this code, it doesn’t return anything. We are simply defining the function in this cell. If we wish to use this function in action, we can call it by writing f_to_c and putting in a value for temp_in_f. For example:

f_to_c(80)
26.666666666666668

If you know anything about how to convert Fahrenheit to Celsius, you can verify that this worked. Otherwise, if you’re a clueless American like this particular writer, you can look it up to check if our function does what we think it does.

There are six components in a user-defined function. The first four are in the function header, which is the first line of the above function:

  1. def tells the computer we are defining a function.

  2. function_name comes next. This is the name we are giving to our function. In the above example, we called it f_to_c, but we could have called it almost anything else as long as it’s one word and not a name that we need to use elsewhere, or that’s already a built-in function (e.g., import would not work).

  3. (parameter) is the value, object, or data that we’ll put into the function. This is known as the argument. We give it a name (such as temp_in_f, or x, etc.) so we can reference it in the body of the function. We can also have more than one argument, as we’ll see below.

  4. : indicates the end of the header. This notation may seem minor, but your function will return an error if you leave this out!

The next component is on the second line:

  1. The '''docstring''' is an optional documentation string that describes (usually in human words!) what the function does. Your function will work without this, but it’s a very good habit to get into as your functions become more complicated and/or you are working with multiple functions. This will make your future lives much easier – and will be appreciated by anyone trying to work with or learn from your code.

Note the annotation with three ''' could also be written with three """. Three quotes (single or double) indicate to the computer that you are creating a docstring, which is read by the interpreter. This is different from when we comment out with a #, which is skipped by the interpreter. A docstring also can be written over multiple lines. Note that you could in many cases get away with simply writing a #comment that explains your function, but it’s worth getting into the habit of writing a docstring now.

The final component of a function is the function body:

  1. The statement is the actual content of the function. This is where you tell the computer what to do; i.e., what you specifically want the function to carry out (in the above example, we instructed the computer to subtract 32 from the Farenheit temperature we supply and then multiply that value by \(5/9\).

All the pieces of a user-defined function put together look as follows:

def function_name(parameter):
    '''docstring'''
    statement(s)

Examples of user-defined functions#

Here are some more examples of simple functions. Try these out on your own, try changing different elements to gain intuition for how they work, and, of course, try writing some of your own as well.

It should be pretty obvious what the below function does:

def triple(number):
    '''Triple a given number'''
    return 3*number

The above function takes any given number and triples it. Let’s check that it works as we think it does:

triple(20)
60
triple(3.14)
9.42
triple(20000000000000000)
60000000000000000

Pretty good! Here’s another, followed by some implementations:

def sq_root(x):
    '''Find the square root of x'''
    return x ** (1/2)                  # note, this is just regular old math, so the ( ) around 1/2 are necessary!
sq_root(9)
3.0
sq_root(200)
14.142135623730951
sq_root(-200)                      
(8.659560562354933e-16+14.142135623730951j)

Notice for fun above we the j suffix, creating a complex number. Remember also, we could have simply imported a function to find the square root, as we’ve done before:

import math
math.sqrt(200)
14.142135623730951

It’s really up to us whether we want to define our own function in this case or import one. As discussed above, later on you may have principled reasons for defining your own versus importing a function (e.g., practice, robustness, aesthetics, puritanism, ease, and more).

Functions with more than one parameter#

We can pass more than one parameter to a function. In principle, you can in fact pass many parameters to a function, but it’s generally advisable to stick to two or perhaps three at most. There are several reasons for this, some a matter of aesthetics/elegance, but more importantly, including many parameters can both obscure and create problems in your code. We’ll leave this here for now, but if you want to read more, you can see the book Clean Code by Robert C. Martin, among many others.

With that, here’s an example with two parameters. It will take two arguments – one for x, and one for y – which we’ll test.

def percent(x, y):
    '''Find what percent x is of y'''
    return (x/y)*100
percent(5, 100)
5.0
percent(66, 75)
88.0

Again, we have some freedom with what we name our parameters:

def total_price(price, tax):
    '''Find the total price of something by adding sales tax to the listed price'''
    return price + tax*price
total_price(2, .15)
2.3

In the above example, we are finding the total price of a good that costs $2.00 and has a sales tax of 15%.

Finally, a common (and very useful) practice is to define our parameters above our function. This is useful because as we write longer and longer functions (or blocks of code with multiple functions, perhaps even all in one cell), we may want to be able to quickly manipulate the arguments we pass to them, without having to scroll through the entire function, or run multiple cells to get our answer. For example, we can do the entire exercise above in just one cell:

price = 2
tax = 0.15

def total_price(price, tax):
    '''Find the total price of something by adding sales tax to the listed price'''
    return price + tax*price

total_price(price, tax)
2.3

This can be handy if later we want to quickly find the total price – all we need to do is simply type something new into the first two lines of our cell. Really, we’ve created our own little module for finding the total price of a good – nice!