Python Forum

Full Version: [split] Code help
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I created a peer-to-peer lending simulation in spyder when I run my main output I get an error in creating one of my plots and Im not sure why. The error comes up as "Unable to create Lender Funds Visualisation" in a message box

Output:
runfile('C:/Users/Emma Robinson/OneDrive/Documents/FIN3028 Python/Developer Showcase/main.py', wdir='C:/Users/Emma Robinson/OneDrive/Documents/FIN3028 Python/Developer Showcase') Lenders loaded: [{'id': 1, 'name': 'Lender A', 'available_funds': 5000, 'min_interest_rate_%': 5, 'max_risk': 2}, {'id': 2, 'name': 'Lender B', 'available_funds': 3000, 'min_interest_rate_%': 4, 'max_risk': 1}, {'id': 3, 'name': 'Lender C', 'available_funds': 7000, 'min_interest_rate_%': 6, 'max_risk': 3}] Borrowers loaded: [{'id': 1, 'name': 'Borrower X', 'amount_needed': 2000, 'interest_rate_%': 6, 'risk': 1, 'duration_years': 3}, {'id': 2, 'name': 'Borrower Y', 'amount_needed': 4000, 'interest_rate_%': 5, 'risk': 2, 'duration_years': 2}, {'id': 3, 'name': 'Borrower Z', 'amount_needed': 1500, 'interest_rate_%': 7, 'risk': 3, 'duration_years': 1}] ERROR:root:Missing 'name' of 'funds' key in lender: {'id': 1, 'name': 'Lender A', 'available_funds': 3000, 'min_interest_rate_%': 5, 'max_risk': 2} ERROR:root:Missing 'name' of 'funds' key in lender: {'id': 2, 'name': 'Lender B', 'available_funds': 3000, 'min_interest_rate_%': 4, 'max_risk': 1} ERROR:root:Missing 'name' of 'funds' key in lender: {'id': 3, 'name': 'Lender C', 'available_funds': 5500, 'min_interest_rate_%': 6, 'max_risk': 3} ERROR:root:No valid lender data available for plotting. Repayment Summary: borrower_id total_payment ... total_interest remaining_balance 0 1 2190.24 ... 190.37 0.0 1 3 1557.48 ... 57.47 0.0 [2 rows x 5 columns] Repayment summary saved as 'repayment_summary.csv'.
This is my main function :
import tkinter as tk
from tkinter import messagebox
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from input_output_data import load_json_data, select_file, validate_json_file, save_results
from matching_generator import match_lenders_borrowers
from repayment_simulator import simulate_repayment_schedule, generate_summary
from visualisation import plot_lender_funds, plot_repayment_schedules
import logging
import sys
import os

# Ensure correct working directory
script_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(script_dir)
sys.path.append(script_dir)

logging.basicConfig(level=logging.INFO)

def show_messagebox(title, message, error=False):
    """
    Display a messagebox for success or error messages.

    Parameters:
        - title (str): Title of the messagebox.
        - message (str): Message content.
        - error (bool): True for an error message, False for an info message.
    """
    if error:
        messagebox.showerror(title, message)
    else:
        messagebox.showinfo(title, message)
        
def display_plot(fig, root, title="Visualisation"):
    """
    Display a matplotlib figure in a tkinter window.

    Parameters:
        - fig: Matplotlib figure object.
        - root: Tkinter root window.
        - title: Title of the plot window.
    """
    if fig:
        canvas = FigureCanvasTkAgg(fig, master=root)
        canvas.get_tk_widget().pack()
        canvas.draw()
    else:
        show_messagebox("Error", f"Unable to generate {title}.", error=True)

# Main function
def main():
    """
    Main function to orchestrate the Peer-to-Peer Lending Simulator.
    """
   
    # Set up tkinter root window for file dialogs and plots
    #file:///C:/Users/Emma%20Robinson/Downloads/FIN3028_Companion_Notes.pdf
    root = tk.Tk()
    root.title("Peer-to-Peer Lending Simulator")
    root.geometry("800x600") #Set window size
    
    logging.info("Starting Peer-to-Peer Lending Simulator.")
    
    # File selection
    lenders_file = select_file("Lenders")
    borrowers_file = select_file("Borrowers")
    
    if not lenders_file or not borrowers_file:
        show_messagebox("Error", "Both lenders and borrowers files are required.", error=True)
        return
    
    try:
        validate_json_file(lenders_file)
        validate_json_file(borrowers_file)
    except ValueError as e:
        show_messagebox("Error", str(e), error=True)
        return
    
    # Load data
    lenders = load_json_data(lenders_file)
    borrowers = load_json_data(borrowers_file)
    
    if not lenders:
        show_messagebox("Error", "Lenders data is empty or invalid.", error = True)
        return
    if not borrowers:
        show_messagebox("Error", "Borrowers data is empty or invalid.", error = True)
        return
    
    # Debug prints to verify structure
    print("Lenders loaded:", lenders)
    print("Borrowers loaded:", borrowers)

    # Process matches
    matches, unmatched_borrowers = match_lenders_borrowers(lenders, borrowers)
    if not matches:
        logging.info(f"Lenders: {lenders}")
        logging.info(f"Borrowers: {borrowers}")
        show_messagebox("Info", "No matches were found. Check logs for details.")
        return
    
    # Simulate repayment schedules
    repayment_schedule = simulate_repayment_schedule(matches, borrowers)
    if not repayment_schedule:
        show_messagebox("Error", "Failed to generate repayment schedule.", error=True)
        return
    
    # Save results
    save_results(matches, repayment_schedule)
    
    # Generate and display summary
    summary = generate_summary(repayment_schedule)
    if not summary.empty:
        print("\nRepayment Summary:\n")
        print(summary)

        # Optional: Save the summary to a CSV file
        summary.to_csv("repayment_summary.csv")
        print("Repayment summary saved as 'repayment_summary.csv'.")
    
    #Visualise lender funds and repayment schedules
    fig1 = plot_lender_funds(lenders)
    display_plot(fig1, root, title="Lender Funds Visualisation")

    fig2 = plot_repayment_schedules(repayment_schedule)
    display_plot(fig2, root, title="Repayment Schedules Visualisation")

    root.mainloop()  # Keep the plots open

