Azure Setup and Account Access
Week 1 Assignment: The Data Cleaning Pipeline
Career relevance: Week 1 in the NL data job market
Going Further: Optional Deep Dives
These six exercises consolidate everything Week 1 introduced: variables, functions, type hints, logging, debugging, file I/O, and CLI habits. Pick the ones that match what felt shaky on a first read; you can do them in any order.
By the end of this chapter, you should have practiced the core Python skills that the Week 1 assignment in the next chapter combines into one larger task.
All Week 1 exercises live under data-track/week-1/ in HYF's Learning-Resources repo. Open it once for the whole week, not once per exercise:
<aside> 💻 Open in GitHub Codespaces
</aside>
The repo's data-track/.devcontainer/ boots Python 3.11 + ruff + Pylance for every exercise. From the Codespace's Explorer, navigate into data-track/week-1/exercise_N/.
Prefer your own editor? Clone locally:
git clone <https://github.com/HackYourFuture/Learning-Resources.git>
cd Learning-Resources/data-track/week-1
code .
Either way, each exercise is its own subfolder: exercise_1/, exercise_2/, ..., exercise_6/. Switch between them in the file explorer; one cold-start covers the whole week.
Each exercise_N/solutions/ folder holds the answer in-place. The same filename as the starter (exercise.py, exercise_2.py, exercise_6.py, assignment_6.py) carries the answer code with the original # TODO and # FIXME comments preserved, plus a # WHY ...: note under each non-obvious choice. The file you read is the question and the answer side-by-side. Read the WHY comments, do not copy the code verbatim. The point of the reference solution is the commentary, not the answer.
The solution sits next to your starter under solutions/ rather than on a separate branch. The folder name and the deliberate "open this folder to see the answer" click are the whole barrier, and they are enough. If you find yourself opening solutions/ reflexively whenever an exercise gets hard, time-box yourself instead: 30 minutes of honest attempt before peeking. The struggle is where the learning happens.
You can diff your attempt against the reference once you have tried:
diff exercise_1/exercise.py exercise_1/solutions/exercise.py
Concepts: Variables (Ch2), Functions (Ch4), Type hints (Ch5), Logging (Ch8).
You are building a small weather station script. Your task is to write a function that converts Celsius to Fahrenheit, but it must be "production-ready".
Instructions:
Import the logging module and configure it to level INFO.
Write a function called convert_c_to_f.
Type Hinting: The function should accept a float and return a float.
Inside the function, calculate the result ((celsius * 9/5) + 32).
Logging: Before returning the value, log an info message: "Converting {celsius}°C to {fahrenheit}°F".
Call the function with three different values (e.g., 0, 25, 100).
<aside>
📦 Files: exercise_1/: use the Codespace you opened at the top of this page.
</aside>
Concepts: Lists (Ch2), Loops & conditionals (Ch3), Debugging (Ch7).
You have received a list of user ages from a database, but the data is dirty. Some values are strings, some are numbers, and some are negative (impossible!).
The buggy code:
Copy this into exercise_2.py. It currently crashes.
ages = [25, 30, "40", "not_available", 20, -5]
def calculate_average_age(age_list):
total = 0
count = 0
for age in age_list:
total += age
count += 1
return total / count
print(calculate_average_age(ages))
Instructions:
Run the code and read the Traceback. What kind of error is it?
Use the VS Code Debugger to step through the loop. Find which value causes the crash.
Modify the loop to fix the code:
"40"), convert it to an int."not_available"), skip it.<aside>
📦 Files: exercise_2/: use the Codespace you opened at the top of this page.
</aside>
Concepts: Floating-point math (Ch2), Debugging (Ch7).
You are processing payments for a transaction system. You have a wallet with 0.1 bitcoin, and you receive 0.2 bitcoin. You want to check if you now have exactly 0.3 bitcoin to execute a trade.
The buggy code:
This code prints "Transaction failed?" even though 0.1 + 0.2 should equal 0.3. Even stranger: the wallet balance prints as 0.30000000000000004, not 0.3.
wallet = 0.1
wallet += 0.2
print(f"Wallet balance:{wallet}")
if wallet == 0.3:
print("Transaction Success!")
else:
print("Transaction failed?")
Instructions:
Run the code. The print shows Wallet balance:0.30000000000000004 (not 0.3), and the if check fails. Both clues point at the same root cause: Python cannot store 0.1 + 0.2 as exactly 0.3.
Set a breakpoint on the if wallet == 0.3: line.
Start the debugger. Hover your mouse over the wallet variable (or look in the Variables pane).
What is the actual value of wallet?
Computers struggle with exact decimal math. Change the code to use the round() function to fix the comparison (e.g., round to 1 decimal place).
<aside>
📦 Files: exercise_3/: use the Codespace you opened at the top of this page.
</aside>
Concepts: Dictionaries (Ch2), Type hints (Ch5), Logging (Ch8), branching logic (Ch3).
Combine everything! You need to process student grades.
Instructions:
student = {"name": "Alice", "grades": [85, 90, 78]}
Write a function process_student(student_data: dict) -> None.
Configure logging to show timestamps.
Inside the function:
INFO message: "Processing grades for [Name]".if / elif / else ladder so exactly one grade is logged: if the average is greater than 80, log INFO "Grade A"; else if the average is greater than 60, log INFO "Grade B"; otherwise log WARNING "Student requires assistance".<aside>
⚠️ The order matters. If you write three separate if blocks instead of if / elif / else, an average of 85 will trigger both "Grade A" and "Grade B" because both conditions are true. The elif is what makes the ladder pick exactly one branch.
</aside>
Run your function with Alice's grades and confirm only one log line is emitted per student.
<aside>
📦 Files: exercise_4/: use the Codespace you opened at the top of this page.
</aside>
Concepts: File I/O & context managers (Ch9), Strings (Ch2).
A big part of data engineering is reading data from files. Let's practice reading a raw text file and processing it.
Instructions:
raw_data.txt in your folder. Add these lines:amsterdam
rotterdam
the hague
utrecht
Create a Python script exercise_5.py.
Use the with open(...) pattern to read the raw_data.txt file.
Loop through the lines and capitalize each city name (e.g., "Amsterdam").
Store the cleaned names in a list.
Use with open(...) again to write the cleaned names into a new file called processed_data.txt.
Check your folder to see if the new file was created.
<aside>
📦 Files: exercise_5/: use the Codespace you opened at the top of this page.
</aside>
Concepts: CLI arguments via argparse (Ch6), Logging levels (Ch8), pathlib (Ch9).
You are given a tiny CSV cleaner that mostly works, but it has three problems any production pipeline would fix on day one:
The input and output paths are hard-coded. A teammate cannot reuse it on a different file.
It always logs at INFO. You cannot quiet it for a clean run, or crank it up to DEBUG when something looks off.
Every message uses logging.info() regardless of what it actually is. Skipped rows should warn; per-row trace should be DEBUG; the summary is INFO.
Goal: make the script runnable like this:
python exercise_6.py --input data/messy_users.csv --output cleaned.json --log-level DEBUG
Instructions:
Add argparse to main() with three flags: --input (required), --output (required), --log-level (default INFO, choices DEBUG INFO WARNING ERROR).
Configure the root logger to use the chosen level. Hint: logging.basicConfig(level=getattr(logging, args.log_level)).