Build a Personal Spending Analyzer
Use Python in Colab to clean expenses and create a spending report.
Introduction
30 Second Summary
Small purchases can feel harmless until you try to explain where your money went. Messy labels can make that answer even harder to trust.
In this project, you will build a personal spending analyzer with Python in Google Colab. Your notebook will turn six fictional expenses into a cleaned summary with a text-based bar chart.
What You'll Build
When you run the finished notebook, six messy expense records become a clear spending report you can show someone else.
By the end of this project, you'll have:
- Visible results: Run each code cell to see its output directly below your Python instruction.
- Spending snapshot: Use a list of dictionaries to calculate the total, average, and largest purchase.
- Trustworthy report: Clean inconsistent category labels to produce reliable totals with a proportional text-based bar chart.
- Secret Mission: Add a weekly budget verdict that reports whether your spending is under or over a chosen limit.
Are there any prerequisites?
You need a Google account and access to a web browser. No programming experience is required.
Google Colab runs in your browser, so you do not need to install development tools.
Before We Start
Before you start coding, lock in what you are building and why it matters. You are creating a Python notebook that turns six fictional expenses into a clear spending report.
Open Your Python Workspace
Your spending analyzer needs a safe place where each Python instruction can run and show a result. Installing development tools would add setup before you can learn from the data.
Google Colab provides that workspace in your browser. In this step, you'll create a notebook that can run Python.
In this step, get ready to:
- Create a blank notebook in Google Colab.
- Enter a Python instruction in your first code cell.
- Run the cell to connect code with visible output.
Open a blank notebook
A Colab notebook keeps your code in cells that you can run separately. Each cell places its output directly underneath the code.
Why Google Colab?
Google Colab executes Python in your browser with zero configuration. That keeps this first project focused on code instead of local setup.
Local development tools are useful for larger projects. This notebook stays in Google Drive so you can return to your saved work later.
You are using your existing Google account here, so there is no new account to create.
- Visit the official Colab Welcome notebook in your web browser.
- Complete the Google sign-in prompt with your existing account if Colab asks.
- Select the create a new Colab notebook link in the Welcome notebook.
You'll see a new notebook with an empty code cell. Colab stores this learner-created notebook in your Google Drive account.
Write your first instruction
The print() function asks Python to display text. Quotation marks identify the characters Python should treat as text.
- Click the empty code cell in your new notebook.
- Enter your first Python instruction by copying the code below:
print("Python is ready!")
What Does This Code Do?
- The print() function sends text to the cell's output area.
- The quotation marks tell Python that "Python is ready!" is text.
Run and observe
Before you run the cell, predict what Python will place below it.
- Run the selected cell with the play button to its left or Command+Enter.
You'll see Python is ready! directly below the cell. You've just connected your first Python instruction to its visible result.
No Output Below the Cell?
Select the code cell before using the play button. A border around the cell shows that it is selected.
Compare each quotation mark and parenthesis with the code above if Colab reports a problem.
Help me run my first Python cell in Google Colab.
Your browser-based Python workspace is ready. Next, you'll add the six fictional expenses that power your analyzer.
Add the Messy Expense Data
Your first Python instruction already runs in Google Colab. Your notebook is ready for the expense records that the analyzer will study.
A list keeps the six expenses together. Each dictionary gives one purchase the same three details.
Keeping the original category text lets you see how capitalization and extra spaces can hide inside data.
In this step, get ready to:
- Store six purchases in a list of dictionaries.
- Use a loop to print each expense.
- Expose capitalization and extra spaces in the category labels.
Create the expense records
A variable gives a value a reusable name. The expenses variable will hold the full collection of purchase records.
- Create an empty code cell beneath your first cell using Colab's add-cell control.
- Define the six expense records by entering this code in the new cell:
expenses = [
{"item": "Coffee", "category": "Food", "amount": 4.50},
{"item": "Bus fare", "category": "Transport", "amount": 3.00},
{"item": "Lunch", "category": "food", "amount": 12.75},
{"item": "Notebook", "category": "Study", "amount": 6.25},
{"item": "Groceries", "category": "Food", "amount": 28.40},
{"item": "Train", "category": " transport ", "amount": 7.50},
]
What does this code do?
- The outer square brackets collect all six expenses under the name expenses.
- Each pair of curly braces creates one dictionary for one purchase.
- The item, category, and amount keys describe the same details for every purchase.
- The category values preserve their original capitalization and spaces.
- Run the cell by clicking the play button to its left.
You will see the execution indicator finish without an error. Python now remembers all six records under expenses.
Cell not finishing successfully?
Check that every dictionary has matching curly braces. Confirm that the list finishes with a closing square bracket.
Compare the quotation marks around each item and category value with the code above.
Help me check my expense list for missing brackets or quotation marks.
Print one record at a time
A for loop visits each dictionary in expenses. The indented instruction runs once for every record.
- Create another empty code cell beneath the expense list using Colab's add-cell control.
- Print the six records by entering this code in the new cell:
print("RAW EXPENSES")
for expense in expenses:
print(
f"{expense['item']} | "
f"[{expense['category']}] | "
f"${expense['amount']:.2f}"
)
How does the loop work?
- The name expense holds the current dictionary during each pass through the loop.
- The indented print() instruction displays the current item, category, and amount.
- The square brackets around each category create visible boundaries around its text.
- The .2f format displays every amount with two digits after the decimal point.
- Run the cell by pressing Command+Enter on your Mac.
You will see six expense lines below the cell. The output begins with Coffee | [Food] | $4.50 and ends with Train | [ transport ] | $7.50.
Good progress. Your notebook now walks through every purchase in the collection.
Only seeing one expense?
Check that the second print() instruction is indented beneath the for loop. Its indentation tells Python to repeat it for every expense.
Help me fix a Python loop that prints only one expense.
Make the messy labels visible
Spaces at the edges of text are easy to miss. Surrounding each category with square brackets makes those hidden spaces visible in the output.
- Create one more empty code cell beneath the raw expense output using Colab's add-cell control.
- Display each original category label by entering this code in the new cell:
print("\nRAW CATEGORY LABELS")
for expense in expenses:
print(f"[{expense['category']}]")
Why add the brackets?
The brackets show exactly where each category begins and ends. A space between a bracket and a word becomes visible immediately.
Capital letters also remain unchanged. Python therefore preserves Food and food as distinct text values.
Before you run this cell, make one prediction about whether capitalization and outside spaces will keep matching words together.
- Run the category cell by clicking the play button to its left.
The output includes [Food] beside [food]. It also includes [Transport] beside [ transport ].
Those boundaries expose capitalization differences and extra spaces. You have made the hidden mess visible before calculating with it.
Labels look clean already?
Check the Train dictionary in your expense list. Its category must contain one space before transport and one space after it.
Confirm that the output line keeps the square brackets around each category.
Help me find why my raw category output does not show the messy labels.
✔️ Awesome, I've got everything!
Your three cells are running successfully. Keep them in this order so the later calculations can reuse expenses.
ⓧ I'd like to double check the full code
expenses = [
{"item": "Coffee", "category": "Food", "amount": 4.50},
{"item": "Bus fare", "category": "Transport", "amount": 3.00},
{"item": "Lunch", "category": "food", "amount": 12.75},
{"item": "Notebook", "category": "Study", "amount": 6.25},
{"item": "Groceries", "category": "Food", "amount": 28.40},
{"item": "Train", "category": " transport ", "amount": 7.50},
]
print("RAW EXPENSES")
for expense in expenses:
print(
f"{expense['item']} | "
f"[{expense['category']}] | "
f"${expense['amount']:.2f}"
)
print("\nRAW CATEGORY LABELS")
for expense in expenses:
print(f"[{expense['category']}]")
How to compare your notebook
Read the reference from top to bottom. Your notebook can keep the same code across three cells while preserving this exact order.
Your notebook now stores six expenses and reveals the inconsistent category labels. Next, you can calculate a spending snapshot and see how those labels distort the category totals.
Calculate the Spending Snapshot
Your Google Colab notebook already displays six expense records. You can see every purchase with its original category label.
Now you will turn those individual records into a total, an average, and the largest purchase. These answers create a useful snapshot of the week.
The report's trustworthiness also depends on how the raw category labels group. You will test that grouping directly before changing the data.
In this step, get ready to:
- Calculate the total, average, and largest expense.
- Group the spending under its exact category labels.
- Inspect whether the raw category breakdown is trustworthy.
Calculate the overall snapshot
A variable can hold a value while Python works through the expenses. A loop can update that value once for each record.
- Place your cursor after the final line containing print(f"[{expense['category']}]") in the open Colab code cell.
- Press Enter twice to leave a blank line.
- Paste the code below at your cursor.
total = 0
largest_expense = expenses[0]
for expense in expenses:
total = total + expense["amount"]
if expense["amount"] > largest_expense["amount"]:
largest_expense = expense
average = total / len(expenses)
print("\nSPENDING SNAPSHOT")
print(f"Total: ${total:.2f}")
print(f"Average: ${average:.2f}")
print(
f"Largest: {largest_expense['item']} "
f"at ${largest_expense['amount']:.2f}"
)
What Does This Code Do?
- The total variable begins at 0 because no amounts have been added yet.
- The largest_expense variable begins with the first expense as a valid comparison point.
- The for loop visits each expense in the list.
- The if condition replaces largest_expense when the current amount is larger.
- The average calculation divides the completed total by the number of expense records.
- The .2f format displays each result with two digits after the decimal point.
Before you run the cell, predict which record Python will identify as the largest:
- Run the updated cell by pressing Command+Enter.
You will see Total: $62.40, Average: $10.40, and Largest: Groceries at $28.40 under the spending snapshot heading.
That is your first calculated spending summary working from start to finish.
Snapshot Output Missing?
- Check that the new code sits below the loop that prints RAW CATEGORY LABELS.
- Check that total = total + expense["amount"] has four spaces before it.
- Check that largest_expense = expense has eight spaces before it.
Help me debug my spending snapshot code.
Group the raw categories
A dictionary can store a running total under each category label. Each exact label becomes a key that Python can look up and update.
- Place your cursor after the final closing parenthesis in the spending snapshot code.
- Press Enter twice to leave a blank line.
- Paste the code below at your cursor.
raw_category_totals = {}
for expense in expenses:
category = expense["category"]
raw_category_totals[category] = (
raw_category_totals.get(category, 0) + expense["amount"]
)
print("\nRAW CATEGORY TOTALS")
for category in raw_category_totals:
print(f"[{category}]: ${raw_category_totals[category]:.2f}")
How Does the Grouping Work?
- The empty raw_category_totals dictionary provides a place to store the grouped amounts.
- The category variable holds the current expense's exact category label.
- The raw_category_totals.get(category, 0) expression retrieves the amount already stored for that label.
- The default value of 0 starts a total when Python encounters a new label.
- The final loop prints each category key with its accumulated amount.
Before you run the cell, predict how many category rows the raw report will contain:
- Run the updated cell by pressing Command+Enter.
The Raw Report Splits the Categories
You will see five rows in the raw category report. Python has grouped every exact label separately.
- The output shows [Food]: $32.90.
- The output shows [Transport]: $3.00.
- The output shows [food]: $12.75.
- The output shows [Study]: $6.25.
- The output shows [ transport ]: $7.50.
The overall snapshot is correct. The category breakdown is misleading because equivalent labels remain divided.
Raw Totals Not Appearing?
- Check that every occurrence of raw_category_totals uses the same spelling.
- Check that category = expense["category"] has four spaces before it.
- Check that the final print() line sits inside the second category loop.
Help me debug my raw category totals.
Confirm the misleading result
Your notebook now contains the complete raw spending report. Use this comparison to catch any missing lines before the final check.
✔️ Awesome, I've got everything!
Great work. Your notebook now contains the original records, the spending snapshot, and the raw category totals.
ⓧ I'd like to double check the full code
expenses = [
{"item": "Coffee", "category": "Food", "amount": 4.50},
{"item": "Bus fare", "category": "Transport", "amount": 3.00},
{"item": "Lunch", "category": "food", "amount": 12.75},
{"item": "Notebook", "category": "Study", "amount": 6.25},
{"item": "Groceries", "category": "Food", "amount": 28.40},
{"item": "Train", "category": " transport ", "amount": 7.50},
]
print("RAW EXPENSES")
for expense in expenses:
print(
f"{expense['item']} | "
f"[{expense['category']}] | "
f"${expense['amount']:.2f}"
)
print("\nRAW CATEGORY LABELS")
for expense in expenses:
print(f"[{expense['category']}]")
total = 0
largest_expense = expenses[0]
for expense in expenses:
total = total + expense["amount"]
if expense["amount"] > largest_expense["amount"]:
largest_expense = expense
average = total / len(expenses)
print("\nSPENDING SNAPSHOT")
print(f"Total: ${total:.2f}")
print(f"Average: ${average:.2f}")
print(
f"Largest: {largest_expense['item']} "
f"at ${largest_expense['amount']:.2f}"
)
raw_category_totals = {}
for expense in expenses:
category = expense["category"]
raw_category_totals[category] = (
raw_category_totals.get(category, 0) + expense["amount"]
)
print("\nRAW CATEGORY TOTALS")
for category in raw_category_totals:
print(f"[{category}]: ${raw_category_totals[category]:.2f}")
Before the final run, predict which labels will remain separate in the raw category report:
- Run the complete cell using the play button to its left.
Final Output Check
You will see Total: $62.40, Average: $10.40, and Largest: Groceries at $28.40.
You will also see separate totals for Food, food, Transport, Study, and transport .
Your snapshot answers the overall spending questions. Next, you will repair the split category labels so the final report becomes trustworthy.
Clean Categories and Draw the Report
Your raw totals revealed the problem: small text differences split one category into several groups. The food amount is fragmented.
Python can normalize every category before rebuilding the totals. This step turns those clean groups into a proportional text chart you can compare at a glance.
In this step, get ready to:
- Normalize every category label with consistent spacing and capitalization.
- Rebuild the category totals from the cleaned labels.
- Draw a proportional text chart with bars made from # characters.
Normalize each category label
Python treats Food plus food as different strings. Standardizing the spaces plus capitalization gives equivalent categories one shared label.
- Use the add-cell control in Google Colab to insert a code cell directly below the raw category totals cell.
for expense in expenses:
expense["category"] = expense["category"].strip().lower()
How does normalization work?
- The loop visits every dictionary stored in expenses.
- The strip() method removes whitespace from the beginning or end of each category.
- The lower() method returns the category with consistent lowercase letters.
- The assignment stores the normalized label back inside the same expense dictionary.
- Run the new normalization cell with its play button.
- Scroll back to the earlier RAW CATEGORY LABELS cell.
- Run that earlier cell again with its play button.
You should now see [food], [transport], plus [study] without capital letters or outside spaces.
This quick check reads the cleaned records currently held by the notebook. A complete top-to-bottom run still preserves the original raw-label demonstration before the cleaning loop runs.
Still seeing messy labels?
Make sure the normalization cell ran before you reran the earlier labels cell. Check that both method names include their parentheses.
Confirm that the assignment uses the same expense["category"] key on both sides.
Help me fix the category normalization loop.
Rebuild the clean totals
Each normalized label becomes a dictionary key for its combined amount. The largest combined amount provides the scale for the chart.
- Use Colab's add-cell control to insert another code cell directly below the normalization cell.
cleaned_category_totals = {}
for expense in expenses:
category = expense["category"]
cleaned_category_totals[category] = (
cleaned_category_totals.get(category, 0) + expense["amount"]
)
max_category_total = 0
for category in cleaned_category_totals:
amount = cleaned_category_totals[category]
if amount > max_category_total:
max_category_total = amount
How are the clean totals built?
- The empty cleaned_category_totals dictionary becomes the home for the corrected groups.
- The dict.get() pattern starts an unseen category at 0 before adding its expense amount.
- The second loop compares each combined amount with max_category_total.
- The largest category total becomes the reference value for a full-length bar.
- Run the clean-totals cell with its play button.
The cell should finish without an error message. The notebook now holds the cleaned groups plus the maximum value needed by the chart.
Clean totals cell not completing?
Check that the closing parenthesis lines up with the assignment to cleaned_category_totals[category]. Make sure the two indented lines under the if comparison use consistent spacing.
Help me debug the cleaned totals and maximum loops.
Draw the proportional report
A proportional bar turns each category's amount into a visible length. The largest category receives 20 characters while smaller categories receive shorter bars.
- Use Colab's add-cell control to insert the final report cell directly below the clean-totals cell.
print("\nCLEANED CATEGORY REPORT")
for category in sorted(cleaned_category_totals):
amount = cleaned_category_totals[category]
bar_length = round((amount / max_category_total) * 20)
bar = "#" * bar_length
print(f"{category}: {bar} ${amount:.2f}")
How does the chart work?
- The sorted() function presents the category names in a predictable order.
- Dividing each amount by max_category_total measures its size relative to the largest category.
- Multiplying that ratio by 20 converts it into the chart's maximum width.
- The round() function turns the result into a whole-number bar length.
- Repeating # draws the bar before the amount appears with two decimal places.
Before you run the full notebook, predict which category will receive the longest bar.
- Run every code cell from top to bottom using each cell's play button.
Under CLEANED CATEGORY REPORT, you should see food: #################### $45.65, study: ### $6.25, plus transport: ##### $10.50.
That is the cleanup paying off: each category now has one trustworthy total. The bar lengths make the spending pattern visible immediately.
Report missing or looking wrong?
Run every cell from top to bottom so the report receives the data created earlier. Duplicate category names usually mean the normalization loop did not run before the clean totals were rebuilt.
Check that bar_length divides by max_category_total before multiplying by 20.
Help me debug my cleaned category chart.
✔️ Awesome, I've got everything!
Your notebook now matches the finished core spending analyzer.
ⓧ I'd like to double check the full code
expenses = [
{"item": "Coffee", "category": "Food", "amount": 4.50},
{"item": "Bus fare", "category": "Transport", "amount": 3.00},
{"item": "Lunch", "category": "food", "amount": 12.75},
{"item": "Notebook", "category": "Study", "amount": 6.25},
{"item": "Groceries", "category": "Food", "amount": 28.40},
{"item": "Train", "category": " transport ", "amount": 7.50},
]
print("RAW EXPENSES")
for expense in expenses:
print(
f"{expense['item']} | "
f"[{expense['category']}] | "
f"${expense['amount']:.2f}"
)
print("\nRAW CATEGORY LABELS")
for expense in expenses:
print(f"[{expense['category']}]")
total = 0
largest_expense = expenses[0]
for expense in expenses:
total = total + expense["amount"]
if expense["amount"] > largest_expense["amount"]:
largest_expense = expense
average = total / len(expenses)
print("\nSPENDING SNAPSHOT")
print(f"Total: ${total:.2f}")
print(f"Average: ${average:.2f}")
print(
f"Largest: {largest_expense['item']} "
f"at ${largest_expense['amount']:.2f}"
)
raw_category_totals = {}
for expense in expenses:
category = expense["category"]
raw_category_totals[category] = (
raw_category_totals.get(category, 0) + expense["amount"]
)
print("\nRAW CATEGORY TOTALS")
for category in raw_category_totals:
print(f"[{category}]: ${raw_category_totals[category]:.2f}")
for expense in expenses:
expense["category"] = expense["category"].strip().lower()
cleaned_category_totals = {}
for expense in expenses:
category = expense["category"]
cleaned_category_totals[category] = (
cleaned_category_totals.get(category, 0) + expense["amount"]
)
max_category_total = 0
for category in cleaned_category_totals:
amount = cleaned_category_totals[category]
if amount > max_category_total:
max_category_total = amount
print("\nCLEANED CATEGORY REPORT")
for category in sorted(cleaned_category_totals):
amount = cleaned_category_totals[category]
bar_length = round((amount / max_category_total) * 20)
bar = "#" * bar_length
print(f"{category}: {bar} ${amount:.2f}")
Secret mission
Add a Weekly Budget Verdict
Compare your weekly spending with a chosen limit. Then make Python print how far under or over budget the week landed.
Clean Up Your Resources
Clean Up Your Resources
Your spending analyzer lives in one Google Colab notebook stored in Google Drive. This project has no ongoing costs.
Resources you used:
- One Google Colab notebook stored in Google Drive.
Keep everything running
No action needed. Choose this if you want to rerun the analyzer or change its expenses later.
- Leave the notebook in Google Drive so you can return to it later.
- Keep the browser tab open if you want to continue experimenting now.
Pause - I'll come back to this later
Pausing lets the temporary runtime end while your saved notebook remains available.
- Close the Google Colab browser tab.
- Return to the saved notebook from Google Drive when you are ready to continue.
The temporary runtime can end after being idle. Your notebook remains saved in Google Drive.
Delete - I don't want to use this again
Deleting your first notebook can feel final. Choose this only when you no longer need the notebook.
- Switch to Google Drive in your browser.
- Locate the notebook that contains your spending analyzer.
- Move that notebook file to Google Drive's trash.
- Use Google Drive's trash view to locate the notebook you just removed.
- Permanently delete the notebook from the trash.
- Confirm the notebook no longer appears in Google Drive.
Nice Work!
Nice Work!
You made it! Your first Python data project now turns six messy expense records into a trustworthy spending report with a budget verdict.
What you learned:
- Stored six expense records in a list of dictionaries. Each dictionary keeps one purchase together.
- Calculated a spending snapshot with variables plus loops. The output shows the total. It also shows the average. It identifies the largest purchase.
- Cleaned inconsistent category labels to merge duplicate groups. Turned the corrected totals into a proportional text bar chart.
- Secret Mission: Added a weekly budget verdict with conditional logic. Tested the under-budget path. Tested the over-budget path.
Ready to quiz yourself?