🧮 Building a Modern Statistics Calculator in Python with Tkinter

🧮 Building a Modern Statistics Calculator in Python with Tkinter

Source: Dev.to

Python is often praised for its data science ecosystem, but you don’t always need Pandas or NumPy to build something useful. In this post, we’ll build StatMate — a clean, modern desktop statistics calculator using: 🪟 Tkinter (Python’s standard GUI library) 🎨 sv-ttk for modern theming 📊 Python’s built-in statistics module By the end, you’ll have a polished GUI app that calculates: Min / Max
with real-time input validation and configurable decimal precision. Modern light theme using sv-ttk Real-time input validation Adjustable decimal precision Clean status bar feedback Graceful error handling Install the only external dependency: Everything else comes from Python’s standard library. The app is structured into logical sections: Regex-based numeric input filtering Uses Python’s statistics module Cards, labels, buttons, and result panel 🧮 How the Calculation Works The user enters numbers as a comma-separated list: Parse and sanitize the input Convert values to floats Compute statistics safely Format output based on selected precision Edge cases (like no unique mode or a single value) are handled gracefully. 🧑‍💻 Full Source Code 🚀 Possible Improvements Export results to CSV Add histogram visualization Tkinter doesn’t have to look outdated. With a bit of structure, validation, and theming, you can build clean, professional desktop apps using only Python. If you enjoyed this project, consider extending it—or turning it into a reusable statistics toolkit. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK:
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
import statistics
import re # =========================
# Helpers
# =========================
def set_status(msg): status_var.set(msg) root.update_idletasks() def fmt(value): try: p = precision_var.get() return f"{value:.{p}f}" except Exception: return str(value) def calculate_stats(): raw_input = numbers_entry.get() if raw_input.strip() == 'e.g., 1, 2, 3, 4': messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.") return try: numbers = [float(x.strip()) for x in raw_input.split(",") if x.strip() != ""] if not numbers: raise ValueError mean_val = statistics.mean(numbers) median_val = statistics.median(numbers) try: mode_val = statistics.mode(numbers) except statistics.StatisticsError: mode_val = "No unique mode" variance_val = statistics.variance(numbers) if len(numbers) > 1 else 0 stdev_val = statistics.stdev(numbers) if len(numbers) > 1 else 0 result_var.set( f"📊 Mean: {fmt(mean_val)}\n" f"📌 Median: {fmt(median_val)}\n" f"🎯 Mode: {mode_val}\n" f"📈 Variance: {fmt(variance_val)}\n" f"📉 Std. Dev.: {fmt(stdev_val)}\n" f"🔽 Min: {fmt(min(numbers))}\n" f"🔼 Max: {fmt(max(numbers))}" ) set_status("✅ Statistics calculated successfully") except ValueError: messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.") def validate_input(new_value): if new_value == '' or new_value == 'e.g., 1, 2, 3, 4': return True return re.match(r'^[0-9.,\s-]*$', new_value) is not None # =========================
# App Setup
# =========================
root = tk.Tk()
root.title("StatMate - Full Statistics Tool")
root.geometry("950x650")
root.minsize(950, 650)
sv_ttk.set_theme("light") precision_var = tk.IntVar(value=4)
status_var = tk.StringVar(value="Ready") ttk.Label(root, textvariable=status_var, anchor="w", font=("Segoe UI", 10), padding=(10, 5)
).pack(side="bottom", fill="x") main_frame = ttk.Frame(root, padding=20)
main_frame.pack(expand=True, fill="both") ttk.Label(main_frame, text="StatMate", font=("Segoe UI", 28, "bold")
).pack(anchor="w") ttk.Label(main_frame, text="Full Statistics Calculator", font=("Segoe UI", 14), foreground="#555"
).pack(anchor="w", pady=(0, 20)) input_card = ttk.LabelFrame(main_frame, text="Input Numbers & Precision", padding=20)
input_card.pack(fill="x", pady=(0, 20)) input_row = ttk.Frame(input_card)
input_row.pack(fill="x") def on_entry_click(event): if numbers_entry.get() == 'e.g., 1, 2, 3, 4': numbers_entry.delete(0, "end") numbers_entry.config(fg='black') def on_focusout(event): if numbers_entry.get() == '': numbers_entry.insert(0, 'e.g., 1, 2, 3, 4') numbers_entry.config(fg='grey') ttk.Label(input_row, text="Numbers:").grid(row=0, column=0)
numbers_entry = tk.Entry(input_row, font=("Segoe UI", 14), width=40, fg='grey')
numbers_entry.insert(0, 'e.g., 1, 2, 3, 4')
numbers_entry.bind('<FocusIn>', on_entry_click)
numbers_entry.bind('<FocusOut>', on_focusout)
numbers_entry.grid(row=0, column=1, padx=(5, 20)) vcmd = (root.register(validate_input), '%P')
numbers_entry.config(validate='key', validatecommand=vcmd) ttk.Label(input_row, text="Decimal Precision:").grid(row=0, column=2)
ttk.Spinbox(input_row, from_=0, to=10, textvariable=precision_var, width=5
).grid(row=0, column=3, padx=(5, 0)) ttk.Button(main_frame, text="📊 Calculate Statistics", command=calculate_stats, style="Accent.TButton"
).pack(pady=20, ipadx=10, ipady=10) result_card = ttk.LabelFrame(main_frame, text="Results", padding=20)
result_card.pack(fill="both", expand=True) result_var = tk.StringVar(value="—")
tk.Label(result_card, textvariable=result_var, font=("Segoe UI", 16, "bold"), justify="left", anchor="nw", bd=2, relief="groove", padx=10, pady=10
).pack(fill="both", expand=True) root.mainloop() Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
import statistics
import re # =========================
# Helpers
# =========================
def set_status(msg): status_var.set(msg) root.update_idletasks() def fmt(value): try: p = precision_var.get() return f"{value:.{p}f}" except Exception: return str(value) def calculate_stats(): raw_input = numbers_entry.get() if raw_input.strip() == 'e.g., 1, 2, 3, 4': messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.") return try: numbers = [float(x.strip()) for x in raw_input.split(",") if x.strip() != ""] if not numbers: raise ValueError mean_val = statistics.mean(numbers) median_val = statistics.median(numbers) try: mode_val = statistics.mode(numbers) except statistics.StatisticsError: mode_val = "No unique mode" variance_val = statistics.variance(numbers) if len(numbers) > 1 else 0 stdev_val = statistics.stdev(numbers) if len(numbers) > 1 else 0 result_var.set( f"📊 Mean: {fmt(mean_val)}\n" f"📌 Median: {fmt(median_val)}\n" f"🎯 Mode: {mode_val}\n" f"📈 Variance: {fmt(variance_val)}\n" f"📉 Std. Dev.: {fmt(stdev_val)}\n" f"🔽 Min: {fmt(min(numbers))}\n" f"🔼 Max: {fmt(max(numbers))}" ) set_status("✅ Statistics calculated successfully") except ValueError: messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.") def validate_input(new_value): if new_value == '' or new_value == 'e.g., 1, 2, 3, 4': return True return re.match(r'^[0-9.,\s-]*$', new_value) is not None # =========================
# App Setup
# =========================
root = tk.Tk()
root.title("StatMate - Full Statistics Tool")
root.geometry("950x650")
root.minsize(950, 650)
sv_ttk.set_theme("light") precision_var = tk.IntVar(value=4)
status_var = tk.StringVar(value="Ready") ttk.Label(root, textvariable=status_var, anchor="w", font=("Segoe UI", 10), padding=(10, 5)
).pack(side="bottom", fill="x") main_frame = ttk.Frame(root, padding=20)
main_frame.pack(expand=True, fill="both") ttk.Label(main_frame, text="StatMate", font=("Segoe UI", 28, "bold")
).pack(anchor="w") ttk.Label(main_frame, text="Full Statistics Calculator", font=("Segoe UI", 14), foreground="#555"
).pack(anchor="w", pady=(0, 20)) input_card = ttk.LabelFrame(main_frame, text="Input Numbers & Precision", padding=20)
input_card.pack(fill="x", pady=(0, 20)) input_row = ttk.Frame(input_card)
input_row.pack(fill="x") def on_entry_click(event): if numbers_entry.get() == 'e.g., 1, 2, 3, 4': numbers_entry.delete(0, "end") numbers_entry.config(fg='black') def on_focusout(event): if numbers_entry.get() == '': numbers_entry.insert(0, 'e.g., 1, 2, 3, 4') numbers_entry.config(fg='grey') ttk.Label(input_row, text="Numbers:").grid(row=0, column=0)
numbers_entry = tk.Entry(input_row, font=("Segoe UI", 14), width=40, fg='grey')
numbers_entry.insert(0, 'e.g., 1, 2, 3, 4')
numbers_entry.bind('<FocusIn>', on_entry_click)
numbers_entry.bind('<FocusOut>', on_focusout)
numbers_entry.grid(row=0, column=1, padx=(5, 20)) vcmd = (root.register(validate_input), '%P')
numbers_entry.config(validate='key', validatecommand=vcmd) ttk.Label(input_row, text="Decimal Precision:").grid(row=0, column=2)
ttk.Spinbox(input_row, from_=0, to=10, textvariable=precision_var, width=5
).grid(row=0, column=3, padx=(5, 0)) ttk.Button(main_frame, text="📊 Calculate Statistics", command=calculate_stats, style="Accent.TButton"
).pack(pady=20, ipadx=10, ipady=10) result_card = ttk.LabelFrame(main_frame, text="Results", padding=20)
result_card.pack(fill="both", expand=True) result_var = tk.StringVar(value="—")
tk.Label(result_card, textvariable=result_var, font=("Segoe UI", 16, "bold"), justify="left", anchor="nw", bd=2, relief="groove", padx=10, pady=10
).pack(fill="both", expand=True) root.mainloop() COMMAND_BLOCK:
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
import statistics
import re # =========================
# Helpers
# =========================
def set_status(msg): status_var.set(msg) root.update_idletasks() def fmt(value): try: p = precision_var.get() return f"{value:.{p}f}" except Exception: return str(value) def calculate_stats(): raw_input = numbers_entry.get() if raw_input.strip() == 'e.g., 1, 2, 3, 4': messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.") return try: numbers = [float(x.strip()) for x in raw_input.split(",") if x.strip() != ""] if not numbers: raise ValueError mean_val = statistics.mean(numbers) median_val = statistics.median(numbers) try: mode_val = statistics.mode(numbers) except statistics.StatisticsError: mode_val = "No unique mode" variance_val = statistics.variance(numbers) if len(numbers) > 1 else 0 stdev_val = statistics.stdev(numbers) if len(numbers) > 1 else 0 result_var.set( f"📊 Mean: {fmt(mean_val)}\n" f"📌 Median: {fmt(median_val)}\n" f"🎯 Mode: {mode_val}\n" f"📈 Variance: {fmt(variance_val)}\n" f"📉 Std. Dev.: {fmt(stdev_val)}\n" f"🔽 Min: {fmt(min(numbers))}\n" f"🔼 Max: {fmt(max(numbers))}" ) set_status("✅ Statistics calculated successfully") except ValueError: messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.") def validate_input(new_value): if new_value == '' or new_value == 'e.g., 1, 2, 3, 4': return True return re.match(r'^[0-9.,\s-]*$', new_value) is not None # =========================
# App Setup
# =========================
root = tk.Tk()
root.title("StatMate - Full Statistics Tool")
root.geometry("950x650")
root.minsize(950, 650)
sv_ttk.set_theme("light") precision_var = tk.IntVar(value=4)
status_var = tk.StringVar(value="Ready") ttk.Label(root, textvariable=status_var, anchor="w", font=("Segoe UI", 10), padding=(10, 5)
).pack(side="bottom", fill="x") main_frame = ttk.Frame(root, padding=20)
main_frame.pack(expand=True, fill="both") ttk.Label(main_frame, text="StatMate", font=("Segoe UI", 28, "bold")
).pack(anchor="w") ttk.Label(main_frame, text="Full Statistics Calculator", font=("Segoe UI", 14), foreground="#555"
).pack(anchor="w", pady=(0, 20)) input_card = ttk.LabelFrame(main_frame, text="Input Numbers & Precision", padding=20)
input_card.pack(fill="x", pady=(0, 20)) input_row = ttk.Frame(input_card)
input_row.pack(fill="x") def on_entry_click(event): if numbers_entry.get() == 'e.g., 1, 2, 3, 4': numbers_entry.delete(0, "end") numbers_entry.config(fg='black') def on_focusout(event): if numbers_entry.get() == '': numbers_entry.insert(0, 'e.g., 1, 2, 3, 4') numbers_entry.config(fg='grey') ttk.Label(input_row, text="Numbers:").grid(row=0, column=0)
numbers_entry = tk.Entry(input_row, font=("Segoe UI", 14), width=40, fg='grey')
numbers_entry.insert(0, 'e.g., 1, 2, 3, 4')
numbers_entry.bind('<FocusIn>', on_entry_click)
numbers_entry.bind('<FocusOut>', on_focusout)
numbers_entry.grid(row=0, column=1, padx=(5, 20)) vcmd = (root.register(validate_input), '%P')
numbers_entry.config(validate='key', validatecommand=vcmd) ttk.Label(input_row, text="Decimal Precision:").grid(row=0, column=2)
ttk.Spinbox(input_row, from_=0, to=10, textvariable=precision_var, width=5
).grid(row=0, column=3, padx=(5, 0)) ttk.Button(main_frame, text="📊 Calculate Statistics", command=calculate_stats, style="Accent.TButton"
).pack(pady=20, ipadx=10, ipady=10) result_card = ttk.LabelFrame(main_frame, text="Results", padding=20)
result_card.pack(fill="both", expand=True) result_var = tk.StringVar(value="—")
tk.Label(result_card, textvariable=result_var, font=("Segoe UI", 16, "bold"), justify="left", anchor="nw", bd=2, relief="groove", padx=10, pady=10
).pack(fill="both", expand=True) root.mainloop()