ATOC 4815/5815

Python Fundamentals - Week 1

Will Chapman

CU Boulder ATOC

2026-01-01

Python Fundamentals I

Today’s Objectives

  • Understand why Python for atmospheric science
  • Work with essential data types (numbers, strings, lists)
  • Access and manipulate data with indexing
  • Organize data with dictionaries
  • Control program flow with if/else and loops
  • Write reusable functions

Reminders

Due Sunday at 12pm:

  • Lab 2
  • HW2

Office Hours:

Will: Tu / Th 11:15-12:15p Aerospace Cafe

Aiden: M / W 4-5p DUAN D319

DUAN Building

Python Ecosystem

Why Python for Atmospheric Science?

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

Two Ways to Write Python

Notebook (.ipynb)

  • Jupyter / VS Code
  • When: Exploring data, prototyping
  • Mix code, text, plots
  • Great for: homework, reports
  • Example: “Let me see what this dataset looks like”

Script (.py)

  • Plain text file
  • When: Production code, automation
  • Version control friendly
  • Run on HPC clusters
  • Example: “Process 100 years of data overnight”

Pro tip: Start in notebook → Move working code to scripts → Import scripts into notebooks

Variables & Types

Variables: Labeled Boxes for Data

Think of a variable as a labeled box that holds a value:

# The box labeled "temperature" holds the value 20.5
temperature = 20.5
print(temperature)
20.5

Good Names

temperature_celsius = 20.5
station_elevation = 1655
wind_speed_ms = 12.3

Bad Names

t = 20.5  # What is t?
x = 1655  # Elevation? Pressure?
a = 12.3  # Unclear

Why it matters: You’ll read your code 10× more than you write it!

Python is Case Sensitive

These are three different variables:

temperature = 20
Temperature = 25
TEMPERATURE = 30

print(f"temperature = {temperature}")
print(f"Temperature = {Temperature}")
print(f"TEMPERATURE = {TEMPERATURE}")
temperature = 20
Temperature = 25
TEMPERATURE = 30

⚠️ Common mistake: calling Temperature when you meant temperature

Essential Data Types

Python has several data types. For now, focus on these:

Numbers

# Integer (whole numbers)
days = 365
print(f"days: {days}, type: {type(days)}")

# Float (decimals)
temp = 20.5
print(f"temp: {temp}, type: {type(temp)}")
days: 365, type: <class 'int'>
temp: 20.5, type: <class 'float'>

Text

# String (text in quotes)
location = "Boulder"
print(f"location: {location}")
print(f"type: {type(location)}")
location: Boulder
type: <class 'str'>

Note: There are other types (complex, boolean, None) - we’ll see them later when needed

Check Your Understanding 🤔

What will this print?

temperature = "20"
pressure = 1013

print(type(temperature))
print(type(pressure))

Answer:

<class 'str'>
<class 'int'>

⚠️ "20" in quotes is a string, not a number!

Common Type Error

Predict the output:

temperature = "20"
adjustment = 5
result = temperature + adjustment
TypeError: can only concatenate str (not "int") to str

The Fix:

temperature = "20"
adjustment = 5
result = int(temperature) + adjustment  # Convert string to int
print(result)
25

Numeric Operations

Basic Math Operations

Python follows standard math rules (PEMDAS):

# Addition, subtraction
temp_day = 20
temp_night = 15
temp_range = temp_day - temp_night
print(f"Range: {temp_range}°C")
Range: 5°C
# Multiplication, division
wind_kmh = 36
wind_ms = wind_kmh / 3.6
print(f"Wind: {wind_ms} m/s")
Wind: 10.0 m/s
# Exponentiation
area_km = 10
area_m = area_km ** 2  # 10 squared
print(f"Area: {area_m} km²")
Area: 100 km²
# Modulus (remainder)
hours = 25
days = hours % 24
print(f"Remainder: {days} hours")
Remainder: 1 hours

Atmospheric Science Example

Calculate apparent temperature (feels-like temperature):

# Steadman's formula (simplified)
temperature_c = 20
wind_speed_kmh = 30
humidity_percent = 60

# Apparent temperature calculation
wind_chill = temperature_c - 0.4 * (temperature_c - 10) * wind_speed_kmh / 36
print(f"Actual: {temperature_c}°C")
print(f"Feels like: {wind_chill:.1f}°C")
Actual: 20°C
Feels like: 16.7°C

