BPR17/EDMproject
0
1from pathlib import Path2import pandas as pd3import numpy as np4import streamlit as st5import plotly.express as px6import warnings7 8warnings.filterwarnings("ignore")9 10COL_STATION = "Numero"11DIAS_SEMANA = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]12MESES_VERANO = {6: "June", 7: "July", 8: "August"}13 14 15@st.cache_data16def cargar_predicciones_2026() -> pd.DataFrame:17 base_dir = Path(__file__).resolve().parent18 pred_path = base_dir / "predicciones_2026.parquet"19 20 if not pred_path.exists():21 st.error(22 "No se ha encontrado `predicciones_2026.parquet`.\n\n"23 "Ejecuta primero `python predecir_2026.py` para generar las predicciones."24 )25 st.stop()26 27 df = pd.read_parquet(pred_path)28 df["fecha_prediccion"] = pd.to_datetime(df["fecha_prediccion"])29 df[COL_STATION] = pd.to_numeric(df[COL_STATION], errors="coerce").astype(int)30 31 return df32 33 34def clasificar_ocupacion(pct):35 if pct < 25:36 return "Low occupancy"37 elif pct < 60:38 return "Medium occupancy"39 else:40 return "High occupancy"41 42 43st.set_page_config(44 page_title="Valenbisi 2026",45 page_icon="🚲",46 layout="wide"47)48 49st.title("🚲 Valenbisi Smart Forecast")50st.caption("Interactive app to predict Valenbisi occupancy during the summer of 2026")51 52with st.spinner("Cargando predicciones 2026..."):53 df_predicciones = cargar_predicciones_2026()54 55st.header("Availability forecast by station and date")56 57estaciones_disponibles = sorted(df_predicciones[COL_STATION].dropna().unique())58ano_pred = 202659 60col_a, col_b, col_c = st.columns(3)61 62with col_a:63 estacion_pred = st.selectbox("Station", estaciones_disponibles, key="estacion_pred")64 st.text_input("Year", value="2026", disabled=True)65 66with col_b:67 mes_pred = st.selectbox(68 "Month",69 list(MESES_VERANO.keys()),70 format_func=lambda x: MESES_VERANO[x],71 key="mes_pred"72 )73 dia_pred = st.slider("Day of the month", 1, 31, 15, key="dia_pred")74 75with col_c:76 hora_pred = st.slider("Hour of the day", 0, 23, 12, key="hora_pred")77 minuto_pred = st.selectbox("Minute", [0, 15, 30, 45], key="minuto_pred")78 79 80try:81 fecha_elegida = pd.Timestamp(82 year=int(ano_pred),83 month=int(mes_pred),84 day=int(dia_pred),85 hour=int(hora_pred),86 minute=int(minuto_pred)87 )88except ValueError:89 st.warning("Esa combinación de día y mes no es válida. Por ejemplo, el 31 de junio no existe.")90 st.stop()91 92 93df_instante = df_predicciones[df_predicciones["fecha_prediccion"] == fecha_elegida]94 95if df_instante.empty:96 st.error("No hay predicciones guardadas para esa fecha y hora.")97 st.stop()98 99 100resultado_individual = df_instante[df_instante[COL_STATION] == estacion_pred]101 102if resultado_individual.empty:103 st.error("No hay predicción guardada para esa estación en esa fecha y hora.")104 st.stop()105 106resultado_individual = resultado_individual.iloc[0]107 108bicis_pred = int(resultado_individual["Bicis_predichas"])109huecos_pred = int(resultado_individual["Huecos_predichos"])110capacidad_estacion = int(resultado_individual["Capacidad"])111 112dia_semana_pred = fecha_elegida.dayofweek113 114st.caption(115 f"{fecha_elegida.strftime('%d/%m/%Y')} ({DIAS_SEMANA[dia_semana_pred]}) · "116 f"{hora_pred:02d}:{minuto_pred:02d}h · Station Nº {estacion_pred}"117)118 119res1, res2, res3 = st.columns(3)120res1.metric("Estimated bikes available", f"{bicis_pred} bikes")121res2.metric("Estimated free docks", f"{huecos_pred} docks")122res3.metric("Station capacity", f"{capacidad_estacion} docks")123 124 125st.header("Estimated station occupancy map")126st.write("The map displays all stations for the selected date and time.")127 128columnas_necesarias = {"Latitud", "Longitud", "Bicis_predichas", "Huecos_predichos", "Capacidad"}129 130if not columnas_necesarias.issubset(df_instante.columns):131 st.warning(132 "El archivo `predicciones_2026.parquet` no contiene todas las columnas necesarias "133 "para dibujar el mapa. Debe incluir Latitud y Longitud."134 )135else:136 df_mapa = df_instante.dropna(subset=["Latitud", "Longitud"]).copy()137 138 df_mapa["Ocupación (%)"] = np.where(139 df_mapa["Capacidad"] > 0,140 df_mapa["Bicis_predichas"] / df_mapa["Capacidad"] * 100,141 0142 )143 144 df_mapa["Occupancy level"] = df_mapa["Ocupación (%)"].apply(clasificar_ocupacion)145 146 df_mapa = df_mapa.rename(columns={147 "Bicis_predichas": "Bicis estimadas",148 "Huecos_predichos": "Huecos estimados"149 })150 151 fig_mapa = px.scatter_mapbox(152 df_mapa,153 lat="Latitud",154 lon="Longitud",155 color="Occupancy level",156 color_discrete_map={157 158 "Low occupancy":"#2ECC71",159 "Medium occupancy":"#F1C40F",160 "High occupancy":"#E74C3C"161 },162 hover_name=COL_STATION,163 hover_data={164 "Bicis estimadas": True,165 "Huecos estimados": True,166 "Capacidad": True,167 "Ocupación (%)": ":.1f",168 "Latitud": False,169 "Longitud": False170 },171 zoom=12,172 height=650,173 mapbox_style="open-street-map",174 title="Estimated station occupancy map"175 )176 fig_mapa.update_traces(177 marker=dict(size=18)178 )179 180 fig_mapa.update_layout(181 legend_title_text="Nivel de ocupación"182 )183 184 st.plotly_chart(fig_mapa, use_container_width=True)185 186 187with st.expander("Ver tabla de predicciones para la fecha y hora seleccionadas"):188 columnas_tabla = [189 COL_STATION,190 "fecha_prediccion",191 "Bicis_predichas",192 "Huecos_predichos",193 "Capacidad",194 "Latitud",195 "Longitud"196 ]197 198 columnas_tabla = [col for col in columnas_tabla if col in df_instante.columns]199 200 st.dataframe(201 df_instante[columnas_tabla].sort_values(COL_STATION),202 use_container_width=True203 )204 