DEV Community

Mate Technologies
Mate Technologies

Posted on

Build an Exam Result Predictor in Python with Tkinter & Machine Learning

Ever wondered if you could predict student exam results based on study habits and previous grades? In this tutorial, we’ll build ExamResultPredictor v2.0, a Python app that predicts exam grades using linear regression and a friendly GUI.

We’ll use:

Tkinter for GUI

ttkbootstrap for modern styling

scikit-learn for predictive modeling

By the end, you’ll have a working app where you can add students manually or via CSV, predict their grades, and export results.

GitHub repo: ExamResultPredictor

Step 1: Set Up Your Environment

Make sure you have Python 3.8+ installed. Then install the required packages:

pip install numpy scikit-learn tkinter ttkbootstrap

Step 2: Import Libraries

We’ll start by importing the necessary libraries.

import os
import sys
import threading
import csv
import tkinter as tk
from tkinter import messagebox, filedialog, ttk

import ttkbootstrap as tb
from ttkbootstrap.constants import *

from sklearn.linear_model import LinearRegression
import numpy as np
Enter fullscreen mode Exit fullscreen mode

Explanation:

tkinter & ttkbootstrap → For GUI components

threading → To run predictions without freezing the app

scikit-learn → For building the linear regression model

numpy → For numeric arrays

Step 3: Utility Function

We create a helper function to handle file paths, especially for executable builds.

def resource_path(file_name):
    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, file_name)
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a Prediction Worker

We’ll define a worker class to run predictions in a separate thread.

class PredictorWorker:
    def __init__(self, data, model, callbacks):
        self.data = data
        self.model = model
        self.callbacks = callbacks
        self._running = True

    def stop(self):
        self._running = False

    def run(self):
        results = []
        for i, student in enumerate(self.data):
            if not self._running:
                break
            features = np.array([[student["study_hours"], student["attendance"], student["previous_grade"]]])
            predicted_score = self.model.predict(features)[0]
            grade = "A" if predicted_score >= 80 else "B" if predicted_score >= 60 else "C" if predicted_score >= 40 else "F"
            results.append({**student, "predicted_grade": grade})

            if "found" in self.callbacks:
                self.callbacks["found"](student, grade)
            if "progress" in self.callbacks:
                self.callbacks["progress"](int((i + 1) / len(self.data) * 100))

        if "finished" in self.callbacks:
            self.callbacks["finished"](results)
Enter fullscreen mode Exit fullscreen mode

Explanation:

Runs predictions in a thread to avoid freezing the GUI

Predicts grades and maps numeric scores to letters

Step 5: Build the Main App Class

We now create the main application GUI.

class ExamResultPredictorApp:
    APP_NAME = "ExamResultPredictor"
    APP_VERSION = "2.0"

    def __init__(self):
        self.root = tb.Window(themename="darkly")
        self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
        self.root.minsize(950, 650)

        try:
            self.root.iconbitmap(resource_path("logo.ico"))
        except:
            pass

        self.worker_obj = None
        self.smooth_value = 0
        self.target_progress = 0
        self.student_data = []
        self.model = self._train_dummy_model()

        self._build_ui()
        self._apply_styles()
Enter fullscreen mode Exit fullscreen mode

Explanation:

Initializes the Tkinter window

Sets up variables for data, progress, and the model

Step 6: Train a Dummy Model

We use a simple linear regression model trained on sample data.

def _train_dummy_model(self):
    X = np.array([
        [5, 80, 70],
        [10, 90, 80],
        [2, 60, 50],
        [8, 100, 90],
        [3, 50, 40],
        [6, 70, 65]
    ])
    y = np.array([60, 90, 40, 95, 35, 70])
    model = LinearRegression()
    model.fit(X, y)
    return model
Enter fullscreen mode Exit fullscreen mode

Explanation:

Features: [study_hours, attendance, previous_grade]

Target: current_score
Enter fullscreen mode Exit fullscreen mode

Model predicts score based on inputs

Step 7: Build the GUI

We’ll create input fields, buttons, progress bars, and a table for results.