Key takeaway: Use descriptive variable names and comments for complex formulas

Strings

Working with Text Data

Strings hold text - essential for file paths, station names, labels:

# Single or double quotes both work
station_name = "Boulder"
data_source = 'NOAA'

# Combining strings
full_name = station_name + " (" + data_source + ")"
print(full_name)
Boulder (NOAA)

F-Strings: Modern String Formatting

The old way (avoid):

print("Station: " + station + ", Temp: " + str(temperature) + "°C")

The modern way (use this!):

station = "Boulder"
temperature = 20.547

# Put variables directly in the string with f""
message = f"Station: {station}, Temp: {temperature}°C"
print(message)

# Control decimal places
message = f"Station: {station}, Temp: {temperature:.1f}°C"
print(message)
Station: Boulder, Temp: 20.547°C
Station: Boulder, Temp: 20.5°C

Why f-strings? Cleaner, faster, fewer errors than old methods

Try It Yourself 💻

With your neighbor (2 min): What does this print?

city = "Denver"
elevation = 1609  # meters
temp = 15.7

report = f"{city} (elevation {elevation}m): {temp:.0f}°C"
print(report)

Answer:

Denver (elevation 1609m): 16°C

Note: .0f rounds to 0 decimal places

Lists & Indexing

Why Lists? → Time Series Data!

Atmospheric data is often sequential: - Hourly temperatures over a day - Daily rainfall over a month - Pressure readings from a sensor

Lists store ordered sequences:

# Temperature readings every hour (24 values)
temps = [15.2, 14.8, 14.5, 14.1, 14.0, 14.2,
         15.0, 16.5, 18.2, 20.1, 21.5, 22.3]

print(f"First reading: {temps[0]}°C")
print(f"Number of readings: {len(temps)}")
First reading: 15.2°C
Number of readings: 12

Python Indexing: Start at 0

Coming from Matlab/Fortran? This is different!

Python (0-indexed)

temps = [15.2, 18.7, 22.1, 19.8]
         ↓     ↓     ↓     ↓
Index:   0     1     2     3
temps = [15.2, 18.7, 22.1, 19.8]
print(f"First: temps[0] = {temps[0]}")
print(f"Second: temps[1] = {temps[1]}")
First: temps[0] = 15.2
Second: temps[1] = 18.7

Why 0-indexing?

  • Array offset from memory address
  • Most programming languages use it
  • Makes math simpler: length = end - start

Negative indices count from end:

temps = [15.2, 18.7, 22.1, 19.8]
print(f"Last: temps[-1] = {temps[-1]}")
print(f"Second to last: temps[-2] = {temps[-2]}")
Last: temps[-1] = 19.8
Second to last: temps[-2] = 22.1

Common Index Error

Predict what happens:

temps = [15.2, 18.7, 22.1]  # 3 items (indices 0,1,2)
print(temps[3])  # Try to access index 3
IndexError: list index out of range

Remember: For a list of length n, valid indices are 0 to n-1

temps = [15.2, 18.7, 22.1]
print(f"Length: {len(temps)}")
print(f"Valid indices: 0 to {len(temps)-1}")
print(f"Last item: temps[{len(temps)-1}] = {temps[len(temps)-1]}")
Length: 3
Valid indices: 0 to 2
Last item: temps[2] = 22.1

Slicing: Extracting Ranges

Get multiple elements with slicing: list[start:stop:step]

temps = [10, 12, 15, 18, 20, 22, 21, 19, 16, 14, 12, 11]  # 12 hourly readings
# First 3 values (indices 0,1,2)
morning = temps[0:3]
print(f"Morning: {morning}")

# Shortcut: omit 0
morning = temps[:3]
print(f"Morning: {morning}")
Morning: [10, 12, 15]
Morning: [10, 12, 15]
# Last 3 values
evening = temps[-3:]
print(f"Evening: {evening}")

# Every other value (step=2)
every_2hrs = temps[::2]
print(f"Every 2hrs: {every_2hrs}")
Evening: [14, 12, 11]
Every 2hrs: [10, 15, 20, 21, 16, 12]

Key insight: [start:stop] includes start, excludes stop (like range())

