Code
import pandas as pd
url = 'https://eds-217-essential-python.github.io/data/marine_microplastics.csv'
plastics = pd.read_csv(url)
plastics.shape(16245, 22)
βοΈ Write It Once, Name It, Use It

A lightbulb, in the middle of having an idea. MidJourney 5
Last night you wrote this three times, changing one word each time:
and we told you to notice how that felt. Copying a line and editing one word in the copy is how almost every data-analysis bug gets written. You fix the original, forget the copies, and now your notebook contains two answers to the same question.
A function is the cure. You write the work once, give it a name, and then you have a new verb that you and pandas can both use.
By the end of this session you will be able to:
def and hand a value back with return.apply()Create a new notebook:
Ctrl + Shift + P (Cmd + Shift + P on macOS) and run Create: New Jupyter Notebook.Save your notebook (Ctrl + S, or Cmd + S on macOS) as: Session_4B_Functions.ipynb
Add a title cell (Markdown), updating the date to today:
# Day 4: Session 4B - Write It Once, Name It, Use It
[Session Webpage](https://eds-217-essential-python.github.io/course-materials/interactive-sessions/4b_functions.html)
Date: 09/03/2026(16245, 22)
Save your work frequently with Ctrl+S (Cmd+S on macOS).
def and returnHere is a whole function:
Four pieces, and they are always in this order:
def announces that a definition is coming.celsius_to_fahrenheit is the name. You choose it, and you should choose it well.(celsius) is the parameter: a name for the value that will be handed in.def line is the body, and return says what comes back.Running that cell does not convert anything. It teaches Python a new word. To use it, call it:
The indentation is not decoration. Python uses it to decide where the body ends, exactly the way it decided which lines belonged to an if block yesterday.
Write a function called meters_to_feet that takes a number of metres and returns the same distance in feet. One metre is 3.28084 feet. Call it on 1000 and check that you get about 3,281.
The name is most of the value. Compare:
They compute the same thing. Only one of them will still make sense to you in November.
The line in triple quotes is a docstring. It is optional, it goes on the first line of the body, and Python will show it back to you when you ask for help:
Help on function ppm_to_ppb in module __main__:
ppm_to_ppb(concentration_ppm)
Convert a concentration from parts per million to parts per billion.
Write one whenever the functionβs name does not fully explain itself.
A parameter can carry a default, which is used when the caller does not supply that argument:
Notice what is inside that function: the if/elif/else you learned yesterday, doing exactly what it did yesterday. Functions are not a new kind of code. They are a wrapper that lets you name code you already know how to write.
Python needs to match the values you pass to the parameters in order, so every parameter with a default has to come after every parameter without one. The error message says non-default argument follows default argument, which is worth recognising on sight.
When you call a function, you can name the arguments instead of relying on their order:
Both give the same answer, because naming the arguments makes their order irrelevant. This is the same thing you have been doing all week without thinking about it:
pd.read_csv(url, index_col='entity')
df.sort_values('visitors', ascending=False)
df.dropna(subset=['Measurement'])index_col=, ascending= and subset= are keyword arguments to somebody elseβs functions. Now you know what the equals sign was doing.
Write a function classify_latitude that takes a latitude and returns 'tropical' if it is between -23.5 and 23.5, 'polar' if it is below -66.5 or above 66.5, and 'temperate' otherwise. Test it on 0, 45, and -70.
Keep this function. You will use it in the next section.
.apply()You now have functions that work on one number. Your data has ten thousand numbers in a column.
.apply() runs a function once for every value in a column and gives you back a column of the results:
0 -58.428300
1 -51.308200
2 -51.826667
3 -31.696000
4 6.350000
Name: Latitude, dtype: float64
0 temperate
1 temperate
2 temperate
3 temperate
4 tropical
Name: Latitude, dtype: object
Read the call carefully, because the shape of it matters:
The functionβs name goes inside the parentheses, with no parentheses of its own. You are handing pandas the function itself, not the result of calling it. Add parentheses by mistake and Python tries to call your function with no arguments, and tells you so.
Like everything else in pandas, .apply() returns a new column and changes nothing. Assign it to keep it:
zone
temperate 12275
tropical 3914
polar 56
Name: count, dtype: int64
That last line is a real result. Three quarters of this archive was sampled in temperate water, and 56 samples out of 16,245 came from the poles, which tells you something about where the ships were and nothing at all about where the plastic is.
π .apply() hangs off a column, like .isin() and .fillna() do. The function you hand it should take one value and return one value. Pandas takes care of the repetition.
Write a function hemisphere that returns 'northern' when a latitude is above zero and 'southern' otherwise. Apply it to plastics['Latitude'], store the result in a new column called hemisphere, and use .value_counts() to report the split.
Here is last nightβs problem, solved. The top-N sentence, written once:
| Measurement | Unit | Oceans | |
|---|---|---|---|
| 12590 | 110480.0 | pieces/m3 | Atlantic Ocean |
| 14159 | 103120.0 | pieces/m3 | Atlantic Ocean |
| 15428 | 85600.0 | pieces/m3 | Atlantic Ocean |
| 6622 | 60160.0 | pieces/m3 | Atlantic Ocean |
| 248 | 57680.0 | pieces/m3 | Atlantic Ocean |
| Latitude | Oceans | zone | |
|---|---|---|---|
| 2444 | 88.9613 | Arctic Ocean | polar |
| 15431 | 88.7435 | Arctic Ocean | polar |
| 8603 | 88.6493 | Arctic Ocean | polar |
| 8912 | 88.6113 | Arctic Ocean | polar |
| 14983 | 88.6113 | Arctic Ocean | polar |
| 3010 | 88.6113 | Arctic Ocean | polar |
| 4142 | 88.6113 | Arctic Ocean | polar |
| 15760 | 88.6113 | Arctic Ocean | polar |
| 10182 | 88.6113 | Arctic Ocean | polar |
| 9489 | 88.6097 | Arctic Ocean | polar |
Two parameters without defaults, one with. Change your mind about how ranking should work and there is exactly one line to edit.
Write a function count_missing(df, column) that returns the number of missing values in a named column. Use .isnull().sum() inside it. Call it on 'Measurement', 'Oceans', and 'SubRegions', and check your three answers against the .isnull().sum() you ran in 4A.
def name(parameter): opens a function; the indented body is what it does; return is what it hands back. No return means it hands back None.df['col'].apply(function_name) runs your function down a whole column. Pass the name, with no parentheses of its own.