Files
bike-course-design/bike_demand_gui.py
2026-06-08 23:15:05 +08:00

130 lines
5.5 KiB
Python

import tkinter as tk
from tkinter import ttk, messagebox
from pathlib import Path
import joblib
import pandas as pd
BASE_DIR = Path(__file__).resolve().parent
MODEL_PATH = BASE_DIR / 'outputs' / 'models' / 'best_bike_demand_model.pkl'
FEATURES = ['季节', '', '', '节假日', '星期', '工作日', '天气', '气温', '体感温度', '湿度', '风速']
class BikeDemandApp:
def __init__(self, root):
self.root = root
self.root.title('共享单车日骑行量预测系统')
self.root.geometry('820x560')
self.model = joblib.load(MODEL_PATH)
self.entries = {}
self.history = []
self.build_ui()
def build_ui(self):
container = ttk.Frame(self.root, padding=16)
container.pack(fill=tk.BOTH, expand=True)
left = ttk.LabelFrame(container, text='输入特征', padding=12)
left.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 12))
fields = [
('季节', '1=春 2=夏 3=秋 4=冬', '1'),
('', '0=2011 1=2012 2=2013', '2'),
('', '1~12', '1'),
('节假日', '0=否 1=是', '0'),
('星期', '0=周日 ... 6=周六', '1'),
('工作日', '0=否 1=是', '1'),
('天气', '1=晴好 2=多云 3=小雨雪 4=恶劣', '1'),
('气温', '归一化 0~1', '0.32'),
('体感温度', '归一化 0~1', '0.34'),
('湿度', '归一化 0~1', '0.70'),
('风速', '归一化 0~1', '0.15'),
]
for row, (name, hint, default) in enumerate(fields):
ttk.Label(left, text=name).grid(row=row, column=0, sticky='w', pady=4)
entry = ttk.Entry(left, width=14)
entry.insert(0, default)
entry.grid(row=row, column=1, sticky='ew', pady=4)
ttk.Label(left, text=hint, foreground='#666').grid(row=row, column=2, sticky='w', padx=6)
self.entries[name] = entry
ttk.Button(left, text='预测单场景', command=self.predict_one).grid(row=len(fields), column=0, columnspan=3, sticky='ew', pady=(12, 4))
ttk.Button(left, text='加入对比列表', command=self.add_to_history).grid(row=len(fields) + 1, column=0, columnspan=3, sticky='ew', pady=4)
ttk.Button(left, text='清空对比列表', command=self.clear_history).grid(row=len(fields) + 2, column=0, columnspan=3, sticky='ew', pady=4)
right = ttk.Frame(container)
right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
result_box = ttk.LabelFrame(right, text='预测结果', padding=16)
result_box.pack(fill=tk.X)
self.result_var = tk.StringVar(value='请输入特征后点击预测')
ttk.Label(result_box, textvariable=self.result_var, font=('Arial', 22, 'bold'), foreground='#0f766e').pack(anchor='w')
self.level_var = tk.StringVar(value='')
ttk.Label(result_box, textvariable=self.level_var, font=('Arial', 12)).pack(anchor='w', pady=(8, 0))
table_box = ttk.LabelFrame(right, text='多场景对比', padding=8)
table_box.pack(fill=tk.BOTH, expand=True, pady=(12, 0))
columns = ('序号', '', '天气', '气温', '工作日', '预测总骑行量')
self.tree = ttk.Treeview(table_box, columns=columns, show='headings', height=12)
for col in columns:
self.tree.heading(col, text=col)
self.tree.column(col, width=90, anchor='center')
self.tree.pack(fill=tk.BOTH, expand=True)
def collect_features(self):
values = {}
for feature, entry in self.entries.items():
try:
values[feature] = float(entry.get())
except ValueError as exc:
raise ValueError(f'{feature} 必须是数字') from exc
for feature in ['季节', '', '', '节假日', '星期', '工作日', '天气']:
values[feature] = int(values[feature])
for feature in ['气温', '体感温度', '湿度', '风速']:
if not 0 <= values[feature] <= 1:
raise ValueError(f'{feature} 应在 0~1 之间')
return values
def predict_value(self):
values = self.collect_features()
frame = pd.DataFrame([values], columns=FEATURES)
prediction = int(max(round(self.model.predict(frame)[0]), 0))
return values, prediction
def predict_one(self):
try:
_, prediction = self.predict_value()
except Exception as exc:
messagebox.showerror('输入错误', str(exc))
return
self.result_var.set(f'预计日骑行量:{prediction:,}')
if prediction >= 5000:
level = '需求等级:高,需要提前调度车辆。'
elif prediction >= 3000:
level = '需求等级:中,维持常规运维。'
else:
level = '需求等级:低,可减少热点补车频率。'
self.level_var.set(level)
def add_to_history(self):
try:
values, prediction = self.predict_value()
except Exception as exc:
messagebox.showerror('输入错误', str(exc))
return
self.history.append((values, prediction))
idx = len(self.history)
self.tree.insert('', 'end', values=(idx, values[''], values['天气'], values['气温'], values['工作日'], prediction))
self.predict_one()
def clear_history(self):
self.history.clear()
for item in self.tree.get_children():
self.tree.delete(item)
if __name__ == '__main__':
root = tk.Tk()
app = BikeDemandApp(root)
root.mainloop()