From e33da1d1de6228dbd565e9e87be04136c7a8acd8 Mon Sep 17 00:00:00 2001 From: NeurArk Date: Thu, 22 May 2025 13:47:51 +0200 Subject: [PATCH] Add time series analysis page --- app.py | 1 + pages/data_explorer.py | 4 +++ pages/time_series.py | 67 ++++++++++++++++++++++++++++++++++++++++++ tests/test_pages.py | 25 ++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 pages/time_series.py diff --git a/app.py b/app.py index b02b65d..ac2b112 100644 --- a/app.py +++ b/app.py @@ -18,6 +18,7 @@ def main() -> None: st.page_link("pages/data_explorer.py", label="Data Explorer", icon="📊") st.page_link("pages/modeling.py", label="Modeling", icon="🧠") st.page_link("pages/prediction.py", label="Prediction", icon="🔮") + st.page_link("pages/time_series.py", label="Time Series", icon="📈") st.page_link("pages/report.py", label="Report", icon="📄") diff --git a/pages/data_explorer.py b/pages/data_explorer.py index 512bc06..e9d43fc 100644 --- a/pages/data_explorer.py +++ b/pages/data_explorer.py @@ -49,6 +49,9 @@ def main() -> None: st.session_state["data"] = data_utils.convert_dtypes( st.session_state["data"] ) + st.session_state["datetime_cols"] = eda.detect_datetime_columns( + st.session_state["data"] + ) st.success(f"{name} loaded!") with st.expander("Help"): @@ -59,6 +62,7 @@ def main() -> None: df = data_utils.load_data(uploaded_file) df = data_utils.convert_dtypes(df) st.session_state["data"] = df + st.session_state["datetime_cols"] = eda.detect_datetime_columns(df) st.success("File loaded successfully!") except ValueError as exc: st.error(f"Failed to load file: {exc}") diff --git a/pages/time_series.py b/pages/time_series.py new file mode 100644 index 0000000..7090807 --- /dev/null +++ b/pages/time_series.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +from pathlib import Path +import tempfile + +import pandas as pd +import streamlit as st + +from utils import ui, viz + +st.set_page_config(page_title="Time Series", layout="wide") + + +def main() -> None: + """Render the time series analysis page.""" + ui.apply_branding() + st.title("Time Series Analysis") + + df = st.session_state.get("data") + if df is None or df.empty: + st.info("No dataset available. Load data on the Data Explorer page.") + return + + datetime_cols = st.session_state.get("datetime_cols") or [] + if not datetime_cols: + st.info("No datetime columns detected in the dataset.") + return + + with st.sidebar: + time_col = st.selectbox("Datetime Column", datetime_cols, key="ts_time") + numeric_cols = df.select_dtypes(include="number").columns.tolist() + value_col = st.selectbox("Value Column", numeric_cols, key="ts_value") + export_fmt = st.selectbox("Export Format", ["png", "jpg"], key="ts_fmt") + period = st.number_input( + "Seasonal Period", min_value=2, value=2, step=1, key="ts_period" + ) + + if st.button("Generate Plots"): + ts_fig = viz.time_series_plot(df, time_col, value_col, title="Time Series") + st.plotly_chart(ts_fig, use_container_width=True) + with tempfile.NamedTemporaryFile(suffix=f".{export_fmt}") as tmp: + viz.export_figure(ts_fig, Path(tmp.name)) + tmp.seek(0) + st.download_button( + "Download Time Series", + data=tmp.read(), + file_name=f"time_series.{export_fmt}", + mime=f"image/{export_fmt}", + ) + + dec_fig = viz.decomposition_plot( + df.set_index(time_col)[value_col], period=period, title="Decomposition" + ) + st.pyplot(dec_fig) + with tempfile.NamedTemporaryFile(suffix=f".{export_fmt}") as tmp: + viz.export_figure(dec_fig, Path(tmp.name)) + tmp.seek(0) + st.download_button( + "Download Decomposition", + data=tmp.read(), + file_name=f"decomposition.{export_fmt}", + mime=f"image/{export_fmt}", + ) + + +if __name__ == "__main__": + main() diff --git a/tests/test_pages.py b/tests/test_pages.py index 47b8d3c..934351e 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -11,6 +11,7 @@ "pages.modeling", "pages.prediction", "pages.report", + "pages.time_series", ] @pytest.mark.parametrize("mod_name", PAGES) @@ -65,3 +66,27 @@ def test_modeling_page_widgets_exist(): assert "Train Models" in content assert "export_model" in content + +def test_time_series_page_runs(monkeypatch): + import streamlit as st + from pages import time_series + + df = pd.DataFrame( + { + "date": pd.date_range("2021-01-01", periods=5, freq="D"), + "value": range(5), + } + ) + st.session_state.clear() + st.session_state["data"] = df + st.session_state["datetime_cols"] = ["date"] + monkeypatch.setattr(time_series.ui, "apply_branding", lambda: None) + time_series.main() + + +def test_time_series_page_contents(): + with open("pages/time_series.py", "r", encoding="utf-8") as f: + content = f.read() + assert "time_series_plot" in content + assert "decomposition_plot" in content +