| import streamlit as st |
| import pandas as pd |
| import requests |
| import plotly.graph_objects as go |
| from plotly.subplots import make_subplots |
| import io |
| import os |
| import numpy as np |
| import yaml |
| from datetime import datetime |
| import logging |
| import csv |
| from dotenv import load_dotenv |
| from plotly.colors import n_colors |
| from fastapi import FastAPI, HTTPException |
| from pydantic import BaseModel |
| from typing import List, Optional |
| from nixtla import NixtlaClient |
|
|
| load_dotenv() |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
|
|
| |
| |
| FASTAPI_URL = "https://huggingface.co/spaces/anujkum0x/backender/forecast" |
| st.set_page_config( |
| page_title="๐ฎ Time Series Forecasting", layout="wide", initial_sidebar_state="expanded" |
| ) |
|
|
| |
| st.markdown( |
| """ |
| <style> |
| /* General app background */ |
| .reportview-container { |
| background: linear-gradient(to right, #f0f2f6, #e1e8f2) !important; /* Light background */ |
| } |
| /* Sidebar background */ |
| .sidebar .sidebar-content { |
| background: linear-gradient(to bottom, #f0f2f6, #e1e8f2) !important; /* Light sidebar */ |
| } |
| /* Headers and text */ |
| h1, h2, h3, h4, h5, h6, p, div, label { |
| color: #333333 !important; /* Darker text for contrast */ |
| } |
| /* Buttons */ |
| .stButton>button { |
| color: #007bff !important; /* Primary blue color */ |
| border: 2px solid #007bff !important; |
| background-color: transparent !important; |
| transition: all 0.3s ease !important; |
| } |
| .stButton>button:hover { |
| background-color: #007bff !important; |
| color: white !important; |
| } |
| /* Input fields */ |
| .stTextInput>label, .stNumberInput>label, .stSelectbox>label, .stDateInput>label { |
| color: #555555 !important; |
| } |
| /* Add a subtle shadow to elements */ |
| .element-container { |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; |
| border-radius: 5px !important; |
| padding: 10px !important; |
| margin-bottom: 10px !important; |
| background-color: rgba(255, 255, 255, 0.8) !important; /* Semi-transparent white for content boxes */ |
| } |
| </style> |
| """, |
| unsafe_allow_html=True, |
| ) |
|
|
| st.title("๐ฎ Time Series Forecasting") |
|
|
| |
| with st.sidebar: |
| st.header("โ๏ธ Settings") |
| |
| |
| |
| horizon = st.number_input("Forecast Horizon", min_value=1, max_value=1000, value=30) |
| finetune_steps = st.slider("Finetune Steps", min_value=0, max_value=2000, value=1000) |
| freq = st.selectbox( |
| "Model Frequency", |
| options=['15min', '30min', 'H', '2H', '3H', '4H', '5H', '6H', '12H', 'D', 'W', 'M', 'Y'], |
| index=2, |
| help="Frequency of the time series data for the model." |
| ) |
|
|
| resample_freq = st.selectbox( |
| "Resample Frequency", |
| options=['15min', '30min', 'H', '2H', '3H', '4H', '5H', '6H', '12H', 'D', 'W', 'M', 'Y'], |
| index=2, |
| help="Frequency to resample the input data to." |
| ) |
|
|
| st.sidebar.header("๐ Data Input") |
| uploaded_file = st.sidebar.file_uploader( |
| "Upload your time series data (CSV, Excel, JSON, YAML)", type=["csv", "xlsx", "json", "yaml", "yml"], help="Upload a CSV, Excel, JSON, or YAML file containing your time series data." |
| ) |
|
|
| |
| st.write("About to display the generate forecast button") |
| data_loaded = False |
| df = None |
|
|
| if uploaded_file is not None: |
| try: |
| logger.info(f"Attempting to load file: {uploaded_file.name}") |
| file_extension = uploaded_file.name.split('.')[-1].lower() |
|
|
| if file_extension == 'csv': |
| try: |
| df = pd.read_csv(uploaded_file) |
| logger.info(f"CSV file loaded successfully using Pandas. Shape: {df.shape}") |
| except Exception as e: |
| st.error(f"โ Error parsing CSV file with Pandas: {e}") |
| logger.exception(f"Error parsing CSV with Pandas: {e}") |
| st.stop() |
|
|
| elif file_extension == 'xlsx': |
| try: |
| df = pd.read_excel(uploaded_file) |
| logger.info(f"Excel file loaded successfully using Pandas. Shape: {df.shape}") |
| except Exception as e: |
| st.error(f"โ Error parsing Excel file with Pandas: {e}") |
| logger.exception(f"Error parsing Excel with Pandas: {e}") |
| st.stop() |
|
|
| elif file_extension == 'json': |
| try: |
| df = pd.read_json(uploaded_file) |
| logger.info(f"JSON file loaded successfully using Pandas. Shape: {df.shape}") |
| except Exception as e: |
| st.error(f"โ Error parsing JSON file with Pandas: {e}") |
| logger.exception(f"Error parsing JSON with Pandas: {e}") |
| st.stop() |
|
|
| elif file_extension in ['yaml', 'yml']: |
| try: |
| df = pd.DataFrame(yaml.safe_load(uploaded_file)) |
| logger.info(f"YAML file loaded successfully using Pandas. Shape: {df.shape}") |
| except Exception as e: |
| st.error(f"โ Error parsing YAML file with Pandas: {e}") |
| logger.exception(f"Error parsing YAML with Pandas: {e}") |
| st.stop() |
|
|
| else: |
| st.error("โ Unsupported file format. Please upload a CSV, Excel, JSON, or YAML file.") |
| logger.error(f"Unsupported file format: {file_extension}") |
| st.stop() |
|
|
| st.success("โ
Data loaded successfully!") |
| data_loaded = True |
|
|
| |
| st.sidebar.header("๐ Column Selection") |
| time_col = st.sidebar.selectbox("Select Timestamp Column", df.columns, help="Column containing the timestamps.") |
| value_col = st.sidebar.selectbox("Select Value Column", df.columns, help="Column containing the values to forecast.") |
|
|
| if value_col == time_col: |
| st.error("โ Value column cannot be the same as the Timestamp column") |
| logger.error("Value column and Timestamp column are the same.") |
| st.stop() |
|
|
| |
| try: |
| |
| df[value_col] = pd.to_numeric(df[value_col], errors='coerce') |
| logger.info(f"Value column '{value_col}' converted to numeric.") |
|
|
| |
| if df[value_col].isnull().any(): |
| st.warning(f"Some values in {value_col} could not be converted to numeric and were replaced with NaN.") |
| logger.warning(f"NaN values found in value column '{value_col}'.") |
| df = df.dropna(subset=[value_col]) |
| logger.info(f"Rows with NaN values in '{value_col}' dropped. Shape: {df.shape}") |
|
|
| except Exception as e: |
| st.error(f"Error converting {value_col} to numeric: {e}") |
| logger.exception(f"Error converting value column to numeric: {e}") |
| st.stop() |
|
|
| |
| try: |
| df[time_col] = pd.to_datetime(df[time_col], errors='coerce') |
| logger.info(f"Timestamp column '{time_col}' converted to datetime.") |
|
|
| |
| if df[time_col].isnull().any(): |
| st.warning(f"Some values in {time_col} could not be converted to datetime. These rows will be dropped.") |
| logger.warning(f"NaT values found in timestamp column '{time_col}'.") |
| df = df.dropna(subset=[time_col]) |
| logger.info(f"Rows with NaT values in '{time_col}' dropped. Shape: {df.shape}") |
|
|
| except Exception as e: |
| st.error(f"Error converting {time_col} to datetime: {e}") |
| logger.exception(f"Error converting timestamp column to datetime: {e}") |
| st.stop() |
|
|
| |
| with st.expander("๐ Data Preview", expanded=False): |
| st.dataframe(df.head()) |
|
|
| except Exception as e: |
| st.error(f"โ An error occurred during data loading: {e}") |
| logger.exception(f"An error occurred during data loading: {e}") |
| st.stop() |
|
|
| if data_loaded: |
| if st.button("โจ Generate Forecast"): |
| if df is not None: |
| with st.spinner("โณ Generating forecast..."): |
| try: |
| |
| df = df.dropna(subset=[time_col, value_col]) |
| logger.info(f"Null values dropped before API call. Shape: {df.shape}") |
|
|
| |
| timestamps = [ts.isoformat() for ts in df[time_col]] |
| values = df[value_col].tolist() |
|
|
| payload = { |
| "timestamps": timestamps, |
| "values": values, |
| "forecast_horizon": horizon, |
| "finetune_steps": finetune_steps, |
| "freq": freq, |
| "resample_freq": resample_freq, |
| "target_col": value_col, |
| "format": "json" |
| } |
|
|
| response = requests.post(FASTAPI_URL, json=payload) |
| response.raise_for_status() |
| logger.info(f"API call successful. Status code: {response.status_code}") |
| forecast_data = response.json() |
|
|
| |
| forecast_df = pd.DataFrame(forecast_data) |
|
|
| |
| forecast_value_col = [col for col in forecast_df.columns if col != time_col][0] |
|
|
| |
| forecast_df[time_col] = pd.to_datetime(forecast_df[time_col]) |
|
|
| |
| st.subheader("๐ Time Series Visualization") |
| fig = make_subplots( |
| rows=2, cols=1, |
| shared_xaxes=True, |
| vertical_spacing=0.05, |
| subplot_titles=('Historical Data vs Forecast', 'Combined Data (Inner Join)') |
| ) |
|
|
| |
| fig.add_trace(go.Scatter( |
| x=df[time_col], |
| y=df[value_col], |
| mode='lines', |
| name='Historical Data', |
| line=dict(color='#636EFA'), |
| showlegend=False |
| ), row=1, col=1) |
|
|
| |
| fig.add_trace(go.Scatter( |
| x=forecast_df[time_col], |
| y=forecast_df[forecast_value_col], |
| mode='lines', |
| name='Forecast', |
| line=dict(color='#FFA15A'), |
| showlegend=False |
| ), row=1, col=1) |
|
|
| |
| fig.add_trace(go.Scatter( |
| x=df[time_col], |
| y=df[value_col], |
| mode='lines', |
| name='Historical Data', |
| line=dict(color='#636EFA'), |
| showlegend=False |
| ), row=2, col=1) |
| fig.add_trace(go.Scatter( |
| x=forecast_df[time_col], |
| y=forecast_df[forecast_value_col], |
| mode='lines', |
| name='Forecast', |
| line=dict(color='#FFA15A'), |
| showlegend=False |
| ), row=2, col=1) |
|
|
| fig.update_layout( |
| title="Time Series Forecast", |
| xaxis_title="Time", |
| yaxis_title="Value", |
| template="plotly_white", |
| hovermode="x unified" |
| ) |
|
|
| st.plotly_chart(fig, use_container_width=True) |
|
|
| |
| st.subheader("Forecast Data") |
| st.dataframe(forecast_df) |
|
|
| |
| csv = forecast_df.to_csv(index=False) |
| st.download_button( |
| label="Download forecast data as CSV", |
| data=csv, |
| file_name="forecast.csv", |
| mime="text/csv", |
| ) |
|
|
| except requests.exceptions.RequestException as e: |
| st.error(f"โ Error communicating with backend: {e}") |
| logger.exception(f"Error communicating with backend: {e}") |
| except Exception as e: |
| st.error(f"โ An error occurred during forecasting: {e}") |
| logger.exception(f"Error occurred during forecasting: {e}") |
| else: |
| st.warning("Please upload data and select columns to generate a forecast.") |
|
|
|
|
|
|