Spaces:
Running
Running
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| from prophet import Prophet | |
| import plotly.graph_objects as go | |
| from datetime import datetime, timedelta | |
| # ====================================== | |
| # Page Config | |
| # ====================================== | |
| st.set_page_config( | |
| page_title="✈️ Travel Demand Forecaster", | |
| page_icon="✈️", | |
| layout="wide" | |
| ) | |
| st.title("✈️ Travel Demand Forecaster") | |
| st.markdown("**Powered by Facebook Prophet** — Upload your travel data or use sample data to forecast future demand.") | |
| # ====================================== | |
| # Sidebar Controls | |
| # ====================================== | |
| st.sidebar.header("⚙️ Forecast Settings") | |
| forecast_days = st.sidebar.slider( | |
| "Forecast Period (days)", | |
| min_value=30, | |
| max_value=365, | |
| value=90, | |
| step=30 | |
| ) | |
| seasonality_mode = st.sidebar.selectbox( | |
| "Seasonality Mode", | |
| ["multiplicative", "additive"], | |
| index=0 | |
| ) | |
| yearly_seasonality = st.sidebar.checkbox("Yearly Seasonality", value=True) | |
| weekly_seasonality = st.sidebar.checkbox("Weekly Seasonality", value=True) | |
| daily_seasonality = st.sidebar.checkbox("Daily Seasonality", value=False) | |
| # ====================================== | |
| # Data Input | |
| # ====================================== | |
| st.subheader("📂 Data Input") | |
| data_option = st.radio( | |
| "Choose data source:", | |
| ["Use Sample Travel Data", "Upload CSV File"], | |
| horizontal=True | |
| ) | |
| def generate_sample_data(): | |
| """Generate realistic travel demand sample data""" | |
| np.random.seed(42) | |
| dates = pd.date_range(start="2021-01-01", end="2023-12-31", freq="D") | |
| # Base demand with trend | |
| trend = np.linspace(100, 150, len(dates)) | |
| # Yearly seasonality (peak in summer and holidays) | |
| yearly = 30 * np.sin(2 * np.pi * np.arange(len(dates)) / 365 - np.pi/2) | |
| # Weekly seasonality (weekends higher) | |
| weekly = 10 * np.sin(2 * np.pi * np.arange(len(dates)) / 7) | |
| # Random noise | |
| noise = np.random.normal(0, 8, len(dates)) | |
| demand = trend + yearly + weekly + noise | |
| demand = np.maximum(demand, 10) # no negative demand | |
| df = pd.DataFrame({"ds": dates, "y": demand.round(0)}) | |
| return df | |
| if data_option == "Use Sample Travel Data": | |
| df = generate_sample_data() | |
| st.success("✅ Sample travel demand data loaded! (Jan 2021 — Dec 2023)") | |
| st.dataframe(df.tail(10), use_container_width=True) | |
| else: | |
| uploaded_file = st.file_uploader( | |
| "Upload CSV with 'ds' (date) and 'y' (value) columns", | |
| type=["csv"] | |
| ) | |
| if uploaded_file: | |
| df = pd.read_csv(uploaded_file) | |
| df['ds'] = pd.to_datetime(df['ds']) | |
| st.success(f"✅ Loaded {len(df)} rows of data!") | |
| st.dataframe(df.tail(10), use_container_width=True) | |
| else: | |
| st.info("👆 Please upload a CSV file with columns: **ds** (date) and **y** (value)") | |
| st.stop() | |
| # ====================================== | |
| # Train & Forecast | |
| # ====================================== | |
| st.divider() | |
| st.subheader("🔮 Forecast") | |
| if st.button("🚀 Generate Forecast", type="primary"): | |
| with st.spinner("Training Prophet model..."): | |
| # Train model | |
| model = Prophet( | |
| seasonality_mode=seasonality_mode, | |
| yearly_seasonality=yearly_seasonality, | |
| weekly_seasonality=weekly_seasonality, | |
| daily_seasonality=daily_seasonality | |
| ) | |
| model.fit(df) | |
| # Make future dataframe | |
| future = model.make_future_dataframe(periods=forecast_days) | |
| forecast = model.predict(future) | |
| st.success(f"✅ Forecast generated for next {forecast_days} days!") | |
| # ====================================== | |
| # Plot Results | |
| # ====================================== | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.metric( | |
| "Average Forecasted Demand", | |
| f"{forecast['yhat'].tail(forecast_days).mean():.0f}", | |
| delta=f"+{forecast['yhat'].tail(forecast_days).mean() - df['y'].mean():.0f} vs historical" | |
| ) | |
| with col2: | |
| st.metric( | |
| "Peak Forecasted Demand", | |
| f"{forecast['yhat'].tail(forecast_days).max():.0f}", | |
| delta="Next period peak" | |
| ) | |
| # Main forecast plot | |
| fig = go.Figure() | |
| # Historical data | |
| fig.add_trace(go.Scatter( | |
| x=df['ds'], y=df['y'], | |
| name='Historical', | |
| mode='lines', | |
| line=dict(color='#1f77b4', width=1.5) | |
| )) | |
| # Forecast | |
| forecast_future = forecast[forecast['ds'] > df['ds'].max()] | |
| fig.add_trace(go.Scatter( | |
| x=forecast_future['ds'], y=forecast_future['yhat'], | |
| name='Forecast', | |
| mode='lines', | |
| line=dict(color='#ff7f0e', width=2, dash='dash') | |
| )) | |
| # Confidence interval | |
| fig.add_trace(go.Scatter( | |
| x=pd.concat([forecast_future['ds'], forecast_future['ds'][::-1]]), | |
| y=pd.concat([forecast_future['yhat_upper'], forecast_future['yhat_lower'][::-1]]), | |
| fill='toself', | |
| fillcolor='rgba(255,127,14,0.2)', | |
| line=dict(color='rgba(255,255,255,0)'), | |
| name='Confidence Interval' | |
| )) | |
| fig.update_layout( | |
| title="Travel Demand Forecast", | |
| xaxis_title="Date", | |
| yaxis_title="Demand", | |
| hovermode='x unified', | |
| height=450 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Components plot | |
| st.subheader("📊 Forecast Components") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Trend | |
| fig_trend = go.Figure() | |
| fig_trend.add_trace(go.Scatter( | |
| x=forecast['ds'], y=forecast['trend'], | |
| mode='lines', line=dict(color='green', width=2) | |
| )) | |
| fig_trend.update_layout(title="Trend", height=300) | |
| st.plotly_chart(fig_trend, use_container_width=True) | |
| with col2: | |
| # Yearly seasonality | |
| if yearly_seasonality: | |
| fig_yearly = go.Figure() | |
| fig_yearly.add_trace(go.Scatter( | |
| x=forecast['ds'], y=forecast['yearly'], | |
| mode='lines', line=dict(color='purple', width=2) | |
| )) | |
| fig_yearly.update_layout(title="Yearly Seasonality", height=300) | |
| st.plotly_chart(fig_yearly, use_container_width=True) | |
| # Forecast table | |
| st.subheader("📋 Forecast Data") | |
| forecast_display = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(forecast_days) | |
| forecast_display.columns = ['Date', 'Forecast', 'Lower Bound', 'Upper Bound'] | |
| forecast_display = forecast_display.round(2) | |
| st.dataframe(forecast_display, use_container_width=True) | |
| # Download button | |
| csv = forecast_display.to_csv(index=False) | |
| st.download_button( | |
| label="📥 Download Forecast CSV", | |
| data=csv, | |
| file_name="travel_forecast.csv", | |
| mime="text/csv" | |
| ) | |