Check Your Understanding 🤔

What will this print?

daily_temps = [12, 14, 16, 18, 20, 19, 17, 15]

print(daily_temps[2:5])
print(daily_temps[::3])
print(daily_temps[-2])

Answer:

daily_temps = [12, 14, 16, 18, 20, 19, 17, 15]

print(daily_temps[2:5])    # [16, 18, 20]  (indices 2,3,4)
print(daily_temps[::3])    # [12, 18, 17]  (every 3rd)
print(daily_temps[-2])     # 17           (second from end)
[16, 18, 20]
[12, 18, 17]
17

Dictionaries

Why Dictionaries? → Station Metadata!

Real atmospheric data has attributes: - Station name, location, elevation - Variable names and units - Quality control flags

Dictionaries store key-value pairs:

station = {
    "name": "Boulder",
    "lat": 40.01,
    "lon": -105.25,
    "elevation": 1655,
    "active": True
}

print(f"Station: {station['name']}")
print(f"Elevation: {station['elevation']}m")
Station: Boulder
Elevation: 1655m

Accessing Dictionary Values

Use square brackets with the key:

station = {
    "name": "Boulder",
    "temp_c": 20.5,
    "pressure_hpa": 835
}

# Access values
name = station["name"]
temp = station["temp_c"]
print(f"{name}: {temp}°C")
Boulder: 20.5°C

⚠️ Not dot notation! station.name won’t work (common mistake from other languages)

Common Dictionary Error

Predict what happens:

station = {"name": "Boulder", "temp_c": 20.5}
print(station["humidity"])  # Key doesn't exist
KeyError: 'humidity'

The Safe Way:

station = {"name": "Boulder", "temp_c": 20.5}

# Use .get() with default value
humidity = station.get("humidity", "N/A")
print(f"Humidity: {humidity}")

# Check if key exists
if "humidity" in station:
    print(station["humidity"])
else:
    print("Humidity not available")
Humidity: N/A
Humidity not available

Lists vs Dictionaries: When to Use Each?

Use a List when:

  • ✅ Order matters
  • ✅ Sequential data (time series)
  • ✅ Access by position
  • ✅ All same type of thing

Example:

temperatures = [15, 16, 18, 20, 19]

Use a Dictionary when:

  • ✅ Named attributes
  • ✅ Metadata / properties
  • ✅ Access by name
  • ✅ Mixed types

Example:

station = {
    "name": "Boulder",
    "temp": 20.5,
    "active": True
}

Control Flow

Making Decisions with if/elif/else

Programs need to make decisions:

  • “If temperature < 0, issue frost warning”
  • “If wind speed > 15, flag as high wind day”
temperature = 25

if temperature >= 30:
    print("Hot day! Stay hydrated")
elif temperature >= 20:
    print("Pleasant day")
elif temperature >= 10:
    print("Cool day, bring a jacket")
else:
    print("Cold day!")
Pleasant day

Comparison Operators

Use these to create conditions:

temp = 20

print(f"temp == 20: {temp == 20}")  # Equal
print(f"temp != 15: {temp != 15}")  # Not equal
print(f"temp > 15: {temp > 15}")    # Greater than
print(f"temp <= 20: {temp <= 20}")  # Less or equal
temp == 20: True
temp != 15: True
temp > 15: True
temp <= 20: True

Combine conditions:

temp = 22
humid = 70

# Both conditions must be True
if temp > 20 and humid > 60:
    print("Warm and humid")

# Either condition can be True
if temp > 30 or humid > 80:
    print("Uncomfortable conditions")
Warm and humid

Python Syntax: Indentation Matters!

Python uses indentation to show code blocks:

Correct:

if temperature > 20:
    print("It's warm")
    print("Stay cool!")
print("This always runs")

Output:

It's warm
Stay cool!
This always runs

Wrong indentation:

if temperature > 20:
print("It's warm")  # IndentationError!

Rules:

  • Use 4 spaces (not tabs!)
  • : starts a block
  • All lines in block must align
  • Blank lines OK

Pro tip: Configure your editor to insert 4 spaces when you press Tab

Try It Yourself 💻

With your neighbor (3 min): What does this print when temp = 18 and wind = 25?

temp = 18
wind = 25

if temp < 10:
    print("Cold!")
