Courses/Streamlit: 20 Real Apps/Module 4: Data Apps
Module 4 · Lesson 130 minBeginner

Project 9: Expense Tracker

What you'll build
Build a personal finance app — logging, summaries, and charts from one CSV.

Introduction

An expense tracker is the project where Python stops being a hobby and starts saving you money. In about 120 lines you get a form for logging expenses, automatic monthly summaries, and charts that make overspending painfully obvious. It combines the state handling of the to-do list app with the data-crunching patterns from the Pandas dashboard.

The trick that keeps this app simple is Pandas as the storage engine: expenses live in a CSV file, so filtering by month, grouping by category, and summing totals are one-liners instead of hand-rolled loops.

Features

  • Log expenses — amount, category, and note via a form.
  • Category breakdown — pie chart of spending by category.
  • Monthly summary — total, average per day, and biggest expense.
  • History table — recent expenses, newest first.
  • Delete entries — remove mistakes with one click.
  • Prerequisites

  • Python 3.8+ — from python.org.
  • Dependencies — install with pip:
  • code
    pip install streamlit pandas matplotlib

    Step 1: Create the Script

    Save this as expense_tracker.py:

    code
    import streamlit as st
    import pandas as pd
    from datetime import date
    import os
    
    CSV_FILE = "expenses.csv"
    CATEGORIES = ["Food", "Travel", "Bills", "Shopping", "Fun", "Other"]
    
    
    def load_expenses():
        if os.path.exists(CSV_FILE):
            return pd.read_csv(CSV_FILE, parse_dates=["date"])
        return pd.DataFrame(columns=["date", "amount", "category", "note"])
    
    
    st.set_page_config(page_title="Expense Tracker", page_icon="💰", layout="centered")
    st.title("💰 Expense Tracker")
    
    expenses = load_expenses()
    
    # --- Add expense form ---
    with st.form("add_expense", clear_on_submit=True):
        col1, col2 = st.columns(2)
        amount = col1.number_input("Amount", min_value=0.01, format="%.2f")
        category = col2.selectbox("Category", CATEGORIES)
        note = st.text_input("Note (optional)", placeholder="Groceries, fuel, movie...")
        submitted = st.form_submit_button("Add Expense")
        if submitted:
            new_row = pd.DataFrame(
                [{"date": date.today(), "amount": amount, "category": category, "note": note}]
            )
            expenses = pd.concat([new_row, expenses], ignore_index=True)
            expenses.to_csv(CSV_FILE, index=False)
            st.success(f"Added {category} expense!")
    
    # --- Summary ---
    if not expenses.empty:
        expenses["month"] = expenses["date"].dt.to_period("M")
        this_month = expenses[expenses["month"] == pd.Period(date.today(), "M")]
    
        c1, c2, c3 = st.columns(3)
        c1.metric("This month", f"₹{this_month['amount'].sum():,.0f}")
        c2.metric("Daily average", f"₹{this_month['amount'].mean():,.0f}")
        c3.metric("Entries", len(this_month))
    
        left, right = st.columns(2)
        with left:
            by_cat = this_month.groupby("category")["amount"].sum()
            st.bar_chart(by_cat)
        with right:
            st.dataframe(
                this_month[["date", "amount", "category", "note"]]
                .sort_values("date", ascending=False),
                use_container_width=True,
                hide_index=True,
            )
    else:
        st.info("No expenses logged yet.")

    Step 2: Run the App

    code
    streamlit run expense_tracker.py

    Log a few expenses across categories, then watch the bar chart and metrics update instantly.

    How It Works

    Everything rides on two Pandas idioms. First, df["date"].dt.to_period("M") converts timestamps into month periods, so filtering "this month" is a simple equality check — no manual date-range math. Second, groupby("category")["amount"].sum() produces the aggregated series that st.bar_chart renders directly; Pandas indexes become chart labels for free.

    The form uses st.form, which batches its widgets into a single rerun. That matters here: without a form, every keystroke in the amount box would trigger a full script rerun. Forms are the standard answer to *"my Streamlit app reruns too much"*.

    Storage stays boring on purpose — a CSV round-tripped with to_csv/read_csv. It has zero setup, opens in Excel, and is trivially portable to the home server you might host it on later.

    Common Errors & Fixes

  • `KeyError: 'date'` on first runread_csv on a missing file returns an empty frame without columns; the code guards with os.path.exists, so check the file path if you renamed it.
  • Charts show nothing — you grouped by a column with all NaN amounts; make sure number_input has min_value=0.01 so amounts are always real.
  • Duplicate rows after refresh — the insert ran outside the form guard; keep the to_csv write inside if submitted:.
  • Dates parsed as strings — pass parse_dates=["date"] to read_csv, otherwise .dt accessors fail.
  • Key Concepts

  • `st.form` — batches inputs into one rerun; essential for entry forms.
  • Period filteringto_period("M") makes month comparisons trivial.
  • `groupby` + chart — aggregated Series render as labeled charts directly.
  • CSV as a database — fine for single-user, file-sized datasets.
  • What to Try Next

  • Add a monthly budget input and color the total metric green/red against it.
  • Export a monthly report with to_csv through st.download_button.
  • Add a trend line of total spend per month using st.line_chart on a grouped series.
  • Support multiple users with a name column and a sidebar filter.
  • FAQ

    Why CSV instead of SQLite?

    For one user and a few thousand rows, CSV is simpler, human-readable, and opens in Excel. Switch to SQLite when you need concurrent writes or faster queries on large data.

    Can I track income too?

    Yes — add a type column ("income"/"expense") with a radio in the form, and compute net savings as income minus expenses per month.

    How do I back up my data?

    Copy expenses.csv — that's the entire database. For automation, a nightly copy script on your home server works well.

    Adapted from: Expense Tracker using Python and Streamlit

    Checkpoint
    Adding an expense updates the metrics and bar chart instantly, and data survives refreshes.
    What you learned
    • st.form to batch inputs
    • groupby + chart rendering
    • Month filtering with to_period