20.5
Python Fundamentals - Week 1
CU Boulder ATOC
2026-01-01
Due Sunday at 12pm:
Office Hours:
Will: Tu / Th 11:15-12:15p Aerospace Cafe
Aiden: M / W 4-5p DUAN D319

What You’ll Do: - Read NetCDF climate data files - Process time series (temperature, precipitation) - Create maps and plots - Run statistical analyses - Automate repetitive tasks
Why Python? - Free and open source - Huge scientific community - Libraries: NumPy, Pandas, Xarray - Same tool for analysis AND visualization - Reproducible research
.ipynb).py)Pro tip: Start in notebook → Move working code to scripts → Import scripts into notebooks
Think of a variable as a labeled box that holds a value:
Why it matters: You’ll read your code 10× more than you write it!
These are three different variables:
temperature = 20
Temperature = 25
TEMPERATURE = 30
⚠️ Common mistake: calling Temperature when you meant temperature
Python has several data types. For now, focus on these:
Note: There are other types (complex, boolean, None) - we’ll see them later when needed
What will this print?
Answer:
<class 'str'>
<class 'int'>
⚠️ "20" in quotes is a string, not a number!
Predict the output:
Python follows standard math rules (PEMDAS):
Range: 5°C
Calculate apparent temperature (feels-like temperature):
Actual: 20°C
Feels like: 16.7°C
Key takeaway: Use descriptive variable names and comments for complex formulas
Strings hold text - essential for file paths, station names, labels:
The old way (avoid):
The modern way (use this!):
Station: Boulder, Temp: 20.547°C
Station: Boulder, Temp: 20.5°C
Why f-strings? Cleaner, faster, fewer errors than old methods
With your neighbor (2 min): What does this print?
Answer:
Denver (elevation 1609m): 16°C
Note: .0f rounds to 0 decimal places
Atmospheric data is often sequential: - Hourly temperatures over a day - Daily rainfall over a month - Pressure readings from a sensor
Lists store ordered sequences:
Coming from Matlab/Fortran? This is different!
temps = [15.2, 18.7, 22.1, 19.8]
↓ ↓ ↓ ↓
Index: 0 1 2 3
length = end - startNegative indices count from end:
Predict what happens:
IndexError: list index out of range
Remember: For a list of length n, valid indices are 0 to n-1
Get multiple elements with slicing: list[start:stop:step]
Key insight: [start:stop] includes start, excludes stop (like range())
What will this print?
Real atmospheric data has attributes: - Station name, location, elevation - Variable names and units - Quality control flags
Dictionaries store key-value pairs:
Use square brackets with the key:
Boulder: 20.5°C
⚠️ Not dot notation! station.name won’t work (common mistake from other languages)
Predict what happens:
KeyError: 'humidity'
The Safe Way:
Humidity: N/A
Humidity not available
Programs need to make decisions:
Use these to create conditions:
Python uses indentation to show code blocks:
✅ Correct:
Output:
It's warm
Stay cool!
This always runs
Pro tip: Configure your editor to insert 4 spaces when you press Tab
With your neighbor (3 min): What does this print when temp = 18 and wind = 25?
Answer: "Chilly and windy" (meets second condition)
Atmospheric science = LOTS of data:
Loops repeat operations:
Structure: for item in sequence:
Sometimes you need both the value and its position:
Hour 0: 15.2°C
Hour 1: 18.7°C
Hour 2: 22.1°C
Hour 0: 15.2°C
Hour 1: 18.7°C
Hour 2: 22.1°C
Prefer enumerate - cleaner and less error-prone
For loops: “Do this N times” (most common)
While loops: “Do this until condition is False” (less common, risky)
Readings: [10, 12, 14, 16, 18]
⚠️ Danger: If condition never becomes False → infinite loop! Make sure loop will eventually exit.
You’ll write the same operations repeatedly:
Functions make code reusable and testable:
20°C = 68.0°F
0°C = 32.0°F
-40°C = -40.0°F
Key parts:
def starts function definitionreturn sends value backdef wind_chill(temp_c, wind_kmh):
"""
Calculate wind chill temperature
Args:
temp_c: Air temperature in Celsius
wind_kmh: Wind speed in km/h
Returns:
Wind chill temperature in Celsius
"""
if wind_kmh < 4.8: # No wind chill at low speeds
return temp_c
wc = 13.12 + 0.6215*temp_c - 11.37*(wind_kmh**0.16) + 0.3965*temp_c*(wind_kmh**0.16)
return round(wc, 1)
# Test the function
print(f"Wind chill: {wind_chill(-10, 30)}°C")
print(f"Wind chill: {wind_chill(20, 5)}°C")Wind chill: -19.5°C
Wind chill: 21.1°C
Defining ≠ Running:
Average: 18.0°C
Think of it like: Define = “write recipe”, Call = “cook the food”
Good candidates for functions:
Example:
1. What’s wrong with this code?
2. How do you safely access a dictionary key?
3. What does this output?
Answers: 1) IndexError (index 3 doesn’t exist), 2) Use .get(), 3) Prints 0, 2, 4
Atmospheric datasets combine multiple types:
# List of weather stations (each is a dictionary)
stations = [
{
"name": "Boulder",
"lat": 40.01,
"temps": [15, 18, 20, 19, 16] # List inside dictionary!
},
{
"name": "Denver",
"lat": 39.74,
"temps": [17, 20, 22, 21, 18]
}
]
# Access nested data
print(f"Station: {stations[0]['name']}")
print(f"First temp at Boulder: {stations[0]['temps'][0]}°C")Station: Boulder
First temp at Boulder: 15°C
Loop through stations and analyze each:
stations = [
{"name": "Boulder", "temps": [15, 18, 20, 19, 16]},
{"name": "Denver", "temps": [17, 20, 22, 21, 18]}
]
for station in stations:
name = station["name"]
temps = station["temps"]
avg_temp = sum(temps) / len(temps)
max_temp = max(temps)
print(f"{name}:")
print(f" Average: {avg_temp:.1f}°C")
print(f" Maximum: {max_temp}°C")Boulder:
Average: 17.6°C
Maximum: 20°C
Denver:
Average: 19.6°C
Maximum: 22°C
def flag_outliers(measurements, threshold=2):
"""Flag measurements > threshold std devs from mean"""
import statistics
mean = statistics.mean(measurements)
stdev = statistics.stdev(measurements)
flagged = []
for i, value in enumerate(measurements):
z_score = abs(value - mean) / stdev
if z_score > threshold:
flagged.append({"index": i, "value": value, "z_score": z_score})
return flagged
# Test with outlier
temps = [20, 21, 19, 22, 200, 20, 14, 14, 22] # 200 is clearly wrong
outliers = flag_outliers(temps)
print(f"Found {len(outliers)} outliers: {outliers}")Found 1 outliers: [{'index': 4, 'value': 200, 'z_score': 2.66328493540662}]
Everyone writes bugs. Even experienced programmers.
The difference: Experienced programmers debug efficiently.
print(type(x))print(len(data))1. IndexError
2. KeyError
3. TypeError
Strategy 1: Print Everything
i=0, temp=15, type=<class 'int'>
temp_f=59.0
i=1, temp=18, type=<class 'int'>
temp_f=64.4
i=2, temp=20, type=<class 'int'>
temp_f=68.0
Strategy 2: Test with Simple Data
Strategy 3: Add Assert Statements
This code has bugs. Find them with your neighbor (5 min):
Bugs: 1. range(len(stations) + 1) → IndexError (goes to 4, but only 3 items) 2. Fix: range(len(stations))
You are writing for:
# Calculate potential temperature
# Θ = T * (P0/P)^(R/cp)
# where P0 = 1000 hPa, R/cp = 0.286
def potential_temperature(temp_k, pressure_hpa):
"""
Calculate potential temperature
Args:
temp_k: Temperature in Kelvin
pressure_hpa: Pressure in hPa
Returns:
Potential temperature in Kelvin
"""
P0 = 1000 # Reference pressure (hPa)
kappa = 0.286 # R/cp for dry air
return temp_k * (P0 / pressure_hpa) ** kappa
print(f"Theta = {potential_temperature(280, 850):.1f}K")Theta = 293.3K
❌ Bad (obvious):
❌ Bad (outdated):
Rule of thumb: Comment the why, not the what
Always include docstrings for functions:
def saturation_vapor_pressure(temp_c):
"""
Calculate saturation vapor pressure using Tetens formula
Valid for temperatures between -40°C and 50°C
Args:
temp_c (float): Temperature in Celsius
Returns:
float: Saturation vapor pressure in hPa
Example:
>>> saturation_vapor_pressure(20)
23.39
References:
Tetens, O. (1930). Über einige meteorologische Begriffe.
Zeitschrift für Geophysik, 6, 297-309.
"""
return 6.112 * (2.71828 ** ((17.67 * temp_c) / (temp_c + 243.5)))
# Docstring shows up in help!
print(f"es(20°C) = {saturation_vapor_pressure(20):.2f} hPa")es(20°C) = 23.37 hPa
Jupyter notebooks support LaTeX math - essential for atmospheric science!
Use single $ for inline equations:
Markdown:
Renders as:
The ideal gas law is \(PV = nRT\) where \(P\) is pressure.
Common LaTeX symbols you’ll need:
Renders: - \(\theta\), \(\rho\), \(\omega\) - \(T_2\), \(T_{2m}\) - \(x^2\), \(x^{n+1}\) - \(\frac{a}{b}\)
Pro tip: Use LaTeX equation editor to build complex equations visually
Work with your neighbor (10 min):
Given this weather station data, write code to:
Bonus: Write a function to make it reusable!
stations = [
{"name": "Boulder", "temps": [20, 22, 21, 19, 20]},
{"name": "Denver", "temps": [22, 24, 23, 21, 22]},
{"name": "Vail", "temps": [-5, -2, 0, -3, -4]},
{"name": "Suspect", "temps": [100, 105, 98, 102, 99]}
]
def analyze_station(station):
"""Calculate stats and QC for a station"""
temps = station["temps"]
avg = sum(temps) / len(temps)
# Quality control
valid = all(-50 <= t <= 50 for t in temps)
return {
"name": station["name"],
"avg_temp": avg,
"valid": valid
}
# Process all stations
results = [analyze_station(s) for s in stations]
valid_results = [r for r in results if r["valid"]]
warmest = max(valid_results, key=lambda x: x["avg_temp"])
print(f"Warmest station: {warmest['name']} ({warmest['avg_temp']:.1f}°C)")
for r in results:
status = "✓" if r["valid"] else "✗ FLAGGED"
print(f"{r['name']}: {r['avg_temp']:.1f}°C {status}")Warmest station: Denver (22.4°C)
Boulder: 20.4°C ✓
Denver: 22.4°C ✓
Vail: -2.8°C ✓
Suspect: 100.8°C ✗ FLAGGED
For homework, you’ll work with: - Actual NOAA weather station data - Multiple stations across Colorado - Calculating degree days (growing season metrics) - Handling missing data - Creating summary statistics
This week’s skills prepare you for that!
Why arrays?
Preview:
Can you:
If any are unclear, ask now or in office hours!
Due Sunday at 12pm:
1. Lab 1
2. HW1:
Start early! Don’t wait until Saturday night 😅
Stuck on homework?
Online resources:
Remember: Asking questions is part of learning!
Today we covered:
Most important: Practice! Use these skills in Lab 1 and HW1.
Prof. Will Chapman
📧 wchapman@colorado.edu 🌐 willychap.github.io 🏢 SEEC Building (N258), East Campus
Office Hours:
See you next week!

ATOC 4815/5815 - Week 1