elif temp < 20 and wind > 20:
    print("Chilly and windy")
elif temp < 20:
    print("Mild weather")
else:
    print("Warm day")

Answer: "Chilly and windy" (meets second condition)

Loops & Iteration

Why Loops? → Process Multiple Measurements

Atmospheric science = LOTS of data:

  • Convert 100 temperature values from C to F
  • Calculate statistics on 1000 measurements
  • Process files from 50 weather stations

Loops repeat operations:

temps_c = [15.2, 18.7, 22.1]

for temp in temps_c:
    temp_f = temp * 9/5 + 32
    print(f"{temp}°C = {temp_f:.1f}°F")
15.2°C = 59.4°F
18.7°C = 65.7°F
22.1°C = 71.8°F

For Loop Anatomy

Structure: for item in sequence:

for temperature in daily_temps:
    # Code here runs once for each temperature
    # 'temperature' holds the current value
    print(temperature)

Loop over a list:

stations = ["Boulder", "Denver", "Vail"]

for station in stations:
    print(f"Processing {station}")
Processing Boulder
Processing Denver
Processing Vail

Loop with range:

# range(n) gives 0,1,2,...,n-1
for i in range(3):
    print(f"Iteration {i}")
Iteration 0
Iteration 1
Iteration 2

Loop with Index (When You Need Position)

Sometimes you need both the value and its position:

temps = [15.2, 18.7, 22.1]

# Method 1: Loop with range(len())
for i in range(len(temps)):
    print(f"Hour {i}: {temps[i]}°C")
Hour 0: 15.2°C
Hour 1: 18.7°C
Hour 2: 22.1°C
# Method 2: enumerate (better!)
for i, temp in enumerate(temps):
    print(f"Hour {i}: {temp}°C")
Hour 0: 15.2°C
Hour 1: 18.7°C
Hour 2: 22.1°C

Prefer enumerate - cleaner and less error-prone

While Loops: Use with Caution

For loops: “Do this N times” (most common)

While loops: “Do this until condition is False” (less common, risky)

# Keep reading data until threshold met
measurement = 10
readings = []

while measurement < 20 and len(readings) < 5:
    readings.append(measurement)
    measurement += 2  # Simulate new reading

print(f"Readings: {readings}")
Readings: [10, 12, 14, 16, 18]

⚠️ Danger: If condition never becomes False → infinite loop! Make sure loop will eventually exit.

Functions

Why Functions? → Reusable Code

You’ll write the same operations repeatedly:

  • Convert temperature units
  • Calculate saturation vapor pressure
  • Read a specific data file format

Functions make code reusable and testable:

def celsius_to_fahrenheit(temp_c):
    """Convert Celsius to Fahrenheit"""
    temp_f = temp_c * 9/5 + 32
    return temp_f

# Now use it many times!
print(f"20°C = {celsius_to_fahrenheit(20)}°F")
print(f"0°C = {celsius_to_fahrenheit(0)}°F")
print(f"-40°C = {celsius_to_fahrenheit(-40)}°F")
20°C = 68.0°F
0°C = 32.0°F
-40°C = -40.0°F

Function Anatomy

def function_name(parameter1, parameter2):
    """
    Docstring: Explain what the function does

    Args:
        parameter1: Description
        parameter2: Description
    Returns:
        Description of return value
    """
    # Function body
    result = parameter1 + parameter2
    return result

Key parts:

  1. def starts function definition
  2. Name should be descriptive (use_underscores)
  3. Parameters in parentheses
  4. Docstring explains function (not optional!)
  5. return sends value back

Function with Multiple Parameters

def 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

Common Function Mistake

Defining ≠ Running:

# This DEFINES the function (but doesn't run it)
def calculate_average(values):
    """Calculate the mean of a list"""
    return sum(values) / len(values)

# Nothing happened yet!

# This CALLS (runs) the function
temps = [15, 18, 20, 19]
avg = calculate_average(temps)
print(f"Average: {avg}°C")
Average: 18.0°C

Think of it like: Define = “write recipe”, Call = “cook the food”

When Should You Write a Function?

Good candidates for functions:

  • ✅ Code you copy-paste more than twice
  • ✅ Complex calculation you’ll reuse
  • ✅ Task you want to test independently
  • ✅ Code that has a clear purpose

