130 lines
5.5 KiB
Python
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()
|