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
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)
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)
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()
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
Explanation:
Features: [study_hours, attendance, previous_grade]
Target: current_score
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)
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"))
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()
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
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.")
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()
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)