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()