Expense Tracker Project Using Python
# ============================================================
# EXPENSE TRACKER
# CBSE Class 12 Computer Science / Informatics Practices
# Investigatory Project
#
# Topics Covered:
# - File Handling (CSV read/write)
# - pandas — DataFrame, filtering, groupby, aggregation
# - matplotlib — pie chart, bar chart, line chart
# - datetime module
# - Functions, Dictionaries, Lists
# ============================================================
#
# REQUIREMENTS:
# pip install pandas matplotlib
#
# HOW TO RUN:
# python expense_tracker.py
#
# FILE CREATED:
# expenses.csv — stores all expense records
# ============================================================
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.gridspec import GridSpec
import os
import csv
from datetime import datetime, date
# ─────────────────────────────────────────────
# CONFIGURATION
# ─────────────────────────────────────────────
CSV_FILE = "expenses.csv"
COLUMNS = ["ID", "Date", "Category", "Description", "Amount", "Payment_Mode"]
CATEGORIES = [
"Food & Dining",
"Transport",
"Education",
"Entertainment",
"Shopping",
"Health & Medical",
"Utilities & Bills",
"Rent",
"Personal Care",
"Others",
]
PAYMENT_MODES = ["Cash", "UPI", "Debit Card", "Credit Card", "Net Banking"]
# Colours per category (used in all charts)
CAT_COLORS = {
"Food & Dining" : "#e74c3c",
"Transport" : "#3498db",
"Education" : "#9b59b6",
"Entertainment" : "#f39c12",
"Shopping" : "#1abc9c",
"Health & Medical" : "#2ecc71",
"Utilities & Bills": "#e67e22",
"Rent" : "#34495e",
"Personal Care" : "#e91e63",
"Others" : "#95a5a6",
}
LINE = "=" * 68
LINE2 = "-" * 68
# ─────────────────────────────────────────────
# FILE HANDLING
# ─────────────────────────────────────────────
def initialize_file():
"""Create CSV with header if it doesn't exist."""
if not os.path.exists(CSV_FILE):
with open(CSV_FILE, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(COLUMNS)
print(f" [✓] New file '{CSV_FILE}' created.")
def load_data():
"""Load CSV into a pandas DataFrame."""
df = pd.read_csv(CSV_FILE)
if df.empty:
return df
df["Date"] = pd.to_datetime(df["Date"], errors="coerce")
df["Amount"] = pd.to_numeric(df["Amount"], errors="coerce").fillna(0)
return df
def save_expense(record: dict):
"""Append one expense record to CSV."""
with open(CSV_FILE, "a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=COLUMNS)
writer.writerow(record)
def next_id():
"""Auto-increment ID."""
df = load_data()
if df.empty or "ID" not in df.columns:
return 1
return int(df["ID"].max()) + 1
def overwrite_data(df: pd.DataFrame):
"""Write entire DataFrame back to CSV."""
df.to_csv(CSV_FILE, index=False)
# ─────────────────────────────────────────────
# HELPERS
# ─────────────────────────────────────────────
def print_header(title):
print("\n" + LINE)
print(f" {title.upper()}")
print(LINE)
def pause():
input("\n Press Enter to continue...")
def pick_from_list(label, options):
"""Show a numbered list and return the chosen item."""
print(f"\n {label}:")
for i, opt in enumerate(options, 1):
print(f" {i:>2}. {opt}")
while True:
try:
n = int(input(" Enter number: ").strip())
if 1 <= n <= len(options):
return options[n - 1]
print(f" [!] Enter 1–{len(options)}")
except ValueError:
print(" [!] Enter a valid number.")
def print_table(df: pd.DataFrame):
"""Print a neat table of expense records."""
if df.empty:
print("\n [!] No records to display.\n")
return
print()
print(f" {'ID':<5} {'Date':<12} {'Category':<20} {'Description':<22} {'Amount':>9} {'Mode'}")
print(" " + LINE2)
for _, row in df.iterrows():
d = row["Date"].strftime("%d-%m-%Y") if pd.notna(row["Date"]) else "N/A"
desc = str(row["Description"])[:20]
print(f" {int(row['ID']):<5} {d:<12} {str(row['Category']):<20} "
f"{desc:<22} ₹{row['Amount']:>8,.2f} {row['Payment_Mode']}")
print(" " + LINE2)
print(f" {'TOTAL':>61} ₹{df['Amount'].sum():>8,.2f}")
# ─────────────────────────────────────────────
# FEATURE 1 — ADD EXPENSE
# ─────────────────────────────────────────────
def add_expense():
print_header("Add New Expense")
# Date
date_str = input(" Date (YYYY-MM-DD) or press Enter for today: ").strip()
if not date_str:
date_str = date.today().isoformat()
else:
try:
datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
print(" [!] Invalid date format. Using today.")
date_str = date.today().isoformat()
category = pick_from_list("Select Category", CATEGORIES)
desc = input(" Description (brief): ").strip() or "—"
while True:
try:
amount = float(input(" Amount (₹): ").strip())
if amount > 0:
break
print(" [!] Amount must be positive.")
except ValueError:
print(" [!] Enter a valid number.")
mode = pick_from_list("Payment Mode", PAYMENT_MODES)
record = {
"ID" : next_id(),
"Date" : date_str,
"Category" : category,
"Description" : desc,
"Amount" : amount,
"Payment_Mode" : mode,
}
save_expense(record)
print(f"\n ✓ Expense of ₹{amount:,.2f} added under '{category}'.")
pause()
# ─────────────────────────────────────────────
# FEATURE 2 — VIEW ALL EXPENSES
# ─────────────────────────────────────────────
def view_all():
print_header("All Expenses")
df = load_data()
if df.empty:
print(" [!] No expenses recorded yet.")
pause(); return
print(f" Total Records : {len(df)}")
print(f" Total Spent : ₹{df['Amount'].sum():,.2f}")
print_table(df.sort_values("Date", ascending=False))
pause()
# ─────────────────────────────────────────────
# FEATURE 3 — FILTER EXPENSES
# ─────────────────────────────────────────────
def filter_expenses():
print_header("Filter Expenses")
print(" Filter by:")
print(" 1. Category")
print(" 2. Month & Year")
print(" 3. Date Range")
print(" 4. Payment Mode")
ch = input(" Choice: ").strip()
df = load_data()
if ch == "1":
cat = pick_from_list("Select Category", CATEGORIES)
result = df[df["Category"] == cat]
print(f"\n Expenses under '{cat}':")
elif ch == "2":
m = input(" Month (1-12): ").strip()
y = input(" Year (e.g. 2025): ").strip()
result = df[(df["Date"].dt.month == int(m)) &
(df["Date"].dt.year == int(y))]
print(f"\n Expenses for {m}/{y}:")
elif ch == "3":
s = input(" Start date (YYYY-MM-DD): ").strip()
e = input(" End date (YYYY-MM-DD): ").strip()
result = df[(df["Date"] >= s) & (df["Date"] <= e)]
print(f"\n Expenses from {s} to {e}:")
elif ch == "4":
mode = pick_from_list("Select Mode", PAYMENT_MODES)
result = df[df["Payment_Mode"] == mode]
print(f"\n Expenses via '{mode}':")
else:
print(" [!] Invalid choice."); pause(); return
print_table(result.sort_values("Date", ascending=False))
pause()
# ─────────────────────────────────────────────
# FEATURE 4 — DELETE EXPENSE
# ─────────────────────────────────────────────
def delete_expense():
print_header("Delete Expense")
df = load_data()
if df.empty:
print(" [!] No records to delete."); pause(); return
try:
eid = int(input(" Enter Expense ID to delete: ").strip())
except ValueError:
print(" [!] Invalid ID."); pause(); return
row = df[df["ID"] == eid]
if row.empty:
print(f" [!] No expense with ID {eid}."); pause(); return
r = row.iloc[0]
print(f"\n Found: [{r['Date'].date()}] {r['Category']} — {r['Description']} ₹{r['Amount']:,.2f}")
confirm = input(" Delete this record? (yes/no): ").strip().lower()
if confirm == "yes":
df = df[df["ID"] != eid]
overwrite_data(df)
print(" ✓ Record deleted.")
else:
print(" Deletion cancelled.")
pause()
# ─────────────────────────────────────────────
# FEATURE 5 — MONTHLY SUMMARY
# ─────────────────────────────────────────────
def monthly_summary():
print_header("Monthly Summary")
df = load_data()
if df.empty:
print(" [!] No data available."); pause(); return
df["Month"] = df["Date"].dt.to_period("M")
summary = df.groupby("Month")["Amount"].agg(
Total="sum", Count="count", Average="mean"
).reset_index()
summary = summary.sort_values("Month", ascending=False)
print(f"\n {'Month':<12} {'Total (₹)':>12} {'Transactions':>14} {'Avg/Txn (₹)':>13}")
print(" " + "-" * 55)
for _, row in summary.iterrows():
print(f" {str(row['Month']):<12} ₹{row['Total']:>11,.2f} "
f"{int(row['Count']):>14} ₹{row['Average']:>12,.2f}")
pause()
# ─────────────────────────────────────────────
# FEATURE 6 — CATEGORY SUMMARY
# ─────────────────────────────────────────────
def category_summary():
print_header("Category-Wise Summary")
df = load_data()
if df.empty:
print(" [!] No data available."); pause(); return
summary = df.groupby("Category")["Amount"].agg(
Total="sum", Count="count"
).reset_index().sort_values("Total", ascending=False)
total_all = summary["Total"].sum()
print(f"\n {'Category':<22} {'Total (₹)':>12} {'Txns':>6} {'Share':>8}")
print(" " + "-" * 54)
for _, row in summary.iterrows():
share = (row["Total"] / total_all * 100) if total_all > 0 else 0
bar = "█" * int(share / 3)
print(f" {row['Category']:<22} ₹{row['Total']:>11,.2f} "
f"{int(row['Count']):>6} {share:>5.1f}% {bar}")
print(" " + "-" * 54)
print(f" {'TOTAL':<22} ₹{total_all:>11,.2f}")
pause()
# ─────────────────────────────────────────────
# FEATURE 7 — SET & CHECK BUDGET
# ─────────────────────────────────────────────
BUDGET_FILE = "budget.csv"
def load_budget():
if not os.path.exists(BUDGET_FILE):
return {}
b = {}
with open(BUDGET_FILE, "r") as f:
reader = csv.DictReader(f)
for row in reader:
b[row["Category"]] = float(row["Budget"])
return b
def save_budget(budget: dict):
with open(BUDGET_FILE, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["Category", "Budget"])
writer.writeheader()
for cat, amt in budget.items():
writer.writerow({"Category": cat, "Budget": amt})
def budget_menu():
print_header("Budget Manager")
budget = load_budget()
df = load_data()
print(" 1. Set Monthly Budget for a Category")
print(" 2. View Budget vs Actual (Current Month)")
ch = input(" Choice: ").strip()
if ch == "1":
cat = pick_from_list("Select Category", CATEGORIES)
while True:
try:
amt = float(input(f" Monthly budget for '{cat}' (₹): ").strip())
if amt >= 0:
break
except ValueError:
pass
print(" [!] Enter a valid amount.")
budget[cat] = amt
save_budget(budget)
print(f" ✓ Budget of ₹{amt:,.2f} set for '{cat}'.")
elif ch == "2":
today = date.today()
month_df = df[(df["Date"].dt.month == today.month) &
(df["Date"].dt.year == today.year)]
actual = month_df.groupby("Category")["Amount"].sum()
print(f"\n Budget vs Actual — {today.strftime('%B %Y')}\n")
print(f" {'Category':<22} {'Budget (₹)':>11} {'Actual (₹)':>11} {'Used':>7} Status")
print(" " + "-" * 70)
for cat in CATEGORIES:
b_amt = budget.get(cat, 0)
a_amt = actual.get(cat, 0)
used = (a_amt / b_amt * 100) if b_amt > 0 else 0
status = "✓ OK" if a_amt <= b_amt else "⚠ OVER"
if b_amt == 0:
status = "—"
print(f" {cat:<22} ₹{b_amt:>10,.2f} ₹{a_amt:>10,.2f} "
f"{used:>6.1f}% {status}")
pause()
# ─────────────────────────────────────────────
# FEATURE 8 — CHARTS (matplotlib)
# ─────────────────────────────────────────────
def charts_menu():
print_header("Charts & Visualisation")
print(" 1. Pie Chart — Spending by Category")
print(" 2. Bar Chart — Monthly Expenses")
print(" 3. Line Chart — Daily Spending Trend")
print(" 4. Horizontal Bar — Category Comparison")
print(" 5. Full Dashboard — All Charts Together")
ch = input(" Choice: ").strip()
df = load_data()
if df.empty:
print(" [!] No data to visualise. Add some expenses first.")
pause(); return
if ch == "1": plot_pie(df)
elif ch == "2": plot_monthly_bar(df)
elif ch == "3": plot_daily_line(df)
elif ch == "4": plot_category_hbar(df)
elif ch == "5": plot_dashboard(df)
else:
print(" [!] Invalid choice.")
pause()
# ── Chart 1: Pie ──────────────────────────────
def plot_pie(df):
cat_totals = df.groupby("Category")["Amount"].sum()
cat_totals = cat_totals[cat_totals > 0]
colors = [CAT_COLORS.get(c, "#cccccc") for c in cat_totals.index]
fig, ax = plt.subplots(figsize=(8, 6))
fig.patch.set_facecolor("#1a1a2e")
ax.set_facecolor("#1a1a2e")
wedges, texts, autotexts = ax.pie(
cat_totals.values,
labels=cat_totals.index,
autopct="%1.1f%%",
colors=colors,
startangle=140,
pctdistance=0.82,
wedgeprops={"linewidth": 1.5, "edgecolor": "#1a1a2e"},
)
for t in texts:
t.set_color("white"); t.set_fontsize(9)
for at in autotexts:
at.set_color("white"); at.set_fontsize(8); at.set_fontweight("bold")
ax.set_title("Spending by Category", color="white",
fontsize=14, fontweight="bold", pad=16)
centre = plt.Circle((0, 0), 0.55, color="#1a1a2e")
ax.add_artist(centre)
ax.text(0, 0, f"₹{df['Amount'].sum():,.0f}\nTotal",
ha="center", va="center", color="white",
fontsize=10, fontweight="bold")
plt.tight_layout()
plt.show()
# ── Chart 2: Monthly Bar ─────────────────────
def plot_monthly_bar(df):
df["Month"] = df["Date"].dt.to_period("M").astype(str)
monthly = df.groupby("Month")["Amount"].sum().sort_index()
fig, ax = plt.subplots(figsize=(10, 5))
fig.patch.set_facecolor("#0f0c29")
ax.set_facecolor("#16213e")
bars = ax.bar(monthly.index, monthly.values,
color="#e94560", edgecolor="#0f0c29",
linewidth=0.8, zorder=3)
for bar in bars:
h = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2, h + 50,
f"₹{h:,.0f}", ha="center", va="bottom",
color="white", fontsize=8)
ax.set_title("Monthly Expenses", color="white",
fontsize=14, fontweight="bold")
ax.set_xlabel("Month", color="#a0a0b0")
ax.set_ylabel("Total Amount (₹)", color="#a0a0b0")
ax.tick_params(colors="white", rotation=30)
ax.yaxis.set_major_formatter(
plt.FuncFormatter(lambda x, _: f"₹{x:,.0f}"))
ax.grid(axis="y", color="#2a2a4a", linestyle="--", alpha=0.7, zorder=0)
for spine in ax.spines.values():
spine.set_edgecolor("#2a2a4a")
plt.tight_layout()
plt.show()
# ── Chart 3: Daily Line ──────────────────────
def plot_daily_line(df):
daily = df.groupby(df["Date"].dt.date)["Amount"].sum().reset_index()
daily.columns = ["Date", "Amount"]
daily = daily.sort_values("Date")
daily["Cumulative"] = daily["Amount"].cumsum()
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(11, 7), sharex=True)
fig.patch.set_facecolor("#0f0c29")
for ax in (ax1, ax2):
ax.set_facecolor("#16213e")
# Daily spend
ax1.fill_between(range(len(daily)), daily["Amount"],
color="#e94560", alpha=0.4)
ax1.plot(range(len(daily)), daily["Amount"],
color="#e94560", linewidth=2, marker="o", markersize=4)
ax1.set_title("Daily Spending", color="white", fontsize=12, fontweight="bold")
ax1.set_ylabel("Amount (₹)", color="#a0a0b0")
ax1.tick_params(colors="white")
ax1.yaxis.set_major_formatter(
plt.FuncFormatter(lambda x, _: f"₹{x:,.0f}"))
ax1.grid(color="#2a2a4a", linestyle="--", alpha=0.5)
# Cumulative
ax2.fill_between(range(len(daily)), daily["Cumulative"],
color="#6c5ce7", alpha=0.35)
ax2.plot(range(len(daily)), daily["Cumulative"],
color="#a29bfe", linewidth=2, marker="s", markersize=4)
ax2.set_title("Cumulative Spending", color="white",
fontsize=12, fontweight="bold")
ax2.set_ylabel("Amount (₹)", color="#a0a0b0")
ax2.set_xticks(range(len(daily)))
ax2.set_xticklabels(
[str(d) for d in daily["Date"]], rotation=35,
ha="right", fontsize=7, color="white"
)
ax2.yaxis.set_major_formatter(
plt.FuncFormatter(lambda x, _: f"₹{x:,.0f}"))
ax2.grid(color="#2a2a4a", linestyle="--", alpha=0.5)
for spine in ax2.spines.values():
spine.set_edgecolor("#2a2a4a")
plt.tight_layout()
plt.show()
# ── Chart 4: Horizontal Bar ──────────────────
def plot_category_hbar(df):
cat_totals = df.groupby("Category")["Amount"].sum().sort_values()
colors = [CAT_COLORS.get(c, "#cccccc") for c in cat_totals.index]
fig, ax = plt.subplots(figsize=(10, 6))
fig.patch.set_facecolor("#0f0c29")
ax.set_facecolor("#16213e")
bars = ax.barh(cat_totals.index, cat_totals.values,
color=colors, edgecolor="#0f0c29",
linewidth=0.8, height=0.6)
for bar, val in zip(bars, cat_totals.values):
ax.text(val + cat_totals.max() * 0.01,
bar.get_y() + bar.get_height() / 2,
f"₹{val:,.0f}", va="center", color="white", fontsize=9)
ax.set_title("Category-Wise Comparison", color="white",
fontsize=14, fontweight="bold")
ax.set_xlabel("Total Amount (₹)", color="#a0a0b0")
ax.tick_params(colors="white")
ax.xaxis.set_major_formatter(
plt.FuncFormatter(lambda x, _: f"₹{x:,.0f}"))
ax.grid(axis="x", color="#2a2a4a", linestyle="--", alpha=0.6)
for spine in ax.spines.values():
spine.set_edgecolor("#2a2a4a")
plt.tight_layout()
plt.show()
# ── Chart 5: Full Dashboard ──────────────────
def plot_dashboard(df):
fig = plt.figure(figsize=(14, 9))
fig.patch.set_facecolor("#0f0c29")
gs = GridSpec(2, 3, figure=fig, hspace=0.45, wspace=0.4)
DARK = "#16213e"
WHITE = "white"
SUB = "#a0a0b0"
def style_ax(ax):
ax.set_facecolor(DARK)
ax.tick_params(colors=WHITE, labelsize=8)
for sp in ax.spines.values():
sp.set_edgecolor("#2a2a4a")
ax.grid(color="#2a2a4a", linestyle="--", alpha=0.5)
# Title
fig.suptitle("💰 EXPENSE TRACKER — DASHBOARD",
color=WHITE, fontsize=16, fontweight="bold", y=0.98)
# ── Top KPI strip ──
ax_kpi = fig.add_subplot(gs[0, :])
ax_kpi.set_facecolor("#16213e")
ax_kpi.axis("off")
today = date.today()
m_df = df[(df["Date"].dt.month == today.month) &
(df["Date"].dt.year == today.year)]
kpis = [
("Total Spent", f"₹{df['Amount'].sum():,.2f}"),
("This Month", f"₹{m_df['Amount'].sum():,.2f}"),
("Transactions", str(len(df))),
("Avg per Day", f"₹{df.groupby(df['Date'].dt.date)['Amount'].sum().mean():,.2f}"),
("Largest Expense", f"₹{df['Amount'].max():,.2f}"),
("Top Category", df.groupby('Category')['Amount'].sum().idxmax()),
]
for i, (label, val) in enumerate(kpis):
x = 0.08 + i * 0.155
ax_kpi.text(x, 0.75, val, transform=ax_kpi.transAxes,
color="#e94560", fontsize=13, fontweight="bold", ha="center")
ax_kpi.text(x, 0.2, label, transform=ax_kpi.transAxes,
color=SUB, fontsize=8, ha="center")
ax_kpi.set_title("Key Metrics", color=WHITE, fontsize=10,
loc="left", pad=4)
# ── Pie ──
ax1 = fig.add_subplot(gs[1, 0])
ax1.set_facecolor(DARK)
cat_totals = df.groupby("Category")["Amount"].sum()
cat_totals = cat_totals[cat_totals > 0]
colors = [CAT_COLORS.get(c,"#ccc") for c in cat_totals.index]
wedges, _, autotexts = ax1.pie(
cat_totals.values, autopct="%1.0f%%", colors=colors,
startangle=140, pctdistance=0.75,
wedgeprops={"linewidth":1,"edgecolor":DARK}
)
for at in autotexts:
at.set_color(WHITE); at.set_fontsize(7)
centre = plt.Circle((0,0),0.5,color=DARK)
ax1.add_artist(centre)
ax1.set_title("By Category", color=WHITE, fontsize=10)
# ── Monthly Bar ──
ax2 = fig.add_subplot(gs[1, 1])
style_ax(ax2)
df["Month"] = df["Date"].dt.to_period("M").astype(str)
monthly = df.groupby("Month")["Amount"].sum().sort_index().tail(6)
ax2.bar(range(len(monthly)), monthly.values, color="#e94560",
edgecolor=DARK, zorder=3)
ax2.set_xticks(range(len(monthly)))
ax2.set_xticklabels(monthly.index, rotation=30, ha="right",
fontsize=7, color=WHITE)
ax2.set_title("Monthly Trend", color=WHITE, fontsize=10)
ax2.set_ylabel("₹", color=SUB, fontsize=8)
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x,_: f"₹{x/1000:.0f}k"))
# ── Horizontal Bar ──
ax3 = fig.add_subplot(gs[1, 2])
style_ax(ax3)
top5 = df.groupby("Category")["Amount"].sum().nlargest(5).sort_values()
clrs = [CAT_COLORS.get(c,"#ccc") for c in top5.index]
ax3.barh(range(len(top5)), top5.values, color=clrs,
edgecolor=DARK, height=0.5)
ax3.set_yticks(range(len(top5)))
ax3.set_yticklabels(top5.index, fontsize=7, color=WHITE)
ax3.set_title("Top 5 Categories", color=WHITE, fontsize=10)
ax3.xaxis.set_major_formatter(plt.FuncFormatter(lambda x,_: f"₹{x/1000:.0f}k"))
plt.savefig("dashboard.png", dpi=120, bbox_inches="tight",
facecolor=fig.get_facecolor())
print(" [✓] Dashboard saved as 'dashboard.png'")
plt.show()
# ─────────────────────────────────────────────
# FEATURE 9 — STATISTICS REPORT
# ─────────────────────────────────────────────
def statistics_report():
print_header("Statistics Report")
df = load_data()
if df.empty:
print(" [!] No data available."); pause(); return
total = df["Amount"].sum()
count = len(df)
avg_txn = df["Amount"].mean()
max_exp = df.loc[df["Amount"].idxmax()]
min_exp = df.loc[df["Amount"].idxmin()]
top_cat = df.groupby("Category")["Amount"].sum().idxmax()
top_mode = df["Payment_Mode"].value_counts().idxmax()
daily_avg = df.groupby(df["Date"].dt.date)["Amount"].sum().mean()
monthly_df = df.groupby(df["Date"].dt.to_period("M"))["Amount"].sum()
print(f"\n {'Total Amount Spent':<30} : ₹{total:>12,.2f}")
print(f" {'Total Transactions':<30} : {count:>13}")
print(f" {'Average per Transaction':<30} : ₹{avg_txn:>12,.2f}")
print(f" {'Average Daily Spending':<30} : ₹{daily_avg:>12,.2f}")
print(f" {'Top Spending Category':<30} : {top_cat}")
print(f" {'Most Used Payment Mode':<30} : {top_mode}")
print(f"\n Highest Single Expense:")
print(f" ₹{max_exp['Amount']:,.2f} | {max_exp['Category']} "
f"| {max_exp['Description']} | {str(max_exp['Date'])[:10]}")
print(f"\n Lowest Single Expense:")
print(f" ₹{min_exp['Amount']:,.2f} | {min_exp['Category']} "
f"| {min_exp['Description']} | {str(min_exp['Date'])[:10]}")
if not monthly_df.empty:
print(f"\n Best Month (lowest) : {monthly_df.idxmin()} — ₹{monthly_df.min():,.2f}")
print(f" Worst Month (highest): {monthly_df.idxmax()} — ₹{monthly_df.max():,.2f}")
pause()
# ─────────────────────────────────────────────
# MAIN MENU
# ─────────────────────────────────────────────
def main_menu():
initialize_file()
while True:
print("\n" + LINE)
print(" 💰 EXPENSE TRACKER")
print(" CBSE Class 12 — pandas + matplotlib Project")
print(LINE)
print(" 1. Add New Expense")
print(" 2. View All Expenses")
print(" 3. Filter Expenses")
print(" 4. Delete an Expense")
print(" 5. Monthly Summary")
print(" 6. Category-Wise Summary")
print(" 7. Budget Manager")
print(" 8. Charts & Visualisation")
print(" 9. Statistics Report")
print(" 0. Exit")
print(LINE)
ch = input(" Enter choice: ").strip()
if ch == "1": add_expense()
elif ch == "2": view_all()
elif ch == "3": filter_expenses()
elif ch == "4": delete_expense()
elif ch == "5": monthly_summary()
elif ch == "6": category_summary()
elif ch == "7": budget_menu()
elif ch == "8": charts_menu()
elif ch == "9": statistics_report()
elif ch == "0":
print("\n Thank you for using Expense Tracker. Goodbye!\n")
break
else:
print(" [!] Invalid choice. Enter 0–9.")
# ─────────────────────────────────────────────
# ENTRY POINT
# ─────────────────────────────────────────────
if __name__ == "__main__":
main_menu()