def _build_ui(self):
    main = tb.Frame(self.root, padding=10)
    main.pack(fill=BOTH, expand=True)

    tb.Label(main, text=f"📚 {self.APP_NAME} - Academic Predictor",
             font=("Segoe UI", 22, "bold")).pack(pady=(0, 4))
    tb.Label(main, text="Predict Exam Results Based on Input Parameters or CSV",
             font=("Segoe UI", 10, "italic"), foreground="#9ca3af").pack(pady=(0, 20))

    form_frame = tb.Frame(main)
    form_frame.pack(fill=X, pady=(0,6))

    self.name_input = self._create_form_row(form_frame, "Student Name:")
    self.study_input = self._create_form_row(form_frame, "Study Hours per Week:")
    self.attendance_input = self._create_form_row(form_frame, "Attendance %:")
    self.prev_grade_input = self._create_form_row(form_frame, "Previous Grade (0-100):")

    tb.Button(form_frame, text="➕ Add Student", bootstyle=SUCCESS, command=self.add_student).grid(row=4, column=0, columnspan=2, pady=5)
    tb.Button(form_frame, text="📁 Import CSV", bootstyle=INFO, command=self.import_csv).grid(row=5, column=0, columnspan=2, pady=5)
Enter fullscreen mode Exit fullscreen mode

Explanation:

Input fields for student data

Buttons for adding students and importing CSV

Step 8: Add Student Data & CSV Import

def add_student(self):
    try:
        student = {
            "name": self.name_input.get().strip(),
            "study_hours": float(self.study_input.get()),
            "attendance": float(self.attendance_input.get()),
            "previous_grade": float(self.prev_grade_input.get())
        }
        self.student_data.append(student)
        self.tree.insert("", END, values=(student["name"], student["study_hours"],
                                          student["attendance"], student["previous_grade"], "Pending"))
    except ValueError:
        messagebox.showerror("Invalid Input", "Please enter valid numbers.")

def import_csv(self):
    path = filedialog.askopenfilename(filetypes=[("CSV Files", "*.csv")])
    if not path:
        return
    with open(path, newline="", encoding="utf-8") as csvfile:
        reader = csv.DictReader(csvfile)
        for row in reader:
            student = {
                "name": row["name"].strip(),
                "study_hours": float(row["study_hours"]),
                "attendance": float(row["attendance"]),
                "previous_grade": float(row["previous_grade"])
            }
            self.student_data.append(student)
            self.tree.insert("", END, values=(student["name"], student["study_hours"], student["attendance"], student["previous_grade"], "Pending"))
Enter fullscreen mode Exit fullscreen mode

Explanation:

Users can add a student manually or import from CSV

Data is stored in self.student_data and displayed in a table

Step 9: Run Predictions

def start(self):
    if not self.student_data:
        messagebox.showwarning("No Data", "Add at least one student before predicting.")
        return
    self.start_btn.config(state=DISABLED)
    self.cancel_btn.config(state=NORMAL)
    threading.Thread(target=self._run_worker, daemon=True).start()
Enter fullscreen mode Exit fullscreen mode

Explanation:

Starts predictions in a separate thread

Prevents the GUI from freezing

Step 10: Display Results

def update_student_grade(self, student, grade):
    for i in self.tree.get_children():
        vals = self.tree.item(i)["values"]
        if vals[0] == student["name"]:
            self.tree.item(i, values=(vals[0], vals[1], vals[2], vals[3], grade))
            break
Enter fullscreen mode Exit fullscreen mode

Explanation:

Updates table with predicted grade in real-time

Step 11: Export Results

def export_results(self):
    path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV Files","*.csv")])
    if path:
        with open(path,"w",newline="",encoding="utf-8") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(["name","study_hours","attendance","previous_grade","predicted_grade"])
            for i in self.tree.get_children():
                writer.writerow(self.tree.item(i)["values"])
        messagebox.showinfo("Export", "Export completed successfully.")
Enter fullscreen mode Exit fullscreen mode

Explanation:

Save the predicted results as a CSV file

Easy for sharing or record-keeping

Step 12: Run the App

Finally, run the application:

if __name__ == "__main__":
    app = ExamResultPredictorApp()
    app.run()
Enter fullscreen mode Exit fullscreen mode

Result:
You now have a fully functioning ExamResultPredictor v2.0!

Add students manually or via CSV

Predict grades using a machine learning model

Export results for analysis

🎉 Next Steps for Learners:

Replace the dummy model with real historical data

Experiment with different machine learning models

Add charts or analytics for visualization

GitHub link for the full code: ExamResultPredictor

Top comments (0)