flexbus-plzen / app.py
simikkk's picture
Update app.py
6ff7d5c verified
Raw
History Blame
8.09 kB
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error
import gradio as gr
import warnings
warnings.filterwarnings('ignore')
# --- Načtení a příprava dat ---
df = pd.read_csv('02_simpleml_komplet_pohyb.csv')
zony = pd.read_csv('00_zony.csv')
# Odstranění duplicit a chyb
df = df.dropna()
df = df[df['pocet_cest'] >= 0]
df = df[df['vzdalenost'] >= 0]
# Přidáme vlastnosti cílové zóny
zony_cols = zony[['nazev', 'velikost', 'izolace', 'skola', 'zamestnavatel', 'uzel']].copy()
zony_cols.columns = ['cil', 'cil_velikost', 'cil_izolace', 'cil_skola', 'cil_zamestnavatel', 'cil_uzel']
df = df.merge(zony_cols, on='cil', how='left')
zony_src = zony[['nazev', 'velikost', 'izolace']].copy()
zony_src.columns = ['zdroj', 'zdroj_velikost', 'zdroj_izolace']
df = df.merge(zony_src, on='zdroj', how='left')
df = df.fillna(df.median(numeric_only=True))
# Encoding kategorií
le_dict = {}
cat_cols = ['denni_typ', 'casove_okno', 'vekova_skupina', 'ucel', 'hlavni_mod']
for col in cat_cols:
le = LabelEncoder()
df[col + '_enc'] = le.fit_transform(df[col].astype(str))
le_dict[col] = le
# Features pro model
feature_cols = [
'rok', 'ctvrtleti', 'denni_typ_enc', 'casove_okno_enc',
'vekova_skupina_enc', 'ucel_enc', 'hlavni_mod_enc',
'udalost', 'udalost_velikost', 'podil_spatne_pocasi',
'vzdalenost', 'cil_velikost', 'cil_izolace', 'cil_skola',
'cil_zamestnavatel', 'cil_uzel', 'zdroj_velikost', 'zdroj_izolace'
]
X = df[feature_cols]
y = df['pocet_cest']
# Trénink modelu
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = GradientBoostingRegressor(n_estimators=200, learning_rate=0.1, max_depth=4, random_state=42)
model.fit(X_train, y_train)
# Baseline (průměr stejného typu dne + časového okna)
baseline_map = df.groupby(['denni_typ', 'casove_okno'])['pocet_cest'].mean().to_dict()
y_pred = model.predict(X_test)
baseline_pred = X_test.apply(
lambda r: baseline_map.get((
[k for k,v in le_dict['denni_typ'].classes_ if False] or
le_dict['denni_typ'].inverse_transform([int(r['denni_typ_enc'])])[0],
le_dict['casove_okno'].inverse_transform([int(r['casove_okno_enc'])])[0]
), y_train.mean()), axis=1
)
mae_model = mean_absolute_error(y_test, y_pred)
mae_baseline = mean_absolute_error(y_test, [y_train.mean()] * len(y_test))
print(f"Model MAE: {mae_model:.2f} | Baseline MAE: {mae_baseline:.2f}")
# Seznamy pro UI
vsechny_zony = sorted(df['zdroj'].unique().tolist())
denny_typy = ['vsedni', 'sobota', 'nedele']
casova_okna = ['rano_spicka', 'dopoledne', 'odpoledne', 'vecer_spicka', 'vecer_pozde']
vekove_skupiny = sorted(df['vekova_skupina'].unique().tolist())
ucely = sorted(df['ucel'].unique().tolist())
# --- Predikční funkce ---
def predikuj(zdroj, cil, denni_typ, casove_okno, vekova_skupina, ucel, udalost, udalost_velikost, pocasi, rok=2025, ctvrtleti=2):
try:
row = {
'rok': rok,
'ctvrtleti': ctvrtleti,
'denni_typ_enc': le_dict['denni_typ'].transform([denni_typ])[0] if denni_typ in le_dict['denni_typ'].classes_ else 0,
'casove_okno_enc': le_dict['casove_okno'].transform([casove_okno])[0] if casove_okno in le_dict['casove_okno'].classes_ else 0,
'vekova_skupina_enc': le_dict['vekova_skupina'].transform([vekova_skupina])[0] if vekova_skupina in le_dict['vekova_skupina'].classes_ else 0,
'ucel_enc': le_dict['ucel'].transform([ucel])[0] if ucel in le_dict['ucel'].classes_ else 0,
'hlavni_mod_enc': le_dict['hlavni_mod'].transform(['autobus'])[0],
'udalost': 1 if udalost else 0,
'udalost_velikost': float(udalost_velikost),
'podil_spatne_pocasi': float(pocasi),
'vzdalenost': float(df[((df['zdroj']==zdroj) & (df['cil']==cil))]['vzdalenost'].mean() or df['vzdalenost'].mean()),
}
zony_cil = zony[zony['nazev'] == cil]
zony_src = zony[zony['nazev'] == zdroj]
row['cil_velikost'] = float(zony_cil['velikost'].values[0]) if len(zony_cil) else 0.5
row['cil_izolace'] = float(zony_cil['izolace'].values[0]) if len(zony_cil) else 0.5
row['cil_skola'] = int(zony_cil['skola'].values[0]) if len(zony_cil) else 0
row['cil_zamestnavatel'] = int(zony_cil['zamestnavatel'].values[0]) if len(zony_cil) else 0
row['cil_uzel'] = int(zony_cil['uzel'].values[0]) if len(zony_cil) else 0
row['zdroj_velikost'] = float(zony_src['velikost'].values[0]) if len(zony_src) else 0.5
row['zdroj_izolace'] = float(zony_src['izolace'].values[0]) if len(zony_src) else 0.5
X_input = pd.DataFrame([row])[feature_cols]
pocet = float(model.predict(X_input)[0])
pocet = max(0, round(pocet, 1))
if pocet >= 8:
doporuceni = "🟢 VYSLAT — dostatečná poptávka"
flexbus = "Pevný spoj"
elif pocet >= 4:
doporuceni = "🟡 ČEKAT NA OBJEDNÁVKY — on-demand mikrobus"
flexbus = "FlexBus (objednat 1 hod předem)"
else:
doporuceni = "🔴 NEVYSÍLAT — příliš nízká poptávka"
flexbus = "Nevysílat (možnost taxi záchrana)"
nejistota = "±2–4 cestující (model pracuje s trendem, ne s jistotou)"
return (
f"**Předpověď:** {pocet} cestujících",
f"**Doporučení:** {doporuceni}",
f"**Režim:** {flexbus}",
f"**Přesnost modelu:** MAE = {mae_model:.2f} vs baseline {mae_baseline:.2f}",
f"**Nejistota:** {nejistota}"
)
except Exception as e:
return f"Chyba: {e}", "", "", "", ""
# --- Gradio UI ---
with gr.Blocks(title="FlexBus — Chytrá mobilita Plzeňský kraj") as demo:
gr.Markdown("# 🚌 FlexBus Prediktor\n### AI doporučení pro dispečera mikrobusu — Plzeňský kraj")
gr.Markdown(f"*Model trénován na reálných datech | MAE modelu: {mae_model:.2f} | Baseline MAE: {mae_baseline:.2f}*")
with gr.Row():
with gr.Column():
zdroj_input = gr.Dropdown(vsechny_zony, label="Odkud (zdroj)", value=vsechny_zony[0])
cil_input = gr.Dropdown(vsechny_zony, label="Kam (cíl)", value=vsechny_zony[1])
denni_typ_input = gr.Dropdown(denny_typy, label="Typ dne", value="sobota")
casove_okno_input = gr.Dropdown(casova_okna, label="Čas odjezdu", value="vecer_spicka")
vekova_input = gr.Dropdown(vekove_skupiny, label="Věková skupina", value=vekove_skupiny[0])
ucel_input = gr.Dropdown(ucely, label="Účel cesty", value=ucely[0])
with gr.Column():
udalost_input = gr.Checkbox(label="Koná se událost v cíli? (koncert, zápas...)")
udalost_vel_input = gr.Slider(0, 5, value=0, step=1, label="Velikost události (0=žádná, 5=velká)")
pocasi_input = gr.Slider(0.0, 1.0, value=0.3, step=0.1, label="Podíl špatného počasí (0=hezky, 1=hodně špatně)")
btn = gr.Button("🔮 Predikovat poptávku", variant="primary")
out1 = gr.Markdown()
out2 = gr.Markdown()
out3 = gr.Markdown()
out4 = gr.Markdown()
out5 = gr.Markdown()
btn.click(
predikuj,
inputs=[zdroj_input, cil_input, denni_typ_input, casove_okno_input,
vekova_input, ucel_input, udalost_input, udalost_vel_input, pocasi_input],
outputs=[out1, out2, out3, out4, out5]
)
gr.Markdown("""
---
### Jak číst výsledek
- 🟢 **8+ cestujících** → pevný spoj jede automaticky
- 🟡 **4–7 cestujících** → FlexBus čeká na objednávky (cestující objednají 1 hod předem)
- 🔴 **0–3 cestující** → spoj nejede, k dispozici záchranný taxi tarif
*AI etika: pracujeme pouze s agregovanými počty, žádné sledování jednotlivců.*
""")
demo.launch()