End interactive session 1C
Code
import pandas as pd
import matplotlib.pyplot as plt
url = "https://eds-217-essential-python.github.io/data/toolik_weather.csv"
df = pd.read_csv(url)π¬ The Python Data Science Workflow, Part 1: Import to Aggregate
This afternoon we run a complete data science workflow, from a raw file sitting on the internet to a monthly climate summary of Arctic air temperature. We run through all of it once today, and over the next six days (Days 2 through 7) we will learn how each part of it was built.
You are not expected to understand every line today. Your job is to copy code, run it, and change one thing at a time. Our job (thatβs Cella and Kelly) is to show you what a whole workflow looks like from one end to the other. By the end of Day 7 you should know how all of it works.
We will use real Arctic weather data from the Arctic LTER station at Toolik Lake, Alaska: daily measurements from 1988 onward. Part 1 takes one path through the ten steps: Import, Explore, Group and Aggregate, which are steps 1, 2, 7 and 8. We skip Clean, step 3, because the one column we work with today has no missing values, though other columns in the file do have gaps. Session 1D picks up with Visualize and Export, and names all ten steps of the workflow we are working through.
Set up your notebook with the usual ritual:
Create the file. In the Explorer, hover over the EDS217 heading and click New Fileβ¦, then type the name in full, extension included: Session_1C_Workflow_1.ipynb
Check the kernel. The Kernel Selector in the notebookβs action bar should read Python 3.11.15 (Conda: eds217). If it reads anything else, click it, choose Change Kernel, and pick the eds217 entry.
Add a title cell. Click + Markdown in the action bar, and give it this content, with todayβs date:
# Day 1: Session 1C - The Python Data Science Workflow, Part 1
[Session Webpage](https://eds-217-essential-python.github.io/course-materials/interactive-sessions/1c_data_science_workflow_1.html)
Date: 08/31/2026Ctrl + S (Cmd + S on macOS), and keep saving as you go.Save often (Ctrl+S / Cmd+S).
This afternoon we run code you cannot fully read yet. To make that manageable, every step of the workflow follows the same small pattern. We chose this pattern for Day 1 to help you build confidence before you build understanding.
Each step has up to three parts:
df, so they leave your data alone. The next canonical cell brings everyone back together.Our goal on Day 1 is to see the whole workflow and get a feel for what each step is for, and mastering the syntax comes later. Running working code shows you what each step does before you have to write it yourself, changing one thing gives you a safe way to experiment without breaking your data, and writing a sentence about the result gives you some practice with the one skill you already have.
Import means loading your tools and then loading your data.
β Canonical cell. Copy and run it exactly:
The import ... as ... lines load Python toolkits under short nicknames. Two of them run today: pd for pandas (data) and plt for matplotlib (plots). A third, np for numpy (math), turns up later in the course. pd.read_csv(url) reads a CSV straight from the web into a DataFrame, a table, which we store in df.
Today read_csv() needs only one thing: the url of a CSV file. It can do a great deal more, and we will get to the rest of it on Day 2.
read_csv builds a table, and you can give that table any name you like. Load the same data into a new variable, using one name from this menu:
climate = pd.read_csv(url)toolik = pd.read_csv(url)weather = pd.read_csv(url)Your original df stays exactly as it was. You have simply made a second copy under a new name.
Write an f-string that names what you loaded. For example, set dataset = "Toolik weather" and then print f"I loaded the {dataset} dataset."
Explore means getting to know the table before you analyze it.
β Canonical cell. Preview the first rows:
| Year | Month | Date | LTER_Site | Station | Daily_AirTemp_Mean_C | Flag_Daily_AirTemp_Mean_C | Daily_AirTemp_AbsMax_C | Flag_Daily_AirTemp_AbsMax_C | Daily_AirTemp_AbsMin_C | ... | Daily_Precip_Total_mm | Flag_Daily_Precip_Total_mm | Daily_windsp_mean_msec | FLAG_Daily_windsp_mean_msec | Daily_Windspeed_AbsMax_m_s | Daily_globalrad_total_jcm2 | FLAG_Daily_globalrad_total_mjm2 | Moss | Soil20cm | Comments | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1988 | 6 | 19880601 | ARC | TLKMAIN | 8.4 | E | NaN | NaN | NaN | ... | 0.0 | E | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Air temp 1 & 5 meter estimated from regressio... |
| 1 | 1988 | 6 | 19880602 | ARC | TLKMAIN | 6.0 | E | NaN | NaN | NaN | ... | 0.0 | E | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Air temp 1 & 5 meter estimated from regressio... |
| 2 | 1988 | 6 | 19880603 | ARC | TLKMAIN | 5.8 | E | NaN | NaN | NaN | ... | 0.0 | E | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Air temp 1 & 5 meter estimated from regressio... |
| 3 | 1988 | 6 | 19880604 | ARC | TLKMAIN | 1.8 | E | NaN | NaN | NaN | ... | 0.0 | E | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Air temp 1 & 5 meter estimated from regressio... |
| 4 | 1988 | 6 | 19880605 | ARC | TLKMAIN | 6.8 | E | NaN | NaN | NaN | ... | 2.5 | E | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Air temp 1 & 5 meter estimated from regressio... |
5 rows Γ 21 columns
Run df.info(), which the required sentence below needs, then try one other no-argument view of the same table:
df.info() shows the column names, types, and how many values are present.df.describe() shows summary statistics for the number columns.Each one looks at df without changing it.
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 11171 entries, 0 to 11170
Data columns (total 21 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Year 11171 non-null int64
1 Month 11171 non-null int64
2 Date 11171 non-null int64
3 LTER_Site 11171 non-null object
4 Station 11171 non-null object
5 Daily_AirTemp_Mean_C 11171 non-null float64
6 Flag_Daily_AirTemp_Mean_C 1310 non-null object
7 Daily_AirTemp_AbsMax_C 11001 non-null float64
8 Flag_Daily_AirTemp_AbsMax_C 1020 non-null object
9 Daily_AirTemp_AbsMin_C 10967 non-null float64
10 Flag_Daily_AirTemp_AbsMin_C 1236 non-null object
11 Daily_Precip_Total_mm 10751 non-null float64
12 Flag_Daily_Precip_Total_mm 3169 non-null object
13 Daily_windsp_mean_msec 10345 non-null float64
14 FLAG_Daily_windsp_mean_msec 1 non-null object
15 Daily_Windspeed_AbsMax_m_s 10325 non-null float64
16 Daily_globalrad_total_jcm2 4118 non-null float64
17 FLAG_Daily_globalrad_total_mjm2 15 non-null object
18 Moss 10333 non-null float64
19 Soil20cm 10351 non-null float64
20 Comments 8924 non-null object
dtypes: float64(9), int64(3), object(9)
memory usage: 1.8+ MB
| Year | Month | Date | Daily_AirTemp_Mean_C | Daily_AirTemp_AbsMax_C | Daily_AirTemp_AbsMin_C | Daily_Precip_Total_mm | Daily_windsp_mean_msec | Daily_Windspeed_AbsMax_m_s | Daily_globalrad_total_jcm2 | Moss | Soil20cm | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 11171.000000 | 11171.000000 | 1.117100e+04 | 11171.000000 | 11001.000000 | 10967.000000 | 10751.000000 | 10345.000000 | 10325.000000 | 4118.000000 | 10333.000000 | 10351.000000 |
| mean | 2003.203384 | 6.570674 | 2.003271e+07 | -8.196437 | -3.447132 | -13.295924 | 0.950953 | 2.995592 | 6.321288 | 1324.307188 | -1.471402 | -2.055647 |
| std | 8.831548 | 3.443637 | 8.830795e+04 | 14.831314 | 14.805298 | 14.727318 | 2.749060 | 1.553574 | 2.878432 | 866.670139 | 7.354649 | 5.555551 |
| min | 1988.000000 | 1.000000 | 1.988060e+07 | -55.600000 | -53.600000 | -57.600000 | 0.000000 | 0.000000 | 0.000000 | -2.000000 | -24.000000 | -21.000000 |
| 25% | 1996.000000 | 4.000000 | 1.996012e+07 | -19.700000 | -14.200000 | -25.600000 | 0.000000 | 2.000000 | 4.300000 | 591.000000 | -7.000000 | -6.000000 |
| 50% | 2003.000000 | 7.000000 | 2.003092e+07 | -7.100000 | -2.900000 | -12.300000 | 0.000000 | 2.800000 | 5.900000 | 1268.000000 | -2.000000 | -1.000000 |
| 75% | 2011.000000 | 10.000000 | 2.011051e+07 | 4.400000 | 8.900000 | -0.100000 | 0.400000 | 3.600000 | 7.800000 | 2026.000000 | 4.000000 | 2.000000 |
| max | 2018.000000 | 12.000000 | 2.018123e+07 | 20.700000 | 28.200000 | 14.900000 | 64.500000 | 12.500000 | 27.000000 | 3205.000000 | 19.000000 | 11.000000 |
Before you trust a dataset, run a quick health check. The health check is one line of code that you will reuse all course long, and it counts the missing values in every column:
Year 0
Month 0
Date 0
LTER_Site 0
Station 0
Daily_AirTemp_Mean_C 0
Flag_Daily_AirTemp_Mean_C 9861
Daily_AirTemp_AbsMax_C 170
Flag_Daily_AirTemp_AbsMax_C 10151
Daily_AirTemp_AbsMin_C 204
Flag_Daily_AirTemp_AbsMin_C 9935
Daily_Precip_Total_mm 420
Flag_Daily_Precip_Total_mm 8002
Daily_windsp_mean_msec 826
FLAG_Daily_windsp_mean_msec 11170
Daily_Windspeed_AbsMax_m_s 846
Daily_globalrad_total_jcm2 7053
FLAG_Daily_globalrad_total_mjm2 11156
Moss 838
Soil20cm 820
Comments 2247
dtype: int64
Look at Daily_AirTemp_Mean_C. It has zero missing values, so the column we are about to analyze is complete. A few other columns do have gaps, which is normal. We only need our column to be clean.
df.info() reported how many rows the dataset has. Read that number, then report it in your own f-string. For example:
n_rows = 11171 # the number info() showed you
print(f"The Toolik dataset has {n_rows} daily records.")Now write a second sentence of your own, maybe about how many columns there are.
The next step in a normal workflow is Clean: fixing missing values, wrong types, and duplicate rows. Our health check already showed that Daily_AirTemp_Mean_C has no missing values, so we have nothing to clean today. Todayβs path now jumps to step 7, because steps 4, 5 and 6, Filter, Sort and Transform, are not ones this analysis needs.
Enjoy this while it lasts. Most real datasets need real cleaning, and it is often the biggest part of a project. We spend Day 4 on cleaning and transforming. Today the data came ready to use.
Group means splitting the rows into buckets by some key. Here we bucket every daily record by its Month, so all the Januaries sit together, all the Februaries, and so on.
β Canonical cell. Copy and run it exactly:
Change only the key you group by. Pick one from this menu:
by_year = df.groupby('Year') buckets records by year, which gives many groups.by_station = df.groupby('Station') gives a single bucket here, because every row is the same station.Both read df without changing it, and neither touches your monthly grouping.
Aggregate means collapsing each bucket down to a single number. Here we take the mean temperature within each month. Notice the two-step pattern: first group, then pick a column and average it.
β Canonical cell. Copy and run it exactly:
Month
1 -22.889032
2 -20.700945
3 -20.692366
4 -11.762556
5 -0.795161
6 8.589892
7 11.222060
8 7.234860
9 -0.110753
10 -10.544225
11 -18.339355
12 -21.442560
Name: Daily_AirTemp_Mean_C, dtype: float64
The result is twelve numbers, one average temperature per month, which summarizes thousands of daily records into a clear seasonal pattern.
Keep the grouping and change only the column you average. Pick one from this menu:
monthly['Daily_Precip_Total_mm'].mean() gives average daily precipitation.monthly['Daily_AirTemp_AbsMax_C'].mean() gives the average daily high temperature.Store the result under a new name so your monthly_means stays put.
Month
1 0.316590
2 0.490909
3 0.264937
4 0.317931
5 0.604227
6 1.537217
7 2.674922
8 2.041935
9 1.249828
10 0.671121
11 0.506000
12 0.425243
Name: Daily_Precip_Total_mm, dtype: float64
Look at your monthly_means, pick one monthβs value, read it off, and report it in an f-string. For example:
july_temp = 11.2 # July's value, read from monthly_means
print(f"At Toolik, the average July temperature is {july_temp} degrees Celsius.")Now write one more sentence for the month you find most surprising.
Now poke at it yourself. Try a different column, a different grouping key, or a deliberate typo. Errors are expected today, and they are welcome: a red error message reports which line Python could not run and what went wrong with it, so read the message before you change anything. See what you can make happen!
When the five minutes are up, run this cell to reload the data and rebuild the canonical results. Everyone returns to the same state, ready for Part 2:
pd.read_csv(url) loads a CSV from the web into a DataFrame. .head(), .info(), and .describe() are your no-argument ways to look at it.df.isnull().sum() is the health check for counting missing values.monthly = df.groupby('Month'), then monthly['Daily_AirTemp_Mean_C'].mean().β‘οΈ Next: In The Python Data Science Workflow, Part 2 you will visualize these monthly means, save your results to a file, and see all ten steps of the workflow laid out in order.
End interactive session 1C