if __name__ == "__main__":
    main()
This is my visualisation code

import matplotlib.pyplot as plt
import logging
import matplotlib.ticker as mticker
import os

logging.basicConfig(level=logging.ERROR)

#Plot amount of funds lenders have remaining after matching loans
def plot_lender_funds(lenders, save_path = None):
    
    """
    Creates a bar chart showing remaining funds each lender has
    
    Parameters:
        - lenders (list of dict): List of lender details
        
    Returns:
        - matplotlib.figure.Figure: The figure object containing the bar chart.
    """
    
    if not lenders:
        logging.error("No lender data available to plot.")
        return None
    
    filtered_lenders = []
    for lender in lenders:
        if 'name' not in lender or 'funds' not in lender:
            logging.error(f"Missing 'name'  of 'funds' key in lender: {lender}")
            continue
        filtered_lenders.append(lender)
        
    if not filtered_lenders:
        logging.error("No valid lender data available for plotting.")
        return None
    
    # Sort and limit to top 10 lenders for large datasets
    filtered_lenders = sorted(filtered_lenders, key=lambda x: x['available_funds'], reverse=True)[:10]
    
    lender_names = [lender['name'] for lender in lenders]
    funds_remaining = [lender['funds'] for lender in lenders]
    
    fig, ax = plt.subplots(figsize = (8,6))
    ax.bar(lender_names, funds_remaining)
    ax.set_title("Lender Funds Remaining")
    ax.set_xlabel("Lender")
    ax.set_ylabel("Funds Remaining (GBP)")
    
    #Format Y-axis for currency
    ax.yaxis.set_major_formatter(mticker.StrMethodFormatter('£{x:,.0f}'))
    plt.xticks(rotation = 45)
    plt.tight_layout()
    
    if save_path:
        save_path = os.path.abspath(save_path)
        os.makedirs(os.path.dirname(save_path), exist_ok=True)  # Ensure directory exists
        fig.savefig(save_path)
        
    return fig

#Plot repayment schedules over time for each borrower - line plot  
def plot_repayment_schedules(repayment_schedule, save_path = None):
    
    """
    Plots repayment schedules for loan over time for each borrower
    
    Parameters:
        - repayment_schdeule (list of dict): The repayment schedule
        
    Returns:
        - matplotlib.figure.Figure: The figure object containing the repayment schedule plot.
    """
    if not repayment_schedule:
        logging.error("No repayment schedule data to plot.")
        return None
    
    #Group repayments by borrower
    borrowers = {entry['borrower_id'] for entry in repayment_schedule}
    
    fig, ax = plt.subplots(figsize = (10, 6))
    
    #Loop through each borrower - extract monthly payments and months
    for borrower_id in borrowers:
        
        borrower_payments = [entry['payment'] for entry in repayment_schedule if entry['borrower_id'] == borrower_id]
        
        months = [entry['month'] for entry in repayment_schedule if entry['borrower_id'] == borrower_id]
        
        ax.plot(months, borrower_payments, label = f"Borrower {borrower_id}")
        
    ax.set_title("Repayment Schedules")
    ax.set_xlabel("Month")
    ax.set_ylabel("Monthly Payment (GBP)")
    ax.legend(title="Borrower ID")
    ax.grid(True)
    plt.tight_layout()
    
    if save_path:
        save_path = os.path.abspath(save_path)
        os.makedirs(os.path.dirname(save_path), exist_ok=True)  # Ensure directory exists
        fig.savefig(save_path)

    return fig
This is my borrower data and lender data i used to test:

Output:
[ { "id": 1, "name": "Lender A", "available_funds": 5000, "min_interest_rate_%": 5, "max_risk": 2 }, { "id": 2, "name": "Lender B", "available_funds": 3000, "min_interest_rate_%": 4, "max_risk": 1 }, { "id": 3, "name": "Lender C", "available_funds": 7000, "min_interest_rate_%": 6, "max_risk": 3 } ] [ { "id": 1, "name": "Borrower X", "amount_needed": 2000, "interest_rate_%": 6, "risk": 1, "duration_years": 3 }, { "id": 2, "name": "Borrower Y", "amount_needed": 4000, "interest_rate_%": 5, "risk": 2, "duration_years": 2 }, { "id": 3, "name": "Borrower Z", "amount_needed": 1500, "interest_rate_%": 7, "risk": 3, "duration_years": 1 } ]
Not sure if my mistake is easy found, just have been looking for ages and would appreciate any help. Thanks
Tthere is error:

ERROR:root:Missing 'name'  of 'funds' key in lender: {'id': 1, 'name': 'Lender A', 'available_funds': 3000, 'min_interest_rate_%': 5, 'max_risk': 2}
There is code which causes it:

 if 'name' not in lender or 'funds' not in lender:
            logging.error(f"Missing 'name'  of 'funds' key in lender: {lender}")
There is no key 'funds' in lender:

>>> lender = {'available_funds': 7000}
>>> 'funds' in lender
False
>>> for key in lender:
...     if 'funds' in key:
...         print(key)
...
available_funds