| | 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.") |
| |
|
| |
|
| |
|