Example:

def quality_control_temp(temp):
    """Flag suspicious temperature readings"""
    if temp < -50 or temp > 50:
        return "SUSPECT"
    elif temp < -40 or temp > 45:
        return "CHECK"
    else:
        return "OK"

# Easy to use and test!
print(quality_control_temp(20))    # OK
print(quality_control_temp(100))   # SUSPECT
OK
SUSPECT

Python Fundamentals II

Today’s Objectives (Part II)

  • Combine data structures (lists of dictionaries)
  • Develop effective debugging strategies
  • Write clear documentation and comments
  • Practice with realistic atmospheric examples
  • Prepare for your first homework

Quick Review: Check Your Understanding

1. What’s wrong with this code?

temps = [10, 15, 20]
print(temps[3])

2. How do you safely access a dictionary key?

3. What does this output?

for i in range(3):
    print(i * 2)

Answers: 1) IndexError (index 3 doesn’t exist), 2) Use .get(), 3) Prints 0, 2, 4

Combining Data Structures

Real Data = Complex Structures

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

Processing Complex Data

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

Practical Example: Data Quality

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}]

Debugging

Debugging Mindset: Errors Are Learning

Everyone writes bugs. Even experienced programmers.

The difference: Experienced programmers debug efficiently.

  1. Slow down and read the error
    • What line failed?
    • What’s the error type?
    • What does message say?
  2. Compare expected vs. actual
    • Print variables
    • Check types: print(type(x))
    • Check shapes: print(len(data))
  1. Make the bug smaller
    • Create minimal example
    • Comment out code
    • Test one piece at a time
  2. Get help effectively
    • Google the error message
    • Stack Overflow (likely answered!)
    • Show: code, error, what you expected

Common Errors You’ll See

1. IndexError

temps = [10, 15, 20]
print(temps[5])  # Only indices 0,1,2 exist
# IndexError: list index out of range

2. KeyError

station = {"name": "Boulder"}
print(station["temp"])  # Key doesn't exist
# KeyError: 'temp'
# Fix: Use station.get("temp", default_value)

3. TypeError

temp = "20"
result = temp + 5  # Can't add string and int
# TypeError: can only concatenate str (not "int") to str
# Fix: result = int(temp) + 5

Debugging Strategies

Strategy 1: Print Everything

temps = [15, 18, 20]

for i, temp in enumerate(temps):
    print(f"i={i}, temp={temp}, type={type(temp)}")  # Debug print
    temp_f = temp * 9/5 + 32
    print(f"temp_f={temp_f}")  # Debug print
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

# Instead of 10,000 values, test with 3
temps = [10, 15, 20]  # Easier to verify output

Strategy 3: Add Assert Statements

def celsius_to_fahrenheit(temp_c):
    assert isinstance(temp_c, (int, float)), "temp must be a number"
    assert temp_c >= -273.15, "temp below absolute zero"
    return temp_c * 9/5 + 32

# This will catch bugs early
print(celsius_to_fahrenheit(20))
68.0

Debugging Exercise 💻

This code has bugs. Find them with your neighbor (5 min):

stations = ["Boulder", "Denver", "Vail"]
temps = [20, 22, 18]

# Calculate average temperature
total = 0
for i in range(len(stations) + 1):  # Bug 1
    print(f"Station: {stations[i]}, Temp: {temps[i]}")
    total = total + temps[i]

average = total / len(stations)
print(f"Average: {average}")

Bugs: 1. range(len(stations) + 1) → IndexError (goes to 4, but only 3 items) 2. Fix: range(len(stations))

Documentation

Why Comment Your Code?

You are writing for:

  1. Future you (6 months from now, you’ll forget)
  2. Collaborators (lab mates, advisor)
  3. Your published research (reproducibility!)
# 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

Good vs Bad Comments

Bad (obvious):

# Increment i
i = i + 1

# Print the temperature
print(temp)

Bad (outdated):

# Convert to Fahrenheit
temp_k = temp_c + 273.15  # WRONG!

Good (explains why):

# Use 2m temperature (not surface)
# because it's less affected by
# local surface heterogeneity
temp = data['t2m']

# Quality control: flag values
# outside climatological range
# based on Zhang et al. (2020)
if temp < -50 or temp > 50:
    flag_data()

