diff --git a/TODO.md b/TODO.md index 6291e97..adb0382 100644 --- a/TODO.md +++ b/TODO.md @@ -180,6 +180,7 @@ After completing a milestone, create a pull request with your changes for review - [x] Integrated evaluation metrics and plots into the Data Explorer page - [x] Implemented modeling page with model selection, training, cross-validation, and export functionality +- [x] Added histogram, box plot, violin plot, and heatmap UI with export options ## PR17: Robust Error Handling diff --git a/pages/data_explorer.py b/pages/data_explorer.py index aef9d4a..25962de 100644 --- a/pages/data_explorer.py +++ b/pages/data_explorer.py @@ -139,13 +139,141 @@ def _corr(df): with tempfile.NamedTemporaryFile(suffix=f".{export_fmt}") as tmp: viz.export_figure(fig_pair, Path(tmp.name)) tmp.seek(0) - st.download_button( + st.download_button( "Download Plot", data=tmp.read(), file_name=f"pair_plot.{export_fmt}", mime=f"image/{export_fmt}", ) + st.subheader("Histogram") + with st.expander("Create Histogram"): + num_cols = data.select_dtypes(include="number").columns.tolist() + hist_col = st.selectbox( + "Column", + options=num_cols, + key="hist_col", + ) + bins = st.slider( + "Bins", + 5, + 100, + 20, + step=5, + key="hist_bins", + ) + density = st.checkbox("Density", key="hist_density") + export_fmt_h = st.selectbox( + "Export Format", + ["png", "jpg"], + key="hist_fmt", + ) + if st.button("Generate Histogram") and hist_col: + fig_hist = viz.histogram( + data, + hist_col, + bins=bins, + density=density, + ) + st.plotly_chart(fig_hist, use_container_width=True) + with tempfile.NamedTemporaryFile(suffix=f".{export_fmt_h}") as tmp: + viz.export_figure(fig_hist, Path(tmp.name)) + tmp.seek(0) + st.download_button( + "Download Histogram", + data=tmp.read(), + file_name=f"histogram.{export_fmt_h}", + mime=f"image/{export_fmt_h}", + ) + + st.subheader("Box Plot") + with st.expander("Create Box Plot"): + x_col = st.selectbox( + "X Column", + options=data.columns.tolist(), + key="box_x", + ) + num_cols = data.select_dtypes(include="number").columns.tolist() + y_col = st.selectbox( + "Y Column", + options=num_cols, + key="box_y", + ) + export_fmt_b = st.selectbox( + "Export Format", + ["png", "jpg"], + key="box_fmt", + ) + if st.button("Generate Box Plot") and x_col and y_col: + fig_box = viz.box_plot(data, x=x_col, y=y_col) + st.plotly_chart(fig_box, use_container_width=True) + with tempfile.NamedTemporaryFile(suffix=f".{export_fmt_b}") as tmp: + viz.export_figure(fig_box, Path(tmp.name)) + tmp.seek(0) + st.download_button( + "Download Box Plot", + data=tmp.read(), + file_name=f"box_plot.{export_fmt_b}", + mime=f"image/{export_fmt_b}", + ) + + st.subheader("Violin Plot") + with st.expander("Create Violin Plot"): + x_col_v = st.selectbox( + "X Column", + options=data.columns.tolist(), + key="violin_x", + ) + num_cols = data.select_dtypes(include="number").columns.tolist() + y_col_v = st.selectbox( + "Y Column", + options=num_cols, + key="violin_y", + ) + export_fmt_v = st.selectbox( + "Export Format", + ["png", "jpg"], + key="violin_fmt", + ) + if st.button("Generate Violin Plot") and x_col_v and y_col_v: + fig_violin = viz.violin_plot(data, x=x_col_v, y=y_col_v) + st.plotly_chart(fig_violin, use_container_width=True) + with tempfile.NamedTemporaryFile(suffix=f".{export_fmt_v}") as tmp: + viz.export_figure(fig_violin, Path(tmp.name)) + tmp.seek(0) + st.download_button( + "Download Violin Plot", + data=tmp.read(), + file_name=f"violin_plot.{export_fmt_v}", + mime=f"image/{export_fmt_v}", + ) + + st.subheader("Heatmap") + with st.expander("Create Heatmap"): + heat_cols = st.multiselect( + "Columns", + options=data.select_dtypes(include="number").columns.tolist(), + default=data.select_dtypes(include="number").columns.tolist(), + key="heat_cols", + ) + export_fmt_hm = st.selectbox( + "Export Format", + ["png", "jpg"], + key="heat_fmt", + ) + if st.button("Generate Heatmap") and heat_cols: + fig_heat = viz.heatmap(data[heat_cols]) + st.plotly_chart(fig_heat, use_container_width=True) + with tempfile.NamedTemporaryFile(suffix=f".{export_fmt_hm}") as tmp: + viz.export_figure(fig_heat, Path(tmp.name)) + tmp.seek(0) + st.download_button( + "Download Heatmap", + data=tmp.read(), + file_name=f"heatmap.{export_fmt_hm}", + mime=f"image/{export_fmt_hm}", + ) + st.subheader("Insights") for insight in eda.data_insights_summary(data): st.write(f"- {insight}") diff --git a/tests/test_pages.py b/tests/test_pages.py index 934351e..1d8f391 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -67,6 +67,15 @@ def test_modeling_page_widgets_exist(): assert "export_model" in content +def test_data_explorer_visualization_widgets_exist(): + with open("pages/data_explorer.py", "r", encoding="utf-8") as f: + content = f.read() + assert "Generate Histogram" in content + assert "Generate Box Plot" in content + assert "Generate Violin Plot" in content + assert "Generate Heatmap" in content + + def test_time_series_page_runs(monkeypatch): import streamlit as st from pages import time_series diff --git a/tests/test_viz.py b/tests/test_viz.py index 83634ae..05630e2 100644 --- a/tests/test_viz.py +++ b/tests/test_viz.py @@ -48,10 +48,15 @@ def test_heatmap(): def test_export_figure(tmp_path): df = sample_df() - fig = viz.bar_chart(df, 'cat', 'num1') - out = tmp_path / 'chart.html' - viz.export_figure(fig, out) - assert out.exists() and out.stat().st_size > 0 + fig_bar = viz.bar_chart(df, 'cat', 'num1') + out_bar = tmp_path / 'chart.html' + viz.export_figure(fig_bar, out_bar) + assert out_bar.exists() and out_bar.stat().st_size > 0 + + fig_hist = viz.histogram(df, 'num1') + out_hist = tmp_path / 'hist.html' + viz.export_figure(fig_hist, out_hist) + assert out_hist.exists() and out_hist.stat().st_size > 0 def test_pair_plot_and_image_export(tmp_path):