Rule of thumb: Comment the why, not the what

Docstrings: Function Documentation

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

Math Equations in Notebooks

Jupyter notebooks support LaTeX math - essential for atmospheric science!

Inline Math

Use single $ for inline equations:

Markdown:

The ideal gas law is $PV = nRT$
where $P$ is pressure.

Renders as:

The ideal gas law is \(PV = nRT\) where \(P\) is pressure.

Display Math

Use double $$ for centered equations:

Markdown:

$$ T_f = T_c \times \frac{9}{5} + 32 $$

Renders as:

\[ T_f = T_c \times \frac{9}{5} + 32 \]

Atmospheric Science Equations

Common LaTeX symbols you’ll need:

Greek letters:
$\theta$ (theta)
$\rho$ (rho)
$\omega$ (omega)

Subscripts/Superscripts:
$T_2$ or $T_{2m}$
$x^2$ or $x^{n+1}$

Fractions:
$\frac{numerator}{denominator}$

Renders: - \(\theta\), \(\rho\), \(\omega\) - \(T_2\), \(T_{2m}\) - \(x^2\), \(x^{n+1}\) - \(\frac{a}{b}\)

Potential Temperature:

$$ \theta = T \left(\frac{P_0}{P}\right)^{\kappa} $$
where $\kappa = \frac{R}{c_p} = 0.286$

Renders: \[ \theta = T \left(\frac{P_0}{P}\right)^{\kappa} \] where \(\kappa = \frac{R}{c_p} = 0.286\)

Pro tip: Use LaTeX equation editor to build complex equations visually

Practice

Putting It All Together 💻

Work with your neighbor (10 min):

Given this weather station data, write code to:

  1. Calculate the average temperature at each station
  2. Find which station is warmest
  3. Flag any stations with temperatures outside -50 to 50°C range
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]}  # Bad data!
]

Bonus: Write a function to make it reusable!

Solution (One Approach)

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

Real-World Challenge 🌎

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!

Looking Ahead

Next Week: NumPy Arrays

Why arrays?

  • Lists are slow for large datasets
  • NumPy arrays = fast vectorized operations
  • Designed for scientific computing

Preview:

import numpy as np

# List way (slow)
temps_f = []
for temp_c in temps:
    temps_f.append(temp_c * 9/5 + 32)

# NumPy way (fast!)
temps_c = np.array([15, 18, 20, 22])
temps_f = temps_c * 9/5 + 32  # Operates on entire array!

Your Learning Checklist

Can you:

  • ✅ Explain why Python for atmospheric science?
  • ✅ Create and use variables with good names?
  • ✅ Work with numbers, strings, lists, dictionaries?
  • ✅ Access data with indexing (including negative indices)?
  • ✅ Use if/else to make decisions?
  • ✅ Write for loops to process data?
  • ✅ Write functions with clear docstrings?
  • ✅ Debug common errors (Index, Key, Type)?
  • ✅ Combine data structures (lists of dicts)?

If any are unclear, ask now or in office hours!

Assignment Reminders

Due Sunday at 12pm:

1. Lab 1

2. HW1:

  • Variables and types exercises
  • List/dictionary practice with weather data
  • Control flow and loops
  • Write functions with docstrings
  • Debug practice (fix broken code)

Start early! Don’t wait until Saturday night 😅

Resources

Stuck on homework?

  • Office hours (Will: Tu/Th 11:15-12:15, Aiden: M/W 4-5)
  • Lab notebooks (reference examples)
  • Teams Discussion Board

Online resources:

Remember: Asking questions is part of learning!

Questions?

Review: Key Concepts

Today we covered:

  1. Variables and types (numbers, strings)
  2. Lists and 0-based indexing
  3. Dictionaries for key-value data
  4. Control flow (if/elif/else)
  5. Loops (for, while)
  6. Functions (def, return, docstrings)
  7. Debugging strategies
  8. Combining data structures

Most important: Practice! Use these skills in Lab 1 and HW1.

Contact

Prof. Will Chapman

📧 wchapman@colorado.edu 🌐 willychap.github.io 🏢 SEEC Building (N258), East Campus

Office Hours:

  • Will: Tu / Th 11:15-12:15p
  • Aiden: M / W 4-5p